diff --git a/.eslintrc.js b/.eslintrc.js index e66331594b4ae..2ce6d279d93a9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -69,26 +69,6 @@ module.exports = { 'jsx-a11y/no-onchange': 'off', }, }, - { - files: ['src/legacy/core_plugins/expressions/**/*.{js,ts,tsx}'], - rules: { - 'react-hooks/exhaustive-deps': 'off', - }, - }, - { - files: [ - 'src/legacy/core_plugins/vis_default_editor/public/components/controls/**/*.{ts,tsx}', - ], - rules: { - 'react-hooks/exhaustive-deps': 'off', - }, - }, - { - files: ['src/legacy/ui/public/vis/**/*.{js,ts,tsx}'], - rules: { - 'react-hooks/exhaustive-deps': 'off', - }, - }, { files: ['src/plugins/es_ui_shared/**/*.{js,ts,tsx}'], rules: { @@ -552,29 +532,6 @@ module.exports = { }, }, - /** - * Graph overrides - */ - { - files: ['x-pack/legacy/plugins/graph/**/*.js'], - globals: { - angular: true, - $: true, - }, - rules: { - 'block-scoped-var': 'off', - camelcase: 'off', - eqeqeq: 'off', - 'guard-for-in': 'off', - 'new-cap': 'off', - 'no-loop-func': 'off', - 'no-redeclare': 'off', - 'no-shadow': 'off', - 'no-unused-vars': 'off', - 'one-var': 'off', - }, - }, - /** * ML overrides */ @@ -771,7 +728,7 @@ module.exports = { * Lens overrides */ { - files: ['x-pack/legacy/plugins/lens/**/*.ts', 'x-pack/legacy/plugins/lens/**/*.tsx'], + files: ['x-pack/legacy/plugins/lens/**/*.{ts,tsx}', 'x-pack/plugins/lens/**/*.{ts,tsx}'], rules: { '@typescript-eslint/no-explicit-any': 'error', }, @@ -885,8 +842,10 @@ module.exports = { * TSVB overrides */ { - files: ['src/legacy/core_plugins/metrics/**/*.js'], - excludedFiles: 'src/legacy/core_plugins/metrics/index.js', + files: [ + 'src/plugins/vis_type_timeseries/**/*.{js,ts,tsx}', + 'src/legacy/core_plugins/vis_type_timeseries/**/*.{js,ts,tsx}', + ], rules: { 'import/no-default-export': 'error', }, diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 1666c30c75a61..1c51fc01201b5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -16,18 +16,20 @@ /src/legacy/core_plugins/metrics/ @elastic/kibana-app /src/legacy/core_plugins/vis_type_vislib/ @elastic/kibana-app /src/legacy/core_plugins/vis_type_xy/ @elastic/kibana-app -# Exclude tutorials folder for now because they are not owned by Kibana app and most will move out soon -/src/plugins/home/public @elastic/kibana-app -/src/plugins/home/server/*.ts @elastic/kibana-app -/src/plugins/home/server/services/ @elastic/kibana-app -# Exclude tutorial resources folder for now because they are not owned by Kibana app and most will move out soon -/src/legacy/core_plugins/kibana/public/home/*.ts @elastic/kibana-app -/src/legacy/core_plugins/kibana/public/home/*.scss @elastic/kibana-app -/src/legacy/core_plugins/kibana/public/home/np_ready/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/timelion/ @elastic/kibana-app -/src/plugins/dev_tools/ @elastic/kibana-app /src/plugins/dashboard/ @elastic/kibana-app +/src/plugins/discover/ @elastic/kibana-app + +# Core UI +# Exclude tutorials folder for now because they are not owned by Kibana app and most will move out soon +/src/plugins/home/public @elastic/kibana-core-ui +/src/plugins/home/server/*.ts @elastic/kibana-core-ui +/src/plugins/home/server/services/ @elastic/kibana-core-ui +# Exclude tutorial resources folder for now because they are not owned by Kibana app and most will move out soon +/src/legacy/core_plugins/kibana/public/home/*.ts @elastic/kibana-core-ui +/src/legacy/core_plugins/kibana/public/home/*.scss @elastic/kibana-core-ui +/src/legacy/core_plugins/kibana/public/home/np_ready/ @elastic/kibana-core-ui # App Architecture /examples/url_generators_examples/ @elastic/kibana-app-arch @@ -86,9 +88,8 @@ /x-pack/test/functional/apps/machine_learning/ @elastic/ml-ui /x-pack/test/functional/services/machine_learning/ @elastic/ml-ui /x-pack/test/functional/services/ml.ts @elastic/ml-ui -# ML team owns the transform plugin, ES team added here for visibility -# because the plugin lives in Kibana's Elasticsearch management section. -/x-pack/plugins/transform/ @elastic/ml-ui @elastic/es-ui +# ML team owns and maintains the transform plugin despite it living in the Elasticsearch management section. +/x-pack/plugins/transform/ @elastic/ml-ui /x-pack/test/functional/apps/transform/ @elastic/ml-ui /x-pack/test/functional/services/transform_ui/ @elastic/ml-ui /x-pack/test/functional/services/transform.ts @elastic/ml-ui @@ -176,6 +177,7 @@ **/*.scss @elastic/kibana-design # Elasticsearch UI +/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 diff --git a/.i18nrc.json b/.i18nrc.json index c293b3103a39c..19d361aed9344 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -24,6 +24,7 @@ "src/legacy/core_plugins/management", "src/plugins/management" ], + "indexPatternManagement": "src/plugins/index_pattern_management", "advancedSettings": "src/plugins/advanced_settings", "kibana_legacy": "src/plugins/kibana_legacy", "kibana_react": "src/legacy/core_plugins/kibana_react", @@ -43,7 +44,7 @@ "tileMap": "src/legacy/core_plugins/tile_map", "timelion": ["src/legacy/core_plugins/timelion", "src/legacy/core_plugins/vis_type_timelion", "src/plugins/timelion"], "uiActions": "src/plugins/ui_actions", - "visDefaultEditor": "src/legacy/core_plugins/vis_default_editor", + "visDefaultEditor": "src/plugins/vis_default_editor", "visTypeMarkdown": "src/legacy/core_plugins/vis_type_markdown", "visTypeMetric": "src/legacy/core_plugins/vis_type_metric", "visTypeTable": "src/legacy/core_plugins/vis_type_table", diff --git a/docs/api/role-management/put.asciidoc b/docs/api/role-management/put.asciidoc index 59e6bc8d37eec..993d75780c87c 100644 --- a/docs/api/role-management/put.asciidoc +++ b/docs/api/role-management/put.asciidoc @@ -17,6 +17,7 @@ experimental[] Create a new {kib} role, or update the attributes of an existing To use the create or update role API, you must have the `manage_security` cluster privilege. +[role="child_attributes"] [[role-management-api-response-body]] ==== Request body @@ -29,8 +30,11 @@ To use the create or update role API, you must have the `manage_security` cluste {ref}/defining-roles.html[Defining roles]. `kibana`:: - (list) Objects that specify the <> for the role: - + (list) Objects that specify the <> for the role. ++ +.Properties of `kibana` +[%collapsible%open] +===== `base` ::: (Optional, list) A base privilege. When specified, the base must be `["all"]` or `["read"]`. When the `base` privilege is specified, you are unable to use the `feature` section. @@ -45,6 +49,7 @@ To use the create or update role API, you must have the `manage_security` cluste `spaces` ::: (list) The spaces to apply the privileges to. To grant access to all spaces, set to `["*"]`, or omit the value. +===== [[role-management-api-put-response-codes]] ==== Response code @@ -52,7 +57,7 @@ To use the create or update role API, you must have the `manage_security` cluste `204`:: Indicates a successful call. -===== Examples +==== Examples Grant access to various features in all spaces: diff --git a/docs/api/spaces-management/copy_saved_objects.asciidoc b/docs/api/spaces-management/copy_saved_objects.asciidoc index e23a137485b2d..4822e7f624302 100644 --- a/docs/api/spaces-management/copy_saved_objects.asciidoc +++ b/docs/api/spaces-management/copy_saved_objects.asciidoc @@ -26,6 +26,7 @@ You can request to overwrite any objects that already exist in the target space `space_id`:: (Optional, string) The ID of the space that contains the saved objects you want to copy. When `space_id` is unspecified in the URL, the default space is used. +[role="child_attributes"] [[spaces-api-copy-saved-objects-request-body]] ==== {api-request-body-title} @@ -34,10 +35,16 @@ You can request to overwrite any objects that already exist in the target space `objects`:: (Required, object array) The saved objects to copy. ++ +.Properties of `objects` +[%collapsible%open] +===== `type`::: (Required, string) The saved object type. + `id`::: (Required, string) The saved object ID. +===== `includeReferences`:: (Optional, boolean) When set to `true`, all saved objects related to the specified saved objects will also be copied into the target spaces. The default value is `false`. @@ -45,27 +52,43 @@ You can request to overwrite any objects that already exist in the target space `overwrite`:: (Optional, boolean) When set to `true`, all conflicts are automatically overidden. When a saved object with a matching `type` and `id` exists in the target space, that version is replaced with the version from the source space. The default value is `false`. - +[role="child_attributes"] [[spaces-api-copy-saved-objects-response-body]] ==== {api-response-body-title} ``:: (object) An object that describes the result of the copy operation for the space. Includes the dynamic keys in the response. ++ +.Properties of `` +[%collapsible%open] +===== `success`::: (boolean) The copy operation was successful. When set to `false`, some objects may have been copied. For additional information, refer to the `successCount` and `errors` properties. + `successCount`::: (number) The number of objects that successfully copied. + `errors`::: - (Optional, array) The errors that occurred during the copy operation. When errors are reported, the `success` flag is set to `false`.v + (Optional, array) The errors that occurred during the copy operation. When errors are reported, the `success` flag is set to `false`. ++ +.Properties of `errors` +[%collapsible%open] +====== `id`:::: (string) The saved object ID that failed to copy. `type`:::: (string) The type of saved object that failed to copy. `error`:::: (object) The error that caused the copy operation to fail. ++ +.Properties of `error` +[%collapsible%open] +======= `type`::::: (string) The type of error. For example, `unsupported_type`, `missing_references`, or `unknown`. Errors marked as `conflict` may be resolved by using the <>. - +======= +====== +===== [[spaces-api-copy-saved-objects-example]] ==== {api-examples-title} diff --git a/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc b/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc index 8e874bb9f94e5..565d12513815b 100644 --- a/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc +++ b/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc @@ -25,51 +25,89 @@ Execute the <>, w `space_id`:: (Optional, string) The ID of the space that contains the saved objects you want to copy. When `space_id` is unspecified in the URL, the default space is used. The `space_id` must be the same value used during the failed <> operation. +[role="child_attributes"] [[spaces-api-resolve-copy-saved-objects-conflicts-request-body]] ==== {api-request-body-title} `objects`:: (Required, object array) The saved objects to copy. The `objects` must be the same values used during the failed <> operation. ++ +.Properties of `objects` +[%collapsible%open] +===== `type`::: (Required, string) The saved object type. + `id`::: (Required, string) The saved object ID. +===== `includeReferences`:: (Optional, boolean) When set to `true`, all saved objects related to the specified saved objects are copied into the target spaces. The `includeReferences` must be the same values used during the failed <> operation. The default value is `false`. `retries`:: (Required, object) The retry operations to attempt. Object keys represent the target space IDs. ++ +.Properties of `retries` +[%collapsible%open] +===== ``::: (Required, array) The errors to resolve for the specified ``. ++ + +.Properties of `` +[%collapsible%open] +====== `type`:::: (Required, string) The saved object type. `id`:::: (Required, string) The saved object ID. `overwrite`:::: (Required, boolean) When set to `true`, the saved object from the source space (desigated by the <>) overwrites the conflicting object in the destination space. When set to `false`, this does nothing. +====== +===== - +[role="child_attributes"] [[spaces-api-resolve-copy-saved-objects-conflicts-response-body]] ==== {api-response-body-title} ``:: (object) An object that describes the result of the copy operation for the space. Includes the dynamic keys in the response. ++ +.Properties of `` +[%collapsible%open] +===== `success`::: (boolean) The copy operation was successful. When set to `false`, some objects may have been copied. For additional information, refer to the `successCount` and `errors` properties. + `successCount`::: (number) The number of objects that successfully copied. + `errors`::: (Optional, array) The errors that occurred during the copy operation. When errors are reported, the `success` flag is set to `false`. ++ + +.Properties of `errors` +[%collapsible%open] +====== `id`:::: (string) The saved object ID that failed to copy. + `type`:::: (string) The type of saved object that failed to copy. + `error`:::: (object) The error that caused the copy operation to fail. - `type`::::: - (string) The type of error. For example, `unsupported_type`, `missing_references`, or `unknown`. ++ +.Properties of `error` +[%collapsible%open] +======= + `type`:::: + (string) The type of error. For example, `unsupported_type`, `missing_references`, or `unknown`. +======= +====== +===== [[spaces-api-resolve-copy-saved-objects-conflicts-example]] ==== {api-examples-title} diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc index b520cc46bef8d..a8f4f4bf0baaa 100644 --- a/docs/apm/api.asciidoc +++ b/docs/apm/api.asciidoc @@ -38,17 +38,22 @@ The following Agent configuration APIs are available: `PUT /api/apm/settings/agent-configuration` +[role="child_attributes"] [[apm-update-config-req-body]] ===== Request body `service`:: (required, object) Service identifying the configuration to create or update. - ++ +.Properties of `service` +[%collapsible%open] +====== `name` ::: (required, string) Name of service `environment` ::: (optional, string) Environment of service +====== `settings`:: (required) Key/value object with settings and their corresponding value. @@ -90,16 +95,21 @@ PUT /api/apm/settings/agent-configuration `DELETE /api/apm/settings/agent-configuration` +[role="child_attributes"] [[apm-delete-config-req-body]] ===== Request body `service`:: (required, object) Service identifying the configuration to delete - ++ +.Properties of `service` +[%collapsible%open] +====== `name` ::: (required, string) Name of service `environment` ::: (optional, string) Environment of service +====== [[apm-delete-config-example]] @@ -201,17 +211,22 @@ GET /api/apm/settings/agent-configuration `POST /api/apm/settings/agent-configuration/search` +[role="child_attributes"] [[apm-search-config-req-body]] ===== Request body `service`:: (required, object) Service identifying the configuration. - ++ +.Properties of `service` +[%collapsible%open] +====== `name` ::: (required, string) Name of service `environment` ::: (optional, string) Environment of service +====== `etag`:: (required) etag is sent by the agent to indicate the etag of the last successfully applied configuration. If the etag matches an existing configuration its `applied_by_agent` property will be set to `true`. Every time a configuration is edited `applied_by_agent` is reset to `false`. diff --git a/docs/apm/images/service-maps-java.png b/docs/apm/images/service-maps-java.png new file mode 100644 index 0000000000000..e1a42f4c76e12 Binary files /dev/null and b/docs/apm/images/service-maps-java.png differ diff --git a/docs/apm/images/service-maps.png b/docs/apm/images/service-maps.png new file mode 100644 index 0000000000000..454ae9bb720fb Binary files /dev/null and b/docs/apm/images/service-maps.png differ diff --git a/docs/apm/service-maps.asciidoc b/docs/apm/service-maps.asciidoc new file mode 100644 index 0000000000000..e0d84f33b4dcb --- /dev/null +++ b/docs/apm/service-maps.asciidoc @@ -0,0 +1,48 @@ +[[service-maps]] +=== Service maps + +beta::[] + +A service map is a real-time diagram of the interactions occurring in your application’s architecture. +It allows you to easily visualize data flow and high-level statistics, like average transaction duration, +requests per minute, errors per minute, and metrics, allowing you to quickly assess the status of your services. + +Our beta offering creates two types of service maps: + +* Global: All services and connections are shown. +* Service-specific: Selecting a specific service will highlight it's connections. + +[role="screenshot"] +image::apm/images/service-maps.png[Example view of service maps in the APM app in Kibana] + +[float] +[[visualize-your-architecture]] +=== Visualize your architecture + +Select the **Service Map** tab to get started. +By default, all services and connections are shown. +Whether your onboarding a new engineer, or just trying to grasp the big picture, +click around, zoom in and out, and begin to visualize how your services are connected. + +If there's a specific service that interests you, select that service to highlight its connections. +Clicking **Focus map** will refocus the map on that specific service and lock the connection highlighting. +From here, select **Service Details**, or click on the **Transaction** tab to jump to the Transaction overview. +You can also use the tabs at the top of the page to easily jump to the **Errors** or **Metrics** overview. + +While it's not possible to query in service maps, it is possible to filter by environment. +This can be useful if you have two or more services, in separate environments, but with the same name. +Use the environment drop down to only see the data you're interested in, like `dev` or `production`. + +[role="screenshot"] +image::apm/images/service-maps-java.png[Example view of service maps with Java highlighted in the APM app in Kibana] + +[float] +[[service-maps-legend]] +=== Legend + +Nodes appear on the map in one of two shapes: + +* **Circle**: Instrumented services. Interior icons are based on the language of the agent used. +* **Diamond**: Databases, external, and messaging. Interior icons represent the generic type, +with specific icons for known entities, like Elasticsearch. +Type and subtype are based on `span.type`, and `span.subtype`. diff --git a/docs/apm/transactions.asciidoc b/docs/apm/transactions.asciidoc index 536ab2ec29c80..5c92afa55109d 100644 --- a/docs/apm/transactions.asciidoc +++ b/docs/apm/transactions.asciidoc @@ -103,9 +103,7 @@ The number of requests per bucket is displayed when hovering over the graph, and [role="screenshot"] image::apm/images/apm-transaction-duration-dist.png[Example view of transactions duration distribution graph] -Most of the requests fall into buckets on the left side of the graph, -with a long tail of smaller buckets to the right. -This is a typical distribution, and indicates most of our requests were served quickly - awesome! +This graph shows a typical distribution, and indicates most of our requests were served quickly - awesome! It's the requests on the right, the ones taking longer than average, that we probably want to focus on. When you select one of these buckets, you're presented with up to ten trace samples. diff --git a/docs/apm/using-the-apm-ui.asciidoc b/docs/apm/using-the-apm-ui.asciidoc index 1361dc046e3b1..b1b7ed7307986 100644 --- a/docs/apm/using-the-apm-ui.asciidoc +++ b/docs/apm/using-the-apm-ui.asciidoc @@ -31,6 +31,8 @@ include::transactions.asciidoc[] include::spans.asciidoc[] +include::service-maps.asciidoc[] + include::errors.asciidoc[] include::metrics.asciidoc[] diff --git a/docs/canvas/canvas-elements.asciidoc b/docs/canvas/canvas-elements.asciidoc index 163579d5763b2..a25460a20eb50 100644 --- a/docs/canvas/canvas-elements.asciidoc +++ b/docs/canvas/canvas-elements.asciidoc @@ -138,7 +138,9 @@ To apply CSS overrides: . Next to *Element style*, click *+*, then select *CSS*. -. Enter the *CSS*. For example, to center the Markdown element, enter: +. Enter the *CSS*. ++ +For example, to center the Markdown element, enter: + [source,text] -------------------------------------------------- diff --git a/docs/canvas/canvas-present-workpad.asciidoc b/docs/canvas/canvas-present-workpad.asciidoc index 486686cd857b5..9cd4ecc9519e1 100644 --- a/docs/canvas/canvas-present-workpad.asciidoc +++ b/docs/canvas/canvas-present-workpad.asciidoc @@ -8,7 +8,7 @@ When you are ready to present your workpad, use and enable the presentation opti [[view-fullscreen-mode]] ==== View your workpad in fullscreen mode -In the upper left corner, click the *Enter fullscreen mode* icon. +Click the *Enter fullscreen mode* icon. [role="screenshot"] image::images/canvas-fullscreen.png[Fullscreen mode] @@ -19,7 +19,7 @@ image::images/canvas-fullscreen.png[Fullscreen mode] Automatically cycle through your workpads pages in fullscreen mode. -. In the upper left corner, click the *Control settings* icon. +. Click the *Control settings* icon. . Under *Change cycling interval*, select the interval you want to use. + diff --git a/docs/canvas/canvas-share-workpad.asciidoc b/docs/canvas/canvas-share-workpad.asciidoc index dbba12865b8ca..ee29926914ad6 100644 --- a/docs/canvas/canvas-share-workpad.asciidoc +++ b/docs/canvas/canvas-share-workpad.asciidoc @@ -10,7 +10,7 @@ When you've finished your workpad, you can share it outside of {kib}. Create a JSON file of your workpad that you can export outside of {kib}. -. From your workpad, click the *Share workpad* icon in the upper left corner. +. From your workpad, click the *Share workpad* icon. . Select *Download as JSON*. + @@ -27,7 +27,7 @@ If you have a license that supports the {report-features}, you can create a PDF For more information, refer to <>. -. From your workpad, click the *Share workpad* icon in the upper left corner, then select *PDF reports*. +. From your workpad, click the *Share workpad* icon, then select *PDF reports*. . Click *Generate PDF*. + @@ -42,7 +42,7 @@ If you have a license that supports the {report-features}, you can create a POST For more information, refer to <>. -. From your workpad, click the *Share workpad* icon in the upper left corner, then select *PDF reports*. +. From your workpad, click the *Share workpad* icon, then select *PDF reports*. . Click *Copy POST URL*. + @@ -55,7 +55,7 @@ image::images/canvas-create-URL.gif[Create POST URL] beta[] Canvas allows you to create _shareables_, which are workpads that you download and securely share on any website. To customize the behavior of the workpad on your website, you can choose to autoplay the pages or hide the workpad toolbar. -. From your workpad, click the *Share this workpad* icon in the upper left corner, then select *Share on a website*. +. From your workpad, click the *Share this workpad* icon, then select *Share on a website*. . On the *Share on a website* pane, follow the instructions. diff --git a/docs/dev-tools/searchprofiler/getting-started.asciidoc b/docs/dev-tools/searchprofiler/getting-started.asciidoc index 2360e4c28ff15..4a87d4b84b783 100644 --- a/docs/dev-tools/searchprofiler/getting-started.asciidoc +++ b/docs/dev-tools/searchprofiler/getting-started.asciidoc @@ -3,10 +3,10 @@ === Getting Started The {searchprofiler} is automatically enabled in {kib}. Go to *Dev Tools > Search Profiler* -to get started. +to get started. {searchprofiler} displays the names of the indices searched, the shards in each index, -and how long it took for the query to complete. To try it out, replace the default `match_all` query +and how long it took for the query to complete. To try it out, replace the default `match_all` query with the query you want to profile and click *Profile*. The following example shows the results of profiling the `match_all` query. @@ -29,8 +29,8 @@ While the Cumulative Time metric is useful for comparing the performance of your indices and shards, it doesn't necessarily represent the actual physical query times. ==== -You can select the name of the shard and then click *View details* to see more profiling information, -including details about the query component(s) that ran on the shard, as well as the timing +You can select the name of the shard and then click *View details* to see more profiling information, +including details about the query component(s) that ran on the shard, as well as the timing breakdown of low-level Lucene methods. For more information, see {ref}/search-profile.html#profiling-queries[Profiling queries]. [float] @@ -40,10 +40,10 @@ By default, all queries executed by the {searchprofiler} are sent to `GET /_search`. It searches across your entire cluster (all indices, all types). If you need to query a specific index or type (or several), you can use the Index -and Type filters at the top left. +and Type filters. In the following example, the query is executed against the indices `test` and `kibana_1` and the type `my_type`. This is equivalent making a request to `GET /test,kibana_1/my_type/_search`. [role="screenshot"] -image::dev-tools/searchprofiler/images/filter.png["Filtering by index and type"] \ No newline at end of file +image::dev-tools/searchprofiler/images/filter.png["Filtering by index and type"] diff --git a/docs/developer/plugin/development-plugin-feature-registration.asciidoc b/docs/developer/plugin/development-plugin-feature-registration.asciidoc index ca61e5309ce85..4702204196bf2 100644 --- a/docs/developer/plugin/development-plugin-feature-registration.asciidoc +++ b/docs/developer/plugin/development-plugin-feature-registration.asciidoc @@ -45,10 +45,15 @@ Registering a feature consists of the following fields. For more information, co |An array of applications this feature enables. Typically, all of your plugin's apps (from `uiExports`) will be included here. |`privileges` (required) -|{repo}blob/{branch}/x-pack/plugins/features/server/feature.ts[`FeatureWithAllOrReadPrivileges`]. +|{repo}blob/{branch}/x-pack/plugins/features/common/feature.ts[`FeatureConfig`]. |See <> and <> |The set of privileges this feature requires to function. +|`subFeatures` (optional) +|{repo}blob/{branch}/x-pack/plugins/features/common/feature.ts[`FeatureConfig`]. +|See <> +|The set of subfeatures that enables finer access control than the `all` and `read` feature privileges. These options are only available in the Gold subscription level and higher. + |`icon` |`string` |"discoverApp" @@ -192,3 +197,78 @@ server.route({ } }); ----------- + +[[example-3-discover]] +==== Example 3: Discover + +Discover takes advantage of subfeature privileges to allow fine-grained access control. In this example, +a single "Create Short URLs" subfeature privilege is defined, which allows users to grant access to this feature without having to grant the `all` privilege to Discover. In other words, you can grant `read` access to Discover, and also grant the ability to create short URLs. + +["source","javascript"] +----------- +init(server) { + const xpackMainPlugin = server.plugins.xpack_main; + xpackMainPlugin.registerFeature({ + { + id: 'discover', + name: i18n.translate('xpack.features.discoverFeatureName', { + defaultMessage: 'Discover', + }), + order: 100, + icon: 'discoverApp', + navLinkId: 'kibana:discover', + app: ['kibana'], + catalogue: ['discover'], + privileges: { + all: { + app: ['kibana'], + catalogue: ['discover'], + savedObject: { + all: ['search', 'query'], + read: ['index-pattern'], + }, + ui: ['show', 'save', 'saveQuery'], + }, + read: { + app: ['kibana'], + catalogue: ['discover'], + savedObject: { + all: [], + read: ['index-pattern', 'search', 'query'], + }, + ui: ['show'], + }, + }, + subFeatures: [ + { + name: i18n.translate('xpack.features.ossFeatures.discoverShortUrlSubFeatureName', { + defaultMessage: 'Short URLs', + }), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + id: 'url_create', + name: i18n.translate( + 'xpack.features.ossFeatures.discoverCreateShortUrlPrivilegeName', + { + defaultMessage: 'Create Short URLs', + } + ), + includeIn: 'all', + savedObject: { + all: ['url'], + read: [], + }, + ui: ['createShortUrl'], + }, + ], + }, + ], + }, + ], + } + }); +} +----------- diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.md index 29fdc37a81176..c10b460da8b4f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.md @@ -23,6 +23,7 @@ export interface CoreSetupHttpServiceSetup | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) | | [metrics](./kibana-plugin-core-server.coresetup.metrics.md) | MetricsServiceSetup | [MetricsServiceSetup](./kibana-plugin-core-server.metricsservicesetup.md) | | [savedObjects](./kibana-plugin-core-server.coresetup.savedobjects.md) | SavedObjectsServiceSetup | [SavedObjectsServiceSetup](./kibana-plugin-core-server.savedobjectsservicesetup.md) | +| [status](./kibana-plugin-core-server.coresetup.status.md) | StatusServiceSetup | [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) | | [uiSettings](./kibana-plugin-core-server.coresetup.uisettings.md) | UiSettingsServiceSetup | [UiSettingsServiceSetup](./kibana-plugin-core-server.uisettingsservicesetup.md) | | [uuid](./kibana-plugin-core-server.coresetup.uuid.md) | UuidServiceSetup | [UuidServiceSetup](./kibana-plugin-core-server.uuidservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.status.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.status.md new file mode 100644 index 0000000000000..f5ea627a9f008 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.status.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreSetup](./kibana-plugin-core-server.coresetup.md) > [status](./kibana-plugin-core-server.coresetup.status.md) + +## CoreSetup.status property + +[StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) + +Signature: + +```typescript +status: StatusServiceSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.corestatus.elasticsearch.md b/docs/development/core/server/kibana-plugin-core-server.corestatus.elasticsearch.md new file mode 100644 index 0000000000000..b41e7020c38e9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.corestatus.elasticsearch.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreStatus](./kibana-plugin-core-server.corestatus.md) > [elasticsearch](./kibana-plugin-core-server.corestatus.elasticsearch.md) + +## CoreStatus.elasticsearch property + +Signature: + +```typescript +elasticsearch: ServiceStatus; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.corestatus.md b/docs/development/core/server/kibana-plugin-core-server.corestatus.md new file mode 100644 index 0000000000000..3fde86a18c58b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.corestatus.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreStatus](./kibana-plugin-core-server.corestatus.md) + +## CoreStatus interface + +Status of core services. + +Signature: + +```typescript +export interface CoreStatus +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [elasticsearch](./kibana-plugin-core-server.corestatus.elasticsearch.md) | ServiceStatus | | +| [savedObjects](./kibana-plugin-core-server.corestatus.savedobjects.md) | ServiceStatus | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.corestatus.savedobjects.md b/docs/development/core/server/kibana-plugin-core-server.corestatus.savedobjects.md new file mode 100644 index 0000000000000..d554c6f70d720 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.corestatus.savedobjects.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreStatus](./kibana-plugin-core-server.corestatus.md) > [savedObjects](./kibana-plugin-core-server.corestatus.savedobjects.md) + +## CoreStatus.savedObjects property + +Signature: + +```typescript +savedObjects: ServiceStatus; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.incompatiblenodes.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.incompatiblenodes.md new file mode 100644 index 0000000000000..f8a45fe9a5a9c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.incompatiblenodes.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) > [incompatibleNodes](./kibana-plugin-core-server.elasticsearchstatusmeta.incompatiblenodes.md) + +## ElasticsearchStatusMeta.incompatibleNodes property + +Signature: + +```typescript +incompatibleNodes: NodesVersionCompatibility['incompatibleNodes']; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.md new file mode 100644 index 0000000000000..2398410fa4b84 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) + +## ElasticsearchStatusMeta interface + + +Signature: + +```typescript +export interface ElasticsearchStatusMeta +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [incompatibleNodes](./kibana-plugin-core-server.elasticsearchstatusmeta.incompatiblenodes.md) | NodesVersionCompatibility['incompatibleNodes'] | | +| [warningNodes](./kibana-plugin-core-server.elasticsearchstatusmeta.warningnodes.md) | NodesVersionCompatibility['warningNodes'] | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.warningnodes.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.warningnodes.md new file mode 100644 index 0000000000000..7374ccd9e7fa8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchstatusmeta.warningnodes.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) > [warningNodes](./kibana-plugin-core-server.elasticsearchstatusmeta.warningnodes.md) + +## ElasticsearchStatusMeta.warningNodes property + +Signature: + +```typescript +warningNodes: NodesVersionCompatibility['warningNodes']; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 793684c1b3796..accab9bf0cb36 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -66,6 +66,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ContextSetup](./kibana-plugin-core-server.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | | [CoreSetup](./kibana-plugin-core-server.coresetup.md) | Context passed to the plugins setup method. | | [CoreStart](./kibana-plugin-core-server.corestart.md) | Context passed to the plugins start method. | +| [CoreStatus](./kibana-plugin-core-server.corestatus.md) | Status of core services. | | [CustomHttpResponseOptions](./kibana-plugin-core-server.customhttpresponseoptions.md) | HTTP response parameters for a response with adjustable status code. | | [DeprecationAPIClientParams](./kibana-plugin-core-server.deprecationapiclientparams.md) | | | [DeprecationAPIResponse](./kibana-plugin-core-server.deprecationapiresponse.md) | | @@ -75,6 +76,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ElasticsearchError](./kibana-plugin-core-server.elasticsearcherror.md) | | | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | | | [ElasticsearchServiceStart](./kibana-plugin-core-server.elasticsearchservicestart.md) | | +| [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) | | | [EnvironmentMode](./kibana-plugin-core-server.environmentmode.md) | | | [ErrorHttpResponseOptions](./kibana-plugin-core-server.errorhttpresponseoptions.md) | HTTP response parameters | | [FakeRequest](./kibana-plugin-core-server.fakerequest.md) | Fake request object created manually by Kibana plugins. | @@ -101,6 +103,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [LoggerFactory](./kibana-plugin-core-server.loggerfactory.md) | The single purpose of LoggerFactory interface is to define a way to retrieve a context-based logger instance. | | [LogMeta](./kibana-plugin-core-server.logmeta.md) | Contextual metadata | | [MetricsServiceSetup](./kibana-plugin-core-server.metricsservicesetup.md) | APIs to retrieves metrics gathered and exposed by the core platform. | +| [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) | | | [OnPostAuthToolkit](./kibana-plugin-core-server.onpostauthtoolkit.md) | A tool set defining an outcome of OnPostAuth interceptor for incoming request. | | [OnPreAuthToolkit](./kibana-plugin-core-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. | | [OnPreResponseExtensions](./kibana-plugin-core-server.onpreresponseextensions.md) | Additional data to extend a response. | @@ -162,15 +165,18 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsResolveImportErrorsOptions](./kibana-plugin-core-server.savedobjectsresolveimporterrorsoptions.md) | Options to control the "resolve import" operation. | | [SavedObjectsServiceSetup](./kibana-plugin-core-server.savedobjectsservicesetup.md) | Saved Objects is Kibana's data persistence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for registering Saved Object types, creating and registering Saved Object client wrappers and factories. | | [SavedObjectsServiceStart](./kibana-plugin-core-server.savedobjectsservicestart.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. | +| [SavedObjectStatusMeta](./kibana-plugin-core-server.savedobjectstatusmeta.md) | Meta information about the SavedObjectService's status. Available to plugins via [CoreSetup.status](./kibana-plugin-core-server.coresetup.status.md). | | [SavedObjectsType](./kibana-plugin-core-server.savedobjectstype.md) | | | [SavedObjectsTypeManagementDefinition](./kibana-plugin-core-server.savedobjectstypemanagementdefinition.md) | Configuration options for the [type](./kibana-plugin-core-server.savedobjectstype.md)'s management section. | | [SavedObjectsTypeMappingDefinition](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) | Describe a saved object type mapping. | | [SavedObjectsUpdateOptions](./kibana-plugin-core-server.savedobjectsupdateoptions.md) | | | [SavedObjectsUpdateResponse](./kibana-plugin-core-server.savedobjectsupdateresponse.md) | | +| [ServiceStatus](./kibana-plugin-core-server.servicestatus.md) | The current status of a service at a point in time. | | [SessionCookieValidationResult](./kibana-plugin-core-server.sessioncookievalidationresult.md) | Return type from a function to validate cookie contents. | | [SessionStorage](./kibana-plugin-core-server.sessionstorage.md) | Provides an interface to store and retrieve data across requests. | | [SessionStorageCookieOptions](./kibana-plugin-core-server.sessionstoragecookieoptions.md) | Configuration used to create HTTP session storage based on top of cookie mechanism. | | [SessionStorageFactory](./kibana-plugin-core-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request | +| [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) | API for accessing status of Core and this plugin's dependencies as well as for customizing this plugin's status. | | [StringValidationRegex](./kibana-plugin-core-server.stringvalidationregex.md) | StringValidation with regex object | | [StringValidationRegexString](./kibana-plugin-core-server.stringvalidationregexstring.md) | StringValidation as regex string | | [UiSettingsParams](./kibana-plugin-core-server.uisettingsparams.md) | UiSettings parameters defined by the plugins. | @@ -184,6 +190,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | Variable | Description | | --- | --- | | [kibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) | Set of helpers used to create KibanaResponse to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-core-server.requesthandler.md) execution. | +| [ServiceStatusLevels](./kibana-plugin-core-server.servicestatuslevels.md) | The current "level" of availability of a service. | | [validBodyOutput](./kibana-plugin-core-server.validbodyoutput.md) | The set of valid body.output | ## Type Aliases @@ -256,6 +263,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsClientWrapperFactory](./kibana-plugin-core-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | | [SavedObjectsFieldMapping](./kibana-plugin-core-server.savedobjectsfieldmapping.md) | Describe a [saved object type mapping](./kibana-plugin-core-server.savedobjectstypemappingdefinition.md) field.Please refer to [elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html) For the mapping documentation | | [ScopeableRequest](./kibana-plugin-core-server.scopeablerequest.md) | A user credentials container. It accommodates the necessary auth credentials to impersonate the current user.See [KibanaRequest](./kibana-plugin-core-server.kibanarequest.md). | +| [ServiceStatusLevel](./kibana-plugin-core-server.servicestatuslevel.md) | A convenience type that represents the union of each value in [ServiceStatusLevels](./kibana-plugin-core-server.servicestatuslevels.md). | | [SharedGlobalConfig](./kibana-plugin-core-server.sharedglobalconfig.md) | | | [StartServicesAccessor](./kibana-plugin-core-server.startservicesaccessor.md) | Allows plugins to get access to APIs available in start inside async handlers. Promise will not resolve until Core and plugin dependencies have completed start. This should only be used inside handlers registered during setup that will only be executed after start lifecycle. | | [StringValidation](./kibana-plugin-core-server.stringvalidation.md) | Allows regex objects or a regex string | diff --git a/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.incompatiblenodes.md b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.incompatiblenodes.md new file mode 100644 index 0000000000000..8e7298d28801c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.incompatiblenodes.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) > [incompatibleNodes](./kibana-plugin-core-server.nodesversioncompatibility.incompatiblenodes.md) + +## NodesVersionCompatibility.incompatibleNodes property + +Signature: + +```typescript +incompatibleNodes: NodeInfo[]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.iscompatible.md b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.iscompatible.md new file mode 100644 index 0000000000000..82a4800a3b4b6 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.iscompatible.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) > [isCompatible](./kibana-plugin-core-server.nodesversioncompatibility.iscompatible.md) + +## NodesVersionCompatibility.isCompatible property + +Signature: + +```typescript +isCompatible: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.kibanaversion.md b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.kibanaversion.md new file mode 100644 index 0000000000000..347f2d3474b11 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.kibanaversion.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) > [kibanaVersion](./kibana-plugin-core-server.nodesversioncompatibility.kibanaversion.md) + +## NodesVersionCompatibility.kibanaVersion property + +Signature: + +```typescript +kibanaVersion: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.md b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.md new file mode 100644 index 0000000000000..6fcfacc3bc908 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) + +## NodesVersionCompatibility interface + +Signature: + +```typescript +export interface NodesVersionCompatibility +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [incompatibleNodes](./kibana-plugin-core-server.nodesversioncompatibility.incompatiblenodes.md) | NodeInfo[] | | +| [isCompatible](./kibana-plugin-core-server.nodesversioncompatibility.iscompatible.md) | boolean | | +| [kibanaVersion](./kibana-plugin-core-server.nodesversioncompatibility.kibanaversion.md) | string | | +| [message](./kibana-plugin-core-server.nodesversioncompatibility.message.md) | string | | +| [warningNodes](./kibana-plugin-core-server.nodesversioncompatibility.warningnodes.md) | NodeInfo[] | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.message.md b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.message.md new file mode 100644 index 0000000000000..415a7825ee2bf --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.message.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) > [message](./kibana-plugin-core-server.nodesversioncompatibility.message.md) + +## NodesVersionCompatibility.message property + +Signature: + +```typescript +message?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.warningnodes.md b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.warningnodes.md new file mode 100644 index 0000000000000..6c017e9fc800c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.nodesversioncompatibility.warningnodes.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [NodesVersionCompatibility](./kibana-plugin-core-server.nodesversioncompatibility.md) > [warningNodes](./kibana-plugin-core-server.nodesversioncompatibility.warningnodes.md) + +## NodesVersionCompatibility.warningNodes property + +Signature: + +```typescript +warningNodes: NodeInfo[]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstatusmeta.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstatusmeta.md new file mode 100644 index 0000000000000..3a0b23d18632f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstatusmeta.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectStatusMeta](./kibana-plugin-core-server.savedobjectstatusmeta.md) + +## SavedObjectStatusMeta interface + +Meta information about the SavedObjectService's status. Available to plugins via [CoreSetup.status](./kibana-plugin-core-server.coresetup.status.md). + +Signature: + +```typescript +export interface SavedObjectStatusMeta +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [migratedIndices](./kibana-plugin-core-server.savedobjectstatusmeta.migratedindices.md) | {
[status: string]: number;
skipped: number;
migrated: number;
} | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectstatusmeta.migratedindices.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectstatusmeta.migratedindices.md new file mode 100644 index 0000000000000..6a29623b2f122 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectstatusmeta.migratedindices.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectStatusMeta](./kibana-plugin-core-server.savedobjectstatusmeta.md) > [migratedIndices](./kibana-plugin-core-server.savedobjectstatusmeta.migratedindices.md) + +## SavedObjectStatusMeta.migratedIndices property + +Signature: + +```typescript +migratedIndices: { + [status: string]: number; + skipped: number; + migrated: number; + }; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatus.detail.md b/docs/development/core/server/kibana-plugin-core-server.servicestatus.detail.md new file mode 100644 index 0000000000000..fa369aa0bdfbb --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatus.detail.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatus](./kibana-plugin-core-server.servicestatus.md) > [detail](./kibana-plugin-core-server.servicestatus.detail.md) + +## ServiceStatus.detail property + +A more detailed description of the service status. + +Signature: + +```typescript +detail?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatus.documentationurl.md b/docs/development/core/server/kibana-plugin-core-server.servicestatus.documentationurl.md new file mode 100644 index 0000000000000..5ef8c1251a602 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatus.documentationurl.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatus](./kibana-plugin-core-server.servicestatus.md) > [documentationUrl](./kibana-plugin-core-server.servicestatus.documentationurl.md) + +## ServiceStatus.documentationUrl property + +A URL to open in a new tab about how to resolve or troubleshoot the problem. + +Signature: + +```typescript +documentationUrl?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatus.level.md b/docs/development/core/server/kibana-plugin-core-server.servicestatus.level.md new file mode 100644 index 0000000000000..551c10c9bff82 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatus.level.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatus](./kibana-plugin-core-server.servicestatus.md) > [level](./kibana-plugin-core-server.servicestatus.level.md) + +## ServiceStatus.level property + +The current availability level of the service. + +Signature: + +```typescript +level: ServiceStatusLevel; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatus.md b/docs/development/core/server/kibana-plugin-core-server.servicestatus.md new file mode 100644 index 0000000000000..d35fc951c57ff --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatus.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatus](./kibana-plugin-core-server.servicestatus.md) + +## ServiceStatus interface + +The current status of a service at a point in time. + +Signature: + +```typescript +export interface ServiceStatus | unknown = unknown> +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [detail](./kibana-plugin-core-server.servicestatus.detail.md) | string | A more detailed description of the service status. | +| [documentationUrl](./kibana-plugin-core-server.servicestatus.documentationurl.md) | string | A URL to open in a new tab about how to resolve or troubleshoot the problem. | +| [level](./kibana-plugin-core-server.servicestatus.level.md) | ServiceStatusLevel | The current availability level of the service. | +| [meta](./kibana-plugin-core-server.servicestatus.meta.md) | Meta | Any JSON-serializable data to be included in the HTTP API response. Useful for providing more fine-grained, machine-readable information about the service status. May include status information for underlying features. | +| [summary](./kibana-plugin-core-server.servicestatus.summary.md) | string | A high-level summary of the service status. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatus.meta.md b/docs/development/core/server/kibana-plugin-core-server.servicestatus.meta.md new file mode 100644 index 0000000000000..a48994daa5a4e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatus.meta.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatus](./kibana-plugin-core-server.servicestatus.md) > [meta](./kibana-plugin-core-server.servicestatus.meta.md) + +## ServiceStatus.meta property + +Any JSON-serializable data to be included in the HTTP API response. Useful for providing more fine-grained, machine-readable information about the service status. May include status information for underlying features. + +Signature: + +```typescript +meta?: Meta; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatus.summary.md b/docs/development/core/server/kibana-plugin-core-server.servicestatus.summary.md new file mode 100644 index 0000000000000..db90afd6f74a6 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatus.summary.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatus](./kibana-plugin-core-server.servicestatus.md) > [summary](./kibana-plugin-core-server.servicestatus.summary.md) + +## ServiceStatus.summary property + +A high-level summary of the service status. + +Signature: + +```typescript +summary: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatuslevel.md b/docs/development/core/server/kibana-plugin-core-server.servicestatuslevel.md new file mode 100644 index 0000000000000..5f995ff5e13e3 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatuslevel.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatusLevel](./kibana-plugin-core-server.servicestatuslevel.md) + +## ServiceStatusLevel type + +A convenience type that represents the union of each value in [ServiceStatusLevels](./kibana-plugin-core-server.servicestatuslevels.md). + +Signature: + +```typescript +export declare type ServiceStatusLevel = typeof ServiceStatusLevels[keyof typeof ServiceStatusLevels]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.servicestatuslevels.md b/docs/development/core/server/kibana-plugin-core-server.servicestatuslevels.md new file mode 100644 index 0000000000000..a66cec78c736b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.servicestatuslevels.md @@ -0,0 +1,37 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ServiceStatusLevels](./kibana-plugin-core-server.servicestatuslevels.md) + +## ServiceStatusLevels variable + +The current "level" of availability of a service. + +Signature: + +```typescript +ServiceStatusLevels: Readonly<{ + available: Readonly<{ + toString: () => "available"; + valueOf: () => 0; + }>; + degraded: Readonly<{ + toString: () => "degraded"; + valueOf: () => 1; + }>; + unavailable: Readonly<{ + toString: () => "unavailable"; + valueOf: () => 2; + }>; + critical: Readonly<{ + toString: () => "critical"; + valueOf: () => 3; + }>; +}> +``` + +## Remarks + +The values implement `valueOf` to allow for easy comparisons between status levels with <, >, etc. Higher values represent higher severities. Note that the default `Array.prototype.sort` implementation does not correctly sort these values. + +A snapshot serializer is available in `src/core/server/test_utils` to ease testing of these values with Jest. + diff --git a/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.core_.md b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.core_.md new file mode 100644 index 0000000000000..6662e68b44d36 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.core_.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) > [core$](./kibana-plugin-core-server.statusservicesetup.core_.md) + +## StatusServiceSetup.core$ property + +Current status for all Core services. + +Signature: + +```typescript +core$: Observable; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.md new file mode 100644 index 0000000000000..0551a217520ad --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.statusservicesetup.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [StatusServiceSetup](./kibana-plugin-core-server.statusservicesetup.md) + +## StatusServiceSetup interface + +API for accessing status of Core and this plugin's dependencies as well as for customizing this plugin's status. + +Signature: + +```typescript +export interface StatusServiceSetup +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [core$](./kibana-plugin-core-server.statusservicesetup.core_.md) | Observable<CoreStatus> | Current status for all Core services. | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md index afb6ea88f9fad..78ac05b9fd386 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.search.md @@ -19,7 +19,7 @@ search: { intervalOptions: ({ display: string; val: string; - enabled(agg: import("./search/aggs/buckets/_bucket_agg_type").IBucketAggConfig): boolean | "" | undefined; + enabled(agg: import("./search/aggs/buckets/bucket_agg_type").IBucketAggConfig): boolean | "" | undefined; } | { display: string; val: string; 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 259d725b3bf0d..e756eb9b72905 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 @@ -23,7 +23,6 @@ | Function | Description | | --- | --- | | [getDefaultSearchParams(config)](./kibana-plugin-plugins-data-server.getdefaultsearchparams.md) | | -| [getTotalLoaded({ total, failed, successful })](./kibana-plugin-plugins-data-server.gettotalloaded.md) | | | [parseInterval(interval)](./kibana-plugin-plugins-data-server.parseinterval.md) | | | [plugin(initializerContext)](./kibana-plugin-plugins-data-server.plugin.md) | Static code to be shared externally | | [shouldReadFieldFromDocValues(aggregatable, esType)](./kibana-plugin-plugins-data-server.shouldreadfieldfromdocvalues.md) | | diff --git a/docs/discover/document-data.asciidoc b/docs/discover/document-data.asciidoc index 6e9218d66c115..477c2ec90e95c 100644 --- a/docs/discover/document-data.asciidoc +++ b/docs/discover/document-data.asciidoc @@ -26,7 +26,7 @@ and click image:images/sort-icon.png[]. The first click sorts by ascending order, the second click sorts by descending order, and the third click removes the field from the sorted fields. -Move a field column:: Hover over the column header and click the move left (<<) or move right icon (>>). +Move a field column:: Hover over the column header and click the (<<) or (>>) icons. Remove a field column :: Hover over the list of *Specified fields* and then click *remove*. Or, use the (x) control in the column header. diff --git a/docs/getting-started/tutorial-visualizing.asciidoc b/docs/getting-started/tutorial-visualizing.asciidoc index a16343aa4850a..acd4d6d908fd4 100644 --- a/docs/getting-started/tutorial-visualizing.asciidoc +++ b/docs/getting-started/tutorial-visualizing.asciidoc @@ -180,5 +180,5 @@ The map now looks like this: image::images/tutorial-visualize-map-2.png[] . Navigate the map by clicking and dragging. Use the controls -on the left to zoom the map and set filters. +to zoom the map and set filters. . *Save* this map with the name `Map Example`. diff --git a/docs/infrastructure/view-metrics.asciidoc b/docs/infrastructure/view-metrics.asciidoc index bbb981acc3ad6..1bd64dde76ee1 100644 --- a/docs/infrastructure/view-metrics.asciidoc +++ b/docs/infrastructure/view-metrics.asciidoc @@ -30,11 +30,3 @@ For complete control over the start and end times, click the start time or end t === Refresh the metrics You can click *Refresh* to manually refresh the metrics. - -[float] -[[infra-view-go-to-chart]] -=== Go to a specific chart - -The charts available for this component are shown in a list on the left of the page. Click a chart in the list to quickly go to that chart. - - diff --git a/docs/management/managing-indices.asciidoc b/docs/management/managing-indices.asciidoc index 933a2ffbf6ee2..946d9ee1b41c7 100644 --- a/docs/management/managing-indices.asciidoc +++ b/docs/management/managing-indices.asciidoc @@ -126,6 +126,23 @@ under the *Mapped fields* tab as follows: [role="screenshot"] image::images/management-index-templates-mappings.png[Mapped fields page] +Alternatively, you can click the *Load JSON* link and define the mapping as JSON: + +[source,js] +---------------------------------- +{ + "properties": { + "geo": { + "properties": { + "coordinates": { + "type": "geo_point" + } + } + } + } +} +---------------------------------- + You can create additional mapping configurations in the *Dynamic templates* and *Advanced options* tabs. No additional mappings are required for this example. diff --git a/docs/management/managing-licenses.asciidoc b/docs/management/managing-licenses.asciidoc index 72accdb5fe2aa..a7ed4e942f3f6 100644 --- a/docs/management/managing-licenses.asciidoc +++ b/docs/management/managing-licenses.asciidoc @@ -15,8 +15,7 @@ already activated a trial for 6.0, you cannot start a new trial until 7.0. You can, however, contact `info@elastic.co` to request an extended trial license. -When you activate a new license level, new features appear in the left sidebar -of the *Management* page. +When you activate a new license level, new features appear in *Management*. [role="screenshot"] image::images/management-license.png[] diff --git a/docs/maps/images/top_hits.png b/docs/maps/images/top_hits.png index 45bbf575f10dd..a791e23b869ef 100644 Binary files a/docs/maps/images/top_hits.png and b/docs/maps/images/top_hits.png differ diff --git a/docs/maps/maps-aggregations.asciidoc b/docs/maps/maps-aggregations.asciidoc index 692e30a6665ed..2b65ae99a381b 100644 --- a/docs/maps/maps-aggregations.asciidoc +++ b/docs/maps/maps-aggregations.asciidoc @@ -20,6 +20,8 @@ You can add the following metric aggregations: * *Sum.* The total value. +* *Top term.* The most common value. + * *Unique count.* The number of distinct values. Use aggregated layers with document layers to show aggregated views when the map shows larger @@ -37,14 +39,24 @@ image::maps/images/grid_to_docs.gif[] The *Grid aggregation* source uses {ref}/search-aggregations-bucket-geotilegrid-aggregation.html[GeoTile grid aggregation] to group your documents into grids. You can calculate metrics for each gridded cell. -You can symbolize grid aggregation metrics as: +Symbolize grid aggregation metrics as: + +*Clusters*:: Creates a <> with a cluster symbol for each gridded cell. +The cluster location is the weighted centroid for all geo-points in the gridded cell. *Grid rectangles*:: Creates a <> with a bounding box polygon for each gridded cell. *Heat map*:: Creates a <> that clusters the weighted centroids for each gridded cell. -*Clusters*:: Creates a <> with a cluster symbol for each gridded cell. -The cluster location is the weighted centroid for all geo-points in the gridded cell. +To enable grid aggregation: + +. Click *Add layer*, then select the *Grid aggregation* source. + +To enable a blended layer that dynamically shows clusters or documents: + +. Click *Add layer*, then select the *Documents* source. +. Configure *Index pattern* and the *Geospatial field*. To enable clustering, the *Geospatial field* must be set to a field mapped as {ref}/geo-point.html[geo_point]. +. In *Scaling*, select *Show clusters when results exceed 10000*. [role="xpack"] @@ -55,10 +67,11 @@ You can display the most relevant documents per entity, for example, the most re To get this data, {es} first groups your data using a {ref}/search-aggregations-bucket-terms-aggregation.html[terms aggregation], then accumulates the most relevant documents based on sort order for each entry using a {ref}/search-aggregations-metrics-top-hits-aggregation.html[top hits metric aggregation]. -Top hits per entity is available for <> with *Documents* source. To enable top hits: -. In *Sorting*, select the *Show documents per entity* checkbox. +. Click *Add layer* button and select *Documents* source. +. Configure *Index pattern* and *Geospatial field*. +. In *Scaling*, select *Show top hits per entity*. . Set *Entity* to the field that identifies entities in your documents. This field will be used in the terms aggregation to group your documents into entity buckets. . Set *Documents per entity* to configure the maximum number of documents accumulated per entity. diff --git a/docs/maps/vector-style.asciidoc b/docs/maps/vector-style.asciidoc index 80e4c4ed5f844..7bc8a909d1ec6 100644 --- a/docs/maps/vector-style.asciidoc +++ b/docs/maps/vector-style.asciidoc @@ -53,7 +53,7 @@ The `bytes` property value for each feature will fit on a linear scale from the To ensure symbols are consistent as you pan, zoom, and filter the map, quantitative data driven styling uses {ref}/search-aggregations-metrics-extendedstats-aggregation.html[extended_stats aggregation] to retrieve statistical metadata. Extended_stats is not available for numeric property values from the <> count, sum, and unique count. -To configure extended_stats,click the gear icon image:maps/images/gear_icon.png[] to configure extended_stats. Set *Sigma* to a smaller value to minimize outliers by moving the range minimum and maximum closer to the average. Clear the *Calculate range from indices* checkbox to turn off the extended_stats aggregation request. The gear icon is not available for numeric property values from the <> count, sum, and unique count. +To configure extended_stats, click the gear icon image:maps/images/gear_icon.png[]. Set *Sigma* to a smaller value to minimize outliers by moving the range minimum and maximum closer to the average. Clear the *Calculate range from indices* checkbox to turn off the extended_stats aggregation request. The gear icon is not available for numeric property values from the <> count, sum, and unique count. NOTE: When extended_stats is not used, symbols might be inconsistent as users pan, zoom, and filter the map. Without extended_stats, the range is calculated with data from the local layer. The range is re-calculated when layer data changes. @@ -76,16 +76,21 @@ When the symbol range minimum and maximum are the same and there is no range: [[maps-vector-style-qualitative-data-driven]] ==== Qualitative data driven styling -Qualitative data driven styling symbolizes non-numeric properties, such as strings and IP addresses, by category. +Qualitative data driven styling symbolizes properties, such as strings and IP addresses, by category. Qualitative data driven styling is available for the following styling properties: +* *Icon* * *Fill color* * *Border color* * *Label color* * *Label border color* -Qualitative data driven styling uses a {ref}/search-aggregations-bucket-terms-aggregation.html[terms aggregation] to retrieve the top nine categories for the property. Feature values within the top categories are assigned a unique color. Feature values outside of the top categories are grouped into the *Other* category. A feature is assigned the *Other* category when the property value is undefined. +To ensure symbols are consistent as you pan, zoom, and filter the map, qualitative data driven styling uses a {ref}/search-aggregations-bucket-terms-aggregation.html[terms aggregation]. The term aggregation retrieves the top nine categories for the property. Feature values within the top categories are assigned a unique style. Feature values outside of the top categories are grouped into the *Other* category. A feature is assigned the *Other* category when the property value is undefined. + +To configure the terms aggregation, click the gear icon image:maps/images/gear_icon.png[]. Clear the *Get categories from indice* checkbox to turn off the terms aggregation request. + +NOTE: When the terms aggregation is not used, symbols might be inconsistent as users pan, zoom, and filter the map. Without terms aggregation, the top categories are calculated with data from the local layer. The top categories are re-calculated when layer data changes. This image shows an example of quantitative data driven styling using the <> data set. The `machine.os.keyword` property determines the color of each symbol based on category. diff --git a/docs/spaces/index.asciidoc b/docs/spaces/index.asciidoc index fb5ef670692dc..990af3a018b1f 100644 --- a/docs/spaces/index.asciidoc +++ b/docs/spaces/index.asciidoc @@ -9,10 +9,10 @@ the dashboards and saved objects that belong to that space. {kib} creates a default space for you. After you create your own spaces, you're asked to choose a space when you log in to Kibana. You can change your -current space at any time by using the menu in the upper left. +current space at any time by using the menu. [role="screenshot"] -image::spaces/images/change-space.png["Change current space"] +image::spaces/images/change-space.png["Change current space menu"] Kibana supports spaces in several ways. You can: diff --git a/docs/user/dashboard.asciidoc b/docs/user/dashboard.asciidoc index 490edb9d26338..a17e46c5b3542 100644 --- a/docs/user/dashboard.asciidoc +++ b/docs/user/dashboard.asciidoc @@ -93,7 +93,7 @@ In *Edit* mode, you can move, resize, customize, and delete panels to suit your * To resize a panel, click the resize control on the lower right and drag to the new dimensions. -* To toggle the use of margins and panel titles, use the *Options* menu in the upper left. +* To toggle the use of margins and panel titles, use the *Options* menu. * To delete a panel, open the panel menu and select *Delete from dashboard.* Deleting a panel from a dashboard does *not* delete the saved visualization or search. diff --git a/docs/user/discover.asciidoc b/docs/user/discover.asciidoc index 7de7d73bf1664..4222ba40debb7 100644 --- a/docs/user/discover.asciidoc +++ b/docs/user/discover.asciidoc @@ -24,7 +24,7 @@ image::images/Discover-Start.png[Discover] === Set up your index pattern The first thing to do in *Discover* is to select an <>, which -defines the data you want to explore and visualize. The current index pattern is in the upper left. +defines the data you want to explore and visualize. If you haven't yet created an index pattern, you can add a <>, which has a pre-built index pattern. @@ -69,7 +69,7 @@ image::images/filter-field.png[height=317] The sortable documents table lists the documents that match your search. By default, the table includes columns for the time field and the document `_source`. -To zero in on a specific field, click *add* next to the field name in the left sidebar. +To zero in on a specific field, click *add* next to the field name. For example, if you add the `currency`, `customer_last_name`, and `day_of_week` fields, the document table includes columns for those three fields. diff --git a/docs/user/security/authorization/kibana-privileges.asciidoc b/docs/user/security/authorization/kibana-privileges.asciidoc index 6a0b7cefab018..21fcb9bdccb87 100644 --- a/docs/user/security/authorization/kibana-privileges.asciidoc +++ b/docs/user/security/authorization/kibana-privileges.asciidoc @@ -43,6 +43,10 @@ Assigning a feature privilege grants access to a specific feature. `all`:: Grants full read-write access. `read`:: Grants read-only access. +===== Sub-feature privileges +Some features allow for finer access control than the `all` and `read` privileges. +This additional level of control is available in the Gold subscription level and higher. + ===== Assigning feature privileges From the role management screen: @@ -62,7 +66,8 @@ PUT /api/security/role/my_kibana_role { "base": [], "feature": { - "dashboard": ["all"] + "visualize": ["all"], + "dashboard": ["read", "url_create"] }, "spaces": ["marketing"] } diff --git a/docs/user/security/images/assign_feature_privilege.png b/docs/user/security/images/assign_feature_privilege.png index e7d000d4554ad..26fbbf20b39ad 100644 Binary files a/docs/user/security/images/assign_feature_privilege.png and b/docs/user/security/images/assign_feature_privilege.png differ diff --git a/docs/visualize/lens.asciidoc b/docs/visualize/lens.asciidoc index e3f61565453b5..35570ea7ca1dc 100644 --- a/docs/visualize/lens.asciidoc +++ b/docs/visualize/lens.asciidoc @@ -32,7 +32,7 @@ Lens supports the following aggregations: [[drag-drop]] === Drag and drop -The data panel in the left column shows the data fields for the selected time period. When +The panel shows the data fields for the selected time period. When you drag a field from the data panel, Lens highlights where you can drop that field. The first time you drag a data field, you'll see two places highlighted in green: @@ -57,7 +57,7 @@ Lens shows you fields based on the <> you have d {kib}, and the current time range. When you change the index pattern or time filter, the list of fields are updated. -To narrow the list of fields you see in the left panel, you can: +To narrow the list of fields, you can: * Enter the field name in *Search field names*. @@ -100,11 +100,7 @@ still allows you to make the change. Lens allows some customizations of the data for each visualization. -. Change the index pattern. - -.. In the left column, click the index pattern name. - -.. Select the new index pattern. +. Click the index pattern name, then select the new index pattern. + If there is a match, Lens displays the new data. All fields that do not match the index pattern are removed. @@ -147,7 +143,7 @@ Drag and drop your data onto the visualization builder pane. . On the *New Visualization* window, click *Lens*. -. In the left column, select the *kibana_sample_data_ecommerce* index. +. Select the *kibana_sample_data_ecommerce* index. . Click image:images/time-filter-calendar.png[], then click *Last 7 days*. The list of data fields are updated. diff --git a/examples/embeddable_examples/public/list_container/embeddable_list_item.tsx b/examples/embeddable_examples/public/list_container/embeddable_list_item.tsx deleted file mode 100644 index 2c80cef8a6364..0000000000000 --- a/examples/embeddable_examples/public/list_container/embeddable_list_item.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { EuiPanel, EuiLoadingSpinner, EuiFlexItem } from '@elastic/eui'; -import { IEmbeddable } from '../../../../src/plugins/embeddable/public'; - -interface Props { - embeddable: IEmbeddable; -} - -export class EmbeddableListItem extends React.Component { - private embeddableRoot: React.RefObject; - private rendered = false; - - constructor(props: Props) { - super(props); - this.embeddableRoot = React.createRef(); - } - - public componentDidMount() { - if (this.embeddableRoot.current && this.props.embeddable) { - this.props.embeddable.render(this.embeddableRoot.current); - this.rendered = true; - } - } - - public componentDidUpdate() { - if (this.embeddableRoot.current && this.props.embeddable && !this.rendered) { - this.props.embeddable.render(this.embeddableRoot.current); - this.rendered = true; - } - } - - public render() { - return ( - - - {this.props.embeddable ? ( -
- ) : ( - - )} - - - ); - } -} diff --git a/examples/embeddable_examples/public/list_container/list_container.tsx b/examples/embeddable_examples/public/list_container/list_container.tsx index bbbd0d6e32304..9e7bec7a1c951 100644 --- a/examples/embeddable_examples/public/list_container/list_container.tsx +++ b/examples/embeddable_examples/public/list_container/list_container.tsx @@ -31,16 +31,14 @@ export class ListContainer extends Container<{}, ContainerInput> { public readonly type = LIST_CONTAINER; private node?: HTMLElement; - constructor( - input: ContainerInput, - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory'] - ) { - super(input, { embeddableLoaded: {} }, getEmbeddableFactory); + constructor(input: ContainerInput, private embeddableServices: EmbeddableStart) { + super(input, { embeddableLoaded: {} }, embeddableServices.getEmbeddableFactory); } - // This container has no input itself. - getInheritedInput(id: string) { - return {}; + getInheritedInput() { + return { + viewMode: this.input.viewMode, + }; } public render(node: HTMLElement) { @@ -48,7 +46,10 @@ export class ListContainer extends Container<{}, ContainerInput> { if (this.node) { ReactDOM.unmountComponentAtNode(this.node); } - ReactDOM.render(, node); + ReactDOM.render( + , + node + ); } public destroy() { diff --git a/examples/embeddable_examples/public/list_container/list_container_component.tsx b/examples/embeddable_examples/public/list_container/list_container_component.tsx index f6e04933ee897..da27889a27603 100644 --- a/examples/embeddable_examples/public/list_container/list_container_component.tsx +++ b/examples/embeddable_examples/public/list_container/list_container_component.tsx @@ -24,30 +24,35 @@ import { withEmbeddableSubscription, ContainerInput, ContainerOutput, + EmbeddableStart, } from '../../../../src/plugins/embeddable/public'; -import { EmbeddableListItem } from './embeddable_list_item'; interface Props { embeddable: IContainer; input: ContainerInput; output: ContainerOutput; + embeddableServices: EmbeddableStart; } -function renderList(embeddable: IContainer, panels: ContainerInput['panels']) { +function renderList( + embeddable: IContainer, + panels: ContainerInput['panels'], + embeddableServices: EmbeddableStart +) { let number = 0; const list = Object.values(panels).map(panel => { const child = embeddable.getChild(panel.explicitInput.id); number++; return ( - +

{number}

- +
@@ -56,12 +61,12 @@ function renderList(embeddable: IContainer, panels: ContainerInput['panels']) { return list; } -export function ListContainerComponentInner(props: Props) { +export function ListContainerComponentInner({ embeddable, input, embeddableServices }: Props) { return (
-

{props.embeddable.getTitle()}

+

{embeddable.getTitle()}

- {renderList(props.embeddable, props.input.panels)} + {renderList(embeddable, input.panels, embeddableServices)}
); } @@ -71,4 +76,9 @@ export function ListContainerComponentInner(props: Props) { // anything on input or output state changes. If you don't want that to happen (for example // if you expect something on input or output state to change frequently that your react // component does not care about, then you should probably hook this up manually). -export const ListContainerComponent = withEmbeddableSubscription(ListContainerComponentInner); +export const ListContainerComponent = withEmbeddableSubscription< + ContainerInput, + ContainerOutput, + IContainer, + { embeddableServices: EmbeddableStart } +>(ListContainerComponentInner); diff --git a/examples/embeddable_examples/public/list_container/list_container_factory.ts b/examples/embeddable_examples/public/list_container/list_container_factory.ts index 1fde254110c62..02a024b95349f 100644 --- a/examples/embeddable_examples/public/list_container/list_container_factory.ts +++ b/examples/embeddable_examples/public/list_container/list_container_factory.ts @@ -26,7 +26,7 @@ import { import { LIST_CONTAINER, ListContainer } from './list_container'; interface StartServices { - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; + embeddableServices: EmbeddableStart; } export class ListContainerFactory implements EmbeddableFactoryDefinition { @@ -40,8 +40,8 @@ export class ListContainerFactory implements EmbeddableFactoryDefinition { } public create = async (initialInput: ContainerInput) => { - const { getEmbeddableFactory } = await this.getStartServices(); - return new ListContainer(initialInput, getEmbeddableFactory); + const { embeddableServices } = await this.getStartServices(); + return new ListContainer(initialInput, embeddableServices); }; public getDisplayName() { diff --git a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx index e33dfab0eaf4a..b2882c97ef501 100644 --- a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx +++ b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx @@ -54,7 +54,7 @@ function wrapSearchTerms(task: string, search?: string) { ); } -function renderTasks(tasks: MultiTaskTodoOutput['tasks'], search?: string) { +function renderTasks(tasks: MultiTaskTodoInput['tasks'], search?: string) { return tasks.map(task => ( + {icon ? : } - +

{wrapSearchTerms(title, search)}

@@ -89,6 +88,8 @@ export function MultiTaskTodoEmbeddableComponentInner({ ); } -export const MultiTaskTodoEmbeddableComponent = withEmbeddableSubscription( - MultiTaskTodoEmbeddableComponentInner -); +export const MultiTaskTodoEmbeddableComponent = withEmbeddableSubscription< + MultiTaskTodoInput, + MultiTaskTodoOutput, + MultiTaskTodoEmbeddable +>(MultiTaskTodoEmbeddableComponentInner); diff --git a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable.tsx b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable.tsx index a2197c9c06fe9..a9e58c5538107 100644 --- a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable.tsx +++ b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_embeddable.tsx @@ -36,30 +36,27 @@ export interface MultiTaskTodoInput extends EmbeddableInput { title: string; } -// This embeddable has output! It's the tasks list that is filtered. -// Output state is something only the embeddable itself can update. It -// can be something completely internal, or it can be state that is +// This embeddable has output! Output state is something only the embeddable itself +// can update. It can be something completely internal, or it can be state that is // derived from input state and updates when input does. export interface MultiTaskTodoOutput extends EmbeddableOutput { - tasks: string[]; + hasMatch: boolean; } -function getFilteredTasks(tasks: string[], search?: string) { - const filteredTasks: string[] = []; - if (search === undefined) return tasks; +function getHasMatch(tasks: string[], title?: string, search?: string) { + if (search === undefined || search === '') return false; - tasks.forEach(task => { - if (task.match(search)) { - filteredTasks.push(task); - } - }); + if (title && title.match(search)) return true; + + const match = tasks.find(task => task.match(search)); + if (match) return true; - return filteredTasks; + return false; } function getOutput(input: MultiTaskTodoInput) { - const tasks = getFilteredTasks(input.tasks, input.search); - return { tasks, hasMatch: tasks.length > 0 || (input.search && input.title.match(input.search)) }; + const hasMatch = getHasMatch(input.tasks, input.title, input.search); + return { hasMatch }; } export class MultiTaskTodoEmbeddable extends Embeddable { diff --git a/examples/embeddable_examples/public/plugin.ts b/examples/embeddable_examples/public/plugin.ts index 5c202d96ceb1a..31a3037332dda 100644 --- a/examples/embeddable_examples/public/plugin.ts +++ b/examples/embeddable_examples/public/plugin.ts @@ -53,20 +53,17 @@ export class EmbeddableExamplesPlugin new MultiTaskTodoEmbeddableFactory() ); - // These are registered in the start method because `getEmbeddableFactory ` - // is only available in start. We could reconsider this I think and make it - // available in both. deps.embeddable.registerEmbeddableFactory( SEARCHABLE_LIST_CONTAINER, new SearchableListContainerFactory(async () => ({ - getEmbeddableFactory: (await core.getStartServices())[1].embeddable.getEmbeddableFactory, + embeddableServices: (await core.getStartServices())[1].embeddable, })) ); deps.embeddable.registerEmbeddableFactory( LIST_CONTAINER, new ListContainerFactory(async () => ({ - getEmbeddableFactory: (await core.getStartServices())[1].embeddable.getEmbeddableFactory, + embeddableServices: (await core.getStartServices())[1].embeddable, })) ); diff --git a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container.tsx b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container.tsx index 06462937c768d..f6efb0b722c4c 100644 --- a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container.tsx +++ b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container.tsx @@ -40,11 +40,8 @@ export class SearchableListContainer extends Container, node); + ReactDOM.render( + , + node + ); } public destroy() { diff --git a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx index b79f86e2a0192..49dbce74788bf 100644 --- a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx +++ b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_component.tsx @@ -34,14 +34,15 @@ import { withEmbeddableSubscription, ContainerOutput, EmbeddableOutput, + EmbeddableStart, } from '../../../../src/plugins/embeddable/public'; -import { EmbeddableListItem } from '../list_container/embeddable_list_item'; import { SearchableListContainer, SearchableContainerInput } from './searchable_list_container'; interface Props { embeddable: SearchableListContainer; input: SearchableContainerInput; output: ContainerOutput; + embeddableServices: EmbeddableStart; } interface State { @@ -111,13 +112,27 @@ export class SearchableListContainerComponentInner extends Component { + const { input, embeddable } = this.props; + const checked: { [key: string]: boolean } = {}; + Object.values(input.panels).map(panel => { + const child = embeddable.getChild(panel.explicitInput.id); + const output = child.getOutput(); + if (hasHasMatchOutput(output) && output.hasMatch) { + checked[panel.explicitInput.id] = true; + } + }); + this.setState({ checked }); + }; + private toggleCheck = (isChecked: boolean, id: string) => { this.setState(prevState => ({ checked: { ...prevState.checked, [id]: isChecked } })); }; public renderControls() { + const { input } = this.props; return ( - + this.deleteChecked()}> @@ -125,6 +140,17 @@ export class SearchableListContainerComponentInner extends Component + + + this.checkMatching()} + > + Check matching + + + -

{embeddable.getTitle()}

- - {this.renderControls()} - - {this.renderList()} -
+ + +

{embeddable.getTitle()}

+ + {this.renderControls()} + + {this.renderList()} +
+
); } private renderList() { + const { embeddableServices, input, embeddable } = this.props; let id = 0; - const list = Object.values(this.props.input.panels).map(panel => { - const embeddable = this.props.embeddable.getChild(panel.explicitInput.id); - if (this.props.input.search && !this.state.hasMatch[panel.explicitInput.id]) return; + const list = Object.values(input.panels).map(panel => { + const childEmbeddable = embeddable.getChild(panel.explicitInput.id); id++; - return embeddable ? ( - - + return childEmbeddable ? ( + + this.toggleCheck(e.target.checked, embeddable.id)} + data-test-subj={`todoCheckBox-${childEmbeddable.id}`} + disabled={!childEmbeddable} + id={childEmbeddable ? childEmbeddable.id : ''} + checked={this.state.checked[childEmbeddable.id]} + onChange={e => this.toggleCheck(e.target.checked, childEmbeddable.id)} /> - + @@ -183,6 +211,9 @@ export class SearchableListContainerComponentInner extends Component(SearchableListContainerComponentInner); diff --git a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts index 382bb65e769ef..34ea43c29462a 100644 --- a/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts +++ b/examples/embeddable_examples/public/searchable_list_container/searchable_list_container_factory.ts @@ -29,7 +29,7 @@ import { } from './searchable_list_container'; interface StartServices { - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; + embeddableServices: EmbeddableStart; } export class SearchableListContainerFactory implements EmbeddableFactoryDefinition { @@ -43,8 +43,8 @@ export class SearchableListContainerFactory implements EmbeddableFactoryDefiniti } public create = async (initialInput: SearchableContainerInput) => { - const { getEmbeddableFactory } = await this.getStartServices(); - return new SearchableListContainer(initialInput, getEmbeddableFactory); + const { embeddableServices } = await this.getStartServices(); + return new SearchableListContainer(initialInput, embeddableServices); }; public getDisplayName() { diff --git a/examples/embeddable_examples/public/todo/todo_component.tsx b/examples/embeddable_examples/public/todo/todo_component.tsx index fbebfc98627b5..a4593bea3cc5e 100644 --- a/examples/embeddable_examples/public/todo/todo_component.tsx +++ b/examples/embeddable_examples/public/todo/todo_component.tsx @@ -51,12 +51,12 @@ function wrapSearchTerms(task: string, search?: string) { export function TodoEmbeddableComponentInner({ input: { icon, title, task, search } }: Props) { return ( - + {icon ? : } - +

{wrapSearchTerms(title || '', search)}

@@ -71,4 +71,8 @@ export function TodoEmbeddableComponentInner({ input: { icon, title, task, searc ); } -export const TodoEmbeddableComponent = withEmbeddableSubscription(TodoEmbeddableComponentInner); +export const TodoEmbeddableComponent = withEmbeddableSubscription< + TodoInput, + EmbeddableOutput, + TodoEmbeddable +>(TodoEmbeddableComponentInner); diff --git a/examples/embeddable_explorer/public/app.tsx b/examples/embeddable_explorer/public/app.tsx index 9c8568454855d..e18012b4b3d80 100644 --- a/examples/embeddable_explorer/public/app.tsx +++ b/examples/embeddable_explorer/public/app.tsx @@ -117,18 +117,7 @@ const EmbeddableExplorerApp = ({ { title: 'Dynamically adding children to a container', id: 'embeddablePanelExamplae', - component: ( - - ), + component: , }, ]; diff --git a/examples/embeddable_explorer/public/embeddable_panel_example.tsx b/examples/embeddable_explorer/public/embeddable_panel_example.tsx index b26111bed7ff2..54cd7c5b5b2c0 100644 --- a/examples/embeddable_explorer/public/embeddable_panel_example.tsx +++ b/examples/embeddable_explorer/public/embeddable_panel_example.tsx @@ -29,43 +29,19 @@ import { EuiText, } from '@elastic/eui'; import { EuiSpacer } from '@elastic/eui'; -import { OverlayStart, CoreStart, SavedObjectsStart, IUiSettingsClient } from 'kibana/public'; -import { - EmbeddablePanel, - EmbeddableStart, - IEmbeddable, -} from '../../../src/plugins/embeddable/public'; +import { EmbeddableStart, IEmbeddable } from '../../../src/plugins/embeddable/public'; import { HELLO_WORLD_EMBEDDABLE, TODO_EMBEDDABLE, MULTI_TASK_TODO_EMBEDDABLE, SEARCHABLE_LIST_CONTAINER, } from '../../embeddable_examples/public'; -import { UiActionsStart } from '../../../src/plugins/ui_actions/public'; -import { Start as InspectorStartContract } from '../../../src/plugins/inspector/public'; -import { getSavedObjectFinder } from '../../../src/plugins/saved_objects/public'; interface Props { - getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - uiActionsApi: UiActionsStart; - overlays: OverlayStart; - notifications: CoreStart['notifications']; - inspector: InspectorStartContract; - savedObject: SavedObjectsStart; - uiSettingsClient: IUiSettingsClient; + embeddableServices: EmbeddableStart; } -export function EmbeddablePanelExample({ - inspector, - notifications, - overlays, - getAllEmbeddableFactories, - getEmbeddableFactory, - uiActionsApi, - savedObject, - uiSettingsClient, -}: Props) { +export function EmbeddablePanelExample({ embeddableServices }: Props) { const searchableInput = { id: '1', title: 'My searchable todo list', @@ -105,7 +81,7 @@ export function EmbeddablePanelExample({ useEffect(() => { ref.current = true; if (!embeddable) { - const factory = getEmbeddableFactory(SEARCHABLE_LIST_CONTAINER); + const factory = embeddableServices.getEmbeddableFactory(SEARCHABLE_LIST_CONTAINER); const promise = factory?.create(searchableInput); if (promise) { promise.then(e => { @@ -134,22 +110,13 @@ export function EmbeddablePanelExample({ You can render your embeddable inside the EmbeddablePanel component. This adds some extra rendering and offers a context menu with pluggable actions. Using EmbeddablePanel - to render your embeddable means you get access to the "e;Add panel flyout"e;. - Now you can see how to add embeddables to your container, and how - "e;getExplicitInput"e; is used to grab input not provided by the container. + to render your embeddable means you get access to the "Add panel flyout". Now + you can see how to add embeddables to your container, and how + "getExplicitInput" is used to grab input not provided by the container. {embeddable ? ( - + ) : ( Loading... )} diff --git a/examples/embeddable_explorer/public/list_container_example.tsx b/examples/embeddable_explorer/public/list_container_example.tsx index 969fdb0ca46db..98ad50418d3fe 100644 --- a/examples/embeddable_explorer/public/list_container_example.tsx +++ b/examples/embeddable_explorer/public/list_container_example.tsx @@ -29,7 +29,11 @@ import { EuiText, } from '@elastic/eui'; import { EuiSpacer } from '@elastic/eui'; -import { EmbeddableFactoryRenderer, EmbeddableStart } from '../../../src/plugins/embeddable/public'; +import { + EmbeddableFactoryRenderer, + EmbeddableStart, + ViewMode, +} from '../../../src/plugins/embeddable/public'; import { HELLO_WORLD_EMBEDDABLE, TODO_EMBEDDABLE, @@ -46,6 +50,7 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) { const listInput = { id: 'hello', title: 'My todo list', + viewMode: ViewMode.VIEW, panels: { '1': { type: HELLO_WORLD_EMBEDDABLE, @@ -76,6 +81,7 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) { const searchableInput = { id: '1', title: 'My searchable todo list', + viewMode: ViewMode.VIEW, panels: { '1': { type: HELLO_WORLD_EMBEDDABLE, @@ -150,7 +156,7 @@ export function ListContainerExample({ getEmbeddableFactory }: Props) {

- Check out the "e;Dynamically adding children"e; section, to see how to add + Check out the "Dynamically adding children" section, to see how to add children to this container, and see it rendered inside an `EmbeddablePanel` component.

diff --git a/package.json b/package.json index 46e0b9adfea25..bd0fec3a5c116 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,8 @@ "examples/*", "test/plugin_functional/plugins/*", "test/interpreter_functional/plugins/*", - "x-pack/test/functional_with_es_ssl/fixtures/plugins/*" + "x-pack/test/functional_with_es_ssl/fixtures/plugins/*", + "x-pack/test/plugin_api_integration/plugins/*" ], "nohoist": [ "**/@types/*", @@ -125,7 +126,7 @@ "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.4.0", - "@elastic/request-crypto": "^1.0.2", + "@elastic/request-crypto": "1.1.2", "@elastic/ui-ace": "0.2.3", "@hapi/good-squeeze": "5.2.1", "@hapi/wreck": "^15.0.2", @@ -375,7 +376,7 @@ "@types/recompose": "^0.30.6", "@types/redux-actions": "^2.6.1", "@types/request": "^2.48.2", - "@types/selenium-webdriver": "^4.0.5", + "@types/selenium-webdriver": "4.0.9", "@types/semver": "^5.5.0", "@types/sinon": "^7.0.13", "@types/strip-ansi": "^3.0.0", @@ -461,6 +462,7 @@ "load-grunt-config": "^3.0.1", "mocha": "^7.1.1", "mock-http-server": "1.3.0", + "ms-chromium-edge-driver": "^0.2.0", "multistream": "^2.1.1", "murmurhash3js": "3.0.1", "mutation-observer": "^1.0.3", @@ -479,7 +481,7 @@ "react-textarea-autosize": "^7.1.2", "regenerate": "^1.4.0", "sass-lint": "^1.12.1", - "selenium-webdriver": "^4.0.0-alpha.5", + "selenium-webdriver": "^4.0.0-alpha.7", "simple-git": "1.116.0", "simplebar-react": "^2.1.0", "sinon": "^7.4.2", diff --git a/packages/kbn-optimizer/package.json b/packages/kbn-optimizer/package.json index b648004760d7c..b3e5a8c518682 100644 --- a/packages/kbn-optimizer/package.json +++ b/packages/kbn-optimizer/package.json @@ -32,6 +32,7 @@ "json-stable-stringify": "^1.0.1", "loader-utils": "^1.2.3", "node-sass": "^4.13.0", + "normalize-path": "^3.0.0", "postcss-loader": "^3.0.0", "raw-loader": "^3.1.0", "resolve-url-loader": "^3.1.1", diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index d52d89eebe2f1..4b4bb1282d939 100644 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -57,6 +57,6 @@ OptimizerConfig { } `; -exports[`builds expected bundles, saves bundle counts to metadata: bar bundle 1`] = `"var __kbnBundles__=typeof __kbnBundles__===\\"object\\"?__kbnBundles__:{};__kbnBundles__[\\"plugin/bar\\"]=function(modules){function webpackJsonpCallback(data){var chunkIds=data[0];var moreModules=data[1];var moduleId,chunkId,i=0,resolves=[];for(;i bundle.id === p.id)) { + return; + } + + // ignore requests that don't include a /data/public, /kibana_react/public, or + // /kibana_utils/public segment as a cheap way to avoid doing path resolution + // for paths that couldn't possibly resolve to what we're looking for + const reqToStaticBundle = STATIC_BUNDLE_PLUGINS.some(p => + request.includes(`/${p.dirname}/public`) + ); + if (!reqToStaticBundle) { + return; + } + + // determine the most acurate resolution string we can without running full resolution + const rootRelative = normalizePath( + Path.relative(bundle.sourceRoot, Path.resolve(context, request)) + ); + for (const { id, dirname } of STATIC_BUNDLE_PLUGINS) { + if (rootRelative === `src/plugins/${dirname}/public`) { + return `__kbnBundles__['plugin/${id}']`; + } + } + + // import doesn't match a root public import + return undefined; +} + export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { const commonConfig: webpack.Configuration = { node: { fs: 'empty' }, @@ -63,7 +117,6 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { // When the entry point is loaded, assign it's exported `plugin` // value to a key on the global `__kbnBundles__` object. library: ['__kbnBundles__', `plugin/${bundle.id}`], - libraryExport: 'plugin', } : {}), }, @@ -72,9 +125,16 @@ export function getWebpackConfig(bundle: Bundle, worker: WorkerConfig) { noEmitOnErrors: true, }, - externals: { - ...UiSharedDeps.externals, - }, + externals: [ + UiSharedDeps.externals, + function(context, request, cb) { + try { + cb(undefined, dynamicExternals(bundle, context, request)); + } catch (error) { + cb(error, undefined); + } + }, + ], plugins: [new CleanWebpackPlugin(), new DisallowedSyntaxPlugin()], diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 0cc1ad6326671..7a858deff41d3 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -79260,7 +79260,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(705); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); -/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(928); +/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(923); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); /* @@ -79445,9 +79445,9 @@ const pAll = __webpack_require__(707); const arrify = __webpack_require__(709); const globby = __webpack_require__(710); const isGlob = __webpack_require__(604); -const cpFile = __webpack_require__(913); -const junk = __webpack_require__(925); -const CpyError = __webpack_require__(926); +const cpFile = __webpack_require__(908); +const junk = __webpack_require__(920); +const CpyError = __webpack_require__(921); const defaultOptions = { ignoreJunk: true @@ -79697,8 +79697,8 @@ const fs = __webpack_require__(23); const arrayUnion = __webpack_require__(711); const glob = __webpack_require__(713); const fastGlob = __webpack_require__(718); -const dirGlob = __webpack_require__(906); -const gitignore = __webpack_require__(909); +const dirGlob = __webpack_require__(901); +const gitignore = __webpack_require__(904); const DEFAULT_FILTER = () => false; @@ -81531,11 +81531,11 @@ module.exports.generateTasks = pkg.generateTasks; Object.defineProperty(exports, "__esModule", { value: true }); var optionsManager = __webpack_require__(720); var taskManager = __webpack_require__(721); -var reader_async_1 = __webpack_require__(877); -var reader_stream_1 = __webpack_require__(901); -var reader_sync_1 = __webpack_require__(902); -var arrayUtils = __webpack_require__(904); -var streamUtils = __webpack_require__(905); +var reader_async_1 = __webpack_require__(872); +var reader_stream_1 = __webpack_require__(896); +var reader_sync_1 = __webpack_require__(897); +var arrayUtils = __webpack_require__(899); +var streamUtils = __webpack_require__(900); /** * Synchronous API. */ @@ -82175,9 +82175,9 @@ var extend = __webpack_require__(838); */ var compilers = __webpack_require__(841); -var parsers = __webpack_require__(873); -var cache = __webpack_require__(874); -var utils = __webpack_require__(875); +var parsers = __webpack_require__(868); +var cache = __webpack_require__(869); +var utils = __webpack_require__(870); var MAX_LENGTH = 1024 * 64; /** @@ -100710,9 +100710,9 @@ var toRegex = __webpack_require__(729); */ var compilers = __webpack_require__(858); -var parsers = __webpack_require__(869); -var Extglob = __webpack_require__(872); -var utils = __webpack_require__(871); +var parsers = __webpack_require__(864); +var Extglob = __webpack_require__(867); +var utils = __webpack_require__(866); var MAX_LENGTH = 1024 * 64; /** @@ -101222,7 +101222,7 @@ var parsers = __webpack_require__(862); * Module dependencies */ -var debug = __webpack_require__(864)('expand-brackets'); +var debug = __webpack_require__(801)('expand-brackets'); var extend = __webpack_require__(738); var Snapdragon = __webpack_require__(768); var toRegex = __webpack_require__(729); @@ -101816,839 +101816,12 @@ exports.createRegex = function(pattern, include) { /* 864 */ /***/ (function(module, exports, __webpack_require__) { -/** - * Detect Electron renderer process, which is node, but we should - * treat as a browser. - */ - -if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(865); -} else { - module.exports = __webpack_require__(868); -} - - -/***/ }), -/* 865 */ -/***/ (function(module, exports, __webpack_require__) { - -/** - * This is the web browser implementation of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = __webpack_require__(866); -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; -exports.storage = 'undefined' != typeof chrome - && 'undefined' != typeof chrome.storage - ? chrome.storage.local - : localstorage(); - -/** - * Colors. - */ - -exports.colors = [ - 'lightseagreen', - 'forestgreen', - 'goldenrod', - 'dodgerblue', - 'darkorchid', - 'crimson' -]; - -/** - * Currently only WebKit-based Web Inspectors, Firefox >= v31, - * and the Firebug extension (any Firefox version) are known - * to support "%c" CSS customizations. - * - * TODO: add a `localStorage` variable to explicitly enable/disable colors - */ - -function useColors() { - // NB: In an Electron preload script, document will be defined but not fully - // initialized. Since we know we're in Chrome, we'll just detect this case - // explicitly - if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') { - return true; - } - - // is webkit? http://stackoverflow.com/a/16459606/376773 - // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 - return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || - // is firebug? http://stackoverflow.com/a/398120/376773 - (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || - // is firefox >= v31? - // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || - // double check webkit in userAgent just in case we are in a worker - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); -} - -/** - * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. - */ - -exports.formatters.j = function(v) { - try { - return JSON.stringify(v); - } catch (err) { - return '[UnexpectedJSONParseError]: ' + err.message; - } -}; - - -/** - * Colorize log arguments if enabled. - * - * @api public - */ - -function formatArgs(args) { - var useColors = this.useColors; - - args[0] = (useColors ? '%c' : '') - + this.namespace - + (useColors ? ' %c' : ' ') - + args[0] - + (useColors ? '%c ' : ' ') - + '+' + exports.humanize(this.diff); - - if (!useColors) return; - - var c = 'color: ' + this.color; - args.splice(1, 0, c, 'color: inherit') - - // the final "%c" is somewhat tricky, because there could be other - // arguments passed either before or after the %c, so we need to - // figure out the correct index to insert the CSS into - var index = 0; - var lastC = 0; - args[0].replace(/%[a-zA-Z%]/g, function(match) { - if ('%%' === match) return; - index++; - if ('%c' === match) { - // we only are interested in the *last* %c - // (the user may have provided their own) - lastC = index; - } - }); - - args.splice(lastC, 0, c); -} - -/** - * Invokes `console.log()` when available. - * No-op when `console.log` is not a "function". - * - * @api public - */ - -function log() { - // this hackery is required for IE8/9, where - // the `console.log` function doesn't have 'apply' - return 'object' === typeof console - && console.log - && Function.prototype.apply.call(console.log, console, arguments); -} - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ - -function save(namespaces) { - try { - if (null == namespaces) { - exports.storage.removeItem('debug'); - } else { - exports.storage.debug = namespaces; - } - } catch(e) {} -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - -function load() { - var r; - try { - r = exports.storage.debug; - } catch(e) {} - - // If debug isn't set in LS, and we're in Electron, try to load $DEBUG - if (!r && typeof process !== 'undefined' && 'env' in process) { - r = process.env.DEBUG; - } - - return r; -} - -/** - * Enable namespaces listed in `localStorage.debug` initially. - */ - -exports.enable(load()); - -/** - * Localstorage attempts to return the localstorage. - * - * This is necessary because safari throws - * when a user disables cookies/localstorage - * and you attempt to access it. - * - * @return {LocalStorage} - * @api private - */ - -function localstorage() { - try { - return window.localStorage; - } catch (e) {} -} - - -/***/ }), -/* 866 */ -/***/ (function(module, exports, __webpack_require__) { - - -/** - * This is the common logic for both the Node.js and web browser - * implementations of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; -exports.coerce = coerce; -exports.disable = disable; -exports.enable = enable; -exports.enabled = enabled; -exports.humanize = __webpack_require__(867); - -/** - * The currently active debug mode names, and names to skip. - */ - -exports.names = []; -exports.skips = []; - -/** - * Map of special "%n" handling functions, for the debug "format" argument. - * - * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". - */ - -exports.formatters = {}; - -/** - * Previous log timestamp. - */ - -var prevTime; - -/** - * Select a color. - * @param {String} namespace - * @return {Number} - * @api private - */ - -function selectColor(namespace) { - var hash = 0, i; - - for (i in namespace) { - hash = ((hash << 5) - hash) + namespace.charCodeAt(i); - hash |= 0; // Convert to 32bit integer - } - - return exports.colors[Math.abs(hash) % exports.colors.length]; -} - -/** - * Create a debugger with the given `namespace`. - * - * @param {String} namespace - * @return {Function} - * @api public - */ - -function createDebug(namespace) { - - function debug() { - // disabled? - if (!debug.enabled) return; - - var self = debug; - - // set `diff` timestamp - var curr = +new Date(); - var ms = curr - (prevTime || curr); - self.diff = ms; - self.prev = prevTime; - self.curr = curr; - prevTime = curr; - - // turn the `arguments` into a proper Array - var args = new Array(arguments.length); - for (var i = 0; i < args.length; i++) { - args[i] = arguments[i]; - } - - args[0] = exports.coerce(args[0]); - - if ('string' !== typeof args[0]) { - // anything else let's inspect with %O - args.unshift('%O'); - } - - // apply any `formatters` transformations - var index = 0; - args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { - // if we encounter an escaped % then don't increase the array index - if (match === '%%') return match; - index++; - var formatter = exports.formatters[format]; - if ('function' === typeof formatter) { - var val = args[index]; - match = formatter.call(self, val); - - // now we need to remove `args[index]` since it's inlined in the `format` - args.splice(index, 1); - index--; - } - return match; - }); - - // apply env-specific formatting (colors, etc.) - exports.formatArgs.call(self, args); - - var logFn = debug.log || exports.log || console.log.bind(console); - logFn.apply(self, args); - } - - debug.namespace = namespace; - debug.enabled = exports.enabled(namespace); - debug.useColors = exports.useColors(); - debug.color = selectColor(namespace); - - // env-specific initialization logic for debug instances - if ('function' === typeof exports.init) { - exports.init(debug); - } - - return debug; -} - -/** - * Enables a debug mode by namespaces. This can include modes - * separated by a colon and wildcards. - * - * @param {String} namespaces - * @api public - */ - -function enable(namespaces) { - exports.save(namespaces); - - exports.names = []; - exports.skips = []; - - var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); - var len = split.length; - - for (var i = 0; i < len; i++) { - if (!split[i]) continue; // ignore empty strings - namespaces = split[i].replace(/\*/g, '.*?'); - if (namespaces[0] === '-') { - exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); - } else { - exports.names.push(new RegExp('^' + namespaces + '$')); - } - } -} - -/** - * Disable debug output. - * - * @api public - */ - -function disable() { - exports.enable(''); -} - -/** - * Returns true if the given mode name is enabled, false otherwise. - * - * @param {String} name - * @return {Boolean} - * @api public - */ - -function enabled(name) { - var i, len; - for (i = 0, len = exports.skips.length; i < len; i++) { - if (exports.skips[i].test(name)) { - return false; - } - } - for (i = 0, len = exports.names.length; i < len; i++) { - if (exports.names[i].test(name)) { - return true; - } - } - return false; -} - -/** - * Coerce `val`. - * - * @param {Mixed} val - * @return {Mixed} - * @api private - */ - -function coerce(val) { - if (val instanceof Error) return val.stack || val.message; - return val; -} - - -/***/ }), -/* 867 */ -/***/ (function(module, exports) { - -/** - * Helpers. - */ - -var s = 1000; -var m = s * 60; -var h = m * 60; -var d = h * 24; -var y = d * 365.25; - -/** - * Parse or format the given `val`. - * - * Options: - * - * - `long` verbose formatting [false] - * - * @param {String|Number} val - * @param {Object} [options] - * @throws {Error} throw an error if val is not a non-empty string or a number - * @return {String|Number} - * @api public - */ - -module.exports = function(val, options) { - options = options || {}; - var type = typeof val; - if (type === 'string' && val.length > 0) { - return parse(val); - } else if (type === 'number' && isNaN(val) === false) { - return options.long ? fmtLong(val) : fmtShort(val); - } - throw new Error( - 'val is not a non-empty string or a valid number. val=' + - JSON.stringify(val) - ); -}; - -/** - * Parse the given `str` and return milliseconds. - * - * @param {String} str - * @return {Number} - * @api private - */ - -function parse(str) { - str = String(str); - if (str.length > 100) { - return; - } - var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec( - str - ); - if (!match) { - return; - } - var n = parseFloat(match[1]); - var type = (match[2] || 'ms').toLowerCase(); - switch (type) { - case 'years': - case 'year': - case 'yrs': - case 'yr': - case 'y': - return n * y; - case 'days': - case 'day': - case 'd': - return n * d; - case 'hours': - case 'hour': - case 'hrs': - case 'hr': - case 'h': - return n * h; - case 'minutes': - case 'minute': - case 'mins': - case 'min': - case 'm': - return n * m; - case 'seconds': - case 'second': - case 'secs': - case 'sec': - case 's': - return n * s; - case 'milliseconds': - case 'millisecond': - case 'msecs': - case 'msec': - case 'ms': - return n; - default: - return undefined; - } -} - -/** - * Short format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function fmtShort(ms) { - if (ms >= d) { - return Math.round(ms / d) + 'd'; - } - if (ms >= h) { - return Math.round(ms / h) + 'h'; - } - if (ms >= m) { - return Math.round(ms / m) + 'm'; - } - if (ms >= s) { - return Math.round(ms / s) + 's'; - } - return ms + 'ms'; -} - -/** - * Long format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function fmtLong(ms) { - return plural(ms, d, 'day') || - plural(ms, h, 'hour') || - plural(ms, m, 'minute') || - plural(ms, s, 'second') || - ms + ' ms'; -} - -/** - * Pluralization helper. - */ - -function plural(ms, n, name) { - if (ms < n) { - return; - } - if (ms < n * 1.5) { - return Math.floor(ms / n) + ' ' + name; - } - return Math.ceil(ms / n) + ' ' + name + 's'; -} - - -/***/ }), -/* 868 */ -/***/ (function(module, exports, __webpack_require__) { - -/** - * Module dependencies. - */ - -var tty = __webpack_require__(478); -var util = __webpack_require__(29); - -/** - * This is the Node.js implementation of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = __webpack_require__(866); -exports.init = init; -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; - -/** - * Colors. - */ - -exports.colors = [6, 2, 3, 4, 5, 1]; - -/** - * Build up the default `inspectOpts` object from the environment variables. - * - * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js - */ - -exports.inspectOpts = Object.keys(process.env).filter(function (key) { - return /^debug_/i.test(key); -}).reduce(function (obj, key) { - // camel-case - var prop = key - .substring(6) - .toLowerCase() - .replace(/_([a-z])/g, function (_, k) { return k.toUpperCase() }); - - // coerce string value into JS value - var val = process.env[key]; - if (/^(yes|on|true|enabled)$/i.test(val)) val = true; - else if (/^(no|off|false|disabled)$/i.test(val)) val = false; - else if (val === 'null') val = null; - else val = Number(val); - - obj[prop] = val; - return obj; -}, {}); - -/** - * The file descriptor to write the `debug()` calls to. - * Set the `DEBUG_FD` env variable to override with another value. i.e.: - * - * $ DEBUG_FD=3 node script.js 3>debug.log - */ - -var fd = parseInt(process.env.DEBUG_FD, 10) || 2; - -if (1 !== fd && 2 !== fd) { - util.deprecate(function(){}, 'except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)')() -} - -var stream = 1 === fd ? process.stdout : - 2 === fd ? process.stderr : - createWritableStdioStream(fd); - -/** - * Is stdout a TTY? Colored output is enabled when `true`. - */ - -function useColors() { - return 'colors' in exports.inspectOpts - ? Boolean(exports.inspectOpts.colors) - : tty.isatty(fd); -} - -/** - * Map %o to `util.inspect()`, all on a single line. - */ - -exports.formatters.o = function(v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts) - .split('\n').map(function(str) { - return str.trim() - }).join(' '); -}; - -/** - * Map %o to `util.inspect()`, allowing multiple lines if needed. - */ - -exports.formatters.O = function(v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts); -}; - -/** - * Adds ANSI color escape codes if enabled. - * - * @api public - */ - -function formatArgs(args) { - var name = this.namespace; - var useColors = this.useColors; - - if (useColors) { - var c = this.color; - var prefix = ' \u001b[3' + c + ';1m' + name + ' ' + '\u001b[0m'; - - args[0] = prefix + args[0].split('\n').join('\n' + prefix); - args.push('\u001b[3' + c + 'm+' + exports.humanize(this.diff) + '\u001b[0m'); - } else { - args[0] = new Date().toUTCString() - + ' ' + name + ' ' + args[0]; - } -} - -/** - * Invokes `util.format()` with the specified arguments and writes to `stream`. - */ - -function log() { - return stream.write(util.format.apply(util, arguments) + '\n'); -} - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ - -function save(namespaces) { - if (null == namespaces) { - // If you set a process.env field to null or undefined, it gets cast to the - // string 'null' or 'undefined'. Just delete instead. - delete process.env.DEBUG; - } else { - process.env.DEBUG = namespaces; - } -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - -function load() { - return process.env.DEBUG; -} - -/** - * Copied from `node/src/node.js`. - * - * XXX: It's lame that node doesn't expose this API out-of-the-box. It also - * relies on the undocumented `tty_wrap.guessHandleType()` which is also lame. - */ - -function createWritableStdioStream (fd) { - var stream; - var tty_wrap = process.binding('tty_wrap'); - - // Note stream._type is used for test-module-load-list.js - - switch (tty_wrap.guessHandleType(fd)) { - case 'TTY': - stream = new tty.WriteStream(fd); - stream._type = 'tty'; - - // Hack to have stream not keep the event loop alive. - // See https://github.com/joyent/node/issues/1726 - if (stream._handle && stream._handle.unref) { - stream._handle.unref(); - } - break; - - case 'FILE': - var fs = __webpack_require__(23); - stream = new fs.SyncWriteStream(fd, { autoClose: false }); - stream._type = 'fs'; - break; - - case 'PIPE': - case 'TCP': - var net = __webpack_require__(806); - stream = new net.Socket({ - fd: fd, - readable: false, - writable: true - }); - - // FIXME Should probably have an option in net.Socket to create a - // stream from an existing fd which is writable only. But for now - // we'll just add this hack and set the `readable` member to false. - // Test: ./node test/fixtures/echo.js < /etc/passwd - stream.readable = false; - stream.read = null; - stream._type = 'pipe'; - - // FIXME Hack to have stream not keep the event loop alive. - // See https://github.com/joyent/node/issues/1726 - if (stream._handle && stream._handle.unref) { - stream._handle.unref(); - } - break; - - default: - // Probably an error on in uv_guess_handle() - throw new Error('Implement me. Unknown stream file type!'); - } - - // For supporting legacy API we put the FD here. - stream.fd = fd; - - stream._isStdio = true; - - return stream; -} - -/** - * Init logic for `debug` instances. - * - * Create a new `inspectOpts` object in case `useColors` is set - * differently for a particular `debug` instance. - */ - -function init (debug) { - debug.inspectOpts = {}; - - var keys = Object.keys(exports.inspectOpts); - for (var i = 0; i < keys.length; i++) { - debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; - } -} - -/** - * Enable namespaces listed in `process.env.DEBUG` initially. - */ - -exports.enable(load()); - - -/***/ }), -/* 869 */ -/***/ (function(module, exports, __webpack_require__) { - "use strict"; var brackets = __webpack_require__(859); -var define = __webpack_require__(870); -var utils = __webpack_require__(871); +var define = __webpack_require__(865); +var utils = __webpack_require__(866); /** * Characters to use in text regex (we want to "not" match @@ -102803,7 +101976,7 @@ module.exports = parsers; /***/ }), -/* 870 */ +/* 865 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102841,7 +102014,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 871 */ +/* 866 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102917,7 +102090,7 @@ utils.createRegex = function(str) { /***/ }), -/* 872 */ +/* 867 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102928,7 +102101,7 @@ utils.createRegex = function(str) { */ var Snapdragon = __webpack_require__(768); -var define = __webpack_require__(870); +var define = __webpack_require__(865); var extend = __webpack_require__(738); /** @@ -102936,7 +102109,7 @@ var extend = __webpack_require__(738); */ var compilers = __webpack_require__(858); -var parsers = __webpack_require__(869); +var parsers = __webpack_require__(864); /** * Customize Snapdragon parser and renderer @@ -103002,7 +102175,7 @@ module.exports = Extglob; /***/ }), -/* 873 */ +/* 868 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103092,14 +102265,14 @@ function textRegex(pattern) { /***/ }), -/* 874 */ +/* 869 */ /***/ (function(module, exports, __webpack_require__) { module.exports = new (__webpack_require__(850))(); /***/ }), -/* 875 */ +/* 870 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103117,7 +102290,7 @@ utils.define = __webpack_require__(837); utils.diff = __webpack_require__(854); utils.extend = __webpack_require__(838); utils.pick = __webpack_require__(855); -utils.typeOf = __webpack_require__(876); +utils.typeOf = __webpack_require__(871); utils.unique = __webpack_require__(741); /** @@ -103415,7 +102588,7 @@ utils.unixify = function(options) { /***/ }), -/* 876 */ +/* 871 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -103550,7 +102723,7 @@ function isBuffer(val) { /***/ }), -/* 877 */ +/* 872 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103569,9 +102742,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(878); -var reader_1 = __webpack_require__(891); -var fs_stream_1 = __webpack_require__(895); +var readdir = __webpack_require__(873); +var reader_1 = __webpack_require__(886); +var fs_stream_1 = __webpack_require__(890); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -103632,15 +102805,15 @@ exports.default = ReaderAsync; /***/ }), -/* 878 */ +/* 873 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(879); -const readdirAsync = __webpack_require__(887); -const readdirStream = __webpack_require__(890); +const readdirSync = __webpack_require__(874); +const readdirAsync = __webpack_require__(882); +const readdirStream = __webpack_require__(885); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -103724,7 +102897,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 879 */ +/* 874 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103732,11 +102905,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(880); +const DirectoryReader = __webpack_require__(875); let syncFacade = { - fs: __webpack_require__(885), - forEach: __webpack_require__(886), + fs: __webpack_require__(880), + forEach: __webpack_require__(881), sync: true }; @@ -103765,7 +102938,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 880 */ +/* 875 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103774,9 +102947,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(27).Readable; const EventEmitter = __webpack_require__(379).EventEmitter; const path = __webpack_require__(16); -const normalizeOptions = __webpack_require__(881); -const stat = __webpack_require__(883); -const call = __webpack_require__(884); +const normalizeOptions = __webpack_require__(876); +const stat = __webpack_require__(878); +const call = __webpack_require__(879); /** * Asynchronously reads the contents of a directory and streams the results @@ -104152,14 +103325,14 @@ module.exports = DirectoryReader; /***/ }), -/* 881 */ +/* 876 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const globToRegExp = __webpack_require__(882); +const globToRegExp = __webpack_require__(877); module.exports = normalizeOptions; @@ -104336,7 +103509,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 882 */ +/* 877 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -104473,13 +103646,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 883 */ +/* 878 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(884); +const call = __webpack_require__(879); module.exports = stat; @@ -104554,7 +103727,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 884 */ +/* 879 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104615,14 +103788,14 @@ function callOnce (fn) { /***/ }), -/* 885 */ +/* 880 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const call = __webpack_require__(884); +const call = __webpack_require__(879); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -104686,7 +103859,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 886 */ +/* 881 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104715,7 +103888,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 887 */ +/* 882 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104723,12 +103896,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(888); -const DirectoryReader = __webpack_require__(880); +const maybe = __webpack_require__(883); +const DirectoryReader = __webpack_require__(875); let asyncFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(889), + forEach: __webpack_require__(884), async: true }; @@ -104770,7 +103943,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 888 */ +/* 883 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104797,7 +103970,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 889 */ +/* 884 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104833,7 +104006,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 890 */ +/* 885 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104841,11 +104014,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(880); +const DirectoryReader = __webpack_require__(875); let streamFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(889), + forEach: __webpack_require__(884), async: true }; @@ -104865,16 +104038,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 891 */ +/* 886 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var deep_1 = __webpack_require__(892); -var entry_1 = __webpack_require__(894); -var pathUtil = __webpack_require__(893); +var deep_1 = __webpack_require__(887); +var entry_1 = __webpack_require__(889); +var pathUtil = __webpack_require__(888); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -104940,13 +104113,13 @@ exports.default = Reader; /***/ }), -/* 892 */ +/* 887 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(893); +var pathUtils = __webpack_require__(888); var patternUtils = __webpack_require__(722); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { @@ -105030,7 +104203,7 @@ exports.default = DeepFilter; /***/ }), -/* 893 */ +/* 888 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105061,13 +104234,13 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 894 */ +/* 889 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(893); +var pathUtils = __webpack_require__(888); var patternUtils = __webpack_require__(722); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { @@ -105153,7 +104326,7 @@ exports.default = EntryFilter; /***/ }), -/* 895 */ +/* 890 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105173,8 +104346,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var fsStat = __webpack_require__(896); -var fs_1 = __webpack_require__(900); +var fsStat = __webpack_require__(891); +var fs_1 = __webpack_require__(895); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -105224,14 +104397,14 @@ exports.default = FileSystemStream; /***/ }), -/* 896 */ +/* 891 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(897); -const statProvider = __webpack_require__(899); +const optionsManager = __webpack_require__(892); +const statProvider = __webpack_require__(894); /** * Asynchronous API. */ @@ -105262,13 +104435,13 @@ exports.statSync = statSync; /***/ }), -/* 897 */ +/* 892 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(898); +const fsAdapter = __webpack_require__(893); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -105281,7 +104454,7 @@ exports.prepare = prepare; /***/ }), -/* 898 */ +/* 893 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105304,7 +104477,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 899 */ +/* 894 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105356,7 +104529,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 900 */ +/* 895 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105387,7 +104560,7 @@ exports.default = FileSystem; /***/ }), -/* 901 */ +/* 896 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105407,9 +104580,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var readdir = __webpack_require__(878); -var reader_1 = __webpack_require__(891); -var fs_stream_1 = __webpack_require__(895); +var readdir = __webpack_require__(873); +var reader_1 = __webpack_require__(886); +var fs_stream_1 = __webpack_require__(890); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -105477,7 +104650,7 @@ exports.default = ReaderStream; /***/ }), -/* 902 */ +/* 897 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105496,9 +104669,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(878); -var reader_1 = __webpack_require__(891); -var fs_sync_1 = __webpack_require__(903); +var readdir = __webpack_require__(873); +var reader_1 = __webpack_require__(886); +var fs_sync_1 = __webpack_require__(898); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -105558,7 +104731,7 @@ exports.default = ReaderSync; /***/ }), -/* 903 */ +/* 898 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105577,8 +104750,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(896); -var fs_1 = __webpack_require__(900); +var fsStat = __webpack_require__(891); +var fs_1 = __webpack_require__(895); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -105624,7 +104797,7 @@ exports.default = FileSystemSync; /***/ }), -/* 904 */ +/* 899 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105640,7 +104813,7 @@ exports.flatten = flatten; /***/ }), -/* 905 */ +/* 900 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105661,13 +104834,13 @@ exports.merge = merge; /***/ }), -/* 906 */ +/* 901 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const pathType = __webpack_require__(907); +const pathType = __webpack_require__(902); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -105733,13 +104906,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 907 */ +/* 902 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const pify = __webpack_require__(908); +const pify = __webpack_require__(903); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -105782,7 +104955,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 908 */ +/* 903 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105873,7 +105046,7 @@ module.exports = (obj, opts) => { /***/ }), -/* 909 */ +/* 904 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105881,9 +105054,9 @@ module.exports = (obj, opts) => { const fs = __webpack_require__(23); const path = __webpack_require__(16); const fastGlob = __webpack_require__(718); -const gitIgnore = __webpack_require__(910); -const pify = __webpack_require__(911); -const slash = __webpack_require__(912); +const gitIgnore = __webpack_require__(905); +const pify = __webpack_require__(906); +const slash = __webpack_require__(907); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -105981,7 +105154,7 @@ module.exports.sync = options => { /***/ }), -/* 910 */ +/* 905 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -106450,7 +105623,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 911 */ +/* 906 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -106525,7 +105698,7 @@ module.exports = (input, options) => { /***/ }), -/* 912 */ +/* 907 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -106543,17 +105716,17 @@ module.exports = input => { /***/ }), -/* 913 */ +/* 908 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); const {constants: fsConstants} = __webpack_require__(23); -const pEvent = __webpack_require__(914); -const CpFileError = __webpack_require__(917); -const fs = __webpack_require__(921); -const ProgressEmitter = __webpack_require__(924); +const pEvent = __webpack_require__(909); +const CpFileError = __webpack_require__(912); +const fs = __webpack_require__(916); +const ProgressEmitter = __webpack_require__(919); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -106667,12 +105840,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 914 */ +/* 909 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(915); +const pTimeout = __webpack_require__(910); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -106963,12 +106136,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 915 */ +/* 910 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(916); +const pFinally = __webpack_require__(911); class TimeoutError extends Error { constructor(message) { @@ -107014,7 +106187,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 916 */ +/* 911 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -107036,12 +106209,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 917 */ +/* 912 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(918); +const NestedError = __webpack_require__(913); class CpFileError extends NestedError { constructor(message, nested) { @@ -107055,10 +106228,10 @@ module.exports = CpFileError; /***/ }), -/* 918 */ +/* 913 */ /***/ (function(module, exports, __webpack_require__) { -var inherits = __webpack_require__(919); +var inherits = __webpack_require__(914); var NestedError = function (message, nested) { this.nested = nested; @@ -107109,7 +106282,7 @@ module.exports = NestedError; /***/ }), -/* 919 */ +/* 914 */ /***/ (function(module, exports, __webpack_require__) { try { @@ -107117,12 +106290,12 @@ try { if (typeof util.inherits !== 'function') throw ''; module.exports = util.inherits; } catch (e) { - module.exports = __webpack_require__(920); + module.exports = __webpack_require__(915); } /***/ }), -/* 920 */ +/* 915 */ /***/ (function(module, exports) { if (typeof Object.create === 'function') { @@ -107151,16 +106324,16 @@ if (typeof Object.create === 'function') { /***/ }), -/* 921 */ +/* 916 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(29); const fs = __webpack_require__(22); -const makeDir = __webpack_require__(922); -const pEvent = __webpack_require__(914); -const CpFileError = __webpack_require__(917); +const makeDir = __webpack_require__(917); +const pEvent = __webpack_require__(909); +const CpFileError = __webpack_require__(912); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -107257,7 +106430,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 922 */ +/* 917 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -107265,7 +106438,7 @@ exports.copyFileSync = (source, destination, flags) => { const fs = __webpack_require__(23); const path = __webpack_require__(16); const {promisify} = __webpack_require__(29); -const semver = __webpack_require__(923); +const semver = __webpack_require__(918); const defaults = { mode: 0o777 & (~process.umask()), @@ -107414,7 +106587,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 923 */ +/* 918 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -109016,7 +108189,7 @@ function coerce (version, options) { /***/ }), -/* 924 */ +/* 919 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -109057,7 +108230,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 925 */ +/* 920 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -109103,12 +108276,12 @@ exports.default = module.exports; /***/ }), -/* 926 */ +/* 921 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(927); +const NestedError = __webpack_require__(922); class CpyError extends NestedError { constructor(message, nested) { @@ -109122,7 +108295,7 @@ module.exports = CpyError; /***/ }), -/* 927 */ +/* 922 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(29).inherits; @@ -109178,7 +108351,7 @@ module.exports = NestedError; /***/ }), -/* 928 */ +/* 923 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts index 66f17ab579ec3..f4b91d154cbb8 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/config/schema.ts @@ -136,7 +136,7 @@ export const schema = Joi.object() browser: Joi.object() .keys({ type: Joi.string() - .valid('chrome', 'firefox', 'ie') + .valid('chrome', 'firefox', 'ie', 'msedge') .default('chrome'), logPollingMs: Joi.number().default(100), diff --git a/packages/kbn-ui-shared-deps/polyfills.js b/packages/kbn-ui-shared-deps/polyfills.js index d2305d643e4d2..1f1392b02baff 100644 --- a/packages/kbn-ui-shared-deps/polyfills.js +++ b/packages/kbn-ui-shared-deps/polyfills.js @@ -20,6 +20,13 @@ require('core-js/stable'); require('regenerator-runtime/runtime'); require('custom-event-polyfill'); + +if (typeof window.Event === 'object') { + // IE11 doesn't support unknown event types, required by react-use + // https://github.com/streamich/react-use/issues/73 + window.Event = CustomEvent; +} + require('whatwg-fetch'); require('abortcontroller-polyfill/dist/polyfill-patch-fetch'); require('./vendor/childnode_remove_polyfill'); diff --git a/src/core/public/chrome/ui/header/header_help_menu.tsx b/src/core/public/chrome/ui/header/header_help_menu.tsx index 80a45b4c61f07..1023a561a0fe3 100644 --- a/src/core/public/chrome/ui/header/header_help_menu.tsx +++ b/src/core/public/chrome/ui/header/header_help_menu.tsx @@ -314,13 +314,14 @@ class HeaderHelpMenuUI extends Component { return ( // @ts-ignore repositionOnScroll doesn't exist in EuiPopover diff --git a/src/core/public/plugins/plugin_loader.test.ts b/src/core/public/plugins/plugin_loader.test.ts index e5cbffc3e2d94..b4e2c3095f14a 100644 --- a/src/core/public/plugins/plugin_loader.test.ts +++ b/src/core/public/plugins/plugin_loader.test.ts @@ -71,7 +71,7 @@ test('`loadPluginBundles` creates a script tag and loads initializer', async () // Setup a fake initializer as if a plugin bundle had actually been loaded. const fakeInitializer = jest.fn(); - coreWindow.__kbnBundles__['plugin/plugin-a'] = fakeInitializer; + coreWindow.__kbnBundles__['plugin/plugin-a'] = { plugin: fakeInitializer }; // Call the onload callback fakeScriptTag.onload(); await expect(loadPromise).resolves.toEqual(fakeInitializer); diff --git a/src/core/public/plugins/plugin_loader.ts b/src/core/public/plugins/plugin_loader.ts index 63aba0dde2af8..bf7711055e97b 100644 --- a/src/core/public/plugins/plugin_loader.ts +++ b/src/core/public/plugins/plugin_loader.ts @@ -32,7 +32,7 @@ export type UnknownPluginInitializer = PluginInitializer new Promise>( (resolve, reject) => { - const script = document.createElement('script'); const coreWindow = (window as unknown) as CoreWindow; + const exportId = `plugin/${pluginName}`; + + const readPluginExport = () => { + const PluginExport: any = coreWindow.__kbnBundles__[exportId]; + if (typeof PluginExport?.plugin !== 'function') { + reject( + new Error(`Definition of plugin "${pluginName}" should be a function (${bundlePath}).`) + ); + } else { + resolve( + PluginExport.plugin as PluginInitializer + ); + } + }; + if (coreWindow.__kbnBundles__[exportId]) { + readPluginExport(); + return; + } + + const script = document.createElement('script'); // Assumes that all plugin bundles get put into the bundles/plugins subdirectory const bundlePath = addBasePath(`/bundles/plugin/${pluginName}/${pluginName}.plugin.js`); script.setAttribute('src', bundlePath); @@ -89,15 +108,7 @@ export const loadPluginBundle: LoadPluginBundle = < // Wire up resolve and reject script.onload = () => { cleanupTag(); - - const initializer = coreWindow.__kbnBundles__[`plugin/${pluginName}`]; - if (!initializer || typeof initializer !== 'function') { - reject( - new Error(`Definition of plugin "${pluginName}" should be a function (${bundlePath}).`) - ); - } else { - resolve(initializer as PluginInitializer); - } + readPluginExport(); }; script.onerror = () => { diff --git a/src/core/server/elasticsearch/elasticsearch_service.mock.ts b/src/core/server/elasticsearch/elasticsearch_service.mock.ts index 389d98a0818c8..da8846f6dddbb 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.mock.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.mock.ts @@ -26,8 +26,10 @@ import { InternalElasticsearchServiceSetup, ElasticsearchServiceSetup, ElasticsearchServiceStart, + ElasticsearchStatusMeta, } from './types'; import { NodesVersionCompatibility } from './version_check/ensure_es_version'; +import { ServiceStatus, ServiceStatusLevels } from '../status'; const createScopedClusterClientMock = (): jest.Mocked => ({ callAsInternalUser: jest.fn(), @@ -102,6 +104,10 @@ const createInternalSetupContractMock = () => { warningNodes: [], kibanaVersion: '8.0.0', }), + status$: new BehaviorSubject>({ + level: ServiceStatusLevels.available, + summary: 'Elasticsearch is available', + }), legacy: { config$: new BehaviorSubject({} as ElasticsearchConfig), }, diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index b92a6edf778ed..684f6e15caff9 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.ts @@ -40,6 +40,7 @@ import { InternalHttpServiceSetup, GetAuthHeaders } from '../http/'; import { InternalElasticsearchServiceSetup, ElasticsearchServiceStart } from './types'; import { CallAPIOptions } from './api_types'; import { pollEsNodesVersion } from './version_check/ensure_es_version'; +import { calculateStatus$ } from './status'; /** @internal */ interface CoreClusterClients { @@ -186,6 +187,7 @@ export class ElasticsearchService adminClient: this.adminClient, dataClient, createClient: this.createClient, + status$: calculateStatus$(esNodesCompatibility$), }; } diff --git a/src/core/server/elasticsearch/index.ts b/src/core/server/elasticsearch/index.ts index cfd72a6fd5e47..2e45f710c4dcf 100644 --- a/src/core/server/elasticsearch/index.ts +++ b/src/core/server/elasticsearch/index.ts @@ -31,3 +31,4 @@ export { config, configSchema, ElasticsearchConfig } from './elasticsearch_confi export { ElasticsearchError, ElasticsearchErrorHelpers } from './errors'; export * from './api_types'; export * from './types'; +export { NodesVersionCompatibility } from './version_check/ensure_es_version'; diff --git a/src/core/server/elasticsearch/status.test.ts b/src/core/server/elasticsearch/status.test.ts new file mode 100644 index 0000000000000..dd5fb04bfd1c6 --- /dev/null +++ b/src/core/server/elasticsearch/status.test.ts @@ -0,0 +1,222 @@ +/* + * 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 { take } from 'rxjs/operators'; +import { Subject, of } from 'rxjs'; + +import { calculateStatus$ } from './status'; +import { ServiceStatusLevels, ServiceStatus } from '../status'; +import { ServiceStatusLevelSnapshotSerializer } from '../status/test_utils'; +import { NodesVersionCompatibility } from './version_check/ensure_es_version'; + +expect.addSnapshotSerializer(ServiceStatusLevelSnapshotSerializer); + +const nodeInfo = { + version: '1.1.1', + ip: '1.1.1.1', + http: { + publish_address: 'https://1.1.1.1:9200', + }, + name: 'node1', +}; + +describe('calculateStatus', () => { + it('starts in unavailable', async () => { + expect( + await calculateStatus$(new Subject()) + .pipe(take(1)) + .toPromise() + ).toEqual({ + level: ServiceStatusLevels.unavailable, + summary: 'Waiting for Elasticsearch', + meta: { + warningNodes: [], + incompatibleNodes: [], + }, + }); + }); + + it('changes to available when isCompatible and no warningNodes', async () => { + expect( + await calculateStatus$( + of({ isCompatible: true, kibanaVersion: '1.1.1', warningNodes: [], incompatibleNodes: [] }) + ) + .pipe(take(2)) + .toPromise() + ).toEqual({ + level: ServiceStatusLevels.available, + summary: 'Elasticsearch is available', + meta: { + warningNodes: [], + incompatibleNodes: [], + }, + }); + }); + + it('changes to degraded when isCompatible and warningNodes present', async () => { + expect( + await calculateStatus$( + of({ + isCompatible: true, + kibanaVersion: '1.1.1', + warningNodes: [nodeInfo], + incompatibleNodes: [], + // this isn't the real message, just used to test that the message + // is forwarded to the status + message: 'Some nodes are a different version', + }) + ) + .pipe(take(2)) + .toPromise() + ).toEqual({ + level: ServiceStatusLevels.degraded, + summary: 'Some nodes are a different version', + meta: { + incompatibleNodes: [], + warningNodes: [nodeInfo], + }, + }); + }); + + it('changes to critical when isCompatible is false', async () => { + expect( + await calculateStatus$( + of({ + isCompatible: false, + kibanaVersion: '2.1.1', + warningNodes: [nodeInfo], + incompatibleNodes: [nodeInfo], + // this isn't the real message, just used to test that the message + // is forwarded to the status + message: 'Incompatible with Elasticsearch', + }) + ) + .pipe(take(2)) + .toPromise() + ).toEqual({ + level: ServiceStatusLevels.critical, + summary: 'Incompatible with Elasticsearch', + meta: { + incompatibleNodes: [nodeInfo], + warningNodes: [nodeInfo], + }, + }); + }); + + it('emits status updates when node compatibility changes', () => { + const nodeCompat$ = new Subject(); + + const statusUpdates: ServiceStatus[] = []; + const subscription = calculateStatus$(nodeCompat$).subscribe(status => + statusUpdates.push(status) + ); + + nodeCompat$.next({ + isCompatible: false, + kibanaVersion: '2.1.1', + incompatibleNodes: [], + warningNodes: [], + message: 'Unable to retrieve version info', + }); + nodeCompat$.next({ + isCompatible: false, + kibanaVersion: '2.1.1', + incompatibleNodes: [nodeInfo], + warningNodes: [], + message: 'Incompatible with Elasticsearch', + }); + nodeCompat$.next({ + isCompatible: true, + kibanaVersion: '1.1.1', + warningNodes: [nodeInfo], + incompatibleNodes: [], + message: 'Some nodes are incompatible', + }); + nodeCompat$.next({ + isCompatible: true, + kibanaVersion: '1.1.1', + warningNodes: [], + incompatibleNodes: [], + }); + + subscription.unsubscribe(); + expect(statusUpdates).toMatchInlineSnapshot(` + Array [ + Object { + "level": unavailable, + "meta": Object { + "incompatibleNodes": Array [], + "warningNodes": Array [], + }, + "summary": "Waiting for Elasticsearch", + }, + Object { + "level": critical, + "meta": Object { + "incompatibleNodes": Array [], + "warningNodes": Array [], + }, + "summary": "Unable to retrieve version info", + }, + Object { + "level": critical, + "meta": Object { + "incompatibleNodes": Array [ + Object { + "http": Object { + "publish_address": "https://1.1.1.1:9200", + }, + "ip": "1.1.1.1", + "name": "node1", + "version": "1.1.1", + }, + ], + "warningNodes": Array [], + }, + "summary": "Incompatible with Elasticsearch", + }, + Object { + "level": degraded, + "meta": Object { + "incompatibleNodes": Array [], + "warningNodes": Array [ + Object { + "http": Object { + "publish_address": "https://1.1.1.1:9200", + }, + "ip": "1.1.1.1", + "name": "node1", + "version": "1.1.1", + }, + ], + }, + "summary": "Some nodes are incompatible", + }, + Object { + "level": available, + "meta": Object { + "incompatibleNodes": Array [], + "warningNodes": Array [], + }, + "summary": "Elasticsearch is available", + }, + ] + `); + }); +}); diff --git a/src/core/server/elasticsearch/status.ts b/src/core/server/elasticsearch/status.ts new file mode 100644 index 0000000000000..1eaa338af1239 --- /dev/null +++ b/src/core/server/elasticsearch/status.ts @@ -0,0 +1,78 @@ +/* + * 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, of } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { ServiceStatus, ServiceStatusLevels } from '../status'; +import { ElasticsearchStatusMeta } from './types'; +import { NodesVersionCompatibility } from './version_check/ensure_es_version'; + +export const calculateStatus$ = ( + esNodesCompatibility$: Observable +): Observable> => + merge( + of({ + level: ServiceStatusLevels.unavailable, + summary: `Waiting for Elasticsearch`, + meta: { + warningNodes: [], + incompatibleNodes: [], + }, + }), + esNodesCompatibility$.pipe( + map( + ({ + isCompatible, + message, + incompatibleNodes, + warningNodes, + }): ServiceStatus => { + if (!isCompatible) { + return { + level: ServiceStatusLevels.critical, + summary: + // Message should always be present, but this is a safe fallback + message ?? + `Some Elasticsearch nodes are not compatible with this version of Kibana`, + meta: { warningNodes, incompatibleNodes }, + }; + } else if (warningNodes.length > 0) { + return { + level: ServiceStatusLevels.degraded, + summary: + // Message should always be present, but this is a safe fallback + message ?? + `Some Elasticsearch nodes are running different versions than this version of Kibana`, + meta: { warningNodes, incompatibleNodes }, + }; + } + + return { + level: ServiceStatusLevels.available, + summary: `Elasticsearch is available`, + meta: { + warningNodes: [], + incompatibleNodes: [], + }, + }; + } + ) + ) + ); diff --git a/src/core/server/elasticsearch/types.ts b/src/core/server/elasticsearch/types.ts index ef8edecfd26ec..3d38935e9fbf0 100644 --- a/src/core/server/elasticsearch/types.ts +++ b/src/core/server/elasticsearch/types.ts @@ -22,6 +22,7 @@ import { ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchClientConfig } from './elasticsearch_client_config'; import { IClusterClient, ICustomClusterClient } from './cluster_client'; import { NodesVersionCompatibility } from './version_check/ensure_es_version'; +import { ServiceStatus } from '../status'; /** * @public @@ -128,4 +129,11 @@ export interface InternalElasticsearchServiceSetup extends ElasticsearchServiceS readonly config$: Observable; }; esNodesCompatibility$: Observable; + status$: Observable>; +} + +/** @public */ +export interface ElasticsearchStatusMeta { + warningNodes: NodesVersionCompatibility['warningNodes']; + incompatibleNodes: NodesVersionCompatibility['incompatibleNodes']; } diff --git a/src/core/server/elasticsearch/version_check/ensure_es_version.ts b/src/core/server/elasticsearch/version_check/ensure_es_version.ts index 3e760ec0efabd..7bd6331978d1d 100644 --- a/src/core/server/elasticsearch/version_check/ensure_es_version.ts +++ b/src/core/server/elasticsearch/version_check/ensure_es_version.ts @@ -142,7 +142,7 @@ export const pollEsNodesVersion = ({ kibanaVersion, ignoreVersionMismatch, esVersionCheckInterval: healthCheckInterval, -}: PollEsNodesVersionOptions): Observable => { +}: PollEsNodesVersionOptions): Observable => { log.debug('Checking Elasticsearch version'); return timer(0, healthCheckInterval).pipe( exhaustMap(() => { diff --git a/src/core/server/http/http_tools.ts b/src/core/server/http/http_tools.ts index 747fd5a10b168..6e785ae9f8f00 100644 --- a/src/core/server/http/http_tools.ts +++ b/src/core/server/http/http_tools.ts @@ -36,6 +36,10 @@ export function getServerOptions(config: HttpConfig, { configureTLS = true } = { host: config.host, port: config.port, routes: { + cache: { + privacy: 'private', + otherwise: 'private, no-cache, no-store, must-revalidate', + }, cors: config.cors, payload: { maxBytes: config.maxPayload.getValueInBytes(), diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index ee5b0c50acafb..af05229f86f20 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -326,6 +326,38 @@ describe('Options', () => { }); }); +describe('Cache-Control', () => { + it('does not allow responses to be cached by default', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.get({ path: '/', validate: false, options: {} }, (context, req, res) => res.ok()); + await server.start(); + + await supertest(innerServer.listener) + .get('/') + .expect('Cache-Control', 'private, no-cache, no-store, must-revalidate'); + }); + + it('allows individual responses override the default cache-control header', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + router.get({ path: '/', validate: false, options: {} }, (context, req, res) => + res.ok({ + headers: { + 'Cache-Control': 'public, max-age=1200', + }, + }) + ); + await server.start(); + + await supertest(innerServer.listener) + .get('/') + .expect('Cache-Control', 'public, max-age=1200'); + }); +}); + describe('Handler', () => { it("Doesn't expose error details if handler throws", async () => { const { server: innerServer, createRouter } = await server.setup(setupDeps); diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 56ce16a951aa2..a298f80f96d8f 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -60,6 +60,7 @@ import { import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; import { UuidServiceSetup } from './uuid'; import { MetricsServiceSetup } from './metrics'; +import { StatusServiceSetup } from './status'; export { bootstrap } from './bootstrap'; export { Capabilities, CapabilitiesProvider, CapabilitiesSwitcher } from './capabilities'; @@ -95,6 +96,8 @@ export { ElasticsearchErrorHelpers, ElasticsearchServiceSetup, ElasticsearchServiceStart, + ElasticsearchStatusMeta, + NodesVersionCompatibility, APICaller, FakeRequest, ScopeableRequest, @@ -226,6 +229,7 @@ export { SavedObjectsUpdateResponse, SavedObjectsServiceStart, SavedObjectsServiceSetup, + SavedObjectStatusMeta, SavedObjectsDeleteOptions, ISavedObjectsRepository, SavedObjectsRepository, @@ -294,6 +298,14 @@ export { LegacyInternals, } from './legacy'; +export { + CoreStatus, + ServiceStatus, + ServiceStatusLevel, + ServiceStatusLevels, + StatusServiceSetup, +} from './status'; + /** * Plugin specific context passed to a route handler. * @@ -348,14 +360,16 @@ export interface CoreSetup; } diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index 825deea99bc23..ede0d3dc9fcc7 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -31,6 +31,7 @@ import { import { InternalUiSettingsServiceSetup, InternalUiSettingsServiceStart } from './ui_settings'; import { UuidServiceSetup } from './uuid'; import { InternalMetricsServiceSetup } from './metrics'; +import { InternalStatusServiceSetup } from './status'; /** @internal */ export interface InternalCoreSetup { @@ -38,10 +39,11 @@ export interface InternalCoreSetup { context: ContextSetup; http: InternalHttpServiceSetup; elasticsearch: InternalElasticsearchServiceSetup; - uiSettings: InternalUiSettingsServiceSetup; + metrics: InternalMetricsServiceSetup; savedObjects: InternalSavedObjectsServiceSetup; + status: InternalStatusServiceSetup; + uiSettings: InternalUiSettingsServiceSetup; uuid: UuidServiceSetup; - metrics: InternalMetricsServiceSetup; } /** diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index c6860086e7784..0cf2ebe55ea10 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -48,6 +48,7 @@ import { findLegacyPluginSpecs } from './plugins'; import { LegacyVars, LegacyServiceSetupDeps, LegacyServiceStartDeps } from './types'; import { LegacyService } from './legacy_service'; import { coreMock } from '../mocks'; +import { statusServiceMock } from '../status/status_service.mock'; const MockKbnServer: jest.Mock = KbnServer as any; @@ -106,6 +107,7 @@ beforeEach(() => { rendering: renderingServiceMock, metrics: metricsServiceMock.createInternalSetupContract(), uuid: uuidSetup, + status: statusServiceMock.createInternalSetupContract(), }, plugins: { 'plugin-id': 'plugin-value' }, }; diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index bb5f6d5617aae..f77230301ce02 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -306,6 +306,9 @@ export class LegacyService implements CoreService { registerType: setupDeps.core.savedObjects.registerType, getImportExportObjectLimit: setupDeps.core.savedObjects.getImportExportObjectLimit, }, + status: { + core$: setupDeps.core.status.core$, + }, uiSettings: { register: setupDeps.core.uiSettings.register, }, diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 31bf17da041af..faf73044cac4d 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -33,6 +33,7 @@ import { InternalCoreSetup, InternalCoreStart } from './internal_types'; import { capabilitiesServiceMock } from './capabilities/capabilities_service.mock'; import { metricsServiceMock } from './metrics/metrics_service.mock'; import { uuidServiceMock } from './uuid/uuid_service.mock'; +import { statusServiceMock } from './status/status_service.mock'; export { httpServerMock } from './http/http_server.mocks'; export { sessionStorageMock } from './http/cookie_session_storage.mocks'; @@ -133,9 +134,10 @@ function createCoreSetupMock({ elasticsearch: elasticsearchServiceMock.createSetup(), http: httpMock, savedObjects: savedObjectsServiceMock.createInternalSetupContract(), + status: statusServiceMock.createSetupContract(), + metrics: metricsServiceMock.createSetupContract(), uiSettings: uiSettingsMock, uuid: uuidServiceMock.createSetupContract(), - metrics: metricsServiceMock.createSetupContract(), getStartServices: jest .fn, object, any]>, []>() .mockResolvedValue([createCoreStartMock(), pluginStartDeps, pluginStartContract]), @@ -161,10 +163,11 @@ function createInternalCoreSetupMock() { context: contextServiceMock.createSetupContract(), elasticsearch: elasticsearchServiceMock.createInternalSetup(), http: httpServiceMock.createSetupContract(), - uiSettings: uiSettingsServiceMock.createSetupContract(), + metrics: metricsServiceMock.createInternalSetupContract(), savedObjects: savedObjectsServiceMock.createInternalSetupContract(), + status: statusServiceMock.createInternalSetupContract(), uuid: uuidServiceMock.createSetupContract(), - metrics: metricsServiceMock.createInternalSetupContract(), + uiSettings: uiSettingsServiceMock.createSetupContract(), }; return setupDeps; } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 32662f07a86f0..61d97aea97459 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -175,6 +175,9 @@ export function createPluginSetupContext( registerType: deps.savedObjects.registerType, getImportExportObjectLimit: deps.savedObjects.getImportExportObjectLimit, }, + status: { + core$: deps.status.core$, + }, uiSettings: { register: deps.uiSettings.register, }, diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index b50e47b9eab73..fe4795cad11a5 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -68,7 +68,11 @@ export { SavedObjectMigrationContext, } from './migrations'; -export { SavedObjectsType, SavedObjectsTypeManagementDefinition } from './types'; +export { + SavedObjectStatusMeta, + SavedObjectsType, + SavedObjectsTypeManagementDefinition, +} from './types'; export { savedObjectsConfig, savedObjectsMigrationConfig } from './saved_objects_config'; export { SavedObjectTypeRegistry, ISavedObjectTypeRegistry } from './saved_objects_type_registry'; diff --git a/src/core/server/saved_objects/migrations/core/index.ts b/src/core/server/saved_objects/migrations/core/index.ts index 4fbadf90f4b60..466d399f653cd 100644 --- a/src/core/server/saved_objects/migrations/core/index.ts +++ b/src/core/server/saved_objects/migrations/core/index.ts @@ -22,4 +22,4 @@ export { IndexMigrator } from './index_migrator'; export { buildActiveMappings } from './build_active_mappings'; export { CallCluster } from './call_cluster'; export { LogFn } from './migration_logger'; -export { MigrationResult } from './migration_coordinator'; +export { MigrationResult, MigrationStatus } from './migration_coordinator'; diff --git a/src/core/server/saved_objects/migrations/core/migration_coordinator.ts b/src/core/server/saved_objects/migrations/core/migration_coordinator.ts index ddd82edd93448..5ba2d0afc692e 100644 --- a/src/core/server/saved_objects/migrations/core/migration_coordinator.ts +++ b/src/core/server/saved_objects/migrations/core/migration_coordinator.ts @@ -39,6 +39,8 @@ import { SavedObjectsMigrationLogger } from './migration_logger'; const DEFAULT_POLL_INTERVAL = 15000; +export type MigrationStatus = 'waiting' | 'running' | 'completed'; + export type MigrationResult = | { status: 'skipped' } | { status: 'patched' } diff --git a/src/core/server/saved_objects/migrations/index.ts b/src/core/server/saved_objects/migrations/index.ts index dc966f0797822..8ddaed3707eb0 100644 --- a/src/core/server/saved_objects/migrations/index.ts +++ b/src/core/server/saved_objects/migrations/index.ts @@ -17,6 +17,7 @@ * under the License. */ +export { MigrationResult } from './core'; export { KibanaMigrator, IKibanaMigrator } from './kibana'; export { SavedObjectMigrationFn, diff --git a/src/core/server/saved_objects/migrations/kibana/index.ts b/src/core/server/saved_objects/migrations/kibana/index.ts index 25772c4c9b0b1..df4751521ac53 100644 --- a/src/core/server/saved_objects/migrations/kibana/index.ts +++ b/src/core/server/saved_objects/migrations/kibana/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { KibanaMigrator, IKibanaMigrator } from './kibana_migrator'; +export { KibanaMigrator, IKibanaMigrator, KibanaMigratorStatus } from './kibana_migrator'; diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts index 2ee656721abd0..257b32c1e4c23 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.mock.ts @@ -17,10 +17,11 @@ * under the License. */ -import { KibanaMigrator } from './kibana_migrator'; +import { KibanaMigrator, KibanaMigratorStatus } from './kibana_migrator'; import { buildActiveMappings } from '../core'; const { mergeTypes } = jest.requireActual('./kibana_migrator'); import { SavedObjectsType } from '../../types'; +import { BehaviorSubject } from 'rxjs'; const defaultSavedObjectTypes: SavedObjectsType[] = [ { @@ -47,6 +48,20 @@ const createMigrator = ( runMigrations: jest.fn(), getActiveMappings: jest.fn(), migrateDocument: jest.fn(), + getStatus$: jest.fn( + () => + new BehaviorSubject({ + status: 'completed', + result: [ + { + status: 'migrated', + destIndex: '.test-kibana_2', + sourceIndex: '.test-kibana_1', + elapsedMs: 10, + }, + ], + }) + ), }; mockMigrator.getActiveMappings.mockReturnValue(buildActiveMappings(mergeTypes(types))); diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts index fd82bf282266e..336eeff99f47b 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { take } from 'rxjs/operators'; import { KibanaMigratorOptions, KibanaMigrator } from './kibana_migrator'; import { loggingServiceMock } from '../../../logging/logging_service.mock'; @@ -79,6 +80,33 @@ describe('KibanaMigrator', () => { .filter(callClusterPath => callClusterPath === 'cat.templates'); expect(callClusterCommands.length).toBe(1); }); + + it('emits results on getMigratorResult$()', async () => { + const options = mockOptions(); + const clusterStub = jest.fn(() => ({ status: 404 })); + + options.callCluster = clusterStub; + const migrator = new KibanaMigrator(options); + const migratorStatus = migrator + .getStatus$() + .pipe(take(3)) + .toPromise(); + await migrator.runMigrations(); + const { status, result } = await migratorStatus; + expect(status).toEqual('completed'); + expect(result![0]).toMatchObject({ + destIndex: '.my-index_1', + elapsedMs: expect.any(Number), + sourceIndex: '.my-index', + status: 'migrated', + }); + expect(result![1]).toMatchObject({ + destIndex: 'other-index_1', + elapsedMs: expect.any(Number), + sourceIndex: 'other-index', + status: 'migrated', + }); + }); }); }); diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts index bc29061b380b8..dafd6c5341196 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts @@ -24,10 +24,17 @@ import { Logger } from 'src/core/server/logging'; import { KibanaConfigType } from 'src/core/server/kibana_config'; +import { BehaviorSubject } from 'rxjs'; import { IndexMapping, SavedObjectsTypeMappingDefinitions } from '../../mappings'; import { SavedObjectUnsanitizedDoc, SavedObjectsSerializer } from '../../serialization'; import { docValidator, PropertyValidators } from '../../validation'; -import { buildActiveMappings, CallCluster, IndexMigrator } from '../core'; +import { + buildActiveMappings, + CallCluster, + IndexMigrator, + MigrationResult, + MigrationStatus, +} from '../core'; import { DocumentMigrator, VersionedTransformer } from '../core/document_migrator'; import { createIndexMap } from '../core/build_index_map'; import { SavedObjectsMigrationConfigType } from '../../saved_objects_config'; @@ -46,6 +53,11 @@ export interface KibanaMigratorOptions { export type IKibanaMigrator = Pick; +export interface KibanaMigratorStatus { + status: MigrationStatus; + result?: MigrationResult[]; +} + /** * Manages the shape of mappings and documents in the Kibana index. */ @@ -58,7 +70,10 @@ export class KibanaMigrator { private readonly mappingProperties: SavedObjectsTypeMappingDefinitions; private readonly typeRegistry: ISavedObjectTypeRegistry; private readonly serializer: SavedObjectsSerializer; - private migrationResult?: Promise>; + private migrationResult?: Promise; + private readonly status$ = new BehaviorSubject({ + status: 'waiting', + }); /** * Creates an instance of KibanaMigrator. @@ -109,12 +124,20 @@ export class KibanaMigrator { Array<{ status: string }> > { if (this.migrationResult === undefined || rerun) { - this.migrationResult = this.runMigrationsInternal(); + this.status$.next({ status: 'running' }); + this.migrationResult = this.runMigrationsInternal().then(result => { + this.status$.next({ status: 'completed', result }); + return result; + }); } return this.migrationResult; } + public getStatus$() { + return this.status$.asObservable(); + } + private runMigrationsInternal() { const kibanaIndexName = this.kibanaConfig.index; const indexMap = createIndexMap({ diff --git a/src/core/server/saved_objects/saved_objects_service.mock.ts b/src/core/server/saved_objects/saved_objects_service.mock.ts index 9fe32b14e6450..7ba4613c857d7 100644 --- a/src/core/server/saved_objects/saved_objects_service.mock.ts +++ b/src/core/server/saved_objects/saved_objects_service.mock.ts @@ -17,6 +17,8 @@ * under the License. */ +import { BehaviorSubject } from 'rxjs'; + import { SavedObjectsService, InternalSavedObjectsServiceSetup, @@ -29,6 +31,7 @@ import { savedObjectsClientProviderMock } from './service/lib/scoped_client_prov import { savedObjectsRepositoryMock } from './service/lib/repository.mock'; import { savedObjectsClientMock } from './service/saved_objects_client.mock'; import { typeRegistryMock } from './saved_objects_type_registry.mock'; +import { ServiceStatusLevels } from '../status'; type SavedObjectsServiceContract = PublicMethodsOf; @@ -75,6 +78,10 @@ const createSetupContractMock = () => { const createInternalSetupContractMock = () => { const internalSetupContract: jest.Mocked = { ...createSetupContractMock(), + status$: new BehaviorSubject({ + level: ServiceStatusLevels.available, + summary: `SavedObjects is available`, + }), }; return internalSetupContract; }; diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index aa440c6454569..62027928c0bb5 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -17,8 +17,8 @@ * under the License. */ -import { Subject } from 'rxjs'; -import { first, filter, take } from 'rxjs/operators'; +import { Subject, Observable } from 'rxjs'; +import { first, filter, take, switchMap } from 'rxjs/operators'; import { CoreService } from '../../types'; import { SavedObjectsClient, @@ -38,7 +38,7 @@ import { SavedObjectConfig, } from './saved_objects_config'; import { KibanaRequest, InternalHttpServiceSetup } from '../http'; -import { SavedObjectsClientContract, SavedObjectsType } from './types'; +import { SavedObjectsClientContract, SavedObjectsType, SavedObjectStatusMeta } from './types'; import { ISavedObjectsRepository, SavedObjectsRepository } from './service/lib/repository'; import { SavedObjectsClientFactoryProvider, @@ -50,6 +50,8 @@ import { SavedObjectTypeRegistry, ISavedObjectTypeRegistry } from './saved_objec import { PropertyValidators } from './validation'; import { SavedObjectsSerializer } from './serialization'; import { registerRoutes } from './routes'; +import { ServiceStatus } from '../status'; +import { calculateStatus$ } from './status'; /** * Saved Objects is Kibana's data persistence mechanism allowing plugins to @@ -164,7 +166,9 @@ export interface SavedObjectsServiceSetup { /** * @internal */ -export type InternalSavedObjectsServiceSetup = SavedObjectsServiceSetup; +export interface InternalSavedObjectsServiceSetup extends SavedObjectsServiceSetup { + status$: Observable>; +} /** * Saved Objects is Kibana's data persisentence mechanism allowing plugins to @@ -321,6 +325,10 @@ export class SavedObjectsService }); return { + status$: calculateStatus$( + this.migrator$.pipe(switchMap(migrator => migrator.getStatus$())), + setupDeps.elasticsearch.status$ + ), setClientFactoryProvider: provider => { if (this.started) { throw new Error('cannot call `setClientFactoryProvider` after service startup.'); diff --git a/src/core/server/saved_objects/status.test.ts b/src/core/server/saved_objects/status.test.ts new file mode 100644 index 0000000000000..8efea1e2c00c6 --- /dev/null +++ b/src/core/server/saved_objects/status.test.ts @@ -0,0 +1,134 @@ +/* + * 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 { of, Observable } from 'rxjs'; +import { ServiceStatus, ServiceStatusLevels } from '../status'; +import { calculateStatus$ } from './status'; +import { take } from 'rxjs/operators'; + +describe('calculateStatus$', () => { + const expectUnavailableDueToEs = (status$: Observable) => + expect(status$.pipe(take(1)).toPromise()).resolves.toEqual({ + level: ServiceStatusLevels.unavailable, + summary: `SavedObjects service is not available without a healthy Elasticearch connection`, + }); + + const expectUnavailableDueToMigrations = (status$: Observable) => + expect(status$.pipe(take(1)).toPromise()).resolves.toEqual({ + level: ServiceStatusLevels.unavailable, + summary: `SavedObjects service is waiting to start migrations`, + }); + + describe('when elasticsearch is unavailable', () => { + const esStatus$ = of({ + level: ServiceStatusLevels.unavailable, + summary: 'xxx', + }); + + it('is unavailable before migrations have ran', async () => { + await expectUnavailableDueToEs(calculateStatus$(of(), esStatus$)); + }); + it('is unavailable after migrations have ran', async () => { + await expectUnavailableDueToEs( + calculateStatus$(of({ status: 'completed', result: [] }), esStatus$) + ); + }); + }); + + describe('when elasticsearch is critical', () => { + const esStatus$ = of({ + level: ServiceStatusLevels.critical, + summary: 'xxx', + }); + + it('is unavailable before migrations have ran', async () => { + await expectUnavailableDueToEs(calculateStatus$(of(), esStatus$)); + }); + it('is unavailable after migrations have ran', async () => { + await expectUnavailableDueToEs( + calculateStatus$( + of({ status: 'completed', result: [{ status: 'migrated' } as any] }), + esStatus$ + ) + ); + }); + }); + + describe('when elasticsearch is available', () => { + const esStatus$ = of({ + level: ServiceStatusLevels.available, + summary: 'Available', + }); + + it('is unavailable before migrations have ran', async () => { + await expectUnavailableDueToMigrations(calculateStatus$(of(), esStatus$)); + }); + it('is unavailable while migrations are running', async () => { + await expect( + calculateStatus$(of({ status: 'running' }), esStatus$) + .pipe(take(2)) + .toPromise() + ).resolves.toEqual({ + level: ServiceStatusLevels.unavailable, + summary: `SavedObjects service is running migrations`, + }); + }); + it('is available after migrations have ran', async () => { + await expect( + calculateStatus$( + of({ status: 'completed', result: [{ status: 'skipped' }, { status: 'patched' }] }), + esStatus$ + ) + .pipe(take(2)) + .toPromise() + ).resolves.toEqual({ + level: ServiceStatusLevels.available, + summary: `SavedObjects service has completed migrations and is available`, + meta: { + migratedIndices: { + migrated: 0, + patched: 1, + skipped: 1, + }, + }, + }); + }); + }); + + describe('when elasticsearch is degraded', () => { + const esStatus$ = of({ level: ServiceStatusLevels.degraded, summary: 'xxx' }); + + it('is unavailable before migrations have ran', async () => { + await expectUnavailableDueToMigrations(calculateStatus$(of(), esStatus$)); + }); + it('is degraded after migrations have ran', async () => { + await expect( + calculateStatus$( + of([{ status: 'skipped' }]), + esStatus$ + ) + .pipe(take(2)) + .toPromise() + ).resolves.toEqual({ + level: ServiceStatusLevels.degraded, + summary: 'SavedObjects service is degraded due to Elasticsearch: [xxx]', + }); + }); + }); +}); diff --git a/src/core/server/saved_objects/status.ts b/src/core/server/saved_objects/status.ts new file mode 100644 index 0000000000000..66a6e2baa17a7 --- /dev/null +++ b/src/core/server/saved_objects/status.ts @@ -0,0 +1,84 @@ +/* + * 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, combineLatest } from 'rxjs'; +import { startWith, map } from 'rxjs/operators'; +import { ServiceStatus, ServiceStatusLevels } from '../status'; +import { SavedObjectStatusMeta } from './types'; +import { KibanaMigratorStatus } from './migrations/kibana'; + +export const calculateStatus$ = ( + rawMigratorStatus$: Observable, + elasticsearchStatus$: Observable +): Observable> => { + const migratorStatus$: Observable> = rawMigratorStatus$.pipe( + map(migrationStatus => { + if (migrationStatus.status === 'waiting') { + return { + level: ServiceStatusLevels.unavailable, + summary: `SavedObjects service is waiting to start migrations`, + }; + } else if (migrationStatus.status === 'running') { + return { + level: ServiceStatusLevels.unavailable, + summary: `SavedObjects service is running migrations`, + }; + } + + const statusCounts: SavedObjectStatusMeta['migratedIndices'] = { migrated: 0, skipped: 0 }; + if (migrationStatus.result) { + migrationStatus.result.forEach(({ status }) => { + statusCounts[status] = (statusCounts[status] ?? 0) + 1; + }); + } + + return { + level: ServiceStatusLevels.available, + summary: `SavedObjects service has completed migrations and is available`, + meta: { + migratedIndices: statusCounts, + }, + }; + }), + startWith({ + level: ServiceStatusLevels.unavailable, + summary: `SavedObjects service is waiting to start migrations`, + }) + ); + + return combineLatest([elasticsearchStatus$, migratorStatus$]).pipe( + map(([esStatus, migratorStatus]) => { + if (esStatus.level >= ServiceStatusLevels.unavailable) { + return { + level: ServiceStatusLevels.unavailable, + summary: `SavedObjects service is not available without a healthy Elasticearch connection`, + }; + } else if (migratorStatus.level === ServiceStatusLevels.unavailable) { + return migratorStatus; + } else if (esStatus.level === ServiceStatusLevels.degraded) { + return { + level: esStatus.level, + summary: `SavedObjects service is degraded due to Elasticsearch: [${esStatus.summary}]`, + }; + } else { + return migratorStatus; + } + }) + ); +}; diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index 962965a08f8b2..f14e9d9efb5e3 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -46,6 +46,19 @@ export { SavedObjectsMigrationVersion, } from '../../types'; +/** + * Meta information about the SavedObjectService's status. Available to plugins via {@link CoreSetup.status}. + * + * @public + */ +export interface SavedObjectStatusMeta { + migratedIndices: { + [status: string]: number; + skipped: number; + migrated: number; + }; +} + /** * * @public diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index e4e2b8d7adbb7..f3e3b7736d8d3 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -638,6 +638,8 @@ export interface CoreSetup ISavedObjectTypeRegistry; } +// @public +export interface SavedObjectStatusMeta { + // (undocumented) + migratedIndices: { + [status: string]: number; + skipped: number; + migrated: number; + }; +} + // @public (undocumented) export interface SavedObjectsType { convertToAliasScript?: string; @@ -2237,6 +2283,38 @@ export class ScopedClusterClient implements IScopedClusterClient { callAsInternalUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; } +// @public +export interface ServiceStatus | unknown = unknown> { + detail?: string; + documentationUrl?: string; + level: ServiceStatusLevel; + meta?: Meta; + summary: string; +} + +// @public +export type ServiceStatusLevel = typeof ServiceStatusLevels[keyof typeof ServiceStatusLevels]; + +// @public +export const ServiceStatusLevels: Readonly<{ + available: Readonly<{ + toString: () => "available"; + valueOf: () => 0; + }>; + degraded: Readonly<{ + toString: () => "degraded"; + valueOf: () => 1; + }>; + unavailable: Readonly<{ + toString: () => "unavailable"; + valueOf: () => 2; + }>; + critical: Readonly<{ + toString: () => "critical"; + valueOf: () => 3; + }>; +}>; + // @public export interface SessionCookieValidationResult { isValid: boolean; @@ -2274,6 +2352,11 @@ export type SharedGlobalConfig = RecursiveReadonly_2<{ // @public export type StartServicesAccessor = () => Promise<[CoreStart, TPluginsStart, TStart]>; +// @public +export interface StatusServiceSetup { + core$: Observable; +} + // @public export type StringValidation = StringValidationRegex | StringValidationRegexString; diff --git a/src/core/server/server.test.mocks.ts b/src/core/server/server.test.mocks.ts index 53d1b742a6494..5d535c9845724 100644 --- a/src/core/server/server.test.mocks.ts +++ b/src/core/server/server.test.mocks.ts @@ -85,3 +85,9 @@ export const mockMetricsService = metricsServiceMock.create(); jest.doMock('./metrics/metrics_service', () => ({ MetricsService: jest.fn(() => mockMetricsService), })); + +import { statusServiceMock } from './status/status_service.mock'; +export const mockStatusService = statusServiceMock.create(); +jest.doMock('./status/status_service', () => ({ + StatusService: jest.fn(() => mockStatusService), +})); diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts index a4b5a9d81df20..24c41d511180a 100644 --- a/src/core/server/server.test.ts +++ b/src/core/server/server.test.ts @@ -29,6 +29,7 @@ import { mockUiSettingsService, mockRenderingService, mockMetricsService, + mockStatusService, } from './server.test.mocks'; import { BehaviorSubject } from 'rxjs'; @@ -63,6 +64,7 @@ test('sets up services on "setup"', async () => { expect(mockUiSettingsService.setup).not.toHaveBeenCalled(); expect(mockRenderingService.setup).not.toHaveBeenCalled(); expect(mockMetricsService.setup).not.toHaveBeenCalled(); + expect(mockStatusService.setup).not.toHaveBeenCalled(); await server.setup(); @@ -74,6 +76,7 @@ test('sets up services on "setup"', async () => { expect(mockUiSettingsService.setup).toHaveBeenCalledTimes(1); expect(mockRenderingService.setup).toHaveBeenCalledTimes(1); expect(mockMetricsService.setup).toHaveBeenCalledTimes(1); + expect(mockStatusService.setup).toHaveBeenCalledTimes(1); }); test('injects legacy dependency to context#setup()', async () => { @@ -141,6 +144,7 @@ test('stops services on "stop"', async () => { expect(mockSavedObjectsService.stop).not.toHaveBeenCalled(); expect(mockUiSettingsService.stop).not.toHaveBeenCalled(); expect(mockMetricsService.stop).not.toHaveBeenCalled(); + expect(mockStatusService.stop).not.toHaveBeenCalled(); await server.stop(); @@ -151,6 +155,7 @@ test('stops services on "stop"', async () => { expect(mockSavedObjectsService.stop).toHaveBeenCalledTimes(1); expect(mockUiSettingsService.stop).toHaveBeenCalledTimes(1); expect(mockMetricsService.stop).toHaveBeenCalledTimes(1); + expect(mockStatusService.stop).toHaveBeenCalledTimes(1); }); test(`doesn't setup core services if config validation fails`, async () => { @@ -167,6 +172,7 @@ test(`doesn't setup core services if config validation fails`, async () => { expect(mockUiSettingsService.setup).not.toHaveBeenCalled(); expect(mockRenderingService.setup).not.toHaveBeenCalled(); expect(mockMetricsService.setup).not.toHaveBeenCalled(); + expect(mockStatusService.setup).not.toHaveBeenCalled(); }); test(`doesn't setup core services if legacy config validation fails`, async () => { @@ -187,4 +193,5 @@ test(`doesn't setup core services if legacy config validation fails`, async () = expect(mockSavedObjectsService.stop).not.toHaveBeenCalled(); expect(mockUiSettingsService.setup).not.toHaveBeenCalled(); expect(mockMetricsService.setup).not.toHaveBeenCalled(); + expect(mockStatusService.setup).not.toHaveBeenCalled(); }); diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 222be572b75e4..07ea431dd3a0d 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -36,6 +36,9 @@ import { UiSettingsService } from './ui_settings'; import { PluginsService, config as pluginsConfig } from './plugins'; import { SavedObjectsService } from '../server/saved_objects'; import { MetricsService, opsConfig } from './metrics'; +import { CapabilitiesService } from './capabilities'; +import { UuidService } from './uuid'; +import { StatusService } from './status/status_service'; import { config as cspConfig } from './csp'; import { config as elasticsearchConfig } from './elasticsearch'; @@ -50,8 +53,6 @@ import { mapToObject } from '../utils'; import { ContextService } from './context'; import { RequestHandlerContext } from '.'; import { InternalCoreSetup, InternalCoreStart } from './internal_types'; -import { CapabilitiesService } from './capabilities'; -import { UuidService } from './uuid'; const coreId = Symbol('core'); const rootConfigPath = ''; @@ -70,6 +71,7 @@ export class Server { private readonly uiSettings: UiSettingsService; private readonly uuid: UuidService; private readonly metrics: MetricsService; + private readonly status: StatusService; private readonly coreApp: CoreApp; private pluginsInitialized?: boolean; @@ -95,6 +97,7 @@ export class Server { this.capabilities = new CapabilitiesService(core); this.uuid = new UuidService(core); this.metrics = new MetricsService(core); + this.status = new StatusService(core); this.coreApp = new CoreApp(core); } @@ -145,15 +148,21 @@ export class Server { const metricsSetup = await this.metrics.setup({ http: httpSetup }); + const statusSetup = this.status.setup({ + elasticsearch: elasticsearchServiceSetup, + savedObjects: savedObjectsSetup, + }); + const coreSetup: InternalCoreSetup = { capabilities: capabilitiesSetup, context: contextServiceSetup, elasticsearch: elasticsearchServiceSetup, http: httpSetup, - uiSettings: uiSettingsSetup, + metrics: metricsSetup, savedObjects: savedObjectsSetup, + status: statusSetup, + uiSettings: uiSettingsSetup, uuid: uuidSetup, - metrics: metricsSetup, }; const pluginsSetup = await this.plugins.setup(coreSetup); @@ -220,6 +229,7 @@ export class Server { await this.uiSettings.stop(); await this.rendering.stop(); await this.metrics.stop(); + await this.status.stop(); } private registerCoreContext(coreSetup: InternalCoreSetup, rendering: RenderingServiceSetup) { diff --git a/src/core/server/status/get_summary_status.test.ts b/src/core/server/status/get_summary_status.test.ts new file mode 100644 index 0000000000000..7516e82ee784d --- /dev/null +++ b/src/core/server/status/get_summary_status.test.ts @@ -0,0 +1,180 @@ +/* + * 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 { ServiceStatus, ServiceStatusLevels } from './types'; +import { getSummaryStatus } from './get_summary_status'; + +describe('getSummaryStatus', () => { + const available: ServiceStatus = { level: ServiceStatusLevels.available, summary: 'Available' }; + const degraded: ServiceStatus = { + level: ServiceStatusLevels.degraded, + summary: 'This is degraded!', + }; + const unavailable: ServiceStatus = { + level: ServiceStatusLevels.unavailable, + summary: 'This is unavailable!', + }; + const critical: ServiceStatus = { + level: ServiceStatusLevels.critical, + summary: 'This is critical!', + }; + + it('returns available when all status are available', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: available, + s2: available, + s3: available, + }) + ) + ).toMatchObject({ + level: ServiceStatusLevels.available, + }); + }); + + it('returns degraded when the worst status is degraded', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: available, + s2: degraded, + s3: available, + }) + ) + ).toMatchObject({ + level: ServiceStatusLevels.degraded, + }); + }); + + it('returns unavailable when the worst status is unavailable', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: available, + s2: degraded, + s3: unavailable, + }) + ) + ).toMatchObject({ + level: ServiceStatusLevels.unavailable, + }); + }); + + it('returns critical when the worst status is critical', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: critical, + s2: degraded, + s3: unavailable, + }) + ) + ).toMatchObject({ + level: ServiceStatusLevels.critical, + }); + }); + + describe('summary', () => { + describe('when a single service is at highest level', () => { + it('returns all information about that single service', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: degraded, + s2: { + level: ServiceStatusLevels.unavailable, + summary: 'Lorem ipsum', + detail: 'Vivamus pulvinar sem ac luctus ultrices.', + documentationUrl: 'http://helpmenow.com/problem1', + meta: { + custom: { data: 'here' }, + }, + }, + }) + ) + ).toEqual({ + level: ServiceStatusLevels.unavailable, + summary: '[s2]: Lorem ipsum', + detail: 'Vivamus pulvinar sem ac luctus ultrices.', + documentationUrl: 'http://helpmenow.com/problem1', + meta: { + custom: { data: 'here' }, + }, + }); + }); + }); + + describe('when multiple services is at highest level', () => { + it('returns aggregated information about the affected services', () => { + expect( + getSummaryStatus( + Object.entries({ + s1: degraded, + s2: { + level: ServiceStatusLevels.unavailable, + summary: 'Lorem ipsum', + detail: 'Vivamus pulvinar sem ac luctus ultrices.', + documentationUrl: 'http://helpmenow.com/problem1', + meta: { + custom: { data: 'here' }, + }, + }, + s3: { + level: ServiceStatusLevels.unavailable, + summary: 'Proin mattis', + detail: 'Nunc quis nulla at mi lobortis pretium.', + documentationUrl: 'http://helpmenow.com/problem2', + meta: { + other: { data: 'over there' }, + }, + }, + }) + ) + ).toEqual({ + level: ServiceStatusLevels.unavailable, + summary: '[2] services are unavailable', + detail: 'See the status page for more information', + meta: { + affectedServices: { + s2: { + level: ServiceStatusLevels.unavailable, + summary: 'Lorem ipsum', + detail: 'Vivamus pulvinar sem ac luctus ultrices.', + documentationUrl: 'http://helpmenow.com/problem1', + meta: { + custom: { data: 'here' }, + }, + }, + s3: { + level: ServiceStatusLevels.unavailable, + summary: 'Proin mattis', + detail: 'Nunc quis nulla at mi lobortis pretium.', + documentationUrl: 'http://helpmenow.com/problem2', + meta: { + other: { data: 'over there' }, + }, + }, + }, + }, + }); + }); + }); + }); +}); diff --git a/src/core/server/status/get_summary_status.ts b/src/core/server/status/get_summary_status.ts new file mode 100644 index 0000000000000..748a54f0bf8bb --- /dev/null +++ b/src/core/server/status/get_summary_status.ts @@ -0,0 +1,84 @@ +/* + * 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 { ServiceStatus, ServiceStatusLevels, ServiceStatusLevel } from './types'; + +/** + * Returns a single {@link ServiceStatus} that summarizes the most severe status level from a group of statuses. + * @param statuses + */ +export const getSummaryStatus = (statuses: Array<[string, ServiceStatus]>): ServiceStatus => { + const grouped = groupByLevel(statuses); + const highestSeverityLevel = getHighestSeverityLevel(grouped.keys()); + const highestSeverityGroup = grouped.get(highestSeverityLevel)!; + + if (highestSeverityLevel === ServiceStatusLevels.available) { + return { + level: ServiceStatusLevels.available, + summary: `All services are available`, + }; + } else if (highestSeverityGroup.size === 1) { + const [serviceName, status] = [...highestSeverityGroup.entries()][0]; + return { + ...status, + summary: `[${serviceName}]: ${status.summary!}`, + }; + } else { + return { + level: highestSeverityLevel, + summary: `[${highestSeverityGroup.size}] services are ${highestSeverityLevel.toString()}`, + // TODO: include URL to status page + detail: `See the status page for more information`, + meta: { + affectedServices: Object.fromEntries([...highestSeverityGroup]), + }, + }; + } +}; + +const groupByLevel = ( + statuses: Array<[string, ServiceStatus]> +): Map> => { + const byLevel = new Map>(); + + for (const [serviceName, status] of statuses) { + let levelMap = byLevel.get(status.level); + if (!levelMap) { + levelMap = new Map(); + byLevel.set(status.level, levelMap); + } + + levelMap.set(serviceName, status); + } + + return byLevel; +}; + +const getHighestSeverityLevel = (levels: Iterable): ServiceStatusLevel => { + const sorted = [...levels].sort((a, b) => { + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } else { + return 0; + } + }); + return sorted[sorted.length - 1] ?? ServiceStatusLevels.available; +}; diff --git a/src/legacy/server/status/collectors/index.js b/src/core/server/status/index.ts similarity index 91% rename from src/legacy/server/status/collectors/index.js rename to src/core/server/status/index.ts index 92d9e601bbb35..c39115d55a682 100644 --- a/src/legacy/server/status/collectors/index.js +++ b/src/core/server/status/index.ts @@ -17,4 +17,5 @@ * under the License. */ -export { registerOpsStatsCollector } from './get_ops_stats_collector'; +export { StatusService } from './status_service'; +export * from './types'; diff --git a/src/core/server/status/status_service.mock.ts b/src/core/server/status/status_service.mock.ts new file mode 100644 index 0000000000000..d550c2f06750b --- /dev/null +++ b/src/core/server/status/status_service.mock.ts @@ -0,0 +1,71 @@ +/* + * 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 { StatusService } from './status_service'; +import { + InternalStatusServiceSetup, + StatusServiceSetup, + ServiceStatusLevels, + ServiceStatus, + CoreStatus, +} from './types'; +import { BehaviorSubject } from 'rxjs'; + +const available: ServiceStatus = { + level: ServiceStatusLevels.available, + summary: 'Service is working', +}; +const availableCoreStatus: CoreStatus = { + elasticsearch: available, + savedObjects: available, +}; + +const createSetupContractMock = () => { + const setupContract: jest.Mocked = { + core$: new BehaviorSubject(availableCoreStatus), + }; + + return setupContract; +}; + +const createInternalSetupContractMock = () => { + const setupContract: jest.Mocked = { + core$: new BehaviorSubject(availableCoreStatus), + overall$: new BehaviorSubject(available), + }; + + return setupContract; +}; + +type StatusServiceContract = PublicMethodsOf; + +const createMock = () => { + const mocked: jest.Mocked = { + setup: jest.fn().mockReturnValue(createInternalSetupContractMock()), + start: jest.fn(), + stop: jest.fn(), + }; + return mocked; +}; + +export const statusServiceMock = { + create: createMock, + createSetupContract: createSetupContractMock, + createInternalSetupContract: createInternalSetupContractMock, +}; diff --git a/src/core/server/status/status_service.test.ts b/src/core/server/status/status_service.test.ts new file mode 100644 index 0000000000000..6d92a266369b9 --- /dev/null +++ b/src/core/server/status/status_service.test.ts @@ -0,0 +1,229 @@ +/* + * 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 { of, BehaviorSubject } from 'rxjs'; + +import { ServiceStatus, ServiceStatusLevels, CoreStatus } from './types'; +import { StatusService } from './status_service'; +import { first } from 'rxjs/operators'; +import { mockCoreContext } from '../core_context.mock'; +import { ServiceStatusLevelSnapshotSerializer } from './test_utils'; + +expect.addSnapshotSerializer(ServiceStatusLevelSnapshotSerializer); + +describe('StatusService', () => { + const available: ServiceStatus = { + level: ServiceStatusLevels.available, + summary: 'Available', + }; + const degraded: ServiceStatus = { + level: ServiceStatusLevels.degraded, + summary: 'This is degraded!', + }; + + describe('setup', () => { + describe('core$', () => { + it('rolls up core status observables into single observable', async () => { + const setup = new StatusService(mockCoreContext.create()).setup({ + elasticsearch: { + status$: of(available), + }, + savedObjects: { + status$: of(degraded), + }, + }); + expect(await setup.core$.pipe(first()).toPromise()).toEqual({ + elasticsearch: available, + savedObjects: degraded, + }); + }); + + it('replays last event', async () => { + const setup = new StatusService(mockCoreContext.create()).setup({ + elasticsearch: { + status$: of(available), + }, + savedObjects: { + status$: of(degraded), + }, + }); + const subResult1 = await setup.core$.pipe(first()).toPromise(); + const subResult2 = await setup.core$.pipe(first()).toPromise(); + const subResult3 = await setup.core$.pipe(first()).toPromise(); + expect(subResult1).toEqual({ + elasticsearch: available, + savedObjects: degraded, + }); + expect(subResult2).toEqual({ + elasticsearch: available, + savedObjects: degraded, + }); + expect(subResult3).toEqual({ + elasticsearch: available, + savedObjects: degraded, + }); + }); + + it('does not emit duplicate events', () => { + const elasticsearch$ = new BehaviorSubject(available); + const savedObjects$ = new BehaviorSubject(degraded); + const setup = new StatusService(mockCoreContext.create()).setup({ + elasticsearch: { + status$: elasticsearch$, + }, + savedObjects: { + status$: savedObjects$, + }, + }); + + const statusUpdates: CoreStatus[] = []; + const subscription = setup.core$.subscribe(status => statusUpdates.push(status)); + + elasticsearch$.next(available); + elasticsearch$.next(available); + elasticsearch$.next({ + level: ServiceStatusLevels.available, + summary: `Wow another summary`, + }); + savedObjects$.next(degraded); + savedObjects$.next(available); + savedObjects$.next(available); + subscription.unsubscribe(); + + expect(statusUpdates).toMatchInlineSnapshot(` + Array [ + Object { + "elasticsearch": Object { + "level": available, + "summary": "Available", + }, + "savedObjects": Object { + "level": degraded, + "summary": "This is degraded!", + }, + }, + Object { + "elasticsearch": Object { + "level": available, + "summary": "Wow another summary", + }, + "savedObjects": Object { + "level": degraded, + "summary": "This is degraded!", + }, + }, + Object { + "elasticsearch": Object { + "level": available, + "summary": "Wow another summary", + }, + "savedObjects": Object { + "level": available, + "summary": "Available", + }, + }, + ] + `); + }); + }); + + describe('overall$', () => { + it('exposes an overall summary', async () => { + const setup = new StatusService(mockCoreContext.create()).setup({ + elasticsearch: { + status$: of(degraded), + }, + savedObjects: { + status$: of(degraded), + }, + }); + expect(await setup.overall$.pipe(first()).toPromise()).toMatchObject({ + level: ServiceStatusLevels.degraded, + summary: '[2] services are degraded', + }); + }); + + it('replays last event', async () => { + const setup = new StatusService(mockCoreContext.create()).setup({ + elasticsearch: { + status$: of(degraded), + }, + savedObjects: { + status$: of(degraded), + }, + }); + const subResult1 = await setup.overall$.pipe(first()).toPromise(); + const subResult2 = await setup.overall$.pipe(first()).toPromise(); + const subResult3 = await setup.overall$.pipe(first()).toPromise(); + expect(subResult1).toMatchObject({ + level: ServiceStatusLevels.degraded, + summary: '[2] services are degraded', + }); + expect(subResult2).toMatchObject({ + level: ServiceStatusLevels.degraded, + summary: '[2] services are degraded', + }); + expect(subResult3).toMatchObject({ + level: ServiceStatusLevels.degraded, + summary: '[2] services are degraded', + }); + }); + + it('does not emit duplicate events', () => { + const elasticsearch$ = new BehaviorSubject(available); + const savedObjects$ = new BehaviorSubject(degraded); + const setup = new StatusService(mockCoreContext.create()).setup({ + elasticsearch: { + status$: elasticsearch$, + }, + savedObjects: { + status$: savedObjects$, + }, + }); + + const statusUpdates: ServiceStatus[] = []; + const subscription = setup.overall$.subscribe(status => statusUpdates.push(status)); + + elasticsearch$.next(available); + elasticsearch$.next(available); + elasticsearch$.next({ + level: ServiceStatusLevels.available, + summary: `Wow another summary`, + }); + savedObjects$.next(degraded); + savedObjects$.next(available); + savedObjects$.next(available); + subscription.unsubscribe(); + + expect(statusUpdates).toMatchInlineSnapshot(` + Array [ + Object { + "level": degraded, + "summary": "[savedObjects]: This is degraded!", + }, + Object { + "level": available, + "summary": "All services are available", + }, + ] + `); + }); + }); + }); +}); diff --git a/src/core/server/status/status_service.ts b/src/core/server/status/status_service.ts new file mode 100644 index 0000000000000..b6697d8221951 --- /dev/null +++ b/src/core/server/status/status_service.ts @@ -0,0 +1,78 @@ +/* + * 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 max-classes-per-file */ + +import { Observable, combineLatest } from 'rxjs'; +import { map, distinctUntilChanged, shareReplay } from 'rxjs/operators'; +import { isDeepStrictEqual } from 'util'; + +import { CoreService } from '../../types'; +import { CoreContext } from '../core_context'; +import { Logger } from '../logging'; +import { InternalElasticsearchServiceSetup } from '../elasticsearch'; +import { InternalSavedObjectsServiceSetup } from '../saved_objects'; + +import { ServiceStatus, CoreStatus, InternalStatusServiceSetup } from './types'; +import { getSummaryStatus } from './get_summary_status'; + +interface SetupDeps { + elasticsearch: Pick; + savedObjects: Pick; +} + +export class StatusService implements CoreService { + private readonly logger: Logger; + + constructor(coreContext: CoreContext) { + this.logger = coreContext.logger.get('status'); + } + + public setup(core: SetupDeps) { + const core$ = this.setupCoreStatus(core); + const overall$: Observable = core$.pipe( + map(coreStatus => { + const summary = getSummaryStatus(Object.entries(coreStatus)); + this.logger.debug(`Recalculated overall status`, { status: summary }); + return summary; + }), + distinctUntilChanged(isDeepStrictEqual) + ); + + return { + core$, + overall$, + }; + } + + public start() {} + + public stop() {} + + private setupCoreStatus({ elasticsearch, savedObjects }: SetupDeps): Observable { + return combineLatest([elasticsearch.status$, savedObjects.status$]).pipe( + map(([elasticsearchStatus, savedObjectsStatus]) => ({ + elasticsearch: elasticsearchStatus, + savedObjects: savedObjectsStatus, + })), + distinctUntilChanged(isDeepStrictEqual), + shareReplay(1) + ); + } +} diff --git a/src/core/server/status/test_utils.ts b/src/core/server/status/test_utils.ts new file mode 100644 index 0000000000000..765fa8771f375 --- /dev/null +++ b/src/core/server/status/test_utils.ts @@ -0,0 +1,25 @@ +/* + * 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 { ServiceStatusLevels, ServiceStatusLevel } from './types'; + +export const ServiceStatusLevelSnapshotSerializer: jest.SnapshotSerializerPlugin = { + test: (val: any) => Object.values(ServiceStatusLevels).includes(val), + print: (val: ServiceStatusLevel) => val.toString(), +}; diff --git a/src/core/server/status/types.ts b/src/core/server/status/types.ts new file mode 100644 index 0000000000000..84a7356c66bbf --- /dev/null +++ b/src/core/server/status/types.ts @@ -0,0 +1,134 @@ +/* + * 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 } from 'rxjs'; +import { deepFreeze } from '../../utils'; + +/** + * The current status of a service at a point in time. + * + * @typeParam Meta - JSON-serializable object. Plugins should export this type to allow other plugins to read the `meta` + * field in a type-safe way. + * @public + */ +export interface ServiceStatus | unknown = unknown> { + /** + * The current availability level of the service. + */ + level: ServiceStatusLevel; + /** + * A high-level summary of the service status. + */ + summary: string; + /** + * A more detailed description of the service status. + */ + detail?: string; + /** + * A URL to open in a new tab about how to resolve or troubleshoot the problem. + */ + documentationUrl?: string; + /** + * Any JSON-serializable data to be included in the HTTP API response. Useful for providing more fine-grained, + * machine-readable information about the service status. May include status information for underlying features. + */ + meta?: Meta; +} + +/** + * The current "level" of availability of a service. + * + * @remarks + * The values implement `valueOf` to allow for easy comparisons between status levels with <, >, etc. Higher values + * represent higher severities. Note that the default `Array.prototype.sort` implementation does not correctly sort + * these values. + * + * A snapshot serializer is available in `src/core/server/test_utils` to ease testing of these values with Jest. + * + * @public + */ +export const ServiceStatusLevels = deepFreeze({ + /** + * Everything is working! + */ + available: { + toString: () => 'available', + valueOf: () => 0, + }, + /** + * Some features may not be working. + */ + degraded: { + toString: () => 'degraded', + valueOf: () => 1, + }, + /** + * The service is unavailable, but other functions that do not depend on this service should work. + */ + unavailable: { + toString: () => 'unavailable', + valueOf: () => 2, + }, + /** + * Block all user functions and display the status page, reserved for Core services only. + */ + critical: { + toString: () => 'critical', + valueOf: () => 3, + }, +}); + +/** + * A convenience type that represents the union of each value in {@link ServiceStatusLevels}. + * @public + */ +export type ServiceStatusLevel = typeof ServiceStatusLevels[keyof typeof ServiceStatusLevels]; + +/** + * Status of core services. + * + * @internalRemarks + * Only contains entries for backend services that could have a non-available `status`. + * For example, `context` cannot possibly be broken, so it is not included. + * + * @public + */ +export interface CoreStatus { + elasticsearch: ServiceStatus; + savedObjects: ServiceStatus; +} + +/** + * API for accessing status of Core and this plugin's dependencies as well as for customizing this plugin's status. + * @public + */ +export interface StatusServiceSetup { + /** + * Current status for all Core services. + */ + core$: Observable; +} + +/** @internal */ +export interface InternalStatusServiceSetup extends StatusServiceSetup { + /** + * Overall system status used for HTTP API + */ + overall$: Observable; +} diff --git a/src/core/server/test_utils.ts b/src/core/server/test_utils.ts index 470b1c2d135b7..f7e6fbcd0c131 100644 --- a/src/core/server/test_utils.ts +++ b/src/core/server/test_utils.ts @@ -18,3 +18,4 @@ */ export { createHttpServer } from './http/test_utils'; +export { ServiceStatusLevelSnapshotSerializer } from './status/test_utils'; diff --git a/src/dev/run_check_published_api_changes.ts b/src/dev/run_check_published_api_changes.ts index 8bb3fb20cea8b..ba3cd1280f34b 100644 --- a/src/dev/run_check_published_api_changes.ts +++ b/src/dev/run_check_published_api_changes.ts @@ -163,6 +163,7 @@ interface Options { accept: boolean; docs: boolean; help: boolean; + filter: string; } async function run( @@ -205,6 +206,7 @@ async function run( const extraFlags: string[] = []; const opts = (getopts(process.argv.slice(2), { boolean: ['accept', 'docs', 'help'], + string: ['filter'], default: { project: undefined, }, @@ -222,6 +224,8 @@ async function run( opts.help = true; } + const folders = ['core/public', 'core/server', 'plugins/data/server', 'plugins/data/public']; + if (opts.help) { process.stdout.write( dedent(chalk` @@ -240,9 +244,13 @@ async function run( {dim # Checks for and automatically accepts and updates documentation for any changes to the Kibana Core API} {dim $} node scripts/check_published_api_changes --accept + {dim # Only checks the core/public directory} + {dim $} node scripts/check_published_api_changes --filter=core/public + Options: --accept {dim Accepts all changes by updating the API Review files and documentation} --docs {dim Updates the Core API documentation} + --only {dim RegExp that folder names must match, folders: [${folders.join(', ')}]} --help {dim Show this message} `) ); @@ -258,9 +266,11 @@ async function run( return false; } - const folders = ['core/public', 'core/server', 'plugins/data/server', 'plugins/data/public']; - - const results = await Promise.all(folders.map(folder => run(folder, { log, opts }))); + const results = await Promise.all( + folders + .filter(folder => (opts.filter.length ? folder.match(opts.filter) : true)) + .map(folder => run(folder, { log, opts })) + ); if (results.find(r => r === false) !== undefined) { process.exitCode = 1; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx index 029e1f149d052..b7114f1029ef2 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.tsx @@ -30,7 +30,7 @@ import { EuiSelect, } from '@elastic/eui'; -import { VisOptionsProps } from 'src/legacy/core_plugins/vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { IIndexPattern } from 'src/plugins/data/public'; import { ControlEditor } from './control_editor'; import { diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.tsx b/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.tsx index 95b14619c3416..cdff6cabad8ba 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.tsx +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/options_tab.tsx @@ -23,7 +23,7 @@ import { EuiForm, EuiFormRow, EuiSwitch } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSwitchEvent } from '@elastic/eui'; -import { VisOptionsProps } from 'src/legacy/core_plugins/vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; interface OptionsTabParams { updateFiltersOnChange: boolean; diff --git a/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts index 023e6ebb7125c..badea68eec19f 100644 --- a/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts +++ b/src/legacy/core_plugins/input_control_vis/public/input_control_vis_type.ts @@ -23,7 +23,7 @@ import { createInputControlVisController } from './vis_controller'; import { getControlsTab } from './components/editor/controls_tab'; import { OptionsTab } from './components/editor/options_tab'; import { InputControlVisDependencies } from './plugin'; -import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/common'; +import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/public'; export function createInputControlVisTypeDefinition(deps: InputControlVisDependencies) { const InputControlVisController = createInputControlVisController(deps); diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index cac9ef30fba4f..1d643418997f5 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -58,7 +58,6 @@ export default function(kibana) { 'plugins/kibana/discover/legacy', 'plugins/kibana/dev_tools', 'plugins/kibana/visualize/legacy', - 'plugins/kibana/dashboard/legacy', ], app: { id: 'kibana', diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.js b/src/legacy/core_plugins/kibana/migrations/migrations.js index d37887c640b90..029dbde555a4b 100644 --- a/src/legacy/core_plugins/kibana/migrations/migrations.js +++ b/src/legacy/core_plugins/kibana/migrations/migrations.js @@ -18,7 +18,10 @@ */ import { get } from 'lodash'; -import { migrations730 as dashboardMigrations730 } from '../public/dashboard/migrations'; +import { + migrateMatchAllQuery, + migrations730 as dashboardMigrations730, +} from '../public/dashboard/migrations'; function migrateIndexPattern(doc) { const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); @@ -60,6 +63,7 @@ function migrateIndexPattern(doc) { export const migrations = { dashboard: { + '6.7.2': migrateMatchAllQuery, '7.0.0': doc => { // Set new "references" attribute doc.references = doc.references || []; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_variables.scss b/src/legacy/core_plugins/kibana/public/dashboard/_variables.scss deleted file mode 100644 index 4a715ab255166..0000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/_variables.scss +++ /dev/null @@ -1 +0,0 @@ -$dshEditingModeHoverColor: transparentize($euiColorWarning, lightOrDarkTheme(.9, .7)); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy.ts deleted file mode 100644 index cedb6fbc9b5ef..0000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { PluginInitializerContext } from 'kibana/public'; -import { npSetup, npStart } from './legacy_imports'; -import { plugin } from './index'; - -(async () => { - const instance = plugin({ - env: npSetup.plugins.kibanaLegacy.env, - } as PluginInitializerContext); - instance.setup(npSetup.core, npSetup.plugins); - instance.start(npStart.core, npStart.plugins); -})(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts deleted file mode 100644 index 86bce5997cdd2..0000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts +++ /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. - */ - -/** - * The imports in this file are static functions and types which still live in legacy folders and are used - * within dashboard. To consolidate them all in one place, they are re-exported from this file. Eventually - * this list should become empty. Imports from the top level of shimmed or moved plugins can be imported - * directly where they are needed. - */ - -export { npSetup, npStart } from 'ui/new_platform'; -export { - configureAppAngularModule, - migrateLegacyQuery, - subscribeWithScope, -} from '../../../../../plugins/kibana_legacy/public'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/index.ts index da2542e854c32..f333ce97d120f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/index.ts @@ -18,3 +18,4 @@ */ export { migrations730 } from './migrations_730'; +export { migrateMatchAllQuery } from './migrate_match_all_query'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.test.ts new file mode 100644 index 0000000000000..8a91c422eed3d --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.test.ts @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { migrateMatchAllQuery } from './migrate_match_all_query'; +import { SavedObjectMigrationContext, SavedObjectMigrationFn } from 'kibana/server'; + +const savedObjectMigrationContext = (null as unknown) as SavedObjectMigrationContext; + +describe('migrate match_all query', () => { + test('should migrate obsolete match_all query', () => { + const migratedDoc = migrateMatchAllQuery( + { + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + query: { + match_all: {}, + }, + }), + }, + }, + } as Parameters[0], + savedObjectMigrationContext + ); + + const migratedSearchSource = JSON.parse( + migratedDoc.attributes.kibanaSavedObjectMeta.searchSourceJSON + ); + + expect(migratedSearchSource).toEqual({ + query: { + query: '', + language: 'kuery', + }, + }); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts new file mode 100644 index 0000000000000..707aae9e5d4ac --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts @@ -0,0 +1,56 @@ +/* + * 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 { SavedObjectMigrationFn } from 'kibana/server'; +import { get } from 'lodash'; +import { DEFAULT_QUERY_LANGUAGE } from '../../../../../../plugins/data/common'; + +export const migrateMatchAllQuery: SavedObjectMigrationFn = doc => { + const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); + + if (searchSourceJSON) { + let searchSource: any; + + try { + searchSource = JSON.parse(searchSourceJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + + if (searchSource.query?.match_all) { + return { + ...doc, + attributes: { + ...doc.attributes, + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + ...searchSource, + query: { + query: '', + language: DEFAULT_QUERY_LANGUAGE, + }, + }), + }, + }, + }; + } + } + + return doc; +}; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts index e37c8de08fec4..4dd71fd8ee5f4 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts @@ -16,28 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -jest.mock( - 'ui/chrome', - () => ({ - getKibanaVersion: () => '6.3.0', - }), - { virtual: true } -); - -jest.mock( - 'ui/notify', - () => ({ - toastNotifications: { - addDanger: () => {}, - }, - }), - { virtual: true } -); - -jest.mock('ui/new_platform'); - import { migratePanelsTo730 } from './migrate_to_730_panels'; -import { SavedDashboardPanelTo60, SavedDashboardPanel730ToLatest } from '../np_ready/types'; import { RawSavedDashboardPanelTo60, RawSavedDashboardPanel610, @@ -46,6 +25,8 @@ import { RawSavedDashboardPanel640To720, DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT, + SavedDashboardPanelTo60, + SavedDashboardPanel730ToLatest, } from '../../../../../../plugins/dashboard/public'; test('6.0 migrates uiState, sort, scales, and gridData', async () => { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts index 047ec15f9a5d6..a19c861f092d5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts @@ -27,14 +27,11 @@ import { RawSavedDashboardPanel730ToLatest, RawSavedDashboardPanel610, RawSavedDashboardPanel620, -} from '../../../../../../plugins/dashboard/public'; - -import { SavedDashboardPanelTo60, SavedDashboardPanel620, SavedDashboardPanel630, SavedDashboardPanel610, -} from '../np_ready/types'; +} from '../../../../../../plugins/dashboard/public'; const PANEL_HEIGHT_SCALE_FACTOR = 5; const PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS = 4; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts index 34bb46ce5d407..5a4970897098d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { migrations } from '../../../migrations/'; +import { migrations } from '../../../migrations'; import { migrations730 } from './migrations_730'; import { DashboardDoc700To720, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts index 7207f601a225e..01a66445e4fc2 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Filter, Query } from '../../../../../../plugins/data/public'; +import { Filter, Query } from 'src/plugins/data/public'; export interface Pre600FilterQuery { // pre 6.0.0 global query:queryString:options were stored per dashboard and would diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts deleted file mode 100644 index 9f8682f13d811..0000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/types.ts +++ /dev/null @@ -1,131 +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 { ViewMode } from 'src/plugins/embeddable/public'; -import { - RawSavedDashboardPanelTo60, - RawSavedDashboardPanel610, - RawSavedDashboardPanel620, - RawSavedDashboardPanel630, - RawSavedDashboardPanel640To720, - RawSavedDashboardPanel730ToLatest, -} from '../../../../../../plugins/dashboard/public'; -import { Query, Filter } from '../../../../../../plugins/data/public'; - -export type NavAction = (anchorElement?: any) => void; - -/** - * This should always represent the latest dashboard panel shape, after all possible migrations. - */ -export type SavedDashboardPanel = SavedDashboardPanel730ToLatest; - -// id becomes optional starting in 7.3.0 -export type SavedDashboardPanel730ToLatest = Pick< - RawSavedDashboardPanel730ToLatest, - Exclude -> & { - readonly id?: string; - readonly type: string; -}; - -export type SavedDashboardPanel640To720 = Pick< - RawSavedDashboardPanel640To720, - Exclude -> & { - readonly id: string; - readonly type: string; -}; - -export type SavedDashboardPanel630 = Pick< - RawSavedDashboardPanel630, - Exclude -> & { - readonly id: string; - readonly type: string; -}; - -export type SavedDashboardPanel620 = Pick< - RawSavedDashboardPanel620, - Exclude -> & { - readonly id: string; - readonly type: string; -}; - -export type SavedDashboardPanel610 = Pick< - RawSavedDashboardPanel610, - Exclude -> & { - readonly id: string; - readonly type: string; -}; - -export type SavedDashboardPanelTo60 = Pick< - RawSavedDashboardPanelTo60, - Exclude -> & { - readonly id: string; - readonly type: string; -}; - -export interface DashboardAppState { - panels: SavedDashboardPanel[]; - fullScreenMode: boolean; - title: string; - description: string; - timeRestore: boolean; - options: { - hidePanelTitles: boolean; - useMargins: boolean; - }; - query: Query | string; - filters: Filter[]; - viewMode: ViewMode; - savedQuery?: string; -} - -export type DashboardAppStateDefaults = DashboardAppState & { - description?: string; -}; - -export interface DashboardAppStateTransitions { - set: ( - state: DashboardAppState - ) => ( - prop: T, - value: DashboardAppState[T] - ) => DashboardAppState; - setOption: ( - state: DashboardAppState - ) => ( - prop: T, - value: DashboardAppState['options'][T] - ) => DashboardAppState; -} - -export interface SavedDashboardPanelMap { - [key: string]: SavedDashboardPanel; -} - -export interface StagedFilter { - field: string; - value: string; - operator: string; - index: string; -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts deleted file mode 100644 index 7452807454fe7..0000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ /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 { BehaviorSubject } from 'rxjs'; -import { filter, map } from 'rxjs/operators'; -import { - App, - AppMountParameters, - CoreSetup, - CoreStart, - Plugin, - PluginInitializerContext, - SavedObjectsClientContract, -} from 'kibana/public'; -import { i18n } from '@kbn/i18n'; -import { RenderDeps } from './np_ready/application'; -import { - DataPublicPluginStart, - DataPublicPluginSetup, - esFilters, -} from '../../../../../plugins/data/public'; -import { EmbeddableStart } from '../../../../../plugins/embeddable/public'; -import { Storage } from '../../../../../plugins/kibana_utils/public'; -import { NavigationPublicPluginStart as NavigationStart } from '../../../../../plugins/navigation/public'; -import { DashboardConstants } from './np_ready/dashboard_constants'; -import { - FeatureCatalogueCategory, - HomePublicPluginSetup, -} from '../../../../../plugins/home/public'; -import { SharePluginStart } from '../../../../../plugins/share/public'; -import { - AngularRenderedAppUpdater, - KibanaLegacySetup, - KibanaLegacyStart, -} from '../../../../../plugins/kibana_legacy/public'; -import { createKbnUrlTracker } from '../../../../../plugins/kibana_utils/public'; -import { DashboardStart } from '../../../../../plugins/dashboard/public'; - -export interface DashboardPluginStartDependencies { - data: DataPublicPluginStart; - embeddable: EmbeddableStart; - navigation: NavigationStart; - share: SharePluginStart; - kibanaLegacy: KibanaLegacyStart; - dashboard: DashboardStart; -} - -export interface DashboardPluginSetupDependencies { - home: HomePublicPluginSetup; - kibanaLegacy: KibanaLegacySetup; - data: DataPublicPluginSetup; -} - -export class DashboardPlugin implements Plugin { - private startDependencies: { - data: DataPublicPluginStart; - savedObjectsClient: SavedObjectsClientContract; - embeddable: EmbeddableStart; - navigation: NavigationStart; - share: SharePluginStart; - dashboardConfig: KibanaLegacyStart['dashboardConfig']; - dashboard: DashboardStart; - } | null = null; - - private appStateUpdater = new BehaviorSubject(() => ({})); - private stopUrlTracking: (() => void) | undefined = undefined; - - constructor(private initializerContext: PluginInitializerContext) {} - - public setup(core: CoreSetup, { home, kibanaLegacy, data }: DashboardPluginSetupDependencies) { - const { appMounted, appUnMounted, stop: stopUrlTracker } = createKbnUrlTracker({ - baseUrl: core.http.basePath.prepend('/app/kibana'), - defaultSubUrl: `#${DashboardConstants.LANDING_PAGE_PATH}`, - shouldTrackUrlUpdate: pathname => { - const targetAppName = pathname.split('/')[1]; - return ( - targetAppName === DashboardConstants.DASHBOARDS_ID || - targetAppName === DashboardConstants.DASHBOARD_ID - ); - }, - storageKey: 'lastUrl:dashboard', - navLinkUpdater$: this.appStateUpdater, - toastNotifications: core.notifications.toasts, - stateParams: [ - { - kbnUrlKey: '_g', - stateUpdate$: data.query.state$.pipe( - filter( - ({ changes }) => !!(changes.globalFilters || changes.time || changes.refreshInterval) - ), - map(({ state }) => ({ - ...state, - filters: state.filters?.filter(esFilters.isFilterPinned), - })) - ), - }, - ], - }); - this.stopUrlTracking = () => { - stopUrlTracker(); - }; - const app: App = { - id: '', - title: 'Dashboards', - mount: async (params: AppMountParameters) => { - const [coreStart] = await core.getStartServices(); - if (this.startDependencies === null) { - throw new Error('not started yet'); - } - appMounted(); - const { - savedObjectsClient, - embeddable, - navigation, - share, - data: dataStart, - dashboardConfig, - dashboard: { getSavedDashboardLoader }, - } = this.startDependencies; - const savedDashboards = getSavedDashboardLoader(); - - const deps: RenderDeps = { - pluginInitializerContext: this.initializerContext, - core: coreStart, - dashboardConfig, - navigation, - share, - data: dataStart, - savedObjectsClient, - savedDashboards, - chrome: coreStart.chrome, - addBasePath: coreStart.http.basePath.prepend, - uiSettings: coreStart.uiSettings, - config: kibanaLegacy.config, - savedQueryService: dataStart.query.savedQueries, - embeddable, - dashboardCapabilities: coreStart.application.capabilities.dashboard, - embeddableCapabilities: { - visualizeCapabilities: coreStart.application.capabilities.visualize, - mapsCapabilities: coreStart.application.capabilities.maps, - }, - localStorage: new Storage(localStorage), - }; - const { renderApp } = await import('./np_ready/application'); - const unmount = renderApp(params.element, params.appBasePath, deps); - return () => { - unmount(); - appUnMounted(); - }; - }, - }; - kibanaLegacy.registerLegacyApp({ - ...app, - id: DashboardConstants.DASHBOARD_ID, - // only register the updater in once app, otherwise all updates would happen twice - updater$: this.appStateUpdater.asObservable(), - navLinkId: 'kibana:dashboard', - }); - kibanaLegacy.registerLegacyApp({ ...app, id: DashboardConstants.DASHBOARDS_ID }); - - home.featureCatalogue.register({ - id: DashboardConstants.DASHBOARD_ID, - title: i18n.translate('kbn.dashboard.featureCatalogue.dashboardTitle', { - defaultMessage: 'Dashboard', - }), - description: i18n.translate('kbn.dashboard.featureCatalogue.dashboardDescription', { - defaultMessage: 'Display and share a collection of visualizations and saved searches.', - }), - icon: 'dashboardApp', - path: `/app/kibana#${DashboardConstants.LANDING_PAGE_PATH}`, - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA, - }); - } - - start( - { savedObjects: { client: savedObjectsClient } }: CoreStart, - { - embeddable, - navigation, - data, - share, - kibanaLegacy: { dashboardConfig }, - dashboard, - }: DashboardPluginStartDependencies - ) { - this.startDependencies = { - data, - savedObjectsClient, - embeddable, - navigation, - share, - dashboardConfig, - dashboard, - }; - } - - stop() { - if (this.stopUrlTracking) { - this.stopUrlTracking(); - } - } -} diff --git a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts index 607d79b81618e..6ccbc13aeeb57 100644 --- a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts +++ b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts @@ -201,7 +201,6 @@ function createDocTableModule() { .directive('docTable', createDocTableDirective) .directive('kbnTableHeader', createTableHeaderDirective) .directive('toolBarPagerText', createToolBarPagerTextDirective) - .directive('toolBarPagerText', createToolBarPagerTextDirective) .directive('kbnTableRow', createTableRowDirective) .directive('toolBarPagerButtons', createToolBarPagerButtonsDirective) .directive('kbnInfiniteScroll', createInfiniteScrollDirective) diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 55f369eaecd2c..98679a8f24d16 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -17,6 +17,8 @@ * under the License. */ import { DiscoverServices } from './build_services'; +import { createGetterSetter } from '../../../../../plugins/kibana_utils/public'; +import { search } from '../../../../../plugins/data/public'; let angularModule: any = null; let services: DiscoverServices | null = null; @@ -50,10 +52,6 @@ export const [getUrlTracker, setUrlTracker] = createGetterSetter<{ setTrackedUrl: (url: string) => void; }>('urlTracker'); -// EXPORT legacy static dependencies, should be migrated when available in a new version; -export { wrapInI18nContext } from 'ui/i18n'; -import { search } from '../../../../../plugins/data/public'; -import { createGetterSetter } from '../../../../../plugins/kibana_utils/common'; export const { getRequestInspectorStats, getResponseInspectorStats, tabifyAggResponse } = search; export { unhashUrl, diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar.tsx index 57ad8e0b1040f..8fcfcba08955c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar.tsx @@ -18,7 +18,7 @@ */ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { EuiButtonEmpty, EuiFieldNumber, @@ -88,77 +88,83 @@ export function ActionBar({ }; return ( -
- {isSuccessor && } - {isSuccessor && showWarning && } - {isSuccessor && showWarning && } - - - { - const value = newDocCount + defaultStepSize; - if (isValid(value)) { - setNewDocCount(value); - onChangeCount(value); - } - }} - flush="right" - > - - - - - - { - setNewDocCount(ev.target.valueAsNumber); - }} - onBlur={() => { - if (newDocCount !== docCount && isValid(newDocCount)) { - onChangeCount(newDocCount); + + + {isSuccessor && } + {isSuccessor && showWarning && ( + + )} + {isSuccessor && showWarning && } + + + { + const value = newDocCount + defaultStepSize; + if (isValid(value)) { + setNewDocCount(value); + onChangeCount(value); } }} - type="number" - value={newDocCount >= 0 ? newDocCount : ''} - /> - - - - - {isSuccessor ? ( - - ) : ( - + + + + + + { + setNewDocCount(ev.target.valueAsNumber); + }} + onBlur={() => { + if (newDocCount !== docCount && isValid(newDocCount)) { + onChangeCount(newDocCount); + } + }} + type="number" + value={newDocCount >= 0 ? newDocCount : ''} /> - )} - - - - {!isSuccessor && showWarning && } - {!isSuccessor && } - + +
+ + + {isSuccessor ? ( + + ) : ( + + )} + + +
+ {!isSuccessor && showWarning && ( + + )} + {!isSuccessor && } + + ); } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar_directive.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar_directive.ts index 697b039adde81..b705b4e4faeb5 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/components/action_bar/action_bar_directive.ts @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import { getAngularModule, wrapInI18nContext } from '../../../../../kibana_services'; +import { getAngularModule } from '../../../../../kibana_services'; import { ActionBar } from './action_bar'; getAngularModule().directive('contextActionBar', function(reactDirective: any) { - return reactDirective(wrapInI18nContext(ActionBar)); + return reactDirective(ActionBar); }); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/index.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/index.js index 5482258e06564..5a999235bf9a5 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/index.js @@ -20,16 +20,12 @@ import { DiscoverNoResults } from './no_results'; import { DiscoverUninitialized } from './uninitialized'; import { DiscoverHistogram } from './histogram'; -import { getAngularModule, wrapInI18nContext } from '../../../kibana_services'; +import { getAngularModule } from '../../../kibana_services'; const app = getAngularModule(); -app.directive('discoverNoResults', reactDirective => - reactDirective(wrapInI18nContext(DiscoverNoResults)) -); +app.directive('discoverNoResults', reactDirective => reactDirective(DiscoverNoResults)); -app.directive('discoverUninitialized', reactDirective => - reactDirective(wrapInI18nContext(DiscoverUninitialized)) -); +app.directive('discoverUninitialized', reactDirective => reactDirective(DiscoverUninitialized)); app.directive('discoverHistogram', reactDirective => reactDirective(DiscoverHistogram)); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/no_results.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/no_results.js index ba02068590c14..ad81d5252a25c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/no_results.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/no_results.js @@ -18,7 +18,7 @@ */ import React, { Component, Fragment } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import PropTypes from 'prop-types'; import { @@ -247,29 +247,31 @@ export class DiscoverNoResults extends Component { } return ( - - + + + - - - - } - color="warning" - iconType="help" - data-test-subj="discoverNoResults" - /> + + + + } + color="warning" + iconType="help" + data-test-subj="discoverNoResults" + /> - {shardFailuresMessage} - {timeFieldMessage} - {luceneQueryMessage} - - - + {shardFailuresMessage} + {timeFieldMessage} + {luceneQueryMessage} +
+ + + ); } } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/uninitialized.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/uninitialized.tsx index f40865800098e..b308607bbfbb0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/uninitialized.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/uninitialized.tsx @@ -18,7 +18,7 @@ */ import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { EuiButton, EuiEmptyPrompt, EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui'; @@ -28,38 +28,40 @@ interface Props { export const DiscoverUninitialized = ({ onRefresh }: Props) => { return ( - - - - - - - } - body={ -

- -

- } - actions={ - - - - } - /> -
-
-
+ + + + + + + + } + body={ +

+ +

+ } + actions={ + + + + } + /> +
+
+
+
); }; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.ts index 459dcfb30d17b..092e3c79b1007 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { getAngularModule, wrapInI18nContext, getServices } from '../../kibana_services'; +import { getAngularModule, getServices } from '../../kibana_services'; // @ts-ignore import { getRootBreadcrumbs } from '../helpers/breadcrumbs'; import html from './doc.html'; @@ -30,7 +30,7 @@ const { timefilter } = getServices(); const app = getAngularModule(); app.directive('discoverDoc', function(reactDirective: any) { return reactDirective( - wrapInI18nContext(Doc), + Doc, [ ['id', { watchDepth: 'value' }], ['index', { watchDepth: 'value' }], diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/index.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/index.ts index f21f3b17c6955..7e155a6b82ca0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/index.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/index.ts @@ -16,14 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import { wrapInI18nContext } from '../../../../../kibana_services'; import { ToolBarPagerText } from './tool_bar_pager_text'; import { ToolBarPagerButtons } from './tool_bar_pager_buttons'; export function createToolBarPagerTextDirective(reactDirective: any) { - return reactDirective(wrapInI18nContext(ToolBarPagerText)); + return reactDirective(ToolBarPagerText); } export function createToolBarPagerButtonsDirective(reactDirective: any) { - return reactDirective(wrapInI18nContext(ToolBarPagerButtons)); + return reactDirective(ToolBarPagerButtons); } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.tsx index 608dadd36b4b9..84338d817c86b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/pager/tool_bar_pager_text.tsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; interface Props { startItem: number; @@ -27,12 +27,14 @@ interface Props { export function ToolBarPagerText({ startItem, endItem, totalItems }: Props) { return ( -
- -
+ +
+ +
+
); } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header.ts index 84d865fd22a9a..5e7ad6a1d1ea0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header.ts @@ -17,13 +17,13 @@ * under the License. */ import { TableHeader } from './table_header/table_header'; -import { wrapInI18nContext, getServices } from '../../../../kibana_services'; +import { getServices } from '../../../../kibana_services'; export function createTableHeaderDirective(reactDirective: any) { const { uiSettings: config } = getServices(); return reactDirective( - wrapInI18nContext(TableHeader), + TableHeader, [ ['columns', { watchDepth: 'collection' }], ['hideTimeColumn', { watchDepth: 'value' }], diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.tsx index 28a17dbdb67b7..f3ceaef57d700 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/doc/doc.tsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { EuiCallOut, EuiLink, EuiLoadingSpinner, EuiPageContent } from '@elastic/eui'; import { IndexPatternsContract } from 'src/plugins/data/public'; import { ElasticRequestState, useEsDocSearch } from './use_es_doc_search'; @@ -65,83 +65,85 @@ export function Doc(props: DocProps) { const [reqState, hit, indexPattern] = useEsDocSearch(props); return ( - - {reqState === ElasticRequestState.NotFoundIndexPattern && ( - - } - /> - )} - {reqState === ElasticRequestState.NotFound && ( - - } - > - + + {reqState === ElasticRequestState.NotFoundIndexPattern && ( + + } /> - - )} - - {reqState === ElasticRequestState.Error && ( - + } + > - } - > - {' '} - + )} + + {reqState === ElasticRequestState.Error && ( + + } > - - - )} + id="kbn.doc.somethingWentWrongDescription" + defaultMessage="{indexName} is missing." + values={{ indexName: props.index }} + />{' '} + + + + + )} - {reqState === ElasticRequestState.Loading && ( - - {' '} - - - )} + {reqState === ElasticRequestState.Loading && ( + + {' '} + + + )} - {reqState === ElasticRequestState.Found && hit !== null && indexPattern && ( -
- -
- )} -
+ {reqState === ElasticRequestState.Found && hit !== null && indexPattern && ( +
+ +
+ )} + + ); } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/fetch_error.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/fetch_error.tsx index 1aad7e953b8de..f8fc966dec351 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/fetch_error.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/fetch_error/fetch_error.tsx @@ -17,9 +17,9 @@ * under the License. */ import React, { Fragment } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiCallOut, EuiCodeBlock, EuiSpacer } from '@elastic/eui'; -import { getAngularModule, wrapInI18nContext, getServices } from '../../../kibana_services'; +import { getAngularModule, getServices } from '../../../kibana_services'; interface Props { fetchError: { @@ -72,26 +72,28 @@ const DiscoverFetchError = ({ fetchError }: Props) => { } return ( - - + + + - - - - {body} + + + + {body} - {fetchError.error} - - - + {fetchError.error} + + + - - + + + ); }; export function createFetchErrorDirective(reactDirective: any) { - return reactDirective(wrapInI18nContext(DiscoverFetchError)); + return reactDirective(DiscoverFetchError); } getAngularModule().directive('discoverFetchError', createFetchErrorDirective); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar.tsx index 5984df9c76e61..0adda0e484843 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar.tsx @@ -20,7 +20,7 @@ import React, { useCallback, useEffect, useState, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButtonIcon, EuiTitle } from '@elastic/eui'; import { sortBy } from 'lodash'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { DiscoverField } from './discover_field'; import { DiscoverIndexPattern } from './discover_index_pattern'; import { DiscoverFieldSearch } from './discover_field_search'; @@ -162,165 +162,175 @@ export function DiscoverSidebar({ } return ( -
- o.attributes.title)} - /> -
-
- - -
-
- {fields.length > 0 && ( - <> - -

- -

-
-
    - {selectedFields.map((field: IndexPatternField, idx: number) => { - return ( -
  • - -
  • - ); - })} -
-
- + +
+ o.attributes.title)} + /> +
+
+ + +
+
+ {fields.length > 0 && ( + <> +

-
- setShowFields(!showFields)} - aria-label={ - showFields - ? i18n.translate( - 'kbn.discover.fieldChooser.filter.indexAndFieldsSectionHideAriaLabel', - { - defaultMessage: 'Hide fields', - } - ) - : i18n.translate( - 'kbn.discover.fieldChooser.filter.indexAndFieldsSectionShowAriaLabel', - { - defaultMessage: 'Show fields', - } - ) - } - /> +
    + {selectedFields.map((field: IndexPatternField, idx: number) => { + return ( +
  • + +
  • + ); + })} +
+
+ +

+ +

+
+
+ setShowFields(!showFields)} + aria-label={ + showFields + ? i18n.translate( + 'kbn.discover.fieldChooser.filter.indexAndFieldsSectionHideAriaLabel', + { + defaultMessage: 'Hide fields', + } + ) + : i18n.translate( + 'kbn.discover.fieldChooser.filter.indexAndFieldsSectionShowAriaLabel', + { + defaultMessage: 'Show fields', + } + ) + } + /> +
+ + )} + {popularFields.length > 0 && ( +
+ + + +
    + {popularFields.map((field: IndexPatternField, idx: number) => { + return ( +
  • + +
  • + ); + })} +
- - )} - {popularFields.length > 0 && ( -
- - - -
    - {popularFields.map((field: IndexPatternField, idx: number) => { - return ( -
  • - -
  • - ); - })} -
-
- )} + )} -
    - {unpopularFields.map((field: IndexPatternField, idx: number) => { - return ( -
  • - -
  • - ); - })} -
-
-
+
    + {unpopularFields.map((field: IndexPatternField, idx: number) => { + return ( +
  • + +
  • + ); + })} +
+
+
+ ); } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar_directive.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar_directive.ts index 9dcb459f83613..624ec0f757894 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar_directive.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/sidebar/discover_sidebar_directive.ts @@ -16,11 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { wrapInI18nContext } from '../../../kibana_services'; import { DiscoverSidebar } from './discover_sidebar'; export function createDiscoverSidebarDirective(reactDirective: any) { - return reactDirective(wrapInI18nContext(DiscoverSidebar), [ + return reactDirective(DiscoverSidebar, [ ['columns', { watchDepth: 'reference' }], ['fieldCounts', { watchDepth: 'reference' }], ['hits', { watchDepth: 'reference' }], diff --git a/src/legacy/core_plugins/kibana/public/discover/plugin.ts b/src/legacy/core_plugins/kibana/public/discover/plugin.ts index fcac7aa74f54a..d05e96ccaaf0b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/discover/plugin.ts @@ -100,7 +100,7 @@ export class DiscoverPlugin implements Plugin { } = createKbnUrlTracker({ baseUrl: core.http.basePath.prepend('/app/kibana'), defaultSubUrl: '#/discover', - storageKey: 'lastUrl:discover', + storageKey: `lastUrl:${core.http.basePath.get()}:discover`, navLinkUpdater$: this.appStateUpdater, toastNotifications: core.notifications.toasts, stateParams: [ diff --git a/src/legacy/core_plugins/kibana/public/index.scss b/src/legacy/core_plugins/kibana/public/index.scss index 547f44652cf2b..9b7d0afcd7e39 100644 --- a/src/legacy/core_plugins/kibana/public/index.scss +++ b/src/legacy/core_plugins/kibana/public/index.scss @@ -27,7 +27,3 @@ // Local application mount wrapper styles @import 'local_application_service/index'; - -// Dashboard styles -// MUST STAY AT THE BOTTOM BECAUSE OF DARK THEME IMPORTS -@import './dashboard/index'; diff --git a/src/legacy/core_plugins/kibana/public/index.ts b/src/legacy/core_plugins/kibana/public/index.ts new file mode 100644 index 0000000000000..a4fffc6eec26d --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/index.ts @@ -0,0 +1,23 @@ +/* + * 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 { + ProcessedImportResponse, + processImportResponse, +} from './management/sections/objects/lib/process_import_response'; diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index df6b08ef76556..bceb3fa7eef8a 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -44,7 +44,6 @@ import 'uiExports/interpreter'; import 'ui/autoload/all'; import './discover/legacy'; import './visualize/legacy'; -import './dashboard/legacy'; import './management'; import './dev_tools'; import 'ui/agg_response'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx index 25bd36829b6d0..40471b95d774c 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { StepIndexPattern } from '../step_index_pattern'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; import { Header } from './components/header'; -import { IndexPatternCreationConfig } from '../../../../../../../../management/public'; +import { IndexPatternCreationConfig } from '../../../../../../../../../../plugins/index_pattern_management/public'; import { coreMock } from '../../../../../../../../../../core/public/mocks'; import { dataPluginMock } from '../../../../../../../../../../plugins/data/public/mocks'; import { SavedObjectsFindResponsePublic } from '../../../../../../../../../../core/public'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx index bbb6bf26e5b31..648bf7f8f9738 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.tsx @@ -39,7 +39,7 @@ import { LoadingIndices } from './components/loading_indices'; import { StatusMessage } from './components/status_message'; import { IndicesList } from './components/indices_list'; import { Header } from './components/header'; -import { IndexPatternCreationConfig } from '../../../../../../../../management/public'; +import { IndexPatternCreationConfig } from '../../../../../../../../../../plugins/index_pattern_management/public'; import { MatchedIndex } from '../../types'; interface StepIndexPatternProps { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.test.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.test.tsx index e0c43105cb320..b23b1e3ad9051 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.test.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.test.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; -import { IndexPatternCreationConfig } from '../../../../../../../../management/public'; +import { IndexPatternCreationConfig } from '../../../../../../../../../../plugins/index_pattern_management/public'; import { IFieldType } from '../../../../../../../../../../plugins/data/public'; import { StepTimeField } from '../step_time_field'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx index 80582cc1fbd92..a58bf10c9dab8 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_time_field/step_time_field.tsx @@ -34,7 +34,7 @@ import { Header } from './components/header'; import { TimeField } from './components/time_field'; import { AdvancedOptions } from './components/advanced_options'; import { ActionButtons } from './components/action_buttons'; -import { IndexPatternCreationConfig } from '../../../../../../../../management/public'; +import { IndexPatternCreationConfig } from '../../../../../../../../../../plugins/index_pattern_management/public'; import { DataPublicPluginStart } from '../../../../../../../../../../plugins/data/public'; interface StepTimeFieldProps { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js index 50c5a58d35db3..47cb773258cb4 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/index.js @@ -20,7 +20,6 @@ import uiRoutes from 'ui/routes'; import angularTemplate from './angular_template.html'; import { npStart } from 'ui/new_platform'; -import { setup as managementSetup } from '../../../../../../management/public/legacy'; import { getCreateBreadcrumbs } from '../breadcrumbs'; import { renderCreateIndexPatternWizard, destroyCreateIndexPatternWizard } from './render'; @@ -33,7 +32,7 @@ uiRoutes.when('/management/kibana/index_pattern', { const kbnUrl = $injector.get('kbnUrl'); $scope.$$postDigest(() => { const $routeParams = $injector.get('$routeParams'); - const indexPatternCreationType = managementSetup.indexPattern.creation.getType( + const indexPatternCreationType = npStart.plugins.indexPatternManagement.creation.getType( $routeParams.type ); const services = { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.test.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.test.ts index 5a8460fcb51ba..40583af7177fe 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.test.ts +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.test.ts @@ -18,7 +18,7 @@ */ import { getIndices } from './get_indices'; -import { IndexPatternCreationConfig } from './../../../../../../../management/public'; +import { IndexPatternCreationConfig } from '../../../../../../../../../plugins/index_pattern_management/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { LegacyApiCaller } from '../../../../../../../../../plugins/data/public/search'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.ts b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.ts index 3848c425e2d49..3b1b7a3b52a5b 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.ts +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/lib/get_indices.ts @@ -18,7 +18,7 @@ */ import { get, sortBy } from 'lodash'; -import { IndexPatternCreationConfig } from '../../../../../../../management/public'; +import { IndexPatternCreationConfig } from '../../../../../../../../../plugins/index_pattern_management/public'; import { DataPublicPluginStart } from '../../../../../../../../../plugins/data/public'; import { MatchedIndex } from '../types'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js index 6d302ac5a74f3..594430ca01f4c 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/edit_index_pattern.js @@ -29,7 +29,6 @@ import { uiModules } from 'ui/modules'; import template from './edit_index_pattern.html'; import { fieldWildcardMatcher } from '../../../../../../../../plugins/kibana_utils/public'; import { subscribeWithScope } from '../../../../../../../../plugins/kibana_legacy/public'; -import { setup as managementSetup } from '../../../../../../management/public/legacy'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { SourceFiltersTable } from './source_filters_table'; @@ -239,14 +238,12 @@ uiModules $scope.editSectionsProvider = Private(IndicesEditSectionsProvider); $scope.kbnUrl = Private(KbnUrlProvider); $scope.indexPattern = $route.current.locals.indexPattern; - $scope.indexPatternListProvider = managementSetup.indexPattern.list; - $scope.indexPattern.tags = managementSetup.indexPattern.list.getIndexPatternTags( + $scope.indexPatternListProvider = npStart.plugins.indexPatternManagement.list; + $scope.indexPattern.tags = npStart.plugins.indexPatternManagement.list.getIndexPatternTags( $scope.indexPattern, $scope.indexPattern.id === config.get('defaultIndex') ); - $scope.getFieldInfo = managementSetup.indexPattern.list.getFieldInfo.bind( - managementSetup.indexPattern.list - ); + $scope.getFieldInfo = npStart.plugins.indexPatternManagement.list.getFieldInfo; docTitle.change($scope.indexPattern.title); const otherPatterns = _.filter($route.current.locals.indexPatterns, pattern => { @@ -257,7 +254,7 @@ uiModules $scope.editSections = $scope.editSectionsProvider( $scope.indexPattern, $scope.fieldFilter, - managementSetup.indexPattern.list + npStart.plugins.indexPatternManagement.list ); $scope.refreshFilters(); $scope.fields = $scope.indexPattern.getNonScriptedFields(); @@ -363,7 +360,7 @@ uiModules $scope.editSections = $scope.editSectionsProvider( $scope.indexPattern, $scope.fieldFilter, - managementSetup.indexPattern.list + npStart.plugins.indexPatternManagement.list ); if ($scope.fieldFilter === undefined) { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js index 310797a7f3a0c..a8376c0e84bf9 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/index.js @@ -18,7 +18,6 @@ */ import { management } from 'ui/management'; -import { setup as managementSetup } from '../../../../../management/public/legacy'; import './create_index_pattern_wizard'; import './edit_index_pattern'; import uiRoutes from 'ui/routes'; @@ -111,7 +110,7 @@ uiModules transclude: true, template: indexTemplate, link: async function($scope) { - const indexPatternCreationOptions = await managementSetup.indexPattern.creation.getIndexPatternCreationOptions( + const indexPatternCreationOptions = await npStart.plugins.indexPatternManagement.creation.getIndexPatternCreationOptions( url => { $scope.$evalAsync(() => kbnUrl.change(url)); } @@ -124,7 +123,7 @@ uiModules const id = pattern.id; const title = pattern.get('title'); const isDefault = $scope.defaultIndex === id; - const tags = managementSetup.indexPattern.list.getIndexPatternTags( + const tags = npStart.plugins.indexPatternManagement.list.getIndexPatternTags( pattern, isDefault ); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js index a5e34f8955fe3..7b9c17640a0f3 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js @@ -19,7 +19,7 @@ import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; -import { mockManagementPlugin } from '../../../../../../../../management/public/np_ready/mocks'; +import { mockManagementPlugin } from '../../../../../../../../../../plugins/index_pattern_management/public/mocks'; import { Query } from '@elastic/eui'; import { ObjectsTable, POSSIBLE_TYPES } from '../objects_table'; @@ -30,7 +30,7 @@ import { extractExportDetails } from '../../../lib/extract_export_details'; jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); -jest.mock('../../../../../../../../management/public/legacy', () => ({ +jest.mock('../../../../../../../../../../plugins/index_pattern_management/public', () => ({ setup: mockManagementPlugin.createSetupContract(), start: mockManagementPlugin.createStartContract(), })); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js index 97c0d5b89d657..5d14c4609b918 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/flyout.test.js @@ -19,7 +19,7 @@ import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; -import { mockManagementPlugin } from '../../../../../../../../../../management/public/np_ready/mocks'; +import { mockManagementPlugin } from '../../../../../../../../../../../../plugins/index_pattern_management/public/mocks'; import { Flyout } from '../flyout'; jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); @@ -48,7 +48,7 @@ jest.mock('../../../../../lib/resolve_saved_objects', () => ({ saveObjects: jest.fn(), })); -jest.mock('../../../../../../../../../../management/public/legacy', () => ({ +jest.mock('../../../../../../../../../../../../plugins/index_pattern_management/public', () => ({ setup: mockManagementPlugin.createSetupContract(), start: mockManagementPlugin.createStartContract(), })); diff --git a/src/legacy/core_plugins/kibana/public/visualize/_index.scss b/src/legacy/core_plugins/kibana/public/visualize/_index.scss index 0632831578bd0..079d82936bb57 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/_index.scss +++ b/src/legacy/core_plugins/kibana/public/visualize/_index.scss @@ -1,2 +1,5 @@ // Visualize plugin styles @import 'np_ready/index'; + +// should be removed while moving the visualize into NP +@import '../../../../../plugins/vis_default_editor/public/index' diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index addc608efd57d..d5440c4677d8a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -36,7 +36,7 @@ import { VisualizationsStart } from '../../../../../plugins/visualizations/publi import { SavedVisualizations } from './np_ready/types'; import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public'; import { KibanaLegacyStart } from '../../../../../plugins/kibana_legacy/public'; -import { DefaultEditorController } from '../../../vis_default_editor/public'; +import { DefaultEditorController } from '../../../../../plugins/vis_default_editor/public'; export interface VisualizeKibanaServices { pluginInitializerContext: PluginInitializerContext; diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index 6a2034d9a62e4..f6d73b987912d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -24,7 +24,7 @@ * directly where they are needed. */ -export { DashboardConstants } from '../dashboard/np_ready/dashboard_constants'; +export { DashboardConstants } from '../../../../../plugins/dashboard/public'; export { VisSavedObject, VISUALIZE_EMBEDDABLE_TYPE, diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index a14c4a44f1c7c..4ffbc307c69a8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -51,7 +51,7 @@ import { HomePublicPluginSetup, } from '../../../../../plugins/home/public'; import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public'; -import { DefaultEditorController } from '../../../vis_default_editor/public'; +import { DefaultEditorController } from '../../../../../plugins/vis_default_editor/public'; export interface VisualizePluginStartDependencies { data: DataPublicPluginStart; @@ -89,7 +89,7 @@ export class VisualizePlugin implements Plugin { const { appMounted, appUnMounted, stop: stopUrlTracker, setActiveUrl } = createKbnUrlTracker({ baseUrl: core.http.basePath.prepend('/app/kibana'), defaultSubUrl: '#/visualize', - storageKey: 'lastUrl:visualize', + storageKey: `lastUrl:${core.http.basePath.get()}:visualize`, navLinkUpdater$: this.appStateUpdater, toastNotifications: core.notifications.toasts, stateParams: [ diff --git a/src/legacy/core_plugins/management/package.json b/src/legacy/core_plugins/management/package.json deleted file mode 100644 index 77d33a7bce3b6..0000000000000 --- a/src/legacy/core_plugins/management/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "management", - "version": "kibana" -} - \ No newline at end of file diff --git a/src/legacy/core_plugins/management/public/index.ts b/src/legacy/core_plugins/management/public/index.ts deleted file mode 100644 index bc3737524e125..0000000000000 --- a/src/legacy/core_plugins/management/public/index.ts +++ /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. - */ - -/** - * Static np-ready code, re-exported here so consumers can import from - * `src/legacy/core_plugins/management/public` - * - * @public - */ - -export { - ManagementSetup, - ManagementStart, - plugin, - IndexPatternCreationConfig, - IndexPatternListConfig, -} from './np_ready'; - -export { - processImportResponse, - ProcessedImportResponse, -} from '../../kibana/public/management/sections/objects/lib/process_import_response'; diff --git a/src/legacy/core_plugins/management/public/legacy.ts b/src/legacy/core_plugins/management/public/legacy.ts deleted file mode 100644 index 96d2c74398a0e..0000000000000 --- a/src/legacy/core_plugins/management/public/legacy.ts +++ /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. - */ - -/** - * New Platform Shim - * - * In this file, we import any legacy dependencies we have, and shim them into - * our plugin by manually constructing the values that the new platform will - * eventually be passing to the `setup/start` method of our plugin definition. - * - * The idea is that our `plugin.ts` can stay "pure" and not contain any legacy - * world code. Then when it comes time to migrate to the new platform, we can - * simply delete this shim file. - * - * We are also calling `setup/start` here and exporting our public contract so that - * other legacy plugins are able to import from '../core_plugins/management/legacy' - * and receive the response value of the `setup/start` contract, mimicking the - * data that will eventually be injected by the new platform. - */ - -import { PluginInitializerContext } from 'src/core/public'; -import { npSetup, npStart } from 'ui/new_platform'; - -import { plugin } from '.'; - -const pluginInstance = plugin({} as PluginInitializerContext); - -export const setup = pluginInstance.setup(npSetup.core, { home: npSetup.plugins.home }); -export const start = pluginInstance.start(npStart.core, {}); diff --git a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx index 4219e2e715150..61cfbf00ded9e 100644 --- a/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx +++ b/src/legacy/core_plugins/region_map/public/components/region_map_options.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { FileLayerField, VectorLayer, ServiceSettings } from 'ui/vis/map/service_settings'; -import { VisOptionsProps } from 'src/legacy/core_plugins/vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { NumberInputOption, SelectOption, SwitchOption } from '../../../vis_type_vislib/public'; import { WmsOptions } from '../../../tile_map/public/components/wms_options'; import { RegionMapVisParams } from '../types'; diff --git a/src/legacy/core_plugins/region_map/public/region_map_type.js b/src/legacy/core_plugins/region_map/public/region_map_type.js index 4faa3f92abb5a..9174b03cf843c 100644 --- a/src/legacy/core_plugins/region_map/public/region_map_type.js +++ b/src/legacy/core_plugins/region_map/public/region_map_type.js @@ -22,7 +22,7 @@ import { mapToLayerWithId } from './util'; import { createRegionMapVisualization } from './region_map_visualization'; import { RegionMapOptions } from './components/region_map_options'; import { truncatedColorSchemas } from '../../../../plugins/charts/public'; -import { Schemas } from '../../vis_default_editor/public'; +import { Schemas } from '../../../../plugins/vis_default_editor/public'; // TODO: reference to TILE_MAP plugin should be removed import { ORIGIN } from '../../tile_map/common/origin'; diff --git a/src/legacy/core_plugins/region_map/public/region_map_visualization.js b/src/legacy/core_plugins/region_map/public/region_map_visualization.js index 25641ea76809d..72f9d66e7d2bf 100644 --- a/src/legacy/core_plugins/region_map/public/region_map_visualization.js +++ b/src/legacy/core_plugins/region_map/public/region_map_visualization.js @@ -164,7 +164,8 @@ export function createRegionMapVisualization({ serviceSettings, $injector, uiSet } this._choroplethLayer.on('select', event => { - const rowIndex = this._chartData.rows.findIndex(row => row[0] === event); + const { rows, columns } = this._chartData; + const rowIndex = rows.findIndex(row => row[columns[0].id] === event); this._vis.API.events.filter({ table: this._chartData, column: 0, diff --git a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx index 168f56b771b7e..9169647aa2aef 100644 --- a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx @@ -21,7 +21,7 @@ import React, { useEffect } from 'react'; import { EuiPanel, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { VisOptionsProps } from 'src/legacy/core_plugins/vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { BasicOptions, RangeOption, diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_type.js b/src/legacy/core_plugins/tile_map/public/tile_map_type.js index 39d39a4c8f8fc..fe82ad5c7352b 100644 --- a/src/legacy/core_plugins/tile_map/public/tile_map_type.js +++ b/src/legacy/core_plugins/tile_map/public/tile_map_type.js @@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n'; import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson'; -import { Schemas } from '../../vis_default_editor/public'; +import { Schemas } from '../../../../plugins/vis_default_editor/public'; import { createTileMapVisualization } from './tile_map_visualization'; import { TileMapOptions } from './components/tile_map_options'; import { MapTypes } from './map_types'; diff --git a/src/legacy/core_plugins/vis_default_editor/index.ts b/src/legacy/core_plugins/vis_default_editor/index.ts deleted file mode 100644 index ee7b5ee4a62ff..0000000000000 --- a/src/legacy/core_plugins/vis_default_editor/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import { Legacy } from 'kibana'; - -import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; - -const vidDefaultEditorPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => - new Plugin({ - id: 'vis_default_editor', - require: [], - publicDir: resolve(__dirname, 'public'), - uiExports: { - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - }, - init: (server: Legacy.Server) => ({}), - config(Joi: any) { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - } as Legacy.PluginSpecOptions); - -// eslint-disable-next-line import/no-default-export -export default vidDefaultEditorPluginInitializer; diff --git a/src/legacy/core_plugins/vis_default_editor/package.json b/src/legacy/core_plugins/vis_default_editor/package.json deleted file mode 100644 index 77dcaff41da6b..0000000000000 --- a/src/legacy/core_plugins/vis_default_editor/package.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "name": "vis_default_editor", - "version": "kibana" -} diff --git a/src/legacy/core_plugins/vis_type_markdown/public/markdown_options.tsx b/src/legacy/core_plugins/vis_type_markdown/public/markdown_options.tsx index 8a4297d3b8149..a6349793619a0 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/markdown_options.tsx +++ b/src/legacy/core_plugins/vis_type_markdown/public/markdown_options.tsx @@ -30,7 +30,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'src/legacy/core_plugins/vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { MarkdownVisParams } from './types'; function MarkdownOptions({ stateParams, setValue }: VisOptionsProps) { diff --git a/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts b/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts index b84d9638eb973..57ea6d9c9bb3d 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts +++ b/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts @@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n'; import { MarkdownVisWrapper } from './markdown_vis_controller'; import { MarkdownOptions } from './markdown_options'; import { SettingsOptions } from './settings_options'; -import { DefaultEditorSize } from '../../vis_default_editor/public'; +import { DefaultEditorSize } from '../../../../plugins/vis_default_editor/public'; export const markdownVisDefinition = { name: 'markdown', diff --git a/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx b/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx index ac1d4bcc82cec..552fd63373554 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx +++ b/src/legacy/core_plugins/vis_type_markdown/public/settings_options.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { VisOptionsProps } from 'src/legacy/core_plugins/vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { RangeOption, SwitchOption } from '../../vis_type_vislib/public'; import { MarkdownVisParams } from './types'; diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx index 661f16d6497ba..5c3032511f09a 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_options.tsx @@ -29,7 +29,7 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'src/legacy/core_plugins/vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { ColorModes, ColorRanges, diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.test.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.test.ts index 22c32895d6803..4094cd4eff060 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.test.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_fn.test.ts @@ -23,10 +23,6 @@ import { functionWrapper } from '../../../../plugins/expressions/common/expressi jest.mock('ui/new_platform'); -jest.mock('../../vis_default_editor/public', () => ({ - Schemas: class {}, -})); - describe('interpreter/functions#metric', () => { const fn = functionWrapper(createMetricVisFn()); const context = { diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts index 459da47556307..706693eff1007 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.test.ts @@ -22,10 +22,6 @@ import { MetricVisComponent } from './components/metric_vis_component'; jest.mock('ui/new_platform'); -jest.mock('../../vis_default_editor/public', () => ({ - Schemas: class {}, -})); - describe('metric_vis - createMetricVisTypeDefinition', () => { it('has metric vis component set', () => { const def = createMetricVisTypeDefinition(); diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts index f29164f7e540d..3bbb8964122e5 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts @@ -24,7 +24,7 @@ import { MetricVisOptions } from './components/metric_vis_options'; import { ColorModes } from '../../vis_type_vislib/public'; import { ColorSchemas, colorSchemas } from '../../../../plugins/charts/public'; import { AggGroupNames } from '../../../../plugins/data/public'; -import { Schemas } from '../../vis_default_editor/public'; +import { Schemas } from '../../../../plugins/vis_default_editor/public'; export const createMetricVisTypeDefinition = () => ({ name: 'metric', diff --git a/src/legacy/core_plugins/vis_type_metric/public/services.ts b/src/legacy/core_plugins/vis_type_metric/public/services.ts index 5af11bc7f0b03..b303ccd5aeed2 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/services.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/services.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createGetterSetter } from '../../../../plugins/kibana_utils/common'; +import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; export const [getFormatService, setFormatService] = createGetterSetter< diff --git a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx index 30a9526273166..d01ab31e0a843 100644 --- a/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx +++ b/src/legacy/core_plugins/vis_type_table/public/components/table_vis_options.tsx @@ -23,7 +23,7 @@ import { EuiIconTip, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'src/legacy/core_plugins/vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { search } from '../../../../../plugins/data/public'; import { NumberInputOption, SwitchOption, SelectOption } from '../../../vis_type_vislib/public'; import { TableVisParams } from '../types'; diff --git a/src/legacy/core_plugins/vis_type_table/public/services.ts b/src/legacy/core_plugins/vis_type_table/public/services.ts index 08efed733cafe..b4b491ac7a555 100644 --- a/src/legacy/core_plugins/vis_type_table/public/services.ts +++ b/src/legacy/core_plugins/vis_type_table/public/services.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createGetterSetter } from '../../../../plugins/kibana_utils/common'; +import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; export const [getFormatService, setFormatService] = createGetterSetter< diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts index d26e860e51272..43816121bc23b 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { AggGroupNames } from '../../../../plugins/data/public'; -import { Schemas } from '../../vis_default_editor/public'; +import { Schemas } from '../../../../plugins/vis_default_editor/public'; import { Vis } from '../../../../plugins/visualizations/public'; import { tableVisResponseHandler } from './table_vis_response_handler'; // @ts-ignore diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx index a9e816f70cf53..80e4e1de7ddab 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/components/tag_cloud_options.tsx @@ -20,8 +20,8 @@ import React from 'react'; import { EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { ValidatedDualRange } from '../../../../../../src/plugins/kibana_react/public'; -import { VisOptionsProps } from '../../../vis_default_editor/public'; import { SelectOption, SwitchOption } from '../../../vis_type_vislib/public'; import { TagCloudVisParams } from '../types'; diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/services.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/services.ts index fef46282eb8dd..272bed3e91a08 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/services.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/services.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createGetterSetter } from '../../../../plugins/kibana_utils/common'; +import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; export const [getFormatService, setFormatService] = createGetterSetter< diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts index 5a8cc3004a315..b7dfa62c93fb9 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; -import { Schemas } from '../../vis_default_editor/public'; +import { Schemas } from '../../../../plugins/vis_default_editor/public'; import { TagCloudOptions } from './components/tag_cloud_options'; diff --git a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx index 6e29b111d422a..8a8e1b22fb78d 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/components/timelion_interval.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { search } from '../../../../../plugins/data/public'; const { isValidEsInterval } = search.aggs; -import { useValidation } from '../../../vis_default_editor/public'; +import { useValidation } from '../../../../../plugins/vis_default_editor/public'; const intervalOptions = [ { diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx index b7c40e15c11fd..afffcf7ccaf7a 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_options.tsx @@ -20,9 +20,9 @@ import React, { useCallback } from 'react'; import { EuiPanel } from '@elastic/eui'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { VisParams } from './timelion_vis_fn'; import { TimelionInterval, TimelionExpressionInput } from './components'; -import { VisOptionsProps } from '../../vis_default_editor/public'; function TimelionOptions({ stateParams, setValue, setValidity }: VisOptionsProps) { const setInterval = useCallback((value: VisParams['interval']) => setValue('interval', value), [ diff --git a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx index 6679553004097..5be77b3e51a6a 100644 --- a/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx +++ b/src/legacy/core_plugins/vis_type_timelion/public/timelion_vis_type.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { KibanaContextProvider } from '../../../../plugins/kibana_react/public'; -import { DefaultEditorSize } from '../../vis_default_editor/public'; +import { DefaultEditorSize } from '../../../../plugins/vis_default_editor/public'; import { getTimelionRequestHandler } from './helpers/timelion_request_handler'; import { TimelionVisComponent, TimelionVisComponentProp } from './components'; import { TimelionOptions } from './timelion_options'; diff --git a/src/legacy/core_plugins/vis_type_timeseries/index.ts b/src/legacy/core_plugins/vis_type_timeseries/index.ts index 3ad8ba3a31c17..596fd5b581a71 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/index.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/index.ts @@ -31,20 +31,6 @@ const metricsPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPlu styleSheetPaths: resolve(__dirname, 'public/index.scss'), hacks: [resolve(__dirname, 'public/legacy')], injectDefaultVars: server => ({}), - mappings: { - 'tsvb-validation-telemetry': { - properties: { - failedRequests: { - type: 'long', - }, - }, - }, - }, - savedObjectSchemas: { - 'tsvb-validation-telemetry': { - isNamespaceAgnostic: true, - }, - }, }, config(Joi: any) { return Joi.object({ diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts index 30c62d778933b..1db35c406eb13 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_type.ts @@ -25,7 +25,7 @@ import { metricsRequestHandler } from './request_handler'; import { EditorController } from './editor_controller'; // @ts-ignore import { PANEL_TYPES } from '../../../../plugins/vis_type_timeseries/common/panel_types'; -import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/common'; +import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/public'; export const metricsVisDefinition = { name: 'metrics', diff --git a/src/legacy/core_plugins/vis_type_vega/public/__mocks__/services.ts b/src/legacy/core_plugins/vis_type_vega/public/__mocks__/services.ts index 64a9aaaf3b7a6..b2f3e5b2241e6 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/__mocks__/services.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/__mocks__/services.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createGetterSetter } from '../../../../../plugins/kibana_utils/common'; +import { createGetterSetter } from '../../../../../plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../../plugins/data/public'; import { IUiSettingsClient, NotificationsStart, SavedObjectsStart } from 'kibana/public'; import { dataPluginMock } from '../../../../../plugins/data/public/mocks'; diff --git a/src/legacy/core_plugins/vis_type_vega/public/components/vega_vis_editor.tsx b/src/legacy/core_plugins/vis_type_vega/public/components/vega_vis_editor.tsx index 707a6830b5ba4..31144065d7b40 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/components/vega_vis_editor.tsx +++ b/src/legacy/core_plugins/vis_type_vega/public/components/vega_vis_editor.tsx @@ -24,11 +24,11 @@ import compactStringify from 'json-stringify-pretty-compact'; import hjson from 'hjson'; import { i18n } from '@kbn/i18n'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { getNotifications } from '../services'; import { VisParams } from '../vega_fn'; import { VegaHelpMenu } from './vega_help_menu'; import { VegaActionsMenu } from './vega_actions_menu'; -import { VisOptionsProps } from '../../../vis_default_editor/public'; const aceOptions = { maxLines: Infinity, diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts index b0ec90d2c378f..f56d7682efc6f 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts @@ -18,11 +18,10 @@ */ import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { DefaultEditorSize } from '../../vis_default_editor/public'; +import { DefaultEditorSize } from '../../../../plugins/vis_default_editor/public'; import { VegaVisualizationDependencies } from './plugin'; import { VegaVisEditor } from './components'; -import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/common'; +import { defaultFeedbackMessage } from '../../../../plugins/kibana_utils/public'; import { createVegaRequestHandler } from './vega_request_handler'; // @ts-ignore diff --git a/src/legacy/core_plugins/vis_type_vislib/public/area.ts b/src/legacy/core_plugins/vis_type_vislib/public/area.ts index e79555470298b..68decacaaa040 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/area.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/area.ts @@ -24,7 +24,7 @@ import { palettes } from '@elastic/eui/lib/services'; import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { AggGroupNames } from '../../../../plugins/data/public'; -import { Schemas } from '../../vis_default_editor/public'; +import { Schemas } from '../../../../plugins/vis_default_editor/public'; import { Positions, ChartTypes, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx index 1138f66d21cfa..baf3e8ecd1b28 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/basic_options.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { VisOptionsProps } from '../../../../vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { SwitchOption } from './switch'; import { SelectOption } from './select'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx index 2c9b1b543e8c2..84c70f10b12da 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_ranges.tsx @@ -22,7 +22,10 @@ import { last } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { RangeValues, RangesParamEditor } from '../../../../vis_default_editor/public'; +import { + RangeValues, + RangesParamEditor, +} from '../../../../../../plugins/vis_default_editor/public'; export type SetColorRangeValue = (paramName: string, value: RangeValues[]) => void; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_schema.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_schema.tsx index 06ce0a2b4af64..35d7f7b13235e 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_schema.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/color_schema.tsx @@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n'; import { EuiLink, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from '../../../../vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { SelectOption } from './select'; import { SwitchOption } from './switch'; import { ColorSchemaVislibParams } from '../../types'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/common/validation_wrapper.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/common/validation_wrapper.tsx index c069d4c935669..718056fd85492 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/common/validation_wrapper.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/common/validation_wrapper.tsx @@ -19,7 +19,7 @@ import React, { useEffect, useState, useCallback } from 'react'; -import { VisOptionsProps } from '../../../../vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; export interface ValidationVisOptionsProps extends VisOptionsProps { setMultipleValidity(paramName: string, isValid: boolean): void; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/index.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/index.tsx index 706035a7b814e..6109b548f9412 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/index.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/gauge/index.tsx @@ -20,7 +20,7 @@ import React, { useCallback } from 'react'; import { EuiSpacer } from '@elastic/eui'; -import { VisOptionsProps } from '../../../../../vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { GaugeVisParams } from '../../../gauge'; import { RangesPanel } from './ranges_panel'; import { StylePanel } from './style_panel'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx index 452b9ed9bdbb1..715b5902b69da 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/index.tsx @@ -23,7 +23,7 @@ import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from '../../../../../vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { BasicOptions, ColorRanges, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx index c74f0ef765c8d..38811bd836459 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/heatmap/labels_panel.tsx @@ -23,7 +23,7 @@ import { EuiColorPicker, EuiFormRow, EuiPanel, EuiSpacer, EuiTitle } from '@elas import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from '../../../../../vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { ValueAxis } from '../../../types'; import { HeatmapVisParams } from '../../../heatmap'; import { SwitchOption } from '../../common'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap index 442bc826d51fc..09e0753d592e5 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/__snapshots__/index.test.tsx.snap @@ -54,6 +54,7 @@ exports[`MetricsAxisOptions component should init with the default set of props } vis={ Object { + "serialize": [MockFunction], "setState": [MockFunction], "type": Object { "schemas": Object { @@ -126,6 +127,7 @@ exports[`MetricsAxisOptions component should init with the default set of props } vis={ Object { + "serialize": [MockFunction], "setState": [MockFunction], "type": Object { "schemas": Object { @@ -169,6 +171,7 @@ exports[`MetricsAxisOptions component should init with the default set of props setCategoryAxis={[Function]} vis={ Object { + "serialize": [MockFunction], "setState": [MockFunction], "type": Object { "schemas": Object { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx index 049df0cdd77be..915885388640c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/category_axis_panel.tsx @@ -23,7 +23,7 @@ import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from 'src/legacy/core_plugins/vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { Axis } from '../../../types'; import { SelectOption, SwitchOption } from '../../common'; import { LabelOptions, SetAxisLabel } from './label_options'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx index a3f150e718817..524792d1460fe 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx @@ -95,6 +95,7 @@ describe('MetricsAxisOptions component', () => { schemas: { metrics: [{ name: 'metric' }] }, }, setState: jest.fn(), + serialize: jest.fn(), }, stateParams: { valueAxes: [axis], diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx index c7b4562b1087e..114305d653dd1 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx @@ -299,7 +299,7 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps) }, [stateParams.seriesParams]); useEffect(() => { - vis.setState({ type: visType } as any); + vis.setState({ ...vis.serialize(), type: visType }); }, [vis, visType]); return isTabSelected ? ( diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/pie.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/pie.tsx index 2182edafb3ebf..4c0be456aad64 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/pie.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/pie.tsx @@ -22,7 +22,7 @@ import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from '../../../../vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { BasicOptions, TruncateLabelsOption, SwitchOption } from '../common'; import { PieVisParams } from '../../pie'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx index db9acafac305c..bb2b3f8fddb49 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/point_series/grid_panel.tsx @@ -22,7 +22,7 @@ import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { VisOptionsProps } from '../../../../../vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { SelectOption, SwitchOption } from '../../common'; import { BasicVislibParams, ValueAxis } from '../../../types'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts b/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts index 4610bd37db5f1..7ad821dbf2f30 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/gauge.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; -import { RangeValues, Schemas } from '../../vis_default_editor/public'; +import { RangeValues, Schemas } from '../../../../plugins/vis_default_editor/public'; import { AggGroupNames } from '../../../../plugins/data/public'; import { GaugeOptions } from './components/options'; import { getGaugeCollections, Alignments, ColorModes, GaugeTypes } from './utils/collections'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/goal.ts b/src/legacy/core_plugins/vis_type_vislib/public/goal.ts index c918128d01f11..6c311bebe0717 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/goal.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/goal.ts @@ -25,7 +25,7 @@ import { createVislibVisController } from './vis_controller'; import { VisTypeVislibDependencies } from './plugin'; import { ColorSchemas } from '../../../../plugins/charts/public'; import { AggGroupNames } from '../../../../plugins/data/public'; -import { Schemas } from '../../vis_default_editor/public'; +import { Schemas } from '../../../../plugins/vis_default_editor/public'; export const createGoalVisTypeDefinition = (deps: VisTypeVislibDependencies) => ({ name: 'goal', diff --git a/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts b/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts index 39a583f3c9641..88b4f0fcaf87e 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/heatmap.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; -import { RangeValues, Schemas } from '../../vis_default_editor/public'; +import { RangeValues, Schemas } from '../../../../plugins/vis_default_editor/public'; import { AggGroupNames } from '../../../../plugins/data/public'; import { AxisTypes, getHeatmapCollections, Positions, ScaleTypes } from './utils/collections'; import { HeatmapOptions } from './components/options'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts b/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts index 15ef369e5150e..54ec8f98203e2 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/histogram.ts @@ -24,7 +24,7 @@ import { palettes } from '@elastic/eui/lib/services'; import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { AggGroupNames } from '../../../../plugins/data/public'; -import { Schemas } from '../../vis_default_editor/public'; +import { Schemas } from '../../../../plugins/vis_default_editor/public'; import { Positions, ChartTypes, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts b/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts index 8b5811628855c..dc47252ccd44f 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/horizontal_bar.ts @@ -24,7 +24,7 @@ import { palettes } from '@elastic/eui/lib/services'; import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { AggGroupNames } from '../../../../plugins/data/public'; -import { Schemas } from '../../vis_default_editor/public'; +import { Schemas } from '../../../../plugins/vis_default_editor/public'; import { Positions, ChartTypes, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/line.ts b/src/legacy/core_plugins/vis_type_vislib/public/line.ts index ac4cda869fe29..885ab295d11e1 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/line.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/line.ts @@ -24,7 +24,7 @@ import { palettes } from '@elastic/eui/lib/services'; import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { AggGroupNames } from '../../../../plugins/data/public'; -import { Schemas } from '../../vis_default_editor/public'; +import { Schemas } from '../../../../plugins/vis_default_editor/public'; import { Positions, ChartTypes, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/pie.ts b/src/legacy/core_plugins/vis_type_vislib/public/pie.ts index 0f1bd93f5b5bd..2774836baa381 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/pie.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/pie.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { AggGroupNames } from '../../../../plugins/data/public'; -import { Schemas } from '../../vis_default_editor/public'; +import { Schemas } from '../../../../plugins/vis_default_editor/public'; import { PieOptions } from './components/options'; import { getPositions, Positions } from './utils/collections'; import { createVislibVisController } from './vis_controller'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/services.ts b/src/legacy/core_plugins/vis_type_vislib/public/services.ts index da50e227d84d2..0d6b1b5e8de58 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/services.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/services.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createGetterSetter } from '../../../../plugins/kibana_utils/common'; +import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; export const [getDataActions, setDataActions] = createGetterSetter< diff --git a/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx b/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx index 6da40686a8b50..de867dc72bba7 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/utils/common_config.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { VisOptionsProps } from '../../../vis_default_editor/public'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; import { PointSeriesOptions, MetricsAxisOptions } from '../components/options'; import { ValidationWrapper } from '../components/common'; import { BasicVislibParams } from '../types'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_labels.js b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_labels.js index 543e689e8b741..155be41523ce7 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_labels.js +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/lib/axis/axis_labels.js @@ -108,9 +108,9 @@ export class AxisLabels { selection.selectAll('.tick text').text(function(d) { const parentNode = d3.select(this.parentNode).node(); - const currentTickCenter = - scaleStartPad + - (config.isHorizontal() ? self.axisScale.scale(d) : upperBound - self.axisScale.scale(d)); + const currentTickCenter = config.isHorizontal() + ? scaleStartPad + self.axisScale.scale(d) + : upperBound - scaleStartPad - self.axisScale.scale(d); const currentTickSize = (config.isHorizontal() ? parentNode.getBBox().width : parentNode.getBBox().height) * padding; diff --git a/src/legacy/server/status/collectors/get_ops_stats_collector.js b/src/legacy/server/status/collectors/get_ops_stats_collector.js deleted file mode 100644 index b733e2e721e3a..0000000000000 --- a/src/legacy/server/status/collectors/get_ops_stats_collector.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { KIBANA_STATS_TYPE } from '../constants'; -import { getKibanaInfoForStats } from '../lib'; - -/* - * Initialize a collector for Kibana Ops Stats - * - * NOTE this collector's fetch method returns the latest stats from the - * Hapi/Good/Even-Better ops event listener. Therefore, the stats reset - * every 5 seconds (the default value of the ops.interval configuration - * setting). That makes it geared for providing the latest "real-time" - * stats. In the long-term, fetch should return stats that constantly - * accumulate over the server's uptime for better machine readability. - * Since the data is captured, timestamped and stored, the historical - * data can provide "real-time" stats by calculating a derivative of - * the metrics. - * See PR comment in https://github.com/elastic/kibana/pull/20577/files#r202416647 - */ -export function getOpsStatsCollector(usageCollection, server, kbnServer) { - return usageCollection.makeStatsCollector({ - type: KIBANA_STATS_TYPE, - fetch: () => { - return { - kibana: getKibanaInfoForStats(server, kbnServer), - ...kbnServer.metrics, // latest metrics captured from the ops event listener in src/legacy/server/status/index - }; - }, - isReady: () => true, - ignoreForInternalUploader: true, // Ignore this one from internal uploader. A different stats collector is used there. - }); -} - -export function registerOpsStatsCollector(usageCollection, server, kbnServer) { - if (usageCollection) { - const collector = getOpsStatsCollector(usageCollection, server, kbnServer); - usageCollection.registerCollector(collector); - } -} diff --git a/src/legacy/server/status/index.js b/src/legacy/server/status/index.js index df02b3c45ec2f..5bd1efa99eb2c 100644 --- a/src/legacy/server/status/index.js +++ b/src/legacy/server/status/index.js @@ -20,7 +20,6 @@ import ServerStatus from './server_status'; import { Metrics } from './lib/metrics'; import { registerStatusPage, registerStatusApi, registerStatsApi } from './routes'; -import { registerOpsStatsCollector } from './collectors'; import Oppsy from 'oppsy'; import { cloneDeep } from 'lodash'; import { getOSInfo } from './lib/get_os_info'; @@ -28,7 +27,6 @@ import { getOSInfo } from './lib/get_os_info'; export function statusMixin(kbnServer, server, config) { kbnServer.status = new ServerStatus(kbnServer.server); const { usageCollection } = server.newPlatform.setup.plugins; - registerOpsStatsCollector(usageCollection, server, kbnServer); const metrics = new Metrics(config, server); diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 25647e4a08897..0779d6472671c 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -88,6 +88,9 @@ const mockCoreStart = { get: sinon.fake.returns(''), }, }, + notifications: { + toasts: {}, + }, i18n: {}, overlays: {}, savedObjects: { @@ -164,8 +167,11 @@ const mockAggTypesRegistry = () => { const registrySetup = registry.setup(); const aggTypes = getAggTypes({ uiSettings: mockCoreSetup.uiSettings, - notifications: mockCoreStart.notifications, query: querySetup, + getInternalStartServices: () => ({ + fieldFormats: getFieldFormatsRegistry(mockCoreStart), + notifications: mockCoreStart.notifications, + }), }); aggTypes.buckets.forEach(type => registrySetup.registerBucket(type)); aggTypes.metrics.forEach(type => registrySetup.registerMetric(type)); @@ -284,6 +290,10 @@ export const npSetup = { }), }, }, + indexPatternManagement: { + list: { addListConfig: sinon.fake() }, + creation: { addCreationConfig: sinon.fake() }, + }, discover: { docViews: { addDocView: sinon.fake(), @@ -319,6 +329,17 @@ export const npStart = { }), }, }, + indexPatternManagement: { + list: { + getType: sinon.fake(), + getIndexPatternCreationOptions: sinon.fake(), + }, + creation: { + getIndexPatternTags: sinon.fake(), + getFieldInfo: sinon.fake(), + areScriptedFieldsEnabled: sinon.fake(), + }, + }, embeddable: { getEmbeddableFactory: sinon.fake(), getEmbeddableFactories: sinon.fake(), diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index b4b5099081759..cdd7e1a994912 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -47,6 +47,10 @@ import { AdvancedSettingsStart, } from '../../../../plugins/advanced_settings/public'; import { ManagementSetup, ManagementStart } from '../../../../plugins/management/public'; +import { + IndexPatternManagementSetup, + IndexPatternManagementStart, +} from '../../../../plugins/index_pattern_management/public'; import { BfetchPublicSetup, BfetchPublicStart } from '../../../../plugins/bfetch/public'; import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public'; import { TelemetryPluginSetup, TelemetryPluginStart } from '../../../../plugins/telemetry/public'; @@ -86,6 +90,7 @@ export interface PluginsSetup { visualizations: VisualizationsSetup; telemetry?: TelemetryPluginSetup; savedObjectsManagement: SavedObjectsManagementPluginSetup; + indexPatternManagement: IndexPatternManagementSetup; } export interface PluginsStart { @@ -107,6 +112,7 @@ export interface PluginsStart { telemetry?: TelemetryPluginStart; dashboard: DashboardStart; savedObjectsManagement: SavedObjectsManagementPluginStart; + indexPatternManagement: IndexPatternManagementStart; } export const npSetup = { diff --git a/src/legacy/ui/ui_render/bootstrap/template.js.hbs b/src/legacy/ui/ui_render/bootstrap/template.js.hbs index ad4aa97d8ea7a..1093153edbbf7 100644 --- a/src/legacy/ui/ui_render/bootstrap/template.js.hbs +++ b/src/legacy/ui/ui_render/bootstrap/template.js.hbs @@ -30,33 +30,27 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { function loadStyleSheet(url, cb) { var dom = document.createElement('link'); + dom.rel = 'stylesheet'; + dom.type = 'text/css'; + dom.href = url; dom.addEventListener('error', failure); - dom.setAttribute('rel', 'stylesheet'); - dom.setAttribute('type', 'text/css'); - dom.setAttribute('href', url); dom.addEventListener('load', cb); document.head.appendChild(dom); } function loadScript(url, cb) { var dom = document.createElement('script'); - dom.setAttribute('async', ''); + {{!-- NOTE: async = false is used to trigger async-download/ordered-execution as outlined here: https://www.html5rocks.com/en/tutorials/speed/script-loading/ --}} + dom.async = false; + dom.src = url; dom.addEventListener('error', failure); - dom.setAttribute('src', url); dom.addEventListener('load', cb); document.head.appendChild(dom); } - function load(urlSet, cb) { - if (urlSet.deps) { - load({ urls: urlSet.deps }, function () { - load({ urls: urlSet.urls }, cb); - }); - return; - } - - var pending = urlSet.urls.length; - urlSet.urls.forEach(function (url) { + function load(urls, cb) { + var pending = urls.length; + urls.forEach(function (url) { var innerCb = function () { pending = pending - 1; if (pending === 0 && typeof cb === 'function') { @@ -74,36 +68,27 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { }); } - load({ - deps: [ + load([ {{#each sharedJsDepFilenames}} '{{../regularBundlePath}}/kbn-ui-shared-deps/{{this}}', {{/each}} - ], - urls: [ - { - deps: [ - '{{regularBundlePath}}/kbn-ui-shared-deps/{{sharedJsFilename}}', - { - deps: [ - '{{dllBundlePath}}/vendors_runtime.bundle.dll.js' - ], - urls: [ - {{#each dllJsChunks}} - '{{this}}', - {{/each}} - ] - }, - '{{regularBundlePath}}/commons.bundle.js', - ], - urls: [ - '{{regularBundlePath}}/{{appId}}.bundle.js', - {{#each styleSheetPaths}} - '{{this}}', - {{/each}} - ] - } - ] + '{{regularBundlePath}}/kbn-ui-shared-deps/{{sharedJsFilename}}', + '{{dllBundlePath}}/vendors_runtime.bundle.dll.js', + {{#each dllJsChunks}} + '{{this}}', + {{/each}} + '{{regularBundlePath}}/commons.bundle.js', + {{!-- '{{regularBundlePath}}/plugin/data/data.plugin.js', --}} + '{{regularBundlePath}}/plugin/kibanaUtils/kibanaUtils.plugin.js', + '{{regularBundlePath}}/plugin/esUiShared/esUiShared.plugin.js', + '{{regularBundlePath}}/plugin/kibanaReact/kibanaReact.plugin.js' + ], function () { + load([ + '{{regularBundlePath}}/{{appId}}.bundle.js', + {{#each styleSheetPaths}} + '{{this}}', + {{/each}} + ]) }); }; } diff --git a/src/plugins/bfetch/server/plugin.ts b/src/plugins/bfetch/server/plugin.ts index fd1fe009e93ae..d2ea52f23bc7d 100644 --- a/src/plugins/bfetch/server/plugin.ts +++ b/src/plugins/bfetch/server/plugin.ts @@ -110,7 +110,6 @@ export class BfetchServerPlugin 'Content-Type': 'application/x-ndjson', Connection: 'keep-alive', 'Transfer-Encoding': 'chunked', - 'Cache-Control': 'no-cache', }; return response.ok({ headers, diff --git a/src/plugins/dashboard/kibana.json b/src/plugins/dashboard/kibana.json index e35599a5f0b66..9bcd999c2dcc0 100644 --- a/src/plugins/dashboard/kibana.json +++ b/src/plugins/dashboard/kibana.json @@ -5,12 +5,12 @@ "data", "embeddable", "inspector", + "kibanaLegacy", + "navigation", "uiActions", "savedObjects" ], - "optionalPlugins": [ - "share" - ], + "optionalPlugins": ["home", "share", "usageCollection"], "server": false, "ui": true } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/__snapshots__/dashboard_empty_screen.test.tsx.snap rename to src/plugins/dashboard/public/application/__snapshots__/dashboard_empty_screen.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss b/src/plugins/dashboard/public/application/_dashboard_app.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss rename to src/plugins/dashboard/public/application/_dashboard_app.scss diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_hacks.scss b/src/plugins/dashboard/public/application/_hacks.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/_hacks.scss rename to src/plugins/dashboard/public/application/_hacks.scss diff --git a/src/plugins/dashboard/public/actions/expand_panel_action.test.tsx b/src/plugins/dashboard/public/application/actions/expand_panel_action.test.tsx similarity index 97% rename from src/plugins/dashboard/public/actions/expand_panel_action.test.tsx rename to src/plugins/dashboard/public/application/actions/expand_panel_action.test.tsx index e9696938b8629..0f4a92a1a7b7d 100644 --- a/src/plugins/dashboard/public/actions/expand_panel_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/expand_panel_action.test.tsx @@ -17,7 +17,7 @@ * under the License. */ -import { isErrorEmbeddable } from '../embeddable_plugin'; +import { isErrorEmbeddable } from '../../embeddable_plugin'; import { ExpandPanelAction } from './expand_panel_action'; import { DashboardContainer } from '../embeddable'; import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers'; @@ -27,7 +27,7 @@ import { ContactCardEmbeddable, ContactCardEmbeddableInput, ContactCardEmbeddableOutput, -} from '../embeddable_plugin_test_samples'; +} from '../../embeddable_plugin_test_samples'; // eslint-disable-next-line import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks'; diff --git a/src/plugins/dashboard/public/actions/expand_panel_action.tsx b/src/plugins/dashboard/public/application/actions/expand_panel_action.tsx similarity index 95% rename from src/plugins/dashboard/public/actions/expand_panel_action.tsx rename to src/plugins/dashboard/public/application/actions/expand_panel_action.tsx index 27d4078411564..d0442fbc26073 100644 --- a/src/plugins/dashboard/public/actions/expand_panel_action.tsx +++ b/src/plugins/dashboard/public/application/actions/expand_panel_action.tsx @@ -18,8 +18,8 @@ */ import { i18n } from '@kbn/i18n'; -import { IEmbeddable } from '../embeddable_plugin'; -import { ActionByType, IncompatibleActionError } from '../ui_actions_plugin'; +import { IEmbeddable } from '../../embeddable_plugin'; +import { ActionByType, IncompatibleActionError } from '../../ui_actions_plugin'; import { DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '../embeddable'; export const ACTION_EXPAND_PANEL = 'togglePanel'; diff --git a/src/plugins/dashboard/public/actions/index.ts b/src/plugins/dashboard/public/application/actions/index.ts similarity index 77% rename from src/plugins/dashboard/public/actions/index.ts rename to src/plugins/dashboard/public/application/actions/index.ts index 304fb98b4f842..23c26dbd280f8 100644 --- a/src/plugins/dashboard/public/actions/index.ts +++ b/src/plugins/dashboard/public/application/actions/index.ts @@ -17,5 +17,13 @@ * under the License. */ -export { ExpandPanelAction, ACTION_EXPAND_PANEL } from './expand_panel_action'; -export { ReplacePanelAction, ACTION_REPLACE_PANEL } from './replace_panel_action'; +export { + ExpandPanelAction, + ExpandPanelActionContext, + ACTION_EXPAND_PANEL, +} from './expand_panel_action'; +export { + ReplacePanelAction, + ReplacePanelActionContext, + ACTION_REPLACE_PANEL, +} from './replace_panel_action'; diff --git a/src/plugins/dashboard/public/actions/open_replace_panel_flyout.tsx b/src/plugins/dashboard/public/application/actions/open_replace_panel_flyout.tsx similarity index 92% rename from src/plugins/dashboard/public/actions/open_replace_panel_flyout.tsx rename to src/plugins/dashboard/public/application/actions/open_replace_panel_flyout.tsx index 3472d208f814c..c676ca052d687 100644 --- a/src/plugins/dashboard/public/actions/open_replace_panel_flyout.tsx +++ b/src/plugins/dashboard/public/application/actions/open_replace_panel_flyout.tsx @@ -17,8 +17,8 @@ * under the License. */ import React from 'react'; -import { CoreStart } from '../../../../core/public'; -import { toMountPoint } from '../../../../plugins/kibana_react/public'; +import { CoreStart } from 'src/core/public'; +import { toMountPoint } from '../../../../../plugins/kibana_react/public'; import { ReplacePanelFlyout } from './replace_panel_flyout'; import { IEmbeddable, @@ -26,7 +26,7 @@ import { EmbeddableOutput, EmbeddableStart, IContainer, -} from '../embeddable_plugin'; +} from '../../embeddable_plugin'; export async function openReplacePanelFlyout(options: { embeddable: IContainer; diff --git a/src/plugins/dashboard/public/actions/replace_panel_action.test.tsx b/src/plugins/dashboard/public/application/actions/replace_panel_action.test.tsx similarity index 96% rename from src/plugins/dashboard/public/actions/replace_panel_action.test.tsx rename to src/plugins/dashboard/public/application/actions/replace_panel_action.test.tsx index 2252928f46f6a..cc06bd41379aa 100644 --- a/src/plugins/dashboard/public/actions/replace_panel_action.test.tsx +++ b/src/plugins/dashboard/public/application/actions/replace_panel_action.test.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { isErrorEmbeddable } from '../embeddable_plugin'; +import { isErrorEmbeddable } from '../../embeddable_plugin'; import { ReplacePanelAction } from './replace_panel_action'; import { DashboardContainer } from '../embeddable'; import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers'; @@ -26,8 +26,8 @@ import { ContactCardEmbeddable, ContactCardEmbeddableInput, ContactCardEmbeddableOutput, -} from '../embeddable_plugin_test_samples'; -import { coreMock } from '../../../../core/public/mocks'; +} from '../../embeddable_plugin_test_samples'; +import { coreMock } from '../../../../../core/public/mocks'; import { CoreStart } from 'kibana/public'; // eslint-disable-next-line diff --git a/src/plugins/dashboard/public/actions/replace_panel_action.tsx b/src/plugins/dashboard/public/application/actions/replace_panel_action.tsx similarity index 93% rename from src/plugins/dashboard/public/actions/replace_panel_action.tsx rename to src/plugins/dashboard/public/application/actions/replace_panel_action.tsx index 4e20aa3c35088..5526af2f83850 100644 --- a/src/plugins/dashboard/public/actions/replace_panel_action.tsx +++ b/src/plugins/dashboard/public/application/actions/replace_panel_action.tsx @@ -18,10 +18,10 @@ */ import { i18n } from '@kbn/i18n'; -import { CoreStart } from '../../../../core/public'; -import { IEmbeddable, ViewMode, EmbeddableStart } from '../embeddable_plugin'; +import { CoreStart } from 'src/core/public'; +import { IEmbeddable, ViewMode, EmbeddableStart } from '../../embeddable_plugin'; import { DASHBOARD_CONTAINER_TYPE, DashboardContainer } from '../embeddable'; -import { ActionByType, IncompatibleActionError } from '../ui_actions_plugin'; +import { ActionByType, IncompatibleActionError } from '../../ui_actions_plugin'; import { openReplacePanelFlyout } from './open_replace_panel_flyout'; export const ACTION_REPLACE_PANEL = 'replacePanel'; diff --git a/src/plugins/dashboard/public/actions/replace_panel_flyout.tsx b/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx similarity index 94% rename from src/plugins/dashboard/public/actions/replace_panel_flyout.tsx rename to src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx index a1cd865f771d4..d182deb813e11 100644 --- a/src/plugins/dashboard/public/actions/replace_panel_flyout.tsx +++ b/src/plugins/dashboard/public/application/actions/replace_panel_flyout.tsx @@ -20,10 +20,15 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui'; -import { EmbeddableStart } from '../../../../../src/plugins/embeddable/public'; +import { NotificationsStart, Toast } from 'src/core/public'; import { DashboardPanelState } from '../embeddable'; -import { NotificationsStart, Toast } from '../../../../core/public'; -import { IContainer, IEmbeddable, EmbeddableInput, EmbeddableOutput } from '../embeddable_plugin'; +import { + IContainer, + IEmbeddable, + EmbeddableInput, + EmbeddableOutput, + EmbeddableStart, +} from '../../embeddable_plugin'; interface Props { container: IContainer; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts b/src/plugins/dashboard/public/application/application.ts similarity index 83% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts rename to src/plugins/dashboard/public/application/application.ts index 877ccab99171d..3134a5bfe2c67 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts +++ b/src/plugins/dashboard/public/application/application.ts @@ -17,6 +17,8 @@ * under the License. */ +import './index.scss'; + import { EuiIcon } from '@elastic/eui'; import angular, { IModule } from 'angular'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; @@ -28,20 +30,21 @@ import { SavedObjectsClientContract, PluginInitializerContext, } from 'kibana/public'; -import { Storage } from '../../../../../../plugins/kibana_utils/public'; -import { configureAppAngularModule } from '../legacy_imports'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; +import { Storage } from '../../../kibana_utils/public'; // @ts-ignore import { initDashboardApp } from './legacy_app'; -import { EmbeddableStart } from '../../../../../../plugins/embeddable/public'; -import { NavigationPublicPluginStart as NavigationStart } from '../../../../../../plugins/navigation/public'; -import { DataPublicPluginStart } from '../../../../../../plugins/data/public'; -import { SharePluginStart } from '../../../../../../plugins/share/public'; +import { EmbeddableStart } from '../../../embeddable/public'; +import { NavigationPublicPluginStart as NavigationStart } from '../../../navigation/public'; +import { DataPublicPluginStart } from '../../../data/public'; +import { SharePluginStart } from '../../../share/public'; import { KibanaLegacyStart, + configureAppAngularModule, createTopNavDirective, createTopNavHelper, -} from '../../../../../../plugins/kibana_legacy/public'; -import { SavedObjectLoader } from '../../../../../../plugins/saved_objects/public'; +} from '../../../kibana_legacy/public'; +import { SavedObjectLoader } from '../../../saved_objects/public'; export interface RenderDeps { pluginInitializerContext: PluginInitializerContext; @@ -62,8 +65,9 @@ export interface RenderDeps { savedQueryService: DataPublicPluginStart['query']['savedQueries']; embeddable: EmbeddableStart; localStorage: Storage; - share: SharePluginStart; + share?: SharePluginStart; config: KibanaLegacyStart['config']; + usageCollection?: UsageCollectionSetup; } let angularModuleInstance: IModule | null = null; @@ -110,13 +114,11 @@ function mountDashboardApp(appBasePath: string, element: HTMLElement) { function createLocalAngularModule(core: AppMountContext['core'], navigation: NavigationStart) { createLocalI18nModule(); - createLocalConfigModule(core); createLocalTopNavModule(navigation); createLocalIconModule(); const dashboardAngularModule = angular.module(moduleName, [ ...thirdPartyAngularDependencies, - 'app/dashboard/Config', 'app/dashboard/I18n', 'app/dashboard/TopNav', 'app/dashboard/icon', @@ -130,16 +132,6 @@ function createLocalIconModule() { .directive('icon', reactDirective => reactDirective(EuiIcon)); } -function createLocalConfigModule(core: AppMountContext['core']) { - angular.module('app/dashboard/Config', []).provider('config', () => { - return { - $get: () => ({ - get: core.uiSettings.get.bind(core.uiSettings), - }), - }; - }); -} - function createLocalTopNavModule(navigation: NavigationStart) { angular .module('app/dashboard/TopNav', ['react']) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.html b/src/plugins/dashboard/public/application/dashboard_app.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.html rename to src/plugins/dashboard/public/application/dashboard_app.html diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx similarity index 87% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx rename to src/plugins/dashboard/public/application/dashboard_app.tsx index cc7299b884890..150cd8f8fcbb5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -21,20 +21,14 @@ import moment from 'moment'; import { Subscription } from 'rxjs'; import { History } from 'history'; -import { ViewMode } from '../../../../../../plugins/embeddable/public'; -import { SavedObjectDashboard } from '../../../../../../plugins/dashboard/public'; -import { DashboardAppState, SavedDashboardPanel } from './types'; -import { - IIndexPattern, - TimeRange, - Query, - Filter, - SavedQuery, -} from '../../../../../../plugins/data/public'; +import { ViewMode } from 'src/plugins/embeddable/public'; +import { IIndexPattern, TimeRange, Query, Filter, SavedQuery } from 'src/plugins/data/public'; +import { IKbnUrlStateStorage } from 'src/plugins/kibana_utils/public'; +import { DashboardAppState, SavedDashboardPanel } from '../types'; import { DashboardAppController } from './dashboard_app_controller'; import { RenderDeps } from './application'; -import { IKbnUrlStateStorage } from '../../../../../../plugins/kibana_utils/public/'; +import { SavedObjectDashboard } from '../saved_dashboards'; export interface DashboardAppScope extends ng.IScope { dash: SavedObjectDashboard; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx similarity index 94% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx rename to src/plugins/dashboard/public/application/dashboard_app_controller.tsx index e38345989598d..d1c7d2d9eba3e 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app_controller.tsx @@ -29,7 +29,6 @@ import { History } from 'history'; import { SavedObjectSaveOpts } from 'src/plugins/saved_objects/public'; import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; -import { migrateLegacyQuery, subscribeWithScope } from '../legacy_imports'; import { connectToQueryState, esFilters, @@ -39,19 +38,15 @@ import { QueryState, SavedQuery, syncQueryStateWithUrl, -} from '../../../../../../plugins/data/public'; -import { - getSavedObjectFinder, - SaveResult, - showSaveModal, -} from '../../../../../../plugins/saved_objects/public'; +} from '../../../data/public'; +import { getSavedObjectFinder, SaveResult, showSaveModal } from '../../../saved_objects/public'; import { DASHBOARD_CONTAINER_TYPE, DashboardContainer, DashboardContainerInput, DashboardPanelState, -} from '../../../../../../plugins/dashboard/public'; +} from './embeddable'; import { EmbeddableFactoryNotFoundError, ErrorEmbeddable, @@ -59,31 +54,29 @@ import { openAddPanelFlyout, ViewMode, ContainerOutput, -} from '../../../../../../plugins/embeddable/public'; -import { NavAction, SavedDashboardPanel } from './types'; +} from '../../../embeddable/public'; +import { NavAction, SavedDashboardPanel } from '../types'; import { showOptionsPopover } from './top_nav/show_options_popover'; import { DashboardSaveModal } from './top_nav/save_modal'; import { showCloneModal } from './top_nav/show_clone_modal'; import { saveDashboard } from './lib'; import { DashboardStateManager } from './dashboard_state_manager'; -import { createDashboardEditUrl, DashboardConstants } from './dashboard_constants'; +import { createDashboardEditUrl, DashboardConstants } from '../dashboard_constants'; import { getTopNavConfig } from './top_nav/get_top_nav_config'; import { TopNavIds } from './top_nav/top_nav_ids'; import { getDashboardTitle } from './dashboard_strings'; import { DashboardAppScope } from './dashboard_app'; import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters'; import { RenderDeps } from './application'; -import { - IKbnUrlStateStorage, - removeQueryParam, - unhashUrl, -} from '../../../../../../plugins/kibana_utils/public'; +import { IKbnUrlStateStorage, removeQueryParam, unhashUrl } from '../../../kibana_utils/public'; import { addFatalError, AngularHttpError, KibanaLegacyStart, -} from '../../../../../../plugins/kibana_legacy/public'; + migrateLegacyQuery, + subscribeWithScope, +} from '../../../kibana_legacy/public'; export interface DashboardAppControllerDependencies extends RenderDeps { $scope: DashboardAppScope; @@ -128,6 +121,7 @@ export class DashboardAppController { }, history, kbnUrlStateStorage, + usageCollection, }: DashboardAppControllerDependencies) { const filterManager = queryService.filterManager; const queryFilter = filterManager; @@ -145,6 +139,7 @@ export class DashboardAppController { kibanaVersion: pluginInitializerContext.env.packageInfo.version, kbnUrlStateStorage, history, + usageCollection, }); // sync initial app filters from state to filterManager @@ -439,7 +434,7 @@ export class DashboardAppController { const updateBreadcrumbs = () => { chrome.setBreadcrumbs([ { - text: i18n.translate('kbn.dashboard.dashboardAppBreadcrumbsTitle', { + text: i18n.translate('dashboard.dashboardAppBreadcrumbsTitle', { defaultMessage: 'Dashboard', }), href: landingPageUrl(), @@ -680,20 +675,20 @@ export class DashboardAppController { overlays .openConfirm( - i18n.translate('kbn.dashboard.changeViewModeConfirmModal.discardChangesDescription', { + i18n.translate('dashboard.changeViewModeConfirmModal.discardChangesDescription', { defaultMessage: `Once you discard your changes, there's no getting them back.`, }), { confirmButtonText: i18n.translate( - 'kbn.dashboard.changeViewModeConfirmModal.confirmButtonLabel', + 'dashboard.changeViewModeConfirmModal.confirmButtonLabel', { defaultMessage: 'Discard changes' } ), cancelButtonText: i18n.translate( - 'kbn.dashboard.changeViewModeConfirmModal.cancelButtonLabel', + 'dashboard.changeViewModeConfirmModal.cancelButtonLabel', { defaultMessage: 'Continue editing' } ), defaultFocusedButton: EUI_MODAL_CANCEL_BUTTON, - title: i18n.translate('kbn.dashboard.changeViewModeConfirmModal.discardChangesTitle', { + title: i18n.translate('dashboard.changeViewModeConfirmModal.discardChangesTitle', { defaultMessage: 'Discard changes to dashboard?', }), } @@ -722,7 +717,7 @@ export class DashboardAppController { .then(function(id) { if (id) { notifications.toasts.addSuccess({ - title: i18n.translate('kbn.dashboard.dashboardWasSavedSuccessMessage', { + title: i18n.translate('dashboard.dashboardWasSavedSuccessMessage', { defaultMessage: `Dashboard '{dashTitle}' was saved`, values: { dashTitle: dash.title }, }), @@ -744,7 +739,7 @@ export class DashboardAppController { }) .catch(error => { notifications.toasts.addDanger({ - title: i18n.translate('kbn.dashboard.dashboardWasNotSavedDangerMessage', { + title: i18n.translate('dashboard.dashboardWasNotSavedDangerMessage', { defaultMessage: `Dashboard '{dashTitle}' was not saved. Error: {errorMessage}`, values: { dashTitle: dash.title, @@ -899,21 +894,25 @@ export class DashboardAppController { }, }); }; - navActions[TopNavIds.SHARE] = anchorElement => { - share.toggleShareContextMenu({ - anchorElement, - allowEmbed: true, - allowShortUrl: - !dashboardConfig.getHideWriteControls() || dashboardCapabilities.createShortUrl, - shareableUrl: unhashUrl(window.location.href), - objectId: dash.id, - objectType: 'dashboard', - sharingData: { - title: dash.title, - }, - isDirty: dashboardStateManager.getIsDirty(), - }); - }; + + if (share) { + // the share button is only availabale if "share" plugin contract enabled + navActions[TopNavIds.SHARE] = anchorElement => { + share.toggleShareContextMenu({ + anchorElement, + allowEmbed: true, + allowShortUrl: + !dashboardConfig.getHideWriteControls() || dashboardCapabilities.createShortUrl, + shareableUrl: unhashUrl(window.location.href), + objectId: dash.id, + objectType: 'dashboard', + sharingData: { + title: dash.title, + }, + isDirty: dashboardStateManager.getIsDirty(), + }); + }; + } updateViewMode(dashboardStateManager.getViewMode()); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.test.tsx b/src/plugins/dashboard/public/application/dashboard_empty_screen.test.tsx similarity index 97% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.test.tsx rename to src/plugins/dashboard/public/application/dashboard_empty_screen.test.tsx index d5e22798b4f24..933475d354cfa 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.test.tsx +++ b/src/plugins/dashboard/public/application/dashboard_empty_screen.test.tsx @@ -21,7 +21,7 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { DashboardEmptyScreen, DashboardEmptyScreenProps } from './dashboard_empty_screen'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; -import { coreMock } from '../../../../../../core/public/mocks'; +import { coreMock } from '../../../../core/public/mocks'; describe('DashboardEmptyScreen', () => { const setupMock = coreMock.createSetup(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx b/src/plugins/dashboard/public/application/dashboard_empty_screen.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx rename to src/plugins/dashboard/public/application/dashboard_empty_screen.tsx diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx b/src/plugins/dashboard/public/application/dashboard_empty_screen_constants.tsx similarity index 76% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx rename to src/plugins/dashboard/public/application/dashboard_empty_screen_constants.tsx index 51fe31913662a..4904d08e958d5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx +++ b/src/plugins/dashboard/public/application/dashboard_empty_screen_constants.tsx @@ -20,70 +20,70 @@ import { i18n } from '@kbn/i18n'; /** READONLY VIEW CONSTANTS **/ -export const emptyDashboardTitle: string = i18n.translate('kbn.dashboard.emptyDashboardTitle', { +export const emptyDashboardTitle: string = i18n.translate('dashboard.emptyDashboardTitle', { defaultMessage: 'This dashboard is empty.', }); export const emptyDashboardAdditionalPrivilege = i18n.translate( - 'kbn.dashboard.emptyDashboardAdditionalPrivilege', + 'dashboard.emptyDashboardAdditionalPrivilege', { defaultMessage: 'You need additional privileges to edit this dashboard.', } ); /** VIEW MODE CONSTANTS **/ -export const fillDashboardTitle: string = i18n.translate('kbn.dashboard.fillDashboardTitle', { +export const fillDashboardTitle: string = i18n.translate('dashboard.fillDashboardTitle', { defaultMessage: 'This dashboard is empty. Let\u2019s fill it up!', }); export const howToStartWorkingOnNewDashboardDescription1: string = i18n.translate( - 'kbn.dashboard.howToStartWorkingOnNewDashboardDescription1', + 'dashboard.howToStartWorkingOnNewDashboardDescription1', { defaultMessage: 'Click', } ); export const howToStartWorkingOnNewDashboardDescription2: string = i18n.translate( - 'kbn.dashboard.howToStartWorkingOnNewDashboardDescription2', + 'dashboard.howToStartWorkingOnNewDashboardDescription2', { defaultMessage: 'in the menu bar above to start adding panels.', } ); export const howToStartWorkingOnNewDashboardEditLinkText: string = i18n.translate( - 'kbn.dashboard.howToStartWorkingOnNewDashboardEditLinkText', + 'dashboard.howToStartWorkingOnNewDashboardEditLinkText', { defaultMessage: 'Edit', } ); export const howToStartWorkingOnNewDashboardEditLinkAriaLabel: string = i18n.translate( - 'kbn.dashboard.howToStartWorkingOnNewDashboardEditLinkAriaLabel', + 'dashboard.howToStartWorkingOnNewDashboardEditLinkAriaLabel', { defaultMessage: 'Edit dashboard', } ); /** EDIT MODE CONSTANTS **/ export const addExistingVisualizationLinkText: string = i18n.translate( - 'kbn.dashboard.addExistingVisualizationLinkText', + 'dashboard.addExistingVisualizationLinkText', { defaultMessage: 'Add an existing', } ); export const addExistingVisualizationLinkAriaLabel: string = i18n.translate( - 'kbn.dashboard.addVisualizationLinkAriaLabel', + 'dashboard.addVisualizationLinkAriaLabel', { defaultMessage: 'Add an existing visualization', } ); export const addNewVisualizationDescription: string = i18n.translate( - 'kbn.dashboard.addNewVisualizationText', + 'dashboard.addNewVisualizationText', { defaultMessage: 'or new object to this dashboard', } ); export const createNewVisualizationButton: string = i18n.translate( - 'kbn.dashboard.createNewVisualizationButton', + 'dashboard.createNewVisualizationButton', { defaultMessage: 'Create new', } ); export const createNewVisualizationButtonAriaLabel: string = i18n.translate( - 'kbn.dashboard.createNewVisualizationButtonAriaLabel', + 'dashboard.createNewVisualizationButtonAriaLabel', { defaultMessage: 'Create new visualization button', } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts b/src/plugins/dashboard/public/application/dashboard_state.test.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts rename to src/plugins/dashboard/public/application/dashboard_state.test.ts index 14af89f80f9aa..cfb7fc7e56e42 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state.test.ts +++ b/src/plugins/dashboard/public/application/dashboard_state.test.ts @@ -17,10 +17,9 @@ * under the License. */ -import './np_core.test.mocks'; import { createBrowserHistory } from 'history'; import { DashboardStateManager } from './dashboard_state_manager'; -import { getSavedDashboardMock } from './test_utils'; +import { getSavedDashboardMock } from './test_helpers'; import { InputTimeRange, TimefilterContract, TimeRange } from 'src/plugins/data/public'; import { ViewMode } from 'src/plugins/embeddable/public'; import { createKbnUrlStateStorage } from 'src/plugins/kibana_utils/public'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts b/src/plugins/dashboard/public/application/dashboard_state_manager.ts similarity index 95% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts rename to src/plugins/dashboard/public/application/dashboard_state_manager.ts index 9b8f75bdcf953..6025f535ae761 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_state_manager.ts +++ b/src/plugins/dashboard/public/application/dashboard_state_manager.ts @@ -23,18 +23,11 @@ import { Observable, Subscription } from 'rxjs'; import { Moment } from 'moment'; import { History } from 'history'; -import { - DashboardContainer, - SavedObjectDashboard, -} from '../../../../../../plugins/dashboard/public'; -import { ViewMode } from '../../../../../../plugins/embeddable/public'; -import { migrateLegacyQuery } from '../legacy_imports'; -import { - Filter, - Query, - TimefilterContract as Timefilter, -} from '../../../../../../plugins/data/public'; +import { Filter, Query, TimefilterContract as Timefilter } from 'src/plugins/data/public'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; +import { migrateLegacyQuery } from '../../../kibana_legacy/public'; +import { ViewMode } from '../embeddable_plugin'; import { getAppStateDefaults, migrateAppState, getDashboardIdFromUrl } from './lib'; import { convertPanelStateToSavedDashboardPanel } from './lib/embeddable_saved_object_converters'; import { FilterUtils } from './lib/filter_utils'; @@ -43,14 +36,16 @@ import { DashboardAppStateDefaults, DashboardAppStateTransitions, SavedDashboardPanel, -} from './types'; +} from '../types'; import { createStateContainer, IKbnUrlStateStorage, ISyncStateRef, ReduxLikeStateContainer, syncState, -} from '../../../../../../plugins/kibana_utils/public'; +} from '../../../kibana_utils/public'; +import { SavedObjectDashboard } from '../saved_dashboards'; +import { DashboardContainer } from './embeddable'; /** * Dashboard state manager handles connecting angular and redux state between the angular and react portions of the @@ -89,6 +84,7 @@ export class DashboardStateManager { private readonly kbnUrlStateStorage: IKbnUrlStateStorage; private readonly stateSyncRef: ISyncStateRef; private readonly history: History; + private readonly usageCollection: UsageCollectionSetup | undefined; /** * @@ -103,22 +99,26 @@ export class DashboardStateManager { kibanaVersion, kbnUrlStateStorage, history, + usageCollection, }: { savedDashboard: SavedObjectDashboard; hideWriteControls: boolean; kibanaVersion: string; kbnUrlStateStorage: IKbnUrlStateStorage; history: History; + usageCollection?: UsageCollectionSetup; }) { this.history = history; this.kibanaVersion = kibanaVersion; this.savedDashboard = savedDashboard; this.hideWriteControls = hideWriteControls; + this.usageCollection = usageCollection; // get state defaults from saved dashboard, make sure it is migrated this.stateDefaults = migrateAppState( getAppStateDefaults(this.savedDashboard, this.hideWriteControls), - kibanaVersion + kibanaVersion, + usageCollection ); this.kbnUrlStateStorage = kbnUrlStateStorage; @@ -130,7 +130,8 @@ export class DashboardStateManager { ...this.stateDefaults, ...this.kbnUrlStateStorage.get(this.STATE_STORAGE_KEY), }, - kibanaVersion + kibanaVersion, + usageCollection ); // setup state container using initial state both from defaults and from url @@ -300,7 +301,8 @@ export class DashboardStateManager { // now. TODO: revisit this! this.stateDefaults = migrateAppState( getAppStateDefaults(this.savedDashboard, this.hideWriteControls), - this.kibanaVersion + this.kibanaVersion, + this.usageCollection ); // The original query won't be restored by the above because the query on this.savedDashboard is applied // in place in order for it to affect the visualizations. @@ -519,7 +521,7 @@ export class DashboardStateManager { public syncTimefilterWithDashboardTime(timeFilter: Timefilter) { if (!this.getIsTimeSavedWithDashboard()) { throw new Error( - i18n.translate('kbn.dashboard.stateManager.timeNotSavedWithDashboardErrorMessage', { + i18n.translate('dashboard.stateManager.timeNotSavedWithDashboardErrorMessage', { defaultMessage: 'The time is not saved with this dashboard so should not be synced.', }) ); @@ -540,7 +542,7 @@ export class DashboardStateManager { public syncTimefilterWithDashboardRefreshInterval(timeFilter: Timefilter) { if (!this.getIsTimeSavedWithDashboard()) { throw new Error( - i18n.translate('kbn.dashboard.stateManager.timeNotSavedWithDashboardErrorMessage', { + i18n.translate('dashboard.stateManager.timeNotSavedWithDashboardErrorMessage', { defaultMessage: 'The time is not saved with this dashboard so should not be synced.', }) ); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_strings.ts b/src/plugins/dashboard/public/application/dashboard_strings.ts similarity index 84% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_strings.ts rename to src/plugins/dashboard/public/application/dashboard_strings.ts index d9406ccba18ba..9109012adcfa6 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_strings.ts +++ b/src/plugins/dashboard/public/application/dashboard_strings.ts @@ -18,7 +18,7 @@ */ import { i18n } from '@kbn/i18n'; -import { ViewMode } from '../../../../../../plugins/embeddable/public'; +import { ViewMode } from '../embeddable_plugin'; /** * @param title {string} the current title of the dashboard @@ -35,18 +35,18 @@ export function getDashboardTitle( ): string { const isEditMode = viewMode === ViewMode.EDIT; let displayTitle: string; - const newDashboardTitle = i18n.translate('kbn.dashboard.savedDashboard.newDashboardTitle', { + const newDashboardTitle = i18n.translate('dashboard.savedDashboard.newDashboardTitle', { defaultMessage: 'New Dashboard', }); const dashboardTitle = isNew ? newDashboardTitle : title; if (isEditMode && isDirty) { - displayTitle = i18n.translate('kbn.dashboard.strings.dashboardUnsavedEditTitle', { + displayTitle = i18n.translate('dashboard.strings.dashboardUnsavedEditTitle', { defaultMessage: 'Editing {title} (unsaved)', values: { title: dashboardTitle }, }); } else if (isEditMode) { - displayTitle = i18n.translate('kbn.dashboard.strings.dashboardEditTitle', { + displayTitle = i18n.translate('dashboard.strings.dashboardEditTitle', { defaultMessage: 'Editing {title}', values: { title: dashboardTitle }, }); diff --git a/src/plugins/dashboard/public/embeddable/dashboard_constants.ts b/src/plugins/dashboard/public/application/embeddable/dashboard_constants.ts similarity index 100% rename from src/plugins/dashboard/public/embeddable/dashboard_constants.ts rename to src/plugins/dashboard/public/application/embeddable/dashboard_constants.ts diff --git a/src/plugins/dashboard/public/embeddable/dashboard_container.test.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx similarity index 97% rename from src/plugins/dashboard/public/embeddable/dashboard_container.test.tsx rename to src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx index 6a734cb68fd9c..6785a373c065b 100644 --- a/src/plugins/dashboard/public/embeddable/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.test.tsx @@ -20,7 +20,7 @@ // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; import { nextTick } from 'test_utils/enzyme_helpers'; -import { isErrorEmbeddable, ViewMode } from '../embeddable_plugin'; +import { isErrorEmbeddable, ViewMode } from '../../embeddable_plugin'; import { DashboardContainer, DashboardContainerOptions } from './dashboard_container'; import { getSampleDashboardInput, getSampleDashboardPanel } from '../test_helpers'; import { @@ -29,7 +29,7 @@ import { ContactCardEmbeddableInput, ContactCardEmbeddable, ContactCardEmbeddableOutput, -} from '../embeddable_plugin_test_samples'; +} from '../../embeddable_plugin_test_samples'; // eslint-disable-next-line import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks'; diff --git a/src/plugins/dashboard/public/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx similarity index 92% rename from src/plugins/dashboard/public/embeddable/dashboard_container.tsx rename to src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index d29ce2e4f38f5..7f3a2913daac3 100644 --- a/src/plugins/dashboard/public/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -20,9 +20,10 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; -import { RefreshInterval, TimeRange, Query, Filter } from '../../../data/public'; -import { CoreStart } from '../../../../core/public'; -import { UiActionsStart } from '../ui_actions_plugin'; +import { RefreshInterval, TimeRange, Query, Filter } from 'src/plugins/data/public'; +import { CoreStart } from 'src/core/public'; +import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; +import { UiActionsStart } from '../../ui_actions_plugin'; import { Container, ContainerInput, @@ -31,17 +32,16 @@ import { EmbeddableFactory, IEmbeddable, EmbeddableStart, -} from '../embeddable_plugin'; +} from '../../embeddable_plugin'; import { DASHBOARD_CONTAINER_TYPE } from './dashboard_constants'; import { createPanelState } from './panel'; import { DashboardPanelState } from './types'; import { DashboardViewport } from './viewport/dashboard_viewport'; -import { Start as InspectorStartContract } from '../../../inspector/public'; import { KibanaContextProvider, KibanaReactContext, KibanaReactContextValue, -} from '../../../kibana_react/public'; +} from '../../../../kibana_react/public'; export interface DashboardContainerInput extends ContainerInput { viewMode: ViewMode; diff --git a/src/plugins/dashboard/public/embeddable/dashboard_container_factory.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx similarity index 88% rename from src/plugins/dashboard/public/embeddable/dashboard_container_factory.tsx rename to src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx index 9ff48cb45adfd..a20ba148115a0 100644 --- a/src/plugins/dashboard/public/embeddable/dashboard_container_factory.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container_factory.tsx @@ -18,18 +18,18 @@ */ import { i18n } from '@kbn/i18n'; -import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; -import { EmbeddableStart } from '../../../../../src/plugins/embeddable/public'; -import { CoreStart } from '../../../../core/public'; +import { UiActionsStart } from 'src/plugins/ui_actions/public'; +import { EmbeddableStart } from 'src/plugins/embeddable/public'; +import { CoreStart } from 'src/core/public'; +import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { ContainerOutput, EmbeddableFactoryDefinition, ErrorEmbeddable, Container, -} from '../embeddable_plugin'; +} from '../../embeddable_plugin'; import { DashboardContainer, DashboardContainerInput } from './dashboard_container'; import { DASHBOARD_CONTAINER_TYPE } from './dashboard_constants'; -import { Start as InspectorStartContract } from '../../../inspector/public'; interface StartServices { capabilities: CoreStart['application']['capabilities']; diff --git a/src/plugins/dashboard/public/embeddable/grid/_dashboard_grid.scss b/src/plugins/dashboard/public/application/embeddable/grid/_dashboard_grid.scss similarity index 100% rename from src/plugins/dashboard/public/embeddable/grid/_dashboard_grid.scss rename to src/plugins/dashboard/public/application/embeddable/grid/_dashboard_grid.scss diff --git a/src/plugins/dashboard/public/embeddable/grid/_index.scss b/src/plugins/dashboard/public/application/embeddable/grid/_index.scss similarity index 100% rename from src/plugins/dashboard/public/embeddable/grid/_index.scss rename to src/plugins/dashboard/public/application/embeddable/grid/_index.scss diff --git a/src/plugins/dashboard/public/embeddable/grid/dashboard_grid.test.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx similarity index 97% rename from src/plugins/dashboard/public/embeddable/grid/dashboard_grid.test.tsx rename to src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx index a946c21765311..948e22e86a302 100644 --- a/src/plugins/dashboard/public/embeddable/grid/dashboard_grid.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.test.tsx @@ -29,8 +29,8 @@ import { getSampleDashboardInput } from '../../test_helpers'; import { CONTACT_CARD_EMBEDDABLE, ContactCardEmbeddableFactory, -} from '../../embeddable_plugin_test_samples'; -import { KibanaContextProvider } from '../../../../kibana_react/public'; +} from '../../../embeddable_plugin_test_samples'; +import { KibanaContextProvider } from '../../../../../kibana_react/public'; // eslint-disable-next-line import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks'; diff --git a/src/plugins/dashboard/public/embeddable/grid/dashboard_grid.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx similarity index 98% rename from src/plugins/dashboard/public/embeddable/grid/dashboard_grid.tsx rename to src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx index 3f1f1056cf1b4..b15a813aff903 100644 --- a/src/plugins/dashboard/public/embeddable/grid/dashboard_grid.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx @@ -29,10 +29,10 @@ import _ from 'lodash'; import React from 'react'; import { Subscription } from 'rxjs'; import ReactGridLayout, { Layout } from 'react-grid-layout'; -import { ViewMode, EmbeddableChildPanel } from '../../embeddable_plugin'; +import { ViewMode, EmbeddableChildPanel } from '../../../embeddable_plugin'; import { DASHBOARD_GRID_COLUMN_COUNT, DASHBOARD_GRID_HEIGHT } from '../dashboard_constants'; import { DashboardPanelState, GridData } from '../types'; -import { withKibana } from '../../../../kibana_react/public'; +import { withKibana } from '../../../../../kibana_react/public'; import { DashboardContainerInput } from '../dashboard_container'; import { DashboardContainer, DashboardReactContextValue } from '../dashboard_container'; diff --git a/src/plugins/dashboard/public/embeddable/grid/index.ts b/src/plugins/dashboard/public/application/embeddable/grid/index.ts similarity index 100% rename from src/plugins/dashboard/public/embeddable/grid/index.ts rename to src/plugins/dashboard/public/application/embeddable/grid/index.ts diff --git a/src/plugins/dashboard/public/embeddable/index.ts b/src/plugins/dashboard/public/application/embeddable/index.ts similarity index 100% rename from src/plugins/dashboard/public/embeddable/index.ts rename to src/plugins/dashboard/public/application/embeddable/index.ts diff --git a/src/plugins/dashboard/public/embeddable/panel/_dashboard_panel.scss b/src/plugins/dashboard/public/application/embeddable/panel/_dashboard_panel.scss similarity index 100% rename from src/plugins/dashboard/public/embeddable/panel/_dashboard_panel.scss rename to src/plugins/dashboard/public/application/embeddable/panel/_dashboard_panel.scss diff --git a/src/plugins/dashboard/public/embeddable/panel/_index.scss b/src/plugins/dashboard/public/application/embeddable/panel/_index.scss similarity index 100% rename from src/plugins/dashboard/public/embeddable/panel/_index.scss rename to src/plugins/dashboard/public/application/embeddable/panel/_index.scss diff --git a/src/plugins/dashboard/public/embeddable/panel/create_panel_state.test.ts b/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.test.ts similarity index 95% rename from src/plugins/dashboard/public/embeddable/panel/create_panel_state.test.ts rename to src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.test.ts index 8889f4dc27544..409cae8b49a53 100644 --- a/src/plugins/dashboard/public/embeddable/panel/create_panel_state.test.ts +++ b/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.test.ts @@ -20,8 +20,8 @@ import { DEFAULT_PANEL_HEIGHT, DEFAULT_PANEL_WIDTH } from '../dashboard_constants'; import { DashboardPanelState } from '../types'; import { createPanelState } from './create_panel_state'; -import { EmbeddableInput } from '../../embeddable_plugin'; -import { CONTACT_CARD_EMBEDDABLE } from '../../embeddable_plugin_test_samples'; +import { EmbeddableInput } from '../../../embeddable_plugin'; +import { CONTACT_CARD_EMBEDDABLE } from '../../../embeddable_plugin_test_samples'; interface TestInput extends EmbeddableInput { test: string; diff --git a/src/plugins/dashboard/public/embeddable/panel/create_panel_state.ts b/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.ts similarity index 97% rename from src/plugins/dashboard/public/embeddable/panel/create_panel_state.ts rename to src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.ts index 7139cddf02b33..f3a48368fe1b3 100644 --- a/src/plugins/dashboard/public/embeddable/panel/create_panel_state.ts +++ b/src/plugins/dashboard/public/application/embeddable/panel/create_panel_state.ts @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { PanelState, EmbeddableInput } from '../../embeddable_plugin'; +import { PanelState, EmbeddableInput } from '../../../embeddable_plugin'; import { DASHBOARD_GRID_COLUMN_COUNT, DEFAULT_PANEL_HEIGHT, diff --git a/src/plugins/dashboard/public/embeddable/panel/index.ts b/src/plugins/dashboard/public/application/embeddable/panel/index.ts similarity index 100% rename from src/plugins/dashboard/public/embeddable/panel/index.ts rename to src/plugins/dashboard/public/application/embeddable/panel/index.ts diff --git a/src/plugins/dashboard/public/embeddable/types.ts b/src/plugins/dashboard/public/application/embeddable/types.ts similarity index 94% rename from src/plugins/dashboard/public/embeddable/types.ts rename to src/plugins/dashboard/public/application/embeddable/types.ts index 480d03552ca68..3df305b0d7f1b 100644 --- a/src/plugins/dashboard/public/embeddable/types.ts +++ b/src/plugins/dashboard/public/application/embeddable/types.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { PanelState, EmbeddableInput } from '../embeddable_plugin'; +import { PanelState, EmbeddableInput } from '../../embeddable_plugin'; export type PanelId = string; export type SavedObjectId = string; diff --git a/src/plugins/dashboard/public/embeddable/viewport/_dashboard_viewport.scss b/src/plugins/dashboard/public/application/embeddable/viewport/_dashboard_viewport.scss similarity index 100% rename from src/plugins/dashboard/public/embeddable/viewport/_dashboard_viewport.scss rename to src/plugins/dashboard/public/application/embeddable/viewport/_dashboard_viewport.scss diff --git a/src/plugins/dashboard/public/embeddable/viewport/_index.scss b/src/plugins/dashboard/public/application/embeddable/viewport/_index.scss similarity index 100% rename from src/plugins/dashboard/public/embeddable/viewport/_index.scss rename to src/plugins/dashboard/public/application/embeddable/viewport/_index.scss diff --git a/src/plugins/dashboard/public/embeddable/viewport/dashboard_viewport.test.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx similarity index 98% rename from src/plugins/dashboard/public/embeddable/viewport/dashboard_viewport.test.tsx rename to src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx index be4d2e3851f11..4f9aa75f52105 100644 --- a/src/plugins/dashboard/public/embeddable/viewport/dashboard_viewport.test.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.test.tsx @@ -30,8 +30,8 @@ import { getSampleDashboardInput } from '../../test_helpers'; import { CONTACT_CARD_EMBEDDABLE, ContactCardEmbeddableFactory, -} from '../../embeddable_plugin_test_samples'; -import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; +} from '../../../embeddable_plugin_test_samples'; +import { KibanaContextProvider } from '../../../../../kibana_react/public'; // eslint-disable-next-line import { embeddablePluginMock } from 'src/plugins/embeddable/public/mocks'; diff --git a/src/plugins/dashboard/public/embeddable/viewport/dashboard_viewport.tsx b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx similarity index 97% rename from src/plugins/dashboard/public/embeddable/viewport/dashboard_viewport.tsx rename to src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx index e7fd379898dd1..ae239bc27fdba 100644 --- a/src/plugins/dashboard/public/embeddable/viewport/dashboard_viewport.tsx +++ b/src/plugins/dashboard/public/application/embeddable/viewport/dashboard_viewport.tsx @@ -19,10 +19,10 @@ import React from 'react'; import { Subscription } from 'rxjs'; -import { PanelState } from '../../embeddable_plugin'; +import { PanelState } from '../../../embeddable_plugin'; import { DashboardContainer, DashboardReactContextValue } from '../dashboard_container'; import { DashboardGrid } from '../grid'; -import { context } from '../../../../kibana_react/public'; +import { context } from '../../../../../kibana_react/public'; export interface DashboardViewportProps { container: DashboardContainer; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/help_menu/help_menu_util.ts b/src/plugins/dashboard/public/application/help_menu/help_menu_util.ts similarity index 95% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/help_menu/help_menu_util.ts rename to src/plugins/dashboard/public/application/help_menu/help_menu_util.ts index bc281c6eb340f..efdff051a25a0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/help_menu/help_menu_util.ts +++ b/src/plugins/dashboard/public/application/help_menu/help_menu_util.ts @@ -25,7 +25,7 @@ export function addHelpMenuToAppChrome( docLinks: CoreStart['docLinks'] ) { chrome.setHelpExtension({ - appName: i18n.translate('kbn.dashboard.helpMenu.appName', { + appName: i18n.translate('dashboard.helpMenu.appName', { defaultMessage: 'Dashboards', }), links: [ diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_index.scss b/src/plugins/dashboard/public/application/index.scss similarity index 56% rename from src/legacy/core_plugins/kibana/public/dashboard/_index.scss rename to src/plugins/dashboard/public/application/index.scss index 35d4127365f02..6e158b2ec2e47 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/_index.scss +++ b/src/plugins/dashboard/public/application/index.scss @@ -1,9 +1,8 @@ -// External libraries -// 'react-grid-layout/css/styles.css'; -// 'react-resizable/css/styles.css'; -// ... are being imported via JS in grid/dashboard_grid.js +@import '../../../embeddable/public/variables'; -@import './variables'; +@import './embeddable/grid/index'; +@import './embeddable/panel/index'; +@import './embeddable/viewport/index'; // Temporary hacks @import './hacks'; @@ -16,3 +15,4 @@ // dshChart__legend-isLoading @import './dashboard_app'; + diff --git a/src/legacy/server/status/constants.js b/src/plugins/dashboard/public/application/index.ts similarity index 88% rename from src/legacy/server/status/constants.js rename to src/plugins/dashboard/public/application/index.ts index 3bb23749bae87..fcd92da33aa5f 100644 --- a/src/legacy/server/status/constants.js +++ b/src/plugins/dashboard/public/application/index.ts @@ -17,4 +17,6 @@ * under the License. */ -export const KIBANA_STATS_TYPE = 'oss_kibana_stats'; // kibana stats per 5s intervals +export * from './embeddable'; +export * from './actions'; +export { RenderDeps } from './application'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js b/src/plugins/dashboard/public/application/legacy_app.js similarity index 92% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js rename to src/plugins/dashboard/public/application/legacy_app.js index dbeaf8a98b461..10243dbf2f979 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js +++ b/src/plugins/dashboard/public/application/legacy_app.js @@ -25,17 +25,17 @@ import dashboardListingTemplate from './listing/dashboard_listing_ng_wrapper.htm import { createHashHistory } from 'history'; import { initDashboardAppDirective } from './dashboard_app'; -import { createDashboardEditUrl, DashboardConstants } from './dashboard_constants'; +import { createDashboardEditUrl, DashboardConstants } from '../dashboard_constants'; import { createKbnUrlStateStorage, ensureDefaultIndexPattern, redirectWhenMissing, InvalidJSONProperty, SavedObjectNotFound, -} from '../../../../../../plugins/kibana_utils/public'; +} from '../../../kibana_utils/public'; import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; -import { syncQueryStateWithUrl } from '../../../../../../plugins/data/public'; +import { syncQueryStateWithUrl } from '../../../data/public'; export function initDashboardApp(app, deps) { initDashboardAppDirective(app, deps); @@ -55,7 +55,7 @@ export function initDashboardApp(app, deps) { }); function createNewDashboardCtrl($scope) { - $scope.visitVisualizeAppLinkText = i18n.translate('kbn.dashboard.visitVisualizeAppLinkText', { + $scope.visitVisualizeAppLinkText = i18n.translate('dashboard.visitVisualizeAppLinkText', { defaultMessage: 'visit the Visualize app', }); addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks); @@ -79,10 +79,10 @@ export function initDashboardApp(app, deps) { } return { - text: i18n.translate('kbn.dashboard.badge.readOnly.text', { + text: i18n.translate('dashboard.badge.readOnly.text', { defaultMessage: 'Read only', }), - tooltip: i18n.translate('kbn.dashboard.badge.readOnly.tooltip', { + tooltip: i18n.translate('dashboard.badge.readOnly.tooltip', { defaultMessage: 'Unable to save dashboards', }), iconType: 'glasses', @@ -94,7 +94,7 @@ export function initDashboardApp(app, deps) { .when(DashboardConstants.LANDING_PAGE_PATH, { ...defaults, template: dashboardListingTemplate, - controller($scope, kbnUrlStateStorage, history) { + controller: function($scope, kbnUrlStateStorage, history) { const service = deps.savedDashboards; const dashboardConfig = deps.dashboardConfig; @@ -124,7 +124,7 @@ export function initDashboardApp(app, deps) { $scope.initialFilter = parse(history.location.search).filter || EMPTY_FILTER; deps.chrome.setBreadcrumbs([ { - text: i18n.translate('kbn.dashboard.dashboardBreadcrumbsTitle', { + text: i18n.translate('dashboard.dashboardBreadcrumbsTitle', { defaultMessage: 'Dashboards', }), }, @@ -222,7 +222,7 @@ export function initDashboardApp(app, deps) { }); deps.core.notifications.toasts.addWarning( - i18n.translate('kbn.dashboard.urlWasRemovedInSixZeroWarningMessage', { + i18n.translate('dashboard.urlWasRemovedInSixZeroWarningMessage', { defaultMessage: 'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.', }) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.test.ts b/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.test.ts similarity index 95% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.test.ts rename to src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.test.ts index d3c3dc46c7057..447563bbfbcfa 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.test.ts +++ b/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.test.ts @@ -16,14 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import '../np_core.test.mocks'; import { convertSavedDashboardPanelToPanelState, convertPanelStateToSavedDashboardPanel, } from './embeddable_saved_object_converters'; -import { SavedDashboardPanel } from '../types'; -import { DashboardPanelState } from 'src/plugins/dashboard/public'; +import { SavedDashboardPanel } from '../../types'; +import { DashboardPanelState } from '../embeddable'; import { EmbeddableInput } from 'src/plugins/embeddable/public'; interface CustomInput extends EmbeddableInput { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.ts b/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.ts similarity index 93% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.ts rename to src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.ts index 500ee7e28daa6..01cd55df0d8e9 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/embeddable_saved_object_converters.ts +++ b/src/plugins/dashboard/public/application/lib/embeddable_saved_object_converters.ts @@ -17,8 +17,8 @@ * under the License. */ import { omit } from 'lodash'; -import { DashboardPanelState } from '../../../../../../../plugins/dashboard/public'; -import { SavedDashboardPanel } from '../types'; +import { SavedDashboardPanel } from '../../types'; +import { DashboardPanelState } from '../embeddable'; export function convertSavedDashboardPanelToPanelState( savedDashboardPanel: SavedDashboardPanel diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/filter_utils.ts b/src/plugins/dashboard/public/application/lib/filter_utils.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/filter_utils.ts rename to src/plugins/dashboard/public/application/lib/filter_utils.ts index f7b45b0371378..1ec231db0c3d2 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/filter_utils.ts +++ b/src/plugins/dashboard/public/application/lib/filter_utils.ts @@ -19,7 +19,7 @@ import _ from 'lodash'; import moment, { Moment } from 'moment'; -import { Filter } from '../../../../../../../plugins/data/public'; +import { Filter } from 'src/plugins/data/public'; /** * @typedef {Object} QueryFilter diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/get_app_state_defaults.ts b/src/plugins/dashboard/public/application/lib/get_app_state_defaults.ts similarity index 87% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/get_app_state_defaults.ts rename to src/plugins/dashboard/public/application/lib/get_app_state_defaults.ts index b3acefeba0146..f008c787cb95d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/get_app_state_defaults.ts +++ b/src/plugins/dashboard/public/application/lib/get_app_state_defaults.ts @@ -17,9 +17,9 @@ * under the License. */ -import { ViewMode } from '../../../../../../../plugins/embeddable/public'; -import { SavedObjectDashboard } from '../../../../../../../plugins/dashboard/public'; -import { DashboardAppStateDefaults } from '../types'; +import { ViewMode } from '../../embeddable_plugin'; +import { SavedObjectDashboard } from '../../saved_dashboards'; +import { DashboardAppStateDefaults } from '../../types'; export function getAppStateDefaults( savedDashboard: SavedObjectDashboard, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/index.ts b/src/plugins/dashboard/public/application/lib/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/index.ts rename to src/plugins/dashboard/public/application/lib/index.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.test.ts b/src/plugins/dashboard/public/application/lib/migrate_app_state.test.ts similarity index 98% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.test.ts rename to src/plugins/dashboard/public/application/lib/migrate_app_state.test.ts index 73336ec951894..d037fb3808406 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.test.ts +++ b/src/plugins/dashboard/public/application/lib/migrate_app_state.test.ts @@ -17,9 +17,7 @@ * under the License. */ -import '../np_core.test.mocks'; - -import { SavedDashboardPanel } from '../types'; +import { SavedDashboardPanel } from '../../types'; import { migrateAppState } from './migrate_app_state'; test('migrate app state from 6.0', async () => { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.ts b/src/plugins/dashboard/public/application/lib/migrate_app_state.ts similarity index 77% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.ts rename to src/plugins/dashboard/public/application/lib/migrate_app_state.ts index 0cd958ced0eb1..8f8de3663518a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/migrate_app_state.ts +++ b/src/plugins/dashboard/public/application/lib/migrate_app_state.ts @@ -19,7 +19,9 @@ import semver from 'semver'; import { i18n } from '@kbn/i18n'; -import { createUiStatsReporter, METRIC_TYPE } from '../../../../../ui_metric/public'; +import { METRIC_TYPE } from '@kbn/analytics'; + +import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { DashboardAppState, SavedDashboardPanelTo60, @@ -29,8 +31,9 @@ import { SavedDashboardPanel640To720, SavedDashboardPanel620, SavedDashboardPanel, -} from '../types'; -import { migratePanelsTo730 } from '../../migrations/migrate_to_730_panels'; +} from '../../types'; +// should be moved in src/plugins/dashboard/common right after https://github.com/elastic/kibana/pull/61895 is merged +import { migratePanelsTo730 } from '../../../../../legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels'; /** * Attempts to migrate the state stored in the URL into the latest version of it. @@ -39,11 +42,12 @@ import { migratePanelsTo730 } from '../../migrations/migrate_to_730_panels'; */ export function migrateAppState( appState: { [key: string]: unknown } & DashboardAppState, - kibanaVersion: string + kibanaVersion: string, + usageCollection?: UsageCollectionSetup ): DashboardAppState { if (!appState.panels) { throw new Error( - i18n.translate('kbn.dashboard.panel.invalidData', { + i18n.translate('dashboard.panel.invalidData', { defaultMessage: 'Invalid data in url', }) ); @@ -61,8 +65,10 @@ export function migrateAppState( const version = (panel as SavedDashboardPanel730ToLatest).version; - // This will help us figure out when to remove support for older style URLs. - createUiStatsReporter('DashboardPanelVersionInUrl')(METRIC_TYPE.LOADED, `${version}`); + if (usageCollection) { + // This will help us figure out when to remove support for older style URLs. + usageCollection.reportUiStats('DashboardPanelVersionInUrl', METRIC_TYPE.LOADED, `${version}`); + } return semver.satisfies(version, '<7.3'); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/save_dashboard.ts b/src/plugins/dashboard/public/application/lib/save_dashboard.ts similarity index 95% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/save_dashboard.ts rename to src/plugins/dashboard/public/application/lib/save_dashboard.ts index db2b1f15247de..c948c25cb2ab5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/save_dashboard.ts +++ b/src/plugins/dashboard/public/application/lib/save_dashboard.ts @@ -18,7 +18,7 @@ */ import { TimefilterContract } from 'src/plugins/data/public'; -import { SavedObjectSaveOpts } from '../../../../../../../plugins/saved_objects/public'; +import { SavedObjectSaveOpts } from 'src/plugins/saved_objects/public'; import { updateSavedDashboard } from './update_saved_dashboard'; import { DashboardStateManager } from '../dashboard_state_manager'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts b/src/plugins/dashboard/public/application/lib/update_saved_dashboard.ts similarity index 93% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts rename to src/plugins/dashboard/public/application/lib/update_saved_dashboard.ts index dee279550aa6a..53dc7d9b460de 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/update_saved_dashboard.ts +++ b/src/plugins/dashboard/public/application/lib/update_saved_dashboard.ts @@ -20,8 +20,8 @@ import _ from 'lodash'; import { RefreshInterval, TimefilterContract } from 'src/plugins/data/public'; import { FilterUtils } from './filter_utils'; -import { SavedObjectDashboard } from '../../../../../../../plugins/dashboard/public'; -import { DashboardAppState } from '../types'; +import { SavedObjectDashboard } from '../../saved_dashboards'; +import { DashboardAppState } from '../../types'; export function updateSavedDashboard( savedDashboard: SavedObjectDashboard, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/url.test.ts b/src/plugins/dashboard/public/application/lib/url.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/url.test.ts rename to src/plugins/dashboard/public/application/lib/url.test.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/url.ts b/src/plugins/dashboard/public/application/lib/url.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/lib/url.ts rename to src/plugins/dashboard/public/application/lib/url.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/__snapshots__/dashboard_listing.test.js.snap b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.js.snap similarity index 86% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/__snapshots__/dashboard_listing.test.js.snap rename to src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.js.snap index 2a9a793ba43c4..8c91776cabc9c 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/__snapshots__/dashboard_listing.test.js.snap +++ b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.js.snap @@ -22,7 +22,7 @@ exports[`after fetch hideWriteControls 1`] = ` > @@ -81,7 +81,7 @@ exports[`after fetch initialFilter 1`] = ` > @@ -91,14 +91,14 @@ exports[`after fetch initialFilter 1`] = `

, @@ -123,7 +123,7 @@ exports[`after fetch initialFilter 1`] = ` > @@ -182,7 +182,7 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = ` > @@ -192,14 +192,14 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = `

, @@ -224,7 +224,7 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = ` > @@ -283,7 +283,7 @@ exports[`after fetch renders table rows 1`] = ` > @@ -293,14 +293,14 @@ exports[`after fetch renders table rows 1`] = `

, @@ -325,7 +325,7 @@ exports[`after fetch renders table rows 1`] = ` > @@ -384,7 +384,7 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` > @@ -394,14 +394,14 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = `

, @@ -426,7 +426,7 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` > @@ -485,7 +485,7 @@ exports[`renders empty page in before initial fetch to avoid flickering 1`] = ` > @@ -495,14 +495,14 @@ exports[`renders empty page in before initial fetch to avoid flickering 1`] = `

, @@ -527,7 +527,7 @@ exports[`renders empty page in before initial fetch to avoid flickering 1`] = ` > diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing.js b/src/plugins/dashboard/public/application/listing/dashboard_listing.js similarity index 84% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing.js rename to src/plugins/dashboard/public/application/listing/dashboard_listing.js index 30bf940069fb7..e123a87bf10d5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing.js +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.js @@ -24,7 +24,7 @@ import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { EuiLink, EuiButton, EuiEmptyPrompt } from '@elastic/eui'; -import { TableListView } from '../../../../../../../plugins/kibana_react/public'; +import { TableListView } from '../../../../kibana_react/public'; export const EMPTY_FILTER = ''; @@ -51,13 +51,13 @@ export class DashboardListing extends React.Component { listingLimit={this.props.listingLimit} initialFilter={this.props.initialFilter} noItemsFragment={this.getNoItemsMessage()} - entityName={i18n.translate('kbn.dashboard.listing.table.entityName', { + entityName={i18n.translate('dashboard.listing.table.entityName', { defaultMessage: 'dashboard', })} - entityNamePlural={i18n.translate('kbn.dashboard.listing.table.entityNamePlural', { + entityNamePlural={i18n.translate('dashboard.listing.table.entityNamePlural', { defaultMessage: 'dashboards', })} - tableListTitle={i18n.translate('kbn.dashboard.listing.dashboardsTitle', { + tableListTitle={i18n.translate('dashboard.listing.dashboardsTitle', { defaultMessage: 'Dashboards', })} toastNotifications={this.props.core.notifications.toasts} @@ -76,7 +76,7 @@ export class DashboardListing extends React.Component { title={

@@ -93,7 +93,7 @@ export class DashboardListing extends React.Component { title={

@@ -102,19 +102,19 @@ export class DashboardListing extends React.Component {

@@ -132,7 +132,7 @@ export class DashboardListing extends React.Component { data-test-subj="createDashboardPromptButton" > @@ -146,7 +146,7 @@ export class DashboardListing extends React.Component { const tableColumns = [ { field: 'title', - name: i18n.translate('kbn.dashboard.listing.table.titleColumnName', { + name: i18n.translate('dashboard.listing.table.titleColumnName', { defaultMessage: 'Title', }), sortable: true, @@ -161,7 +161,7 @@ export class DashboardListing extends React.Component { }, { field: 'description', - name: i18n.translate('kbn.dashboard.listing.table.descriptionColumnName', { + name: i18n.translate('dashboard.listing.table.descriptionColumnName', { defaultMessage: 'Description', }), dataType: 'string', diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing.test.js b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.js similarity index 97% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing.test.js rename to src/plugins/dashboard/public/application/listing/dashboard_listing.test.js index c47a54ad60460..0cdefff6a738e 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing.test.js +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.js @@ -17,16 +17,6 @@ * under the License. */ -jest.mock( - 'ui/notify', - () => ({ - toastNotifications: { - addWarning: () => {}, - }, - }), - { virtual: true } -); - jest.mock( 'lodash', () => ({ diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing_ng_wrapper.html b/src/plugins/dashboard/public/application/listing/dashboard_listing_ng_wrapper.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/listing/dashboard_listing_ng_wrapper.html rename to src/plugins/dashboard/public/application/listing/dashboard_listing_ng_wrapper.html diff --git a/src/plugins/dashboard/public/test_helpers/get_sample_dashboard_input.ts b/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts similarity index 96% rename from src/plugins/dashboard/public/test_helpers/get_sample_dashboard_input.ts rename to src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts index 09478d8e8af35..4ceac90672cb3 100644 --- a/src/plugins/dashboard/public/test_helpers/get_sample_dashboard_input.ts +++ b/src/plugins/dashboard/public/application/test_helpers/get_sample_dashboard_input.ts @@ -17,7 +17,7 @@ * under the License. */ -import { ViewMode, EmbeddableInput } from '../embeddable_plugin'; +import { ViewMode, EmbeddableInput } from '../../embeddable_plugin'; import { DashboardContainerInput, DashboardPanelState } from '../embeddable'; export function getSampleDashboardInput( diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/test_utils/get_saved_dashboard_mock.ts b/src/plugins/dashboard/public/application/test_helpers/get_saved_dashboard_mock.ts similarity index 88% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/test_utils/get_saved_dashboard_mock.ts rename to src/plugins/dashboard/public/application/test_helpers/get_saved_dashboard_mock.ts index 53618f1cfe5fa..57c147ffe3588 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/test_utils/get_saved_dashboard_mock.ts +++ b/src/plugins/dashboard/public/application/test_helpers/get_saved_dashboard_mock.ts @@ -17,8 +17,8 @@ * under the License. */ -import { searchSourceMock } from '../../../../../../../plugins/data/public/mocks'; -import { SavedObjectDashboard } from '../../../../../../../plugins/dashboard/public/'; +import { searchSourceMock } from '../../../../data/public/mocks'; +import { SavedObjectDashboard } from '../../saved_dashboards'; export function getSavedDashboardMock( config?: Partial diff --git a/src/plugins/dashboard/public/test_helpers/index.ts b/src/plugins/dashboard/public/application/test_helpers/index.ts similarity index 92% rename from src/plugins/dashboard/public/test_helpers/index.ts rename to src/plugins/dashboard/public/application/test_helpers/index.ts index 88909826971fc..c22c614e9ffcf 100644 --- a/src/plugins/dashboard/public/test_helpers/index.ts +++ b/src/plugins/dashboard/public/application/test_helpers/index.ts @@ -18,3 +18,4 @@ */ export { getSampleDashboardInput, getSampleDashboardPanel } from './get_sample_dashboard_input'; +export { getSavedDashboardMock } from './get_saved_dashboard_mock'; diff --git a/src/plugins/dashboard/public/tests/dashboard_container.test.tsx b/src/plugins/dashboard/public/application/tests/dashboard_container.test.tsx similarity index 90% rename from src/plugins/dashboard/public/tests/dashboard_container.test.tsx rename to src/plugins/dashboard/public/application/tests/dashboard_container.test.tsx index 72aaca46757a9..0f929e87b3be1 100644 --- a/src/plugins/dashboard/public/tests/dashboard_container.test.tsx +++ b/src/plugins/dashboard/public/application/tests/dashboard_container.test.tsx @@ -23,7 +23,7 @@ import React from 'react'; import { mount } from 'enzyme'; import { nextTick } from 'test_utils/enzyme_helpers'; import { I18nProvider } from '@kbn/i18n/react'; -import { ViewMode, CONTEXT_MENU_TRIGGER, EmbeddablePanel } from '../embeddable_plugin'; +import { ViewMode, CONTEXT_MENU_TRIGGER, EmbeddablePanel } from '../../embeddable_plugin'; import { DashboardContainer, DashboardContainerOptions } from '../embeddable/dashboard_container'; import { getSampleDashboardInput } from '../test_helpers'; import { @@ -33,14 +33,11 @@ import { ContactCardEmbeddable, ContactCardEmbeddableOutput, createEditModeAction, -} from '../embeddable_plugin_test_samples'; -// eslint-disable-next-line -import { embeddablePluginMock } from '../../../embeddable/public/mocks'; -// eslint-disable-next-line -import { inspectorPluginMock } from '../../../inspector/public/mocks'; -import { KibanaContextProvider } from '../../../kibana_react/public'; -// eslint-disable-next-line -import { uiActionsPluginMock } from '../../../ui_actions/public/mocks'; +} from '../../embeddable_plugin_test_samples'; +import { embeddablePluginMock } from '../../../../embeddable/public/mocks'; +import { inspectorPluginMock } from '../../../../inspector/public/mocks'; +import { KibanaContextProvider } from '../../../../kibana_react/public'; +import { uiActionsPluginMock } from '../../../../ui_actions/public/mocks'; test('DashboardContainer in edit mode shows edit mode actions', async () => { const inspector = inspectorPluginMock.createStartContract(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/__snapshots__/clone_modal.test.js.snap b/src/plugins/dashboard/public/application/top_nav/__snapshots__/clone_modal.test.js.snap similarity index 83% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/__snapshots__/clone_modal.test.js.snap rename to src/plugins/dashboard/public/application/top_nav/__snapshots__/clone_modal.test.js.snap index 771d53b73d960..d289d267a2fd6 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/__snapshots__/clone_modal.test.js.snap +++ b/src/plugins/dashboard/public/application/top_nav/__snapshots__/clone_modal.test.js.snap @@ -11,7 +11,7 @@ exports[`renders DashboardCloneModal 1`] = ` @@ -21,7 +21,7 @@ exports[`renders DashboardCloneModal 1`] = `

@@ -43,7 +43,7 @@ exports[`renders DashboardCloneModal 1`] = ` > @@ -55,7 +55,7 @@ exports[`renders DashboardCloneModal 1`] = ` > diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/__snapshots__/save_modal.test.js.snap b/src/plugins/dashboard/public/application/top_nav/__snapshots__/save_modal.test.js.snap similarity index 86% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/__snapshots__/save_modal.test.js.snap rename to src/plugins/dashboard/public/application/top_nav/__snapshots__/save_modal.test.js.snap index 7ac2e2d9dd317..bc4ed477d9eea 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/__snapshots__/save_modal.test.js.snap +++ b/src/plugins/dashboard/public/application/top_nav/__snapshots__/save_modal.test.js.snap @@ -16,7 +16,7 @@ exports[`renders DashboardSaveModal 1`] = ` label={ } @@ -37,7 +37,7 @@ exports[`renders DashboardSaveModal 1`] = ` helpText={ } @@ -49,7 +49,7 @@ exports[`renders DashboardSaveModal 1`] = ` label={ } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/clone_modal.test.js b/src/plugins/dashboard/public/application/top_nav/clone_modal.test.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/clone_modal.test.js rename to src/plugins/dashboard/public/application/top_nav/clone_modal.test.js diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/clone_modal.tsx b/src/plugins/dashboard/public/application/top_nav/clone_modal.tsx similarity index 88% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/clone_modal.tsx rename to src/plugins/dashboard/public/application/top_nav/clone_modal.tsx index 08e2b98d1c73d..16cfced6573ca 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/clone_modal.tsx +++ b/src/plugins/dashboard/public/application/top_nav/clone_modal.tsx @@ -117,7 +117,7 @@ export class DashboardCloneModal extends React.Component { { >

@@ -158,7 +158,7 @@ export class DashboardCloneModal extends React.Component { @@ -168,7 +168,7 @@ export class DashboardCloneModal extends React.Component {

@@ -178,7 +178,7 @@ export class DashboardCloneModal extends React.Component { { @@ -205,7 +205,7 @@ export class DashboardCloneModal extends React.Component { isLoading={this.state.isLoading} > diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/get_top_nav_config.ts b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts similarity index 76% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/get_top_nav_config.ts rename to src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts index 7a3cb4b7dad56..dbdadeb4e4e7c 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/get_top_nav_config.ts +++ b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts @@ -18,9 +18,9 @@ */ import { i18n } from '@kbn/i18n'; -import { ViewMode } from '../../../../../../../plugins/embeddable/public'; +import { ViewMode } from '../../embeddable_plugin'; import { TopNavIds } from './top_nav_ids'; -import { NavAction } from '../types'; +import { NavAction } from '../../types'; /** * @param actions - A mapping of TopNavIds to an action function that should run when the @@ -63,10 +63,10 @@ export function getTopNavConfig( function getFullScreenConfig(action: NavAction) { return { id: 'full-screen', - label: i18n.translate('kbn.dashboard.topNave.fullScreenButtonAriaLabel', { + label: i18n.translate('dashboard.topNave.fullScreenButtonAriaLabel', { defaultMessage: 'full screen', }), - description: i18n.translate('kbn.dashboard.topNave.fullScreenConfigDescription', { + description: i18n.translate('dashboard.topNave.fullScreenConfigDescription', { defaultMessage: 'Full Screen Mode', }), testId: 'dashboardFullScreenMode', @@ -80,10 +80,10 @@ function getFullScreenConfig(action: NavAction) { function getEditConfig(action: NavAction) { return { id: 'edit', - label: i18n.translate('kbn.dashboard.topNave.editButtonAriaLabel', { + label: i18n.translate('dashboard.topNave.editButtonAriaLabel', { defaultMessage: 'edit', }), - description: i18n.translate('kbn.dashboard.topNave.editConfigDescription', { + description: i18n.translate('dashboard.topNave.editConfigDescription', { defaultMessage: 'Switch to edit mode', }), testId: 'dashboardEditMode', @@ -100,10 +100,10 @@ function getEditConfig(action: NavAction) { function getSaveConfig(action: NavAction) { return { id: 'save', - label: i18n.translate('kbn.dashboard.topNave.saveButtonAriaLabel', { + label: i18n.translate('dashboard.topNave.saveButtonAriaLabel', { defaultMessage: 'save', }), - description: i18n.translate('kbn.dashboard.topNave.saveConfigDescription', { + description: i18n.translate('dashboard.topNave.saveConfigDescription', { defaultMessage: 'Save your dashboard', }), testId: 'dashboardSaveMenuItem', @@ -117,10 +117,10 @@ function getSaveConfig(action: NavAction) { function getViewConfig(action: NavAction) { return { id: 'cancel', - label: i18n.translate('kbn.dashboard.topNave.cancelButtonAriaLabel', { + label: i18n.translate('dashboard.topNave.cancelButtonAriaLabel', { defaultMessage: 'cancel', }), - description: i18n.translate('kbn.dashboard.topNave.viewConfigDescription', { + description: i18n.translate('dashboard.topNave.viewConfigDescription', { defaultMessage: 'Cancel editing and switch to view-only mode', }), testId: 'dashboardViewOnlyMode', @@ -134,10 +134,10 @@ function getViewConfig(action: NavAction) { function getCloneConfig(action: NavAction) { return { id: 'clone', - label: i18n.translate('kbn.dashboard.topNave.cloneButtonAriaLabel', { + label: i18n.translate('dashboard.topNave.cloneButtonAriaLabel', { defaultMessage: 'clone', }), - description: i18n.translate('kbn.dashboard.topNave.cloneConfigDescription', { + description: i18n.translate('dashboard.topNave.cloneConfigDescription', { defaultMessage: 'Create a copy of your dashboard', }), testId: 'dashboardClone', @@ -151,10 +151,10 @@ function getCloneConfig(action: NavAction) { function getAddConfig(action: NavAction) { return { id: 'add', - label: i18n.translate('kbn.dashboard.topNave.addButtonAriaLabel', { + label: i18n.translate('dashboard.topNave.addButtonAriaLabel', { defaultMessage: 'add', }), - description: i18n.translate('kbn.dashboard.topNave.addConfigDescription', { + description: i18n.translate('dashboard.topNave.addConfigDescription', { defaultMessage: 'Add a panel to the dashboard', }), testId: 'dashboardAddPanelButton', @@ -170,10 +170,10 @@ function getCreateNewConfig(action: NavAction) { emphasize: true, iconType: 'plusInCircle', id: 'addNew', - label: i18n.translate('kbn.dashboard.topNave.addNewButtonAriaLabel', { + label: i18n.translate('dashboard.topNave.addNewButtonAriaLabel', { defaultMessage: 'Create new', }), - description: i18n.translate('kbn.dashboard.topNave.addNewConfigDescription', { + description: i18n.translate('dashboard.topNave.addNewConfigDescription', { defaultMessage: 'Create a new panel on this dashboard', }), testId: 'dashboardAddNewPanelButton', @@ -184,17 +184,19 @@ function getCreateNewConfig(action: NavAction) { /** * @returns {kbnTopNavConfig} */ -function getShareConfig(action: NavAction) { +function getShareConfig(action: NavAction | undefined) { return { id: 'share', - label: i18n.translate('kbn.dashboard.topNave.shareButtonAriaLabel', { + label: i18n.translate('dashboard.topNave.shareButtonAriaLabel', { defaultMessage: 'share', }), - description: i18n.translate('kbn.dashboard.topNave.shareConfigDescription', { + description: i18n.translate('dashboard.topNave.shareConfigDescription', { defaultMessage: 'Share Dashboard', }), testId: 'shareTopNavButton', run: action, + // disable the Share button if no action specified + disableButton: !action, }; } @@ -204,10 +206,10 @@ function getShareConfig(action: NavAction) { function getOptionsConfig(action: NavAction) { return { id: 'options', - label: i18n.translate('kbn.dashboard.topNave.optionsButtonAriaLabel', { + label: i18n.translate('dashboard.topNave.optionsButtonAriaLabel', { defaultMessage: 'options', }), - description: i18n.translate('kbn.dashboard.topNave.optionsConfigDescription', { + description: i18n.translate('dashboard.topNave.optionsConfigDescription', { defaultMessage: 'Options', }), testId: 'dashboardOptionsButton', diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/options.tsx b/src/plugins/dashboard/public/application/top_nav/options.tsx similarity index 88% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/options.tsx rename to src/plugins/dashboard/public/application/top_nav/options.tsx index af284e6f557cb..3398696ff40db 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/options.tsx +++ b/src/plugins/dashboard/public/application/top_nav/options.tsx @@ -61,12 +61,9 @@ export class OptionsMenu extends Component { { ({ +jest.mock('../../../../saved_objects/public', () => ({ SavedObjectSaveModal: () => null, })); -jest.mock('ui/new_platform'); - import { DashboardSaveModal } from './save_modal'; test('renders DashboardSaveModal', () => { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/save_modal.tsx b/src/plugins/dashboard/public/application/top_nav/save_modal.tsx similarity index 91% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/save_modal.tsx rename to src/plugins/dashboard/public/application/top_nav/save_modal.tsx index 4a4fcb7e1adc8..609ed23472924 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/save_modal.tsx +++ b/src/plugins/dashboard/public/application/top_nav/save_modal.tsx @@ -21,7 +21,7 @@ import React, { Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFormRow, EuiTextArea, EuiSwitch } from '@elastic/eui'; -import { SavedObjectSaveModal } from '../../../../../../../plugins/saved_objects/public'; +import { SavedObjectSaveModal } from '../../../../saved_objects/public'; interface SaveOptions { newTitle: string; @@ -102,7 +102,7 @@ export class DashboardSaveModal extends React.Component { } @@ -117,7 +117,7 @@ export class DashboardSaveModal extends React.Component { } @@ -128,7 +128,7 @@ export class DashboardSaveModal extends React.Component { onChange={this.onTimeRestoreChange} label={ } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/show_clone_modal.tsx b/src/plugins/dashboard/public/application/top_nav/show_clone_modal.tsx similarity index 96% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/show_clone_modal.tsx rename to src/plugins/dashboard/public/application/top_nav/show_clone_modal.tsx index af1020e01e0c5..ee356b81802ee 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/show_clone_modal.tsx +++ b/src/plugins/dashboard/public/application/top_nav/show_clone_modal.tsx @@ -58,7 +58,7 @@ export function showCloneModal( { - constructor(initializerContext: PluginInitializerContext) {} + constructor(private initializerContext: PluginInitializerContext) {} + + private appStateUpdater = new BehaviorSubject(() => ({})); + private stopUrlTracking: (() => void) | undefined = undefined; public setup( - core: CoreSetup, - { share, uiActions, embeddable }: SetupDependencies + core: CoreSetup, + { share, uiActions, embeddable, home, kibanaLegacy, data, usageCollection }: SetupDependencies ): Setup { const expandPanelAction = new ExpandPanelAction(); uiActions.registerAction(expandPanelAction); @@ -128,6 +172,112 @@ export class DashboardEmbeddableContainerPublicPlugin const factory = new DashboardContainerFactory(getStartServices); embeddable.registerEmbeddableFactory(factory.type, factory); + + const { appMounted, appUnMounted, stop: stopUrlTracker } = createKbnUrlTracker({ + baseUrl: core.http.basePath.prepend('/app/kibana'), + defaultSubUrl: `#${DashboardConstants.LANDING_PAGE_PATH}`, + shouldTrackUrlUpdate: pathname => { + const targetAppName = pathname.split('/')[1]; + return ( + targetAppName === DashboardConstants.DASHBOARDS_ID || + targetAppName === DashboardConstants.DASHBOARD_ID + ); + }, + storageKey: `lastUrl:${core.http.basePath.get()}:dashboard`, + navLinkUpdater$: this.appStateUpdater, + toastNotifications: core.notifications.toasts, + stateParams: [ + { + kbnUrlKey: '_g', + stateUpdate$: data.query.state$.pipe( + filter( + ({ changes }) => !!(changes.globalFilters || changes.time || changes.refreshInterval) + ), + map(({ state }) => ({ + ...state, + filters: state.filters?.filter(esFilters.isFilterPinned), + })) + ), + }, + ], + }); + + this.stopUrlTracking = () => { + stopUrlTracker(); + }; + + const app: App = { + id: '', + title: 'Dashboards', + mount: async (params: AppMountParameters) => { + const [coreStart, pluginsStart, dashboardStart] = await core.getStartServices(); + appMounted(); + const { + embeddable: embeddableStart, + navigation, + share: shareStart, + data: dataStart, + kibanaLegacy: { dashboardConfig }, + } = pluginsStart; + + const deps: RenderDeps = { + pluginInitializerContext: this.initializerContext, + core: coreStart, + dashboardConfig, + navigation, + share: shareStart, + data: dataStart, + savedObjectsClient: coreStart.savedObjects.client, + savedDashboards: dashboardStart.getSavedDashboardLoader(), + chrome: coreStart.chrome, + addBasePath: coreStart.http.basePath.prepend, + uiSettings: coreStart.uiSettings, + config: kibanaLegacy.config, + savedQueryService: dataStart.query.savedQueries, + embeddable: embeddableStart, + dashboardCapabilities: coreStart.application.capabilities.dashboard, + embeddableCapabilities: { + visualizeCapabilities: coreStart.application.capabilities.visualize, + mapsCapabilities: coreStart.application.capabilities.maps, + }, + localStorage: new Storage(localStorage), + usageCollection, + }; + const { renderApp } = await import('./application/application'); + const unmount = renderApp(params.element, params.appBasePath, deps); + return () => { + unmount(); + appUnMounted(); + }; + }, + }; + + initAngularBootstrap(); + + kibanaLegacy.registerLegacyApp({ + ...app, + id: DashboardConstants.DASHBOARD_ID, + // only register the updater in once app, otherwise all updates would happen twice + updater$: this.appStateUpdater.asObservable(), + navLinkId: 'kibana:dashboard', + }); + kibanaLegacy.registerLegacyApp({ ...app, id: DashboardConstants.DASHBOARDS_ID }); + + if (home) { + home.featureCatalogue.register({ + id: DashboardConstants.DASHBOARD_ID, + title: i18n.translate('dashboard.featureCatalogue.dashboardTitle', { + defaultMessage: 'Dashboard', + }), + description: i18n.translate('dashboard.featureCatalogue.dashboardDescription', { + defaultMessage: 'Display and share a collection of visualizations and saved searches.', + }), + icon: 'dashboardApp', + path: `/app/kibana#${DashboardConstants.LANDING_PAGE_PATH}`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + }); + } } public start(core: CoreStart, plugins: StartDependencies): DashboardStart { @@ -158,5 +308,9 @@ export class DashboardEmbeddableContainerPublicPlugin }; } - public stop() {} + public stop() { + if (this.stopUrlTracking) { + this.stopUrlTracking(); + } + } } diff --git a/src/plugins/dashboard/public/types.ts b/src/plugins/dashboard/public/types.ts index f58be10e11ba6..7bccd3de6eca8 100644 --- a/src/plugins/dashboard/public/types.ts +++ b/src/plugins/dashboard/public/types.ts @@ -17,7 +17,17 @@ * under the License. */ -import { SavedObject as SavedObjectType, SavedObjectAttributes } from '../../../core/public'; +import { Query, Filter } from 'src/plugins/data/public'; +import { SavedObject as SavedObjectType, SavedObjectAttributes } from 'src/core/public'; +import { + RawSavedDashboardPanelTo60, + RawSavedDashboardPanel610, + RawSavedDashboardPanel620, + RawSavedDashboardPanel630, + RawSavedDashboardPanel640To720, + RawSavedDashboardPanel730ToLatest, +} from './bwc'; +import { ViewMode } from './embeddable_plugin'; export interface DashboardCapabilities { showWriteControls: boolean; @@ -65,3 +75,105 @@ export interface Field { searchable: boolean; subType?: FieldSubType; } + +export type NavAction = (anchorElement?: any) => void; + +/** + * This should always represent the latest dashboard panel shape, after all possible migrations. + */ +export type SavedDashboardPanel = SavedDashboardPanel730ToLatest; + +// id becomes optional starting in 7.3.0 +export type SavedDashboardPanel730ToLatest = Pick< + RawSavedDashboardPanel730ToLatest, + Exclude +> & { + readonly id?: string; + readonly type: string; +}; + +export type SavedDashboardPanel640To720 = Pick< + RawSavedDashboardPanel640To720, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanel630 = Pick< + RawSavedDashboardPanel630, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanel620 = Pick< + RawSavedDashboardPanel620, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanel610 = Pick< + RawSavedDashboardPanel610, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanelTo60 = Pick< + RawSavedDashboardPanelTo60, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export interface DashboardAppState { + panels: SavedDashboardPanel[]; + fullScreenMode: boolean; + title: string; + description: string; + timeRestore: boolean; + options: { + hidePanelTitles: boolean; + useMargins: boolean; + }; + query: Query | string; + filters: Filter[]; + viewMode: ViewMode; + savedQuery?: string; +} + +export type DashboardAppStateDefaults = DashboardAppState & { + description?: string; +}; + +export interface DashboardAppStateTransitions { + set: ( + state: DashboardAppState + ) => ( + prop: T, + value: DashboardAppState[T] + ) => DashboardAppState; + setOption: ( + state: DashboardAppState + ) => ( + prop: T, + value: DashboardAppState['options'][T] + ) => DashboardAppState; +} + +export interface SavedDashboardPanelMap { + [key: string]: SavedDashboardPanel; +} + +export interface StagedFilter { + field: string; + value: string; + operator: string; + index: string; +} diff --git a/src/plugins/data/common/field_formats/mocks.ts b/src/plugins/data/common/field_formats/mocks.ts index bc38374e147cf..394d4c383032f 100644 --- a/src/plugins/data/common/field_formats/mocks.ts +++ b/src/plugins/data/common/field_formats/mocks.ts @@ -17,23 +17,14 @@ * under the License. */ -import { FieldFormat, IFieldFormatsRegistry } from '.'; - -const fieldFormatMock = ({ - convert: jest.fn(), - getConverterFor: jest.fn(), - getParamDefaults: jest.fn(), - param: jest.fn(), - params: jest.fn(), - toJSON: jest.fn(), - type: jest.fn(), - setupContentType: jest.fn(), -} as unknown) as FieldFormat; +import { IFieldFormatsRegistry } from '.'; export const fieldFormatsMock: IFieldFormatsRegistry = { getByFieldType: jest.fn(), getDefaultConfig: jest.fn(), - getDefaultInstance: jest.fn().mockImplementation(() => fieldFormatMock) as any, + getDefaultInstance: jest.fn().mockImplementation(() => ({ + getConverterFor: jest.fn().mockImplementation(() => (t: string) => t), + })) as any, getDefaultInstanceCacheResolver: jest.fn(), getDefaultInstancePlain: jest.fn(), getDefaultType: jest.fn(), diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/plugins/data/public/field_formats/mocks.ts similarity index 50% rename from src/legacy/core_plugins/kibana/public/dashboard/index.ts rename to src/plugins/data/public/field_formats/mocks.ts index 8900d017ef81a..ec1233a085bce 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts +++ b/src/plugins/data/public/field_formats/mocks.ts @@ -17,12 +17,25 @@ * under the License. */ -import { PluginInitializerContext } from 'kibana/public'; -import { DashboardPlugin } from './plugin'; +import { FieldFormatsStart, FieldFormatsSetup, FieldFormatsService } from '.'; +import { fieldFormatsMock } from '../../common/field_formats/mocks'; -export * from './np_ready/dashboard_constants'; +type FieldFormatsServiceClientContract = PublicMethodsOf; -// Core will be looking for this when loading our plugin in the new platform -export const plugin = (context: PluginInitializerContext) => { - return new DashboardPlugin(context); +const createSetupContractMock = () => fieldFormatsMock as FieldFormatsSetup; +const createStartContractMock = () => fieldFormatsMock as FieldFormatsStart; + +const createMock = () => { + const mocked: jest.Mocked = { + setup: jest.fn().mockReturnValue(createSetupContractMock()), + start: jest.fn().mockReturnValue(createStartContractMock()), + }; + + return mocked; +}; + +export const fieldFormatsServiceMock = { + create: createMock, + createSetupContract: createSetupContractMock, + createStartContract: createStartContractMock, }; diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts index 4d4e8d8827b48..0007d1780c25b 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts @@ -45,7 +45,7 @@ export class IndexPatternsApiClient { query, }) .catch((resp: any) => { - if (resp.body.statusCode === 404 && resp.body.statuscode === 'no_matching_indices') { + if (resp.body.statusCode === 404 && resp.body.attributes?.code === 'no_matching_indices') { throw new IndexPatternMissingIndices(resp.body.message); } diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index e3fc0e97af09b..ea1c27550867e 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -17,8 +17,8 @@ * under the License. */ -import { Plugin, DataPublicPluginSetup, DataPublicPluginStart, IndexPatternsContract } from '.'; -import { fieldFormatsMock } from '../common/field_formats/mocks'; +import { Plugin, IndexPatternsContract } from '.'; +import { fieldFormatsServiceMock } from './field_formats/mocks'; import { searchSetupMock, searchStartMock } from './search/mocks'; import { queryServiceMock } from './query/mocks'; @@ -36,7 +36,7 @@ const createSetupContract = (): Setup => { return { autocomplete: autocompleteMock, search: searchSetupMock, - fieldFormats: fieldFormatsMock as DataPublicPluginSetup['fieldFormats'], + fieldFormats: fieldFormatsServiceMock.createSetupContract(), query: querySetupMock, }; }; @@ -49,7 +49,7 @@ const createStartContract = (): Start => { }, autocomplete: autocompleteMock, search: searchStartMock, - fieldFormats: fieldFormatsMock as DataPublicPluginStart['fieldFormats'], + fieldFormats: fieldFormatsServiceMock.createStartContract(), query: queryStartMock, ui: { IndexPatternSelect: jest.fn(), diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index f8add047603ae..8c0757ac56008 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -30,6 +30,7 @@ import { DataPublicPluginStart, DataSetupDependencies, DataStartDependencies, + GetInternalStartServicesFn, } from './types'; import { AutocompleteService } from './autocomplete'; import { SearchService } from './search/search_service'; @@ -47,6 +48,8 @@ import { setQueryService, setSearchService, setUiSettings, + getFieldFormats, + getNotifications, } from './services'; import { createSearchBar } from './ui/search_bar/create_search_bar'; import { esaggs } from './search/expressions'; @@ -100,6 +103,11 @@ export class DataPublicPlugin implements Plugin ({ + fieldFormats: getFieldFormats(), + notifications: getNotifications(), + }); + const queryService = this.queryService.setup({ uiSettings: core.uiSettings, storage: this.storage, @@ -122,6 +130,7 @@ export class DataPublicPlugin implements Plugin { +export interface IDataPluginServices extends Partial { // (undocumented) appName: string; // (undocumented) data: DataPublicPluginStart; // (undocumented) - http: CoreStart_2['http']; + http: CoreStart['http']; // (undocumented) - notifications: CoreStart_2['notifications']; + notifications: CoreStart['notifications']; // (undocumented) - savedObjects: CoreStart_2['savedObjects']; + savedObjects: CoreStart['savedObjects']; // (undocumented) storage: IStorageWrapper; // (undocumented) - uiSettings: CoreStart_2['uiSettings']; + uiSettings: CoreStart['uiSettings']; } // Warning: (ae-missing-release-tag) "IEsSearchRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -1094,7 +1094,7 @@ export type ISearch = // @public (undocumented) export interface ISearchContext { // (undocumented) - core: CoreStart; + core: CoreStart_2; // (undocumented) getSearchStrategy: (name: T) => TSearchStrategyProvider; } @@ -1307,7 +1307,7 @@ export class Plugin implements Plugin_2 { + describe('model presentation formatter', () => { + test('should present objects as strings', () => { expect(toUser({ foo: 'bar' })).toBe('{"foo":"bar"}'); }); - it('should present query_string queries as strings', function() { + test('should present query_string queries as strings', () => { expect(toUser({ query_string: { query: 'lucene query string' } })).toBe( 'lucene query string' ); }); - it('should present query_string queries without a query as an empty string', function() { + test('should present query_string queries without a query as an empty string', () => { expect(toUser({ query_string: {} })).toBe(''); }); - it('should present string as strings', function() { + test('should present string as strings', () => { expect(toUser('foo')).toBe('foo'); }); - it('should present numbers as strings', function() { + test('should present numbers as strings', () => { expect(toUser(400)).toBe('400'); }); }); diff --git a/src/plugins/data/public/query/lib/to_user.ts b/src/plugins/data/public/query/lib/to_user.ts index 1fdb2d8ed03df..1a364534d93fb 100644 --- a/src/plugins/data/public/query/lib/to_user.ts +++ b/src/plugins/data/public/query/lib/to_user.ts @@ -27,9 +27,6 @@ export function toUser(text: { [key: string]: any } | string | number): string { return ''; } if (typeof text === 'object') { - if (text.match_all) { - return ''; - } if (text.query_string) { return toUser(text.query_string.query); } diff --git a/src/plugins/data/public/search/aggs/agg_config.test.ts b/src/plugins/data/public/search/aggs/agg_config.test.ts index f979c9664f458..41d943ef0c94b 100644 --- a/src/plugins/data/public/search/aggs/agg_config.test.ts +++ b/src/plugins/data/public/search/aggs/agg_config.test.ts @@ -26,8 +26,6 @@ import { AggTypesRegistryStart } from './agg_types_registry'; import { mockDataServices, mockAggTypesRegistry } from './test_helpers'; import { Field as IndexPatternField, IndexPattern } from '../../index_patterns'; import { stubIndexPatternWithFields } from '../../../public/stubs'; -import { dataPluginMock } from '../../../public/mocks'; -import { setFieldFormats } from '../../../public/services'; describe('AggConfig', () => { let indexPattern: IndexPattern; @@ -400,13 +398,6 @@ describe('AggConfig', () => { describe('#fieldFormatter - custom getFormat handler', () => { it('returns formatter from getFormat handler', () => { - setFieldFormats({ - ...dataPluginMock.createStartContract().fieldFormats, - getDefaultInstance: jest.fn().mockImplementation(() => ({ - getConverterFor: jest.fn().mockImplementation(() => (t: string) => t), - })) as any, - }); - const ac = new AggConfigs(indexPattern, [], { typesRegistry }); const configStates = { enabled: true, @@ -429,12 +420,6 @@ describe('AggConfig', () => { let aggConfig: AggConfig; beforeEach(() => { - setFieldFormats({ - ...dataPluginMock.createStartContract().fieldFormats, - getDefaultInstance: jest.fn().mockImplementation(() => ({ - getConverterFor: (t?: string) => t || identity, - })) as any, - }); indexPattern.fields.getByName = name => ({ format: { diff --git a/src/plugins/data/public/search/aggs/agg_params.test.ts b/src/plugins/data/public/search/aggs/agg_params.test.ts index b08fcf309e9ed..784be803e2644 100644 --- a/src/plugins/data/public/search/aggs/agg_params.test.ts +++ b/src/plugins/data/public/search/aggs/agg_params.test.ts @@ -22,12 +22,22 @@ import { BaseParamType } from './param_types/base'; import { FieldParamType } from './param_types/field'; import { OptionedParamType } from './param_types/optioned'; import { AggParamType } from '../aggs/param_types/agg'; +import { fieldFormatsServiceMock } from '../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../src/core/public/mocks'; +import { AggTypeDependencies } from './agg_type'; describe('AggParams class', () => { + const aggTypesDependencies: AggTypeDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + describe('constructor args', () => { it('accepts an array of param defs', () => { const params = [{ name: 'one' }, { name: 'two' }] as AggParamType[]; - const aggParams = initParams(params); + const aggParams = initParams(params, aggTypesDependencies); expect(aggParams).toHaveLength(params.length); expect(Array.isArray(aggParams)).toBeTruthy(); @@ -37,7 +47,7 @@ describe('AggParams class', () => { describe('AggParam creation', () => { it('Uses the FieldParamType class for params with the name "field"', () => { const params = [{ name: 'field', type: 'field' }] as AggParamType[]; - const aggParams = initParams(params); + const aggParams = initParams(params, aggTypesDependencies); expect(aggParams).toHaveLength(params.length); expect(aggParams[0] instanceof FieldParamType).toBeTruthy(); @@ -50,7 +60,7 @@ describe('AggParams class', () => { type: 'optioned', }, ] as AggParamType[]; - const aggParams = initParams(params); + const aggParams = initParams(params, aggTypesDependencies); expect(aggParams).toHaveLength(params.length); expect(aggParams[0] instanceof OptionedParamType).toBeTruthy(); @@ -72,7 +82,7 @@ describe('AggParams class', () => { }, ] as AggParamType[]; - const aggParams = initParams(params); + const aggParams = initParams(params, aggTypesDependencies); expect(aggParams).toHaveLength(params.length); diff --git a/src/plugins/data/public/search/aggs/agg_params.ts b/src/plugins/data/public/search/aggs/agg_params.ts index 551cb81529a0a..e7b2f72bae656 100644 --- a/src/plugins/data/public/search/aggs/agg_params.ts +++ b/src/plugins/data/public/search/aggs/agg_params.ts @@ -26,6 +26,7 @@ import { BaseParamType } from './param_types/base'; import { AggConfig } from './agg_config'; import { IAggConfigs } from './agg_configs'; +import { AggTypeDependencies } from './agg_type'; const paramTypeMap = { field: FieldParamType, @@ -45,12 +46,13 @@ export interface AggParamOption { } export const initParams = ( - params: TAggParam[] + params: TAggParam[], + { getInternalStartServices }: AggTypeDependencies ): TAggParam[] => params.map((config: TAggParam) => { const Class = paramTypeMap[config.type] || paramTypeMap._default; - return new Class(config); + return new Class(config, { getInternalStartServices }); }) as TAggParam[]; /** diff --git a/src/plugins/data/public/search/aggs/agg_type.test.ts b/src/plugins/data/public/search/aggs/agg_type.test.ts index 3fb03dc31e2b2..0c9e110c34ae6 100644 --- a/src/plugins/data/public/search/aggs/agg_type.test.ts +++ b/src/plugins/data/public/search/aggs/agg_type.test.ts @@ -17,40 +17,49 @@ * under the License. */ -import { AggType, AggTypeConfig } from './agg_type'; +import { AggType, AggTypeConfig, AggTypeDependencies } from './agg_type'; import { IAggConfig } from './agg_config'; -import { mockDataServices } from './test_helpers'; -import { dataPluginMock } from '../../../public/mocks'; -import { setFieldFormats } from '../../../public/services'; +import { fieldFormatsServiceMock } from '../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../src/core/public/mocks'; describe('AggType Class', () => { + let dependencies: AggTypeDependencies; + beforeEach(() => { - mockDataServices(); + dependencies = { + getInternalStartServices: () => ({ + fieldFormats: { + ...fieldFormatsServiceMock.createStartContract(), + getDefaultInstance: jest.fn(() => 'default') as any, + }, + notifications: notificationServiceMock.createStartContract(), + }), + }; }); describe('constructor', () => { - it("requires a valid config object as it's first param", () => { + test("requires a valid config object as it's first param", () => { expect(() => { const aggConfig: AggTypeConfig = (undefined as unknown) as AggTypeConfig; - new AggType(aggConfig); + new AggType(aggConfig, dependencies); }).toThrowError(); }); describe('application of config properties', () => { - it('assigns the config value to itself', () => { + test('assigns the config value to itself', () => { const config: AggTypeConfig = { name: 'name', title: 'title', }; - const aggType = new AggType(config); + const aggType = new AggType(config, dependencies); expect(aggType.name).toBe('name'); expect(aggType.title).toBe('title'); }); describe('makeLabel', () => { - it('makes a function when the makeLabel config is not specified', () => { + test('makes a function when the makeLabel config is not specified', () => { const makeLabel = () => 'label'; const aggConfig = {} as IAggConfig; const config: AggTypeConfig = { @@ -59,7 +68,7 @@ describe('AggType Class', () => { makeLabel, }; - const aggType = new AggType(config); + const aggType = new AggType(config, dependencies); expect(aggType.makeLabel).toBe(makeLabel); expect(aggType.makeLabel(aggConfig)).toBe('label'); @@ -67,26 +76,32 @@ describe('AggType Class', () => { }); describe('getResponseAggs/getRequestAggs', () => { - it('copies the value', () => { + test('copies the value', () => { const testConfig = (aggConfig: IAggConfig) => [aggConfig]; - const aggType = new AggType({ - name: 'name', - title: 'title', - getResponseAggs: testConfig, - getRequestAggs: testConfig, - }); + const aggType = new AggType( + { + name: 'name', + title: 'title', + getResponseAggs: testConfig, + getRequestAggs: testConfig, + }, + dependencies + ); expect(aggType.getResponseAggs).toBe(testConfig); expect(aggType.getResponseAggs).toBe(testConfig); }); - it('defaults to noop', () => { + test('defaults to noop', () => { const aggConfig = {} as IAggConfig; - const aggType = new AggType({ - name: 'name', - title: 'title', - }); + const aggType = new AggType( + { + name: 'name', + title: 'title', + }, + dependencies + ); const responseAggs = aggType.getRequestAggs(aggConfig); expect(responseAggs).toBe(undefined); @@ -94,11 +109,14 @@ describe('AggType Class', () => { }); describe('params', () => { - it('defaults to AggParams object with JSON param', () => { - const aggType = new AggType({ - name: 'smart agg', - title: 'title', - }); + test('defaults to AggParams object with JSON param', () => { + const aggType = new AggType( + { + name: 'smart agg', + title: 'title', + }, + dependencies + ); expect(Array.isArray(aggType.params)).toBeTruthy(); expect(aggType.params.length).toBe(2); @@ -106,26 +124,32 @@ describe('AggType Class', () => { expect(aggType.params[1].name).toBe('customLabel'); }); - it('can disable customLabel', () => { - const aggType = new AggType({ - name: 'smart agg', - title: 'title', - customLabels: false, - }); + test('can disable customLabel', () => { + const aggType = new AggType( + { + name: 'smart agg', + title: 'title', + customLabels: false, + }, + dependencies + ); expect(aggType.params.length).toBe(1); expect(aggType.params[0].name).toBe('json'); }); - it('passes the params arg directly to the AggParams constructor', () => { + test('passes the params arg directly to the AggParams constructor', () => { const params = [{ name: 'one' }, { name: 'two' }]; const paramLength = params.length + 2; // json and custom label are always appended - const aggType = new AggType({ - name: 'bucketeer', - title: 'title', - params, - }); + const aggType = new AggType( + { + name: 'bucketeer', + title: 'title', + params, + }, + dependencies + ); expect(Array.isArray(aggType.params)).toBeTruthy(); expect(aggType.params.length).toBe(paramLength); @@ -143,11 +167,14 @@ describe('AggType Class', () => { } as unknown) as IAggConfig; }); - it('returns the formatter for the aggConfig', () => { - const aggType = new AggType({ - name: 'name', - title: 'title', - }); + test('returns the formatter for the aggConfig', () => { + const aggType = new AggType( + { + name: 'name', + title: 'title', + }, + dependencies + ); field = { format: 'format', @@ -156,16 +183,14 @@ describe('AggType Class', () => { expect(aggType.getFormat(aggConfig)).toBe('format'); }); - it('returns default formatter', () => { - setFieldFormats({ - ...dataPluginMock.createStartContract().fieldFormats, - getDefaultInstance: jest.fn(() => 'default') as any, - }); - - const aggType = new AggType({ - name: 'name', - title: 'title', - }); + test('returns default formatter', () => { + const aggType = new AggType( + { + name: 'name', + title: 'title', + }, + dependencies + ); field = undefined; diff --git a/src/plugins/data/public/search/aggs/agg_type.ts b/src/plugins/data/public/search/aggs/agg_type.ts index a63d01e196612..70c116d560c6f 100644 --- a/src/plugins/data/public/search/aggs/agg_type.ts +++ b/src/plugins/data/public/search/aggs/agg_type.ts @@ -28,7 +28,7 @@ import { BaseParamType } from './param_types/base'; import { AggParamType } from './param_types/agg'; import { KBN_FIELD_TYPES, IFieldFormat } from '../../../common'; import { ISearchSource } from '../search_source'; -import { getFieldFormats } from '../../../public/services'; +import { GetInternalStartServicesFn } from '../../types'; export interface AggTypeConfig< TAggConfig extends AggConfig = AggConfig, @@ -60,16 +60,13 @@ export interface AggTypeConfig< getKey?: (bucket: any, key: any, agg: TAggConfig) => any; } -const getFormat = (agg: AggConfig) => { - const field = agg.getField(); - const fieldFormatsService = getFieldFormats(); - - return field ? field.format : fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.STRING); -}; - // TODO need to make a more explicit interface for this export type IAggType = AggType; +export interface AggTypeDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + export class AggType< TAggConfig extends AggConfig = AggConfig, TParam extends AggParamType = AggParamType @@ -215,7 +212,10 @@ export class AggType< * @private * @param {object} config - used to set the properties of the AggType */ - constructor(config: AggTypeConfig) { + constructor( + config: AggTypeConfig, + { getInternalStartServices }: AggTypeDependencies + ) { this.name = config.name; this.type = config.type || 'metrics'; this.dslName = config.dslName || config.name; @@ -251,14 +251,22 @@ export class AggType< }); } - this.params = initParams(params); + this.params = initParams(params, { getInternalStartServices }); } this.getRequestAggs = config.getRequestAggs || noop; this.getResponseAggs = config.getResponseAggs || (() => {}); this.decorateAggConfig = config.decorateAggConfig || (() => ({})); this.postFlightRequest = config.postFlightRequest || identity; - this.getFormat = config.getFormat || getFormat; + + this.getFormat = + config.getFormat || + ((agg: TAggConfig) => { + const field = agg.getField(); + const { fieldFormats } = getInternalStartServices(); + + return field ? field.format : fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.STRING); + }); this.getValue = config.getValue || ((agg: TAggConfig, bucket: any) => {}); } } diff --git a/src/plugins/data/public/search/aggs/agg_types.ts b/src/plugins/data/public/search/aggs/agg_types.ts index 556f6b0c93c41..4b154c338d48c 100644 --- a/src/plugins/data/public/search/aggs/agg_types.ts +++ b/src/plugins/data/public/search/aggs/agg_types.ts @@ -17,83 +17,89 @@ * under the License. */ -import { IUiSettingsClient, NotificationsSetup } from 'src/core/public'; +import { IUiSettingsClient } from 'src/core/public'; import { QuerySetup } from '../../query/query_service'; -import { countMetricAgg } from './metrics/count'; -import { avgMetricAgg } from './metrics/avg'; -import { sumMetricAgg } from './metrics/sum'; -import { medianMetricAgg } from './metrics/median'; -import { minMetricAgg } from './metrics/min'; -import { maxMetricAgg } from './metrics/max'; -import { topHitMetricAgg } from './metrics/top_hit'; -import { stdDeviationMetricAgg } from './metrics/std_deviation'; -import { cardinalityMetricAgg } from './metrics/cardinality'; -import { percentilesMetricAgg } from './metrics/percentiles'; -import { geoBoundsMetricAgg } from './metrics/geo_bounds'; -import { geoCentroidMetricAgg } from './metrics/geo_centroid'; -import { percentileRanksMetricAgg } from './metrics/percentile_ranks'; -import { derivativeMetricAgg } from './metrics/derivative'; -import { cumulativeSumMetricAgg } from './metrics/cumulative_sum'; -import { movingAvgMetricAgg } from './metrics/moving_avg'; -import { serialDiffMetricAgg } from './metrics/serial_diff'; +import { getCountMetricAgg } from './metrics/count'; +import { getAvgMetricAgg } from './metrics/avg'; +import { getSumMetricAgg } from './metrics/sum'; +import { getMedianMetricAgg } from './metrics/median'; +import { getMinMetricAgg } from './metrics/min'; +import { getMaxMetricAgg } from './metrics/max'; +import { getTopHitMetricAgg } from './metrics/top_hit'; +import { getStdDeviationMetricAgg } from './metrics/std_deviation'; +import { getCardinalityMetricAgg } from './metrics/cardinality'; +import { getPercentilesMetricAgg } from './metrics/percentiles'; +import { getGeoBoundsMetricAgg } from './metrics/geo_bounds'; +import { getGeoCentroidMetricAgg } from './metrics/geo_centroid'; +import { getPercentileRanksMetricAgg } from './metrics/percentile_ranks'; +import { getDerivativeMetricAgg } from './metrics/derivative'; +import { getCumulativeSumMetricAgg } from './metrics/cumulative_sum'; +import { getMovingAvgMetricAgg } from './metrics/moving_avg'; +import { getSerialDiffMetricAgg } from './metrics/serial_diff'; import { getDateHistogramBucketAgg } from './buckets/date_histogram'; import { getHistogramBucketAgg } from './buckets/histogram'; -import { rangeBucketAgg } from './buckets/range'; +import { getRangeBucketAgg } from './buckets/range'; import { getDateRangeBucketAgg } from './buckets/date_range'; -import { ipRangeBucketAgg } from './buckets/ip_range'; -import { termsBucketAgg } from './buckets/terms'; -import { filterBucketAgg } from './buckets/filter'; +import { getIpRangeBucketAgg } from './buckets/ip_range'; +import { getTermsBucketAgg } from './buckets/terms'; +import { getFilterBucketAgg } from './buckets/filter'; import { getFiltersBucketAgg } from './buckets/filters'; -import { significantTermsBucketAgg } from './buckets/significant_terms'; -import { geoHashBucketAgg } from './buckets/geo_hash'; -import { geoTileBucketAgg } from './buckets/geo_tile'; -import { bucketSumMetricAgg } from './metrics/bucket_sum'; -import { bucketAvgMetricAgg } from './metrics/bucket_avg'; -import { bucketMinMetricAgg } from './metrics/bucket_min'; -import { bucketMaxMetricAgg } from './metrics/bucket_max'; +import { getSignificantTermsBucketAgg } from './buckets/significant_terms'; +import { getGeoHashBucketAgg } from './buckets/geo_hash'; +import { getGeoTitleBucketAgg } from './buckets/geo_tile'; +import { getBucketSumMetricAgg } from './metrics/bucket_sum'; +import { getBucketAvgMetricAgg } from './metrics/bucket_avg'; +import { getBucketMinMetricAgg } from './metrics/bucket_min'; +import { getBucketMaxMetricAgg } from './metrics/bucket_max'; + +import { GetInternalStartServicesFn } from '../../types'; export interface AggTypesDependencies { - notifications: NotificationsSetup; uiSettings: IUiSettingsClient; query: QuerySetup; + getInternalStartServices: GetInternalStartServicesFn; } -export const getAggTypes = ({ notifications, uiSettings, query }: AggTypesDependencies) => ({ +export const getAggTypes = ({ + uiSettings, + query, + getInternalStartServices, +}: AggTypesDependencies) => ({ metrics: [ - countMetricAgg, - avgMetricAgg, - sumMetricAgg, - medianMetricAgg, - minMetricAgg, - maxMetricAgg, - stdDeviationMetricAgg, - cardinalityMetricAgg, - percentilesMetricAgg, - percentileRanksMetricAgg, - topHitMetricAgg, - derivativeMetricAgg, - cumulativeSumMetricAgg, - movingAvgMetricAgg, - serialDiffMetricAgg, - bucketAvgMetricAgg, - bucketSumMetricAgg, - bucketMinMetricAgg, - bucketMaxMetricAgg, - geoBoundsMetricAgg, - geoCentroidMetricAgg, + getCountMetricAgg({ getInternalStartServices }), + getAvgMetricAgg({ getInternalStartServices }), + getSumMetricAgg({ getInternalStartServices }), + getMedianMetricAgg({ getInternalStartServices }), + getMinMetricAgg({ getInternalStartServices }), + getMaxMetricAgg({ getInternalStartServices }), + getStdDeviationMetricAgg({ getInternalStartServices }), + getCardinalityMetricAgg({ getInternalStartServices }), + getPercentilesMetricAgg({ getInternalStartServices }), + getPercentileRanksMetricAgg({ getInternalStartServices }), + getTopHitMetricAgg({ getInternalStartServices }), + getDerivativeMetricAgg({ getInternalStartServices }), + getCumulativeSumMetricAgg({ getInternalStartServices }), + getMovingAvgMetricAgg({ getInternalStartServices }), + getSerialDiffMetricAgg({ getInternalStartServices }), + getBucketAvgMetricAgg({ getInternalStartServices }), + getBucketSumMetricAgg({ getInternalStartServices }), + getBucketMinMetricAgg({ getInternalStartServices }), + getBucketMaxMetricAgg({ getInternalStartServices }), + getGeoBoundsMetricAgg({ getInternalStartServices }), + getGeoCentroidMetricAgg({ getInternalStartServices }), ], buckets: [ - getDateHistogramBucketAgg({ uiSettings, query }), - getHistogramBucketAgg({ uiSettings, notifications }), - rangeBucketAgg, - getDateRangeBucketAgg({ uiSettings }), - ipRangeBucketAgg, - termsBucketAgg, - filterBucketAgg, - getFiltersBucketAgg({ uiSettings }), - significantTermsBucketAgg, - geoHashBucketAgg, - geoTileBucketAgg, + getDateHistogramBucketAgg({ uiSettings, query, getInternalStartServices }), + getHistogramBucketAgg({ uiSettings, getInternalStartServices }), + getRangeBucketAgg({ getInternalStartServices }), + getDateRangeBucketAgg({ uiSettings, getInternalStartServices }), + getIpRangeBucketAgg({ getInternalStartServices }), + getTermsBucketAgg({ getInternalStartServices }), + getFilterBucketAgg({ getInternalStartServices }), + getFiltersBucketAgg({ uiSettings, getInternalStartServices }), + getSignificantTermsBucketAgg({ getInternalStartServices }), + getGeoHashBucketAgg({ getInternalStartServices }), + getGeoTitleBucketAgg({ getInternalStartServices }), ], }); diff --git a/src/plugins/data/public/search/aggs/agg_types_registry.test.ts b/src/plugins/data/public/search/aggs/agg_types_registry.test.ts index 405f83e237de8..58d1a07d965e2 100644 --- a/src/plugins/data/public/search/aggs/agg_types_registry.test.ts +++ b/src/plugins/data/public/search/aggs/agg_types_registry.test.ts @@ -22,7 +22,7 @@ import { AggTypesRegistrySetup, AggTypesRegistryStart, } from './agg_types_registry'; -import { BucketAggType } from './buckets/_bucket_agg_type'; +import { BucketAggType } from './buckets/bucket_agg_type'; import { MetricAggType } from './metrics/metric_agg_type'; const bucketType = { name: 'terms', type: 'bucket' } as BucketAggType; diff --git a/src/plugins/data/public/search/aggs/agg_types_registry.ts b/src/plugins/data/public/search/aggs/agg_types_registry.ts index 8a8746106ae58..5a0c58120d810 100644 --- a/src/plugins/data/public/search/aggs/agg_types_registry.ts +++ b/src/plugins/data/public/search/aggs/agg_types_registry.ts @@ -17,7 +17,7 @@ * under the License. */ -import { BucketAggType } from './buckets/_bucket_agg_type'; +import { BucketAggType } from './buckets/bucket_agg_type'; import { MetricAggType } from './metrics/metric_agg_type'; export type AggTypesRegistrySetup = ReturnType; diff --git a/src/plugins/data/public/search/aggs/buckets/_interval_options.ts b/src/plugins/data/public/search/aggs/buckets/_interval_options.ts index 393d3b745250f..1c4c04c40a5c1 100644 --- a/src/plugins/data/public/search/aggs/buckets/_interval_options.ts +++ b/src/plugins/data/public/search/aggs/buckets/_interval_options.ts @@ -18,7 +18,7 @@ */ import { i18n } from '@kbn/i18n'; -import { IBucketAggConfig } from './_bucket_agg_type'; +import { IBucketAggConfig } from './bucket_agg_type'; export const intervalOptions = [ { diff --git a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts index 9e4b93035384f..c664325a168b1 100644 --- a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts @@ -24,8 +24,8 @@ import { } from './_terms_other_bucket_helper'; import { AggConfigs, CreateAggConfigParams } from '../agg_configs'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { IBucketAggConfig } from './_bucket_agg_type'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; +import { IBucketAggConfig } from './bucket_agg_type'; +import { mockAggTypesRegistry } from '../test_helpers'; const indexPattern = { id: '1234', @@ -223,10 +223,6 @@ describe('Terms Agg Other bucket helper', () => { return new AggConfigs(indexPattern, [...aggs], { typesRegistry }); }; - beforeEach(() => { - mockDataServices(); - }); - describe('buildOtherBucketAgg', () => { test('returns a function', () => { const aggConfigs = getAggConfigs(singleTerm.aggs); diff --git a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts index 4fd988e7b7e66..abda6b5fc5980 100644 --- a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts +++ b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.ts @@ -21,7 +21,7 @@ import { isNumber, keys, values, find, each, cloneDeep, flatten } from 'lodash'; import { buildExistsFilter, buildPhrasesFilter, buildQueryFromFilters } from '../../../../common'; import { AggGroupNames } from '../agg_groups'; import { IAggConfigs } from '../agg_configs'; -import { IBucketAggConfig } from './_bucket_agg_type'; +import { IBucketAggConfig } from './bucket_agg_type'; /** * walks the aggregation DSL and returns DSL starting at aggregation with id of startFromAggId diff --git a/src/plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts b/src/plugins/data/public/search/aggs/buckets/bucket_agg_type.ts similarity index 86% rename from src/plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts rename to src/plugins/data/public/search/aggs/buckets/bucket_agg_type.ts index 03629c3189cbb..f3c95b444dee9 100644 --- a/src/plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts +++ b/src/plugins/data/public/search/aggs/buckets/bucket_agg_type.ts @@ -21,6 +21,7 @@ import { IAggConfig } from '../agg_config'; import { KBN_FIELD_TYPES } from '../../../../common'; import { AggType, AggTypeConfig } from '../agg_type'; import { AggParamType } from '../param_types/agg'; +import { GetInternalStartServicesFn } from '../../../types'; export interface IBucketAggConfig extends IAggConfig { type: InstanceType; @@ -39,6 +40,10 @@ interface BucketAggTypeConfig getKey?: (bucket: any, key: any, agg: IAggConfig) => any; } +interface BucketAggTypeDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + export class BucketAggType extends AggType< TBucketAggConfig, BucketAggParam @@ -46,8 +51,11 @@ export class BucketAggType any; type = bucketType; - constructor(config: BucketAggTypeConfig) { - super(config); + constructor( + config: BucketAggTypeConfig, + dependencies: BucketAggTypeDependencies + ) { + super(config, dependencies); this.getKey = config.getKey || diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts index def354c4557cb..97c940b4ff4b1 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts @@ -29,8 +29,9 @@ import { } from '../date_histogram'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { RangeFilter } from '../../../../../common'; -import { coreMock } from '../../../../../../../core/public/mocks'; +import { coreMock, notificationServiceMock } from '../../../../../../../core/public/mocks'; import { queryServiceMock } from '../../../../query/mocks'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; describe('AggConfig Filters', () => { describe('date_histogram', () => { @@ -46,6 +47,10 @@ describe('AggConfig Filters', () => { aggTypesDependencies = { uiSettings, query: queryServiceMock.createSetupContract(), + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }; mockDataServices(); @@ -90,7 +95,7 @@ describe('AggConfig Filters', () => { filter = createFilterDateHistogram(agg, bucketKey); }; - it('creates a valid range filter', () => { + test('creates a valid range filter', () => { init(); expect(filter).toHaveProperty('range'); @@ -110,7 +115,7 @@ describe('AggConfig Filters', () => { expect(filter.meta).toHaveProperty('index', '1234'); }); - it('extends the filter edge to 1ms before the next bucket for all interval options', () => { + test('extends the filter edge to 1ms before the next bucket for all interval options', () => { intervalOptions.forEach(option => { let duration; if (option.val !== 'custom' && moment(1, option.val).isValid()) { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts index 6a03176959a83..8c0466b769a7e 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts @@ -25,8 +25,9 @@ import { DateFormat } from '../../../../field_formats'; import { AggConfigs } from '../../agg_configs'; import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { IBucketAggConfig } from '../_bucket_agg_type'; -import { coreMock } from '../../../../../../../core/public/mocks'; +import { IBucketAggConfig } from '../bucket_agg_type'; +import { coreMock, notificationServiceMock } from '../../../../../../../core/public/mocks'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; describe('AggConfig Filters', () => { describe('Date range', () => { @@ -37,6 +38,10 @@ describe('AggConfig Filters', () => { aggTypesDependencies = { uiSettings, + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }; }); @@ -71,7 +76,7 @@ describe('AggConfig Filters', () => { ); }; - it('should return a range filter for date_range agg', () => { + test('should return a range filter for date_range agg', () => { const aggConfigs = getAggConfigs(); const from = new Date('1 Feb 2015'); const to = new Date('7 Feb 2015'); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.ts index 9bfded0ce9729..118e9b26e87d5 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.ts @@ -18,7 +18,7 @@ */ import moment from 'moment'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { DateRangeKey } from '../lib/date_range'; import { buildRangeFilter, RangeFilterParams } from '../../../../../common'; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts index 32ada8d57c768..f5a0b5a7b9094 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts @@ -21,8 +21,9 @@ import { getFiltersBucketAgg, FiltersBucketAggDependencies } from '../filters'; import { createFilterFilters } from './filters'; import { AggConfigs } from '../../agg_configs'; import { mockAggTypesRegistry } from '../../test_helpers'; -import { IBucketAggConfig } from '../_bucket_agg_type'; -import { coreMock } from '../../../../../../../core/public/mocks'; +import { IBucketAggConfig } from '../bucket_agg_type'; +import { coreMock, notificationServiceMock } from '../../../../../../../core/public/mocks'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; describe('AggConfig Filters', () => { describe('filters', () => { @@ -33,6 +34,10 @@ describe('AggConfig Filters', () => { aggTypesDependencies = { uiSettings, + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }; }); @@ -67,7 +72,8 @@ describe('AggConfig Filters', () => { { typesRegistry: mockAggTypesRegistry([getFiltersBucketAgg(aggTypesDependencies)]) } ); }; - it('should return a filters filter', () => { + + test('should return a filters filter', () => { const aggConfigs = getAggConfigs(); const filter = createFilterFilters(aggConfigs.aggs[0] as IBucketAggConfig, 'type:nginx'); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts index 3b568d805f7c0..1999b759a23d0 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.ts @@ -18,7 +18,7 @@ */ import { get } from 'lodash'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { buildQueryFilter } from '../../../../../common'; export const createFilterFilters = (aggConfig: IBucketAggConfig, key: string) => { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts index dc8414d80c024..18b388be74877 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts @@ -19,19 +19,13 @@ import { createFilterHistogram } from './histogram'; import { AggConfigs } from '../../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; +import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { BytesFormat, FieldFormatsGetConfigFn } from '../../../../../common'; describe('AggConfig Filters', () => { describe('histogram', () => { - beforeEach(() => { - mockDataServices(); - }); - - const typesRegistry = mockAggTypesRegistry(); - const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -61,11 +55,11 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry() } ); }; - it('should return an range filter for histogram', () => { + test('should return an range filter for histogram', () => { const aggConfigs = getAggConfigs(); const filter = createFilterHistogram(aggConfigs.aggs[0] as IBucketAggConfig, '2048'); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts index d4c00a0991fe2..f8e7747d49147 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { buildRangeFilter, RangeFilterParams } from '../../../../../common'; export const createFilterHistogram = (aggConfig: IBucketAggConfig, key: string) => { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts index ca51094da2f58..b528313b080d0 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts @@ -17,17 +17,26 @@ * under the License. */ -import { ipRangeBucketAgg } from '../ip_range'; +import { getIpRangeBucketAgg } from '../ip_range'; import { createFilterIpRange } from './ip_range'; import { AggConfigs, CreateAggConfigParams } from '../../agg_configs'; import { mockAggTypesRegistry } from '../../test_helpers'; import { IpFormat } from '../../../../../common'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../core/public/mocks'; describe('AggConfig Filters', () => { describe('IP range', () => { - const typesRegistry = mockAggTypesRegistry([ipRangeBucketAgg]); + const typesRegistry = mockAggTypesRegistry([ + getIpRangeBucketAgg({ + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }), + ]); const getAggConfigs = (aggs: CreateAggConfigParams[]) => { const field = { name: 'ip', @@ -46,7 +55,7 @@ describe('AggConfig Filters', () => { return new AggConfigs(indexPattern, aggs, { typesRegistry }); }; - it('should return a range filter for ip_range agg', () => { + test('should return a range filter for ip_range agg', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.IP_RANGE, @@ -75,7 +84,7 @@ describe('AggConfig Filters', () => { expect(filter.range.ip).toHaveProperty('lte', '1.1.1.1'); }); - it('should return a range filter for ip_range agg using a CIDR mask', () => { + test('should return a range filter for ip_range agg using a CIDR mask', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.IP_RANGE, diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts index 2d34c45aaab9d..aae212783b873 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts @@ -18,7 +18,7 @@ */ import { CidrMask } from '../lib/cidr_mask'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { IpRangeKey } from '../lib/ip_range'; import { buildRangeFilter, RangeFilterParams } from '../../../../../common'; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts index 3a6f8b36a9d96..14a7538aa95a4 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts @@ -17,22 +17,31 @@ * under the License. */ -import { rangeBucketAgg } from '../range'; +import { getRangeBucketAgg, RangeBucketAggDependencies } from '../range'; import { createFilterRange } from './range'; import { BytesFormat, FieldFormatsGetConfigFn } from '../../../../../common'; import { AggConfigs } from '../../agg_configs'; import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../core/public/mocks'; describe('AggConfig Filters', () => { describe('range', () => { + let aggTypesDependencies: RangeBucketAggDependencies; + beforeEach(() => { + aggTypesDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + mockDataServices(); }); - const typesRegistry = mockAggTypesRegistry([rangeBucketAgg]); - const getConfig = (() => {}) as FieldFormatsGetConfigFn; const getAggConfigs = () => { const field = { @@ -62,11 +71,11 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry } + { typesRegistry: mockAggTypesRegistry([getRangeBucketAgg(aggTypesDependencies)]) } ); }; - it('should return a range filter for range agg', () => { + test('should return a range filter for range agg', () => { const aggConfigs = getAggConfigs(); const filter = createFilterRange(aggConfigs.aggs[0] as IBucketAggConfig, { gte: 1024, diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts index d3d85f2441a8b..cbad8742bfab6 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { buildRangeFilter } from '../../../../../common'; export const createFilterRange = (aggConfig: IBucketAggConfig, params: any) => { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts index 511af450b0113..c11a7d1a4e6b8 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts @@ -17,17 +17,30 @@ * under the License. */ -import { termsBucketAgg } from '../terms'; +import { getTermsBucketAgg } from '../terms'; import { createFilterTerms } from './terms'; import { AggConfigs, CreateAggConfigParams } from '../../agg_configs'; import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { Filter, ExistsFilter } from '../../../../../common'; +import { RangeBucketAggDependencies } from '../range'; +import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../core/public/mocks'; describe('AggConfig Filters', () => { describe('terms', () => { - const typesRegistry = mockAggTypesRegistry([termsBucketAgg]); + let aggTypesDependencies: RangeBucketAggDependencies; + + beforeEach(() => { + aggTypesDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + }); + const getAggConfigs = (aggs: CreateAggConfigParams[]) => { const indexPattern = { id: '1234', @@ -43,10 +56,12 @@ describe('AggConfig Filters', () => { indexPattern, }; - return new AggConfigs(indexPattern, aggs, { typesRegistry }); + return new AggConfigs(indexPattern, aggs, { + typesRegistry: mockAggTypesRegistry([getTermsBucketAgg(aggTypesDependencies)]), + }); }; - it('should return a match_phrase filter for terms', () => { + test('should return a match_phrase filter for terms', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); @@ -65,7 +80,7 @@ describe('AggConfig Filters', () => { expect(filter.meta).toHaveProperty('index', '1234'); }); - it('should set query to true or false for boolean filter', () => { + test('should set query to true or false for boolean filter', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); @@ -93,7 +108,7 @@ describe('AggConfig Filters', () => { expect(filterTrue.query.match_phrase.field).toBeTruthy(); }); - it('should generate correct __missing__ filter', () => { + test('should generate correct __missing__ filter', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); @@ -110,7 +125,7 @@ describe('AggConfig Filters', () => { expect(filter.meta).toHaveProperty('negate', true); }); - it('should generate correct __other__ filter', () => { + test('should generate correct __other__ filter', () => { const aggConfigs = getAggConfigs([ { type: BUCKET_TYPES.TERMS, schema: 'segment', params: { field: 'field' } }, ]); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.ts index 43ebfc0e90db2..95de19b96abd4 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IBucketAggConfig } from '../_bucket_agg_type'; +import { IBucketAggConfig } from '../bucket_agg_type'; import { buildPhrasesFilter, buildExistsFilter, diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts index 7701f1bbcb4d0..e6fd259fabc92 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { IUiSettingsClient } from 'src/core/public'; import { TimeBuckets } from './lib/time_buckets'; -import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { createFilterDateHistogram } from './create_filter/date_histogram'; import { intervalOptions } from './_interval_options'; @@ -33,8 +33,8 @@ import { isMetricAggType } from '../metrics/metric_agg_type'; import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES } from '../../../../common'; import { TimefilterContract } from '../../../query'; -import { getFieldFormats } from '../../../../public/services'; import { QuerySetup } from '../../../query/query_service'; +import { GetInternalStartServicesFn } from '../../../types'; const detectedTimezone = moment.tz.guess(); const tzOffset = moment().format('Z'); @@ -61,6 +61,7 @@ interface ITimeBuckets { export interface DateHistogramBucketAggDependencies { uiSettings: IUiSettingsClient; query: QuerySetup; + getInternalStartServices: GetInternalStartServicesFn; } export interface IBucketDateHistogramAggConfig extends IBucketAggConfig { @@ -74,211 +75,218 @@ export function isDateHistogramBucketAggConfig(agg: any): agg is IBucketDateHist export const getDateHistogramBucketAgg = ({ uiSettings, query, + getInternalStartServices, }: DateHistogramBucketAggDependencies) => - new BucketAggType({ - name: BUCKET_TYPES.DATE_HISTOGRAM, - title: i18n.translate('data.search.aggs.buckets.dateHistogramTitle', { - defaultMessage: 'Date Histogram', - }), - ordered: { - date: true, - }, - makeLabel(agg) { - let output: Record = {}; + new BucketAggType( + { + name: BUCKET_TYPES.DATE_HISTOGRAM, + title: i18n.translate('data.search.aggs.buckets.dateHistogramTitle', { + defaultMessage: 'Date Histogram', + }), + ordered: { + date: true, + }, + makeLabel(agg) { + let output: Record = {}; - if (this.params) { - output = writeParams(this.params, agg); - } + if (this.params) { + output = writeParams(this.params, agg); + } - const field = agg.getFieldDisplayName(); - return i18n.translate('data.search.aggs.buckets.dateHistogramLabel', { - defaultMessage: '{fieldName} per {intervalDescription}', - values: { - fieldName: field, - intervalDescription: output.metricScaleText || output.bucketInterval.description, - }, - }); - }, - createFilter: createFilterDateHistogram, - decorateAggConfig() { - let buckets: any; + const field = agg.getFieldDisplayName(); + return i18n.translate('data.search.aggs.buckets.dateHistogramLabel', { + defaultMessage: '{fieldName} per {intervalDescription}', + values: { + fieldName: field, + intervalDescription: output.metricScaleText || output.bucketInterval.description, + }, + }); + }, + createFilter: createFilterDateHistogram, + decorateAggConfig() { + let buckets: any; - return { - buckets: { - configurable: true, - get() { - if (buckets) return buckets; + return { + buckets: { + configurable: true, + get() { + if (buckets) return buckets; - const { timefilter } = query.timefilter; - buckets = new TimeBuckets({ uiSettings }); - updateTimeBuckets(this, timefilter, buckets); + const { timefilter } = query.timefilter; + buckets = new TimeBuckets({ uiSettings }); + updateTimeBuckets(this, timefilter, buckets); - return buckets; - }, - } as any, - }; - }, - getFormat(agg) { - const DateFieldFormat = getFieldFormats().getType(FIELD_FORMAT_IDS.DATE); + return buckets; + }, + } as any, + }; + }, + getFormat(agg) { + const { fieldFormats } = getInternalStartServices(); + const DateFieldFormat = fieldFormats.getType(FIELD_FORMAT_IDS.DATE); - if (!DateFieldFormat) { - throw new Error('Unable to retrieve Date Field Format'); - } + if (!DateFieldFormat) { + throw new Error('Unable to retrieve Date Field Format'); + } - return new DateFieldFormat( + return new DateFieldFormat( + { + pattern: agg.buckets.getScaledDateFormat(), + }, + (key: string) => uiSettings.get(key) + ); + }, + params: [ { - pattern: agg.buckets.getScaledDateFormat(), + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.DATE, + default(agg: IBucketDateHistogramAggConfig) { + return agg.getIndexPattern().timeFieldName; + }, + onChange(agg: IBucketDateHistogramAggConfig) { + if (get(agg, 'params.interval') === 'auto' && !agg.fieldIsTimeField()) { + delete agg.params.interval; + } + }, }, - (key: string) => uiSettings.get(key) - ); - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.DATE, - default(agg: IBucketDateHistogramAggConfig) { - return agg.getIndexPattern().timeFieldName; + { + name: 'timeRange', + default: null, + write: noop, }, - onChange(agg: IBucketDateHistogramAggConfig) { - if (get(agg, 'params.interval') === 'auto' && !agg.fieldIsTimeField()) { - delete agg.params.interval; - } + { + name: 'useNormalizedEsInterval', + default: true, + write: noop, }, - }, - { - name: 'timeRange', - default: null, - write: noop, - }, - { - name: 'useNormalizedEsInterval', - default: true, - write: noop, - }, - { - name: 'scaleMetricValues', - default: false, - write: noop, - advanced: true, - }, - { - name: 'interval', - deserialize(state: any, agg) { - // For upgrading from 7.0.x to 7.1.x - intervals are now stored as key of options or custom value - if (state === 'custom') { - return get(agg, 'params.customInterval'); - } + { + name: 'scaleMetricValues', + default: false, + write: noop, + advanced: true, + }, + { + name: 'interval', + deserialize(state: any, agg) { + // For upgrading from 7.0.x to 7.1.x - intervals are now stored as key of options or custom value + if (state === 'custom') { + return get(agg, 'params.customInterval'); + } - const interval = find(intervalOptions, { val: state }); + const interval = find(intervalOptions, { val: state }); - // For upgrading from 4.0.x to 4.1.x - intervals are now stored as 'y' instead of 'year', - // but this maps the old values to the new values - if (!interval && state === 'year') { - return 'y'; - } - return state; - }, - default: 'auto', - options: intervalOptions, - write(agg, output, aggs) { - const { timefilter } = query.timefilter; - updateTimeBuckets(agg, timefilter); + // For upgrading from 4.0.x to 4.1.x - intervals are now stored as 'y' instead of 'year', + // but this maps the old values to the new values + if (!interval && state === 'year') { + return 'y'; + } + return state; + }, + default: 'auto', + options: intervalOptions, + write(agg, output, aggs) { + const { timefilter } = query.timefilter; + updateTimeBuckets(agg, timefilter); - const { useNormalizedEsInterval, scaleMetricValues } = agg.params; - const interval = agg.buckets.getInterval(useNormalizedEsInterval); - output.bucketInterval = interval; - if (interval.expression === '0ms') { - // We are hitting this code a couple of times while configuring in editor - // with an interval of 0ms because the overall time range has not yet been - // set. Since 0ms is not a valid ES interval, we cannot pass it through dateHistogramInterval - // below, since it would throw an exception. So in the cases we still have an interval of 0ms - // here we simply skip the rest of the method and never write an interval into the DSL, since - // this DSL will anyway not be used before we're passing this code with an actual interval. - return; - } - output.params = { - ...output.params, - ...dateHistogramInterval(interval.expression), - }; + const { useNormalizedEsInterval, scaleMetricValues } = agg.params; + const interval = agg.buckets.getInterval(useNormalizedEsInterval); + output.bucketInterval = interval; + if (interval.expression === '0ms') { + // We are hitting this code a couple of times while configuring in editor + // with an interval of 0ms because the overall time range has not yet been + // set. Since 0ms is not a valid ES interval, we cannot pass it through dateHistogramInterval + // below, since it would throw an exception. So in the cases we still have an interval of 0ms + // here we simply skip the rest of the method and never write an interval into the DSL, since + // this DSL will anyway not be used before we're passing this code with an actual interval. + return; + } + output.params = { + ...output.params, + ...dateHistogramInterval(interval.expression), + }; - const scaleMetrics = scaleMetricValues && interval.scaled && interval.scale < 1; - if (scaleMetrics && aggs) { - const metrics = aggs.aggs.filter(a => isMetricAggType(a.type)); - const all = every(metrics, (a: IBucketAggConfig) => { - const { type } = a; + const scaleMetrics = scaleMetricValues && interval.scaled && interval.scale < 1; + if (scaleMetrics && aggs) { + const metrics = aggs.aggs.filter(a => isMetricAggType(a.type)); + const all = every(metrics, (a: IBucketAggConfig) => { + const { type } = a; - if (isMetricAggType(type)) { - return type.isScalable(); + if (isMetricAggType(type)) { + return type.isScalable(); + } + }); + if (all) { + output.metricScale = interval.scale; + output.metricScaleText = interval.preScaled.description; } - }); - if (all) { - output.metricScale = interval.scale; - output.metricScaleText = interval.preScaled.description; } - } + }, }, - }, - { - name: 'time_zone', - default: undefined, - // We don't ever want this parameter to be serialized out (when saving or to URLs) - // since we do all the logic handling it "on the fly" in the `write` method, to prevent - // time_zones being persisted into saved_objects - serialize: noop, - write(agg, output) { - // If a time_zone has been set explicitly always prefer this. - let tz = agg.params.time_zone; - if (!tz && agg.params.field) { - // If a field has been configured check the index pattern's typeMeta if a date_histogram on that - // field requires a specific time_zone - tz = get(agg.getIndexPattern(), [ - 'typeMeta', - 'aggs', - 'date_histogram', - agg.params.field.name, - 'time_zone', - ]); - } - if (!tz) { - // If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz - const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz'); - tz = isDefaultTimezone ? detectedTimezone || tzOffset : uiSettings.get('dateFormat:tz'); - } - output.params.time_zone = tz; + { + name: 'time_zone', + default: undefined, + // We don't ever want this parameter to be serialized out (when saving or to URLs) + // since we do all the logic handling it "on the fly" in the `write` method, to prevent + // time_zones being persisted into saved_objects + serialize: noop, + write(agg, output) { + // If a time_zone has been set explicitly always prefer this. + let tz = agg.params.time_zone; + if (!tz && agg.params.field) { + // If a field has been configured check the index pattern's typeMeta if a date_histogram on that + // field requires a specific time_zone + tz = get(agg.getIndexPattern(), [ + 'typeMeta', + 'aggs', + 'date_histogram', + agg.params.field.name, + 'time_zone', + ]); + } + if (!tz) { + // If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz + const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz'); + tz = isDefaultTimezone + ? detectedTimezone || tzOffset + : uiSettings.get('dateFormat:tz'); + } + output.params.time_zone = tz; + }, }, - }, - { - name: 'drop_partials', - default: false, - write: noop, - shouldShow: agg => { - const field = agg.params.field; - return field && field.name && field.name === agg.getIndexPattern().timeFieldName; + { + name: 'drop_partials', + default: false, + write: noop, + shouldShow: agg => { + const field = agg.params.field; + return field && field.name && field.name === agg.getIndexPattern().timeFieldName; + }, }, - }, - { - name: 'format', - }, - { - name: 'min_doc_count', - default: 1, - }, - { - name: 'extended_bounds', - default: {}, - write(agg, output) { - const val = agg.params.extended_bounds; + { + name: 'format', + }, + { + name: 'min_doc_count', + default: 1, + }, + { + name: 'extended_bounds', + default: {}, + write(agg, output) { + const val = agg.params.extended_bounds; - if (val.min != null || val.max != null) { - output.params.extended_bounds = { - min: moment(val.min).valueOf(), - max: moment(val.max).valueOf(), - }; + if (val.min != null || val.max != null) { + output.params.extended_bounds = { + min: moment(val.min).valueOf(), + max: moment(val.max).valueOf(), + }; - return; - } + return; + } + }, }, - }, - ], - }); + ], + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts index 4ea550492fa09..c050620c3a856 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts @@ -17,11 +17,12 @@ * under the License. */ -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, notificationServiceMock } from '../../../../../../../src/core/public/mocks'; import { getDateRangeBucketAgg, DateRangeBucketAggDependencies } from './date_range'; import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; describe('date_range params', () => { let aggTypesDependencies: DateRangeBucketAggDependencies; @@ -31,6 +32,10 @@ describe('date_range params', () => { aggTypesDependencies = { uiSettings, + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }; }); diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.ts b/src/plugins/data/public/search/aggs/buckets/date_range.ts index 8133a47ec7248..07d927e64a943 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.ts @@ -23,12 +23,12 @@ import { i18n } from '@kbn/i18n'; import { IUiSettingsClient } from 'src/core/public'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { createFilterDateRange } from './create_filter/date_range'; import { convertDateRangeToString, DateRangeKey } from './lib/date_range'; import { KBN_FIELD_TYPES, FieldFormat, TEXT_CONTEXT_TYPE } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; const dateRangeTitle = i18n.translate('data.search.aggs.buckets.dateRangeTitle', { defaultMessage: 'Date Range', @@ -36,76 +36,85 @@ const dateRangeTitle = i18n.translate('data.search.aggs.buckets.dateRangeTitle', export interface DateRangeBucketAggDependencies { uiSettings: IUiSettingsClient; + getInternalStartServices: GetInternalStartServicesFn; } -export const getDateRangeBucketAgg = ({ uiSettings }: DateRangeBucketAggDependencies) => - new BucketAggType({ - name: BUCKET_TYPES.DATE_RANGE, - title: dateRangeTitle, - createFilter: createFilterDateRange, - getKey({ from, to }): DateRangeKey { - return { from, to }; - }, - getFormat(agg) { - const fieldFormatsService = getFieldFormats(); +export const getDateRangeBucketAgg = ({ + uiSettings, + getInternalStartServices, +}: DateRangeBucketAggDependencies) => + new BucketAggType( + { + name: BUCKET_TYPES.DATE_RANGE, + title: dateRangeTitle, + createFilter: createFilterDateRange, + getKey({ from, to }): DateRangeKey { + return { from, to }; + }, + getFormat(agg) { + const { fieldFormats } = getInternalStartServices(); - const formatter = agg.fieldOwnFormatter( - TEXT_CONTEXT_TYPE, - fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.DATE) - ); - const DateRangeFormat = FieldFormat.from(function(range: DateRangeKey) { - return convertDateRangeToString(range, formatter); - }); - return new DateRangeFormat(); - }, - makeLabel(aggConfig) { - return aggConfig.getFieldDisplayName() + ' date ranges'; - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.DATE, - default(agg: IBucketAggConfig) { - return agg.getIndexPattern().timeFieldName; - }, + const formatter = agg.fieldOwnFormatter( + TEXT_CONTEXT_TYPE, + fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.DATE) + ); + const DateRangeFormat = FieldFormat.from(function(range: DateRangeKey) { + return convertDateRangeToString(range, formatter); + }); + return new DateRangeFormat(); }, - { - name: 'ranges', - default: [ - { - from: 'now-1w/w', - to: 'now', - }, - ], + makeLabel(aggConfig) { + return aggConfig.getFieldDisplayName() + ' date ranges'; }, - { - name: 'time_zone', - default: undefined, - // Implimentation method is the same as that of date_histogram - serialize: () => undefined, - write: (agg, output) => { - const field = agg.getParam('field'); - let tz = agg.getParam('time_zone'); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.DATE, + default(agg: IBucketAggConfig) { + return agg.getIndexPattern().timeFieldName; + }, + }, + { + name: 'ranges', + default: [ + { + from: 'now-1w/w', + to: 'now', + }, + ], + }, + { + name: 'time_zone', + default: undefined, + // Implimentation method is the same as that of date_histogram + serialize: () => undefined, + write: (agg, output) => { + const field = agg.getParam('field'); + let tz = agg.getParam('time_zone'); - if (!tz && field) { - tz = get(agg.getIndexPattern(), [ - 'typeMeta', - 'aggs', - 'date_range', - field.name, - 'time_zone', - ]); - } - if (!tz) { - const detectedTimezone = moment.tz.guess(); - const tzOffset = moment().format('Z'); - const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz'); + if (!tz && field) { + tz = get(agg.getIndexPattern(), [ + 'typeMeta', + 'aggs', + 'date_range', + field.name, + 'time_zone', + ]); + } + if (!tz) { + const detectedTimezone = moment.tz.guess(); + const tzOffset = moment().format('Z'); + const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz'); - tz = isDefaultTimezone ? detectedTimezone || tzOffset : uiSettings.get('dateFormat:tz'); - } - output.params.time_zone = tz; + tz = isDefaultTimezone + ? detectedTimezone || tzOffset + : uiSettings.get('dateFormat:tz'); + } + output.params.time_zone = tz; + }, }, - }, - ], - }); + ], + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/filter.ts b/src/plugins/data/public/search/aggs/buckets/filter.ts index 80efc0cf92071..accbdf4dd783d 100644 --- a/src/plugins/data/public/search/aggs/buckets/filter.ts +++ b/src/plugins/data/public/search/aggs/buckets/filter.ts @@ -18,19 +18,28 @@ */ import { i18n } from '@kbn/i18n'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; const filterTitle = i18n.translate('data.search.aggs.buckets.filterTitle', { defaultMessage: 'Filter', }); -export const filterBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.FILTER, - title: filterTitle, - params: [ +export interface FilterBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getFilterBucketAgg = ({ getInternalStartServices }: FilterBucketAggDependencies) => + new BucketAggType( { - name: 'geo_bounding_box', + name: BUCKET_TYPES.FILTER, + title: filterTitle, + params: [ + { + name: 'geo_bounding_box', + }, + ], }, - ], -}); + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/filters.ts b/src/plugins/data/public/search/aggs/buckets/filters.ts index 8b9aca87f8735..a42cb70a62b7d 100644 --- a/src/plugins/data/public/search/aggs/buckets/filters.ts +++ b/src/plugins/data/public/search/aggs/buckets/filters.ts @@ -23,11 +23,12 @@ import { IUiSettingsClient } from 'src/core/public'; import { createFilterFilters } from './create_filter/filters'; import { toAngularJSON } from '../utils'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { Storage } from '../../../../../../plugins/kibana_utils/public'; import { getEsQueryConfig, buildEsQuery, Query } from '../../../../common'; import { getQueryLog } from '../../../query'; +import { GetInternalStartServicesFn } from '../../../types'; const filtersTitle = i18n.translate('data.search.aggs.buckets.filtersTitle', { defaultMessage: 'Filters', @@ -43,69 +44,81 @@ interface FilterValue { export interface FiltersBucketAggDependencies { uiSettings: IUiSettingsClient; + getInternalStartServices: GetInternalStartServicesFn; } -export const getFiltersBucketAgg = ({ uiSettings }: FiltersBucketAggDependencies) => - new BucketAggType({ - name: BUCKET_TYPES.FILTERS, - title: filtersTitle, - createFilter: createFilterFilters, - customLabels: false, - params: [ - { - name: 'filters', - default: [ - { input: { query: '', language: uiSettings.get('search:queryLanguage') }, label: '' }, - ], - write(aggConfig, output) { - const inFilters: FilterValue[] = aggConfig.params.filters; - if (!size(inFilters)) return; +export const getFiltersBucketAgg = ({ + uiSettings, + getInternalStartServices, +}: FiltersBucketAggDependencies) => + new BucketAggType( + { + name: BUCKET_TYPES.FILTERS, + title: filtersTitle, + createFilter: createFilterFilters, + customLabels: false, + params: [ + { + name: 'filters', + default: [ + { input: { query: '', language: uiSettings.get('search:queryLanguage') }, label: '' }, + ], + write(aggConfig, output) { + const inFilters: FilterValue[] = aggConfig.params.filters; + if (!size(inFilters)) return; - inFilters.forEach(filter => { - const persistedLog = getQueryLog( - uiSettings, - new Storage(window.localStorage), - 'vis_default_editor', - filter.input.language - ); - persistedLog.add(filter.input.query); - }); + inFilters.forEach(filter => { + const persistedLog = getQueryLog( + uiSettings, + new Storage(window.localStorage), + 'vis_default_editor', + filter.input.language + ); + persistedLog.add(filter.input.query); + }); - const outFilters = transform( - inFilters, - function(filters, filter) { - const input = cloneDeep(filter.input); + const outFilters = transform( + inFilters, + function(filters, filter) { + const input = cloneDeep(filter.input); - if (!input) { - console.log('malformed filter agg params, missing "input" query'); // eslint-disable-line no-console - return; - } + if (!input) { + console.log('malformed filter agg params, missing "input" query'); // eslint-disable-line no-console + return; + } - const esQueryConfigs = getEsQueryConfig(uiSettings); - const query = buildEsQuery(aggConfig.getIndexPattern(), [input], [], esQueryConfigs); + const esQueryConfigs = getEsQueryConfig(uiSettings); + const query = buildEsQuery( + aggConfig.getIndexPattern(), + [input], + [], + esQueryConfigs + ); - if (!query) { - console.log('malformed filter agg params, missing "query" on input'); // eslint-disable-line no-console - return; - } + if (!query) { + console.log('malformed filter agg params, missing "query" on input'); // eslint-disable-line no-console + return; + } - const matchAllLabel = filter.input.query === '' ? '*' : ''; - const label = - filter.label || - matchAllLabel || - (typeof filter.input.query === 'string' - ? filter.input.query - : toAngularJSON(filter.input.query)); - filters[label] = { query }; - }, - {} - ); + const matchAllLabel = filter.input.query === '' ? '*' : ''; + const label = + filter.label || + matchAllLabel || + (typeof filter.input.query === 'string' + ? filter.input.query + : toAngularJSON(filter.input.query)); + filters[label] = { query }; + }, + {} + ); - if (!size(outFilters)) return; + if (!size(outFilters)) return; - const params = output.params || (output.params = {}); - params.filters = outFilters; + const params = output.params || (output.params = {}); + params.filters = outFilters; + }, }, - }, - ], - }); + ], + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts index 408cdf22bcbc2..24270dd33a576 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts @@ -17,13 +17,29 @@ * under the License. */ -import { geoHashBucketAgg } from './geo_hash'; +import { getGeoHashBucketAgg, GeoHashBucketAggDependencies } from './geo_hash'; import { AggConfigs, IAggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { IBucketAggConfig } from './_bucket_agg_type'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; describe('Geohash Agg', () => { + let aggTypesDependencies: GeoHashBucketAggDependencies; + let geoHashBucketAgg: BucketAggType; + + beforeEach(() => { + aggTypesDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + + geoHashBucketAgg = getGeoHashBucketAgg(aggTypesDependencies); + }); + const getAggConfigs = (params?: Record) => { const indexPattern = { id: '1234', @@ -74,7 +90,7 @@ describe('Geohash Agg', () => { precisionParam = geoHashBucketAgg.params[PRECISION_PARAM_INDEX]; }); - it('should select precision parameter', () => { + test('should select precision parameter', () => { expect(precisionParam.name).toEqual('precision'); }); }); @@ -89,7 +105,7 @@ describe('Geohash Agg', () => { geoHashGridAgg = aggConfigs.aggs[0] as IBucketAggConfig; }); - it('should create filter, geohash_grid, and geo_centroid aggregations', () => { + test('should create filter, geohash_grid, and geo_centroid aggregations', () => { const requestAggs = geoHashBucketAgg.getRequestAggs(geoHashGridAgg) as IBucketAggConfig[]; expect(requestAggs.length).toEqual(3); @@ -101,7 +117,7 @@ describe('Geohash Agg', () => { }); describe('aggregation options', () => { - it('should only create geohash_grid and geo_centroid aggregations when isFilteredByCollar is false', () => { + test('should only create geohash_grid and geo_centroid aggregations when isFilteredByCollar is false', () => { const aggConfigs = getAggConfigs({ isFilteredByCollar: false }); const requestAggs = geoHashBucketAgg.getRequestAggs( aggConfigs.aggs[0] as IBucketAggConfig @@ -112,7 +128,7 @@ describe('Geohash Agg', () => { expect(requestAggs[1].type.name).toEqual('geo_centroid'); }); - it('should only create filter and geohash_grid aggregations when useGeocentroid is false', () => { + test('should only create filter and geohash_grid aggregations when useGeocentroid is false', () => { const aggConfigs = getAggConfigs({ useGeocentroid: false }); const requestAggs = geoHashBucketAgg.getRequestAggs( aggConfigs.aggs[0] as IBucketAggConfig @@ -138,7 +154,7 @@ describe('Geohash Agg', () => { ) as IBucketAggConfig[]; }); - it('should change geo_bounding_box filter aggregation and vis session state when map movement is outside map collar', () => { + test('should change geo_bounding_box filter aggregation and vis session state when map movement is outside map collar', () => { const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs( getAggConfigs({ boundingBox: { @@ -151,7 +167,7 @@ describe('Geohash Agg', () => { expect(originalRequestAggs[1].params).not.toEqual(geoBoxingBox.params); }); - it('should not change geo_bounding_box filter aggregation and vis session state when map movement is within map collar', () => { + test('should not change geo_bounding_box filter aggregation and vis session state when map movement is within map collar', () => { const [, geoBoxingBox] = geoHashBucketAgg.getRequestAggs( getAggConfigs({ boundingBox: { diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash.ts b/src/plugins/data/public/search/aggs/buckets/geo_hash.ts index 3ffec09a84387..eab10edad60f6 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_hash.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_hash.ts @@ -18,9 +18,10 @@ */ import { i18n } from '@kbn/i18n'; -import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { KBN_FIELD_TYPES } from '../../../../common'; import { BUCKET_TYPES } from './bucket_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; const defaultBoundingBox = { top_left: { lat: 1, lon: 1 }, @@ -33,83 +34,91 @@ const geohashGridTitle = i18n.translate('data.search.aggs.buckets.geohashGridTit defaultMessage: 'Geohash', }); -export const geoHashBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.GEOHASH_GRID, - title: geohashGridTitle, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, - }, - { - name: 'autoPrecision', - default: true, - write: () => {}, - }, - { - name: 'precision', - default: defaultPrecision, - write(aggConfig, output) { - output.params.precision = aggConfig.params.precision; - }, - }, - { - name: 'useGeocentroid', - default: true, - write: () => {}, - }, - { - name: 'isFilteredByCollar', - default: true, - write: () => {}, - }, +export interface GeoHashBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getGeoHashBucketAgg = ({ getInternalStartServices }: GeoHashBucketAggDependencies) => + new BucketAggType( { - name: 'boundingBox', - default: null, - write: () => {}, - }, - ], - getRequestAggs(agg) { - const aggs = []; - const params = agg.params; + name: BUCKET_TYPES.GEOHASH_GRID, + title: geohashGridTitle, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + }, + { + name: 'autoPrecision', + default: true, + write: () => {}, + }, + { + name: 'precision', + default: defaultPrecision, + write(aggConfig, output) { + output.params.precision = aggConfig.params.precision; + }, + }, + { + name: 'useGeocentroid', + default: true, + write: () => {}, + }, + { + name: 'isFilteredByCollar', + default: true, + write: () => {}, + }, + { + name: 'boundingBox', + default: null, + write: () => {}, + }, + ], + getRequestAggs(agg) { + const aggs = []; + const params = agg.params; - if (params.isFilteredByCollar && agg.getField()) { - aggs.push( - agg.aggConfigs.createAggConfig( - { - type: 'filter', - id: 'filter_agg', - enabled: true, - params: { - geo_bounding_box: { - ignore_unmapped: true, - [agg.getField().name]: params.boundingBox || defaultBoundingBox, - }, - }, - } as any, - { addToAggConfigs: false } - ) - ); - } + if (params.isFilteredByCollar && agg.getField()) { + aggs.push( + agg.aggConfigs.createAggConfig( + { + type: 'filter', + id: 'filter_agg', + enabled: true, + params: { + geo_bounding_box: { + ignore_unmapped: true, + [agg.getField().name]: params.boundingBox || defaultBoundingBox, + }, + }, + } as any, + { addToAggConfigs: false } + ) + ); + } - aggs.push(agg); + aggs.push(agg); - if (params.useGeocentroid) { - aggs.push( - agg.aggConfigs.createAggConfig( - { - type: 'geo_centroid', - enabled: true, - params: { - field: agg.getField(), - }, - }, - { addToAggConfigs: false } - ) - ); - } + if (params.useGeocentroid) { + aggs.push( + agg.aggConfigs.createAggConfig( + { + type: 'geo_centroid', + enabled: true, + params: { + field: agg.getField(), + }, + }, + { addToAggConfigs: false } + ) + ); + } - return aggs; - }, -}); + return aggs; + }, + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/geo_tile.ts b/src/plugins/data/public/search/aggs/buckets/geo_tile.ts index 759601fc0c180..c981e8400f9a1 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_tile.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_tile.ts @@ -20,53 +20,61 @@ import { i18n } from '@kbn/i18n'; import { noop } from 'lodash'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; -import { IBucketAggConfig } from './_bucket_agg_type'; import { METRIC_TYPES } from '../metrics/metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface GeoTitleBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const geotileGridTitle = i18n.translate('data.search.aggs.buckets.geotileGridTitle', { defaultMessage: 'Geotile', }); -export const geoTileBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.GEOTILE_GRID, - title: geotileGridTitle, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, - }, +export const getGeoTitleBucketAgg = ({ getInternalStartServices }: GeoTitleBucketAggDependencies) => + new BucketAggType( { - name: 'useGeocentroid', - default: true, - write: noop, - }, - { - name: 'precision', - default: 0, - }, - ], - getRequestAggs(agg) { - const aggs = []; - const useGeocentroid = agg.getParam('useGeocentroid'); + name: BUCKET_TYPES.GEOTILE_GRID, + title: geotileGridTitle, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + }, + { + name: 'useGeocentroid', + default: true, + write: noop, + }, + { + name: 'precision', + default: 0, + }, + ], + getRequestAggs(agg) { + const aggs = []; + const useGeocentroid = agg.getParam('useGeocentroid'); - aggs.push(agg); + aggs.push(agg); - if (useGeocentroid) { - const aggConfig = { - type: METRIC_TYPES.GEO_CENTROID, - enabled: true, - params: { - field: agg.getField(), - }, - }; + if (useGeocentroid) { + const aggConfig = { + type: METRIC_TYPES.GEO_CENTROID, + enabled: true, + params: { + field: agg.getField(), + }, + }; - aggs.push(agg.aggConfigs.createAggConfig(aggConfig, { addToAggConfigs: false })); - } + aggs.push(agg.aggConfigs.createAggConfig(aggConfig, { addToAggConfigs: false })); + } - return aggs as IBucketAggConfig[]; - }, -}); + return aggs as IBucketAggConfig[]; + }, + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts index c61b4ff37935a..bbfc263df4268 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, notificationServiceMock } from '../../../../../../../src/core/public/mocks'; import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; @@ -27,17 +27,21 @@ import { AutoBounds, HistogramBucketAggDependencies, } from './histogram'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; describe('Histogram Agg', () => { let aggTypesDependencies: HistogramBucketAggDependencies; beforeEach(() => { - const { uiSettings, notifications } = coreMock.createSetup(); + const { uiSettings } = coreMock.createSetup(); aggTypesDependencies = { uiSettings, - notifications, + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }; }); @@ -87,18 +91,18 @@ describe('Histogram Agg', () => { histogramType = getHistogramBucketAgg(aggTypesDependencies); }); - it('is ordered', () => { + test('is ordered', () => { expect(histogramType.ordered).toBeDefined(); }); - it('is not ordered by date', () => { + test('is not ordered by date', () => { expect(histogramType.ordered).not.toHaveProperty('date'); }); }); describe('params', () => { describe('intervalBase', () => { - it('should not be written to the DSL', () => { + test('should not be written to the DSL', () => { const aggConfigs = getAggConfigs({ intervalBase: 100, field: { @@ -112,7 +116,7 @@ describe('Histogram Agg', () => { }); describe('interval', () => { - it('accepts a whole number', () => { + test('accepts a whole number', () => { const params = getParams({ interval: 100, }); @@ -120,7 +124,7 @@ describe('Histogram Agg', () => { expect(params).toHaveProperty('interval', 100); }); - it('accepts a decimal number', function() { + test('accepts a decimal number', () => { const params = getParams({ interval: 0.1, }); @@ -128,7 +132,7 @@ describe('Histogram Agg', () => { expect(params).toHaveProperty('interval', 0.1); }); - it('accepts a decimal number string', function() { + test('accepts a decimal number string', () => { const params = getParams({ interval: '0.1', }); @@ -136,7 +140,7 @@ describe('Histogram Agg', () => { expect(params).toHaveProperty('interval', 0.1); }); - it('accepts a whole number string', function() { + test('accepts a whole number string', () => { const params = getParams({ interval: '10', }); @@ -144,7 +148,7 @@ describe('Histogram Agg', () => { expect(params).toHaveProperty('interval', 10); }); - it('fails on non-numeric values', function() { + test('fails on non-numeric values', () => { const params = getParams({ interval: [], }); @@ -181,7 +185,7 @@ describe('Histogram Agg', () => { return aggConfig.write(aggConfigs).params; }; - it('will respect the histogram:maxBars setting', () => { + test('will respect the histogram:maxBars setting', () => { const params = getInterval( 5, { interval: 5 }, @@ -194,19 +198,19 @@ describe('Histogram Agg', () => { expect(params).toHaveProperty('interval', 2000); }); - it('will return specified interval, if bars are below histogram:maxBars config', () => { + test('will return specified interval, if bars are below histogram:maxBars config', () => { const params = getInterval(100, { interval: 5 }); expect(params).toHaveProperty('interval', 5); }); - it('will set to intervalBase if interval is below base', () => { + test('will set to intervalBase if interval is below base', () => { const params = getInterval(1000, { interval: 3, intervalBase: 8 }); expect(params).toHaveProperty('interval', 8); }); - it('will round to nearest intervalBase multiple if interval is above base', () => { + test('will round to nearest intervalBase multiple if interval is above base', () => { const roundUp = getInterval(1000, { interval: 46, intervalBase: 10 }); expect(roundUp).toHaveProperty('interval', 50); @@ -214,13 +218,13 @@ describe('Histogram Agg', () => { expect(roundDown).toHaveProperty('interval', 40); }); - it('will not change interval if it is a multiple of base', () => { + test('will not change interval if it is a multiple of base', () => { const output = getInterval(1000, { interval: 35, intervalBase: 5 }); expect(output).toHaveProperty('interval', 35); }); - it('will round to intervalBase after scaling histogram:maxBars', () => { + test('will round to intervalBase after scaling histogram:maxBars', () => { const output = getInterval(100, { interval: 5, intervalBase: 6 }, { min: 0, max: 1000 }); // 100 buckets in 0 to 1000 would result in an interval of 10, so we should @@ -232,7 +236,7 @@ describe('Histogram Agg', () => { describe('min_doc_count', () => { let output: Record; - it('casts true values to 0', () => { + test('casts true values to 0', () => { output = getParams({ min_doc_count: true }); expect(output).toHaveProperty('min_doc_count', 0); @@ -246,7 +250,7 @@ describe('Histogram Agg', () => { expect(output).toHaveProperty('min_doc_count', 0); }); - it('writes 1 for falsy values', () => { + test('writes 1 for falsy values', () => { output = getParams({ min_doc_count: '' }); expect(output).toHaveProperty('min_doc_count', 1); @@ -258,8 +262,8 @@ describe('Histogram Agg', () => { }); }); - describe('extended_bounds', function() { - it('does not write when only eb.min is set', function() { + describe('extended_bounds', () => { + test('does not write when only eb.min is set', () => { const output = getParams({ has_extended_bounds: true, extended_bounds: { min: 0 }, @@ -267,7 +271,7 @@ describe('Histogram Agg', () => { expect(output).not.toHaveProperty('extended_bounds'); }); - it('does not write when only eb.max is set', function() { + test('does not write when only eb.max is set', () => { const output = getParams({ has_extended_bounds: true, extended_bounds: { max: 0 }, @@ -276,7 +280,7 @@ describe('Histogram Agg', () => { expect(output).not.toHaveProperty('extended_bounds'); }); - it('writes when both eb.min and eb.max are set', function() { + test('writes when both eb.min and eb.max are set', () => { const output = getParams({ has_extended_bounds: true, extended_bounds: { min: 99, max: 100 }, @@ -286,7 +290,7 @@ describe('Histogram Agg', () => { expect(output.extended_bounds).toHaveProperty('max', 100); }); - it('does not write when nothing is set', function() { + test('does not write when nothing is set', () => { const output = getParams({ has_extended_bounds: true, extended_bounds: {}, @@ -295,7 +299,7 @@ describe('Histogram Agg', () => { expect(output).not.toHaveProperty('extended_bounds'); }); - it('does not write when has_extended_bounds is false', function() { + test('does not write when has_extended_bounds is false', () => { const output = getParams({ has_extended_bounds: false, extended_bounds: { min: 99, max: 100 }, diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.ts b/src/plugins/data/public/search/aggs/buckets/histogram.ts index bbffc0912bf0d..f8e8720d24ea9 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.ts @@ -19,12 +19,13 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { IUiSettingsClient, NotificationsSetup } from 'src/core/public'; +import { IUiSettingsClient } from 'src/core/public'; -import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { createFilterHistogram } from './create_filter/histogram'; import { BUCKET_TYPES } from './bucket_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; export interface AutoBounds { min: number; @@ -33,7 +34,7 @@ export interface AutoBounds { export interface HistogramBucketAggDependencies { uiSettings: IUiSettingsClient; - notifications: NotificationsSetup; + getInternalStartServices: GetInternalStartServicesFn; } export interface IBucketHistogramAggConfig extends IBucketAggConfig { @@ -43,164 +44,167 @@ export interface IBucketHistogramAggConfig extends IBucketAggConfig { export const getHistogramBucketAgg = ({ uiSettings, - notifications, + getInternalStartServices, }: HistogramBucketAggDependencies) => - new BucketAggType({ - name: BUCKET_TYPES.HISTOGRAM, - title: i18n.translate('data.search.aggs.buckets.histogramTitle', { - defaultMessage: 'Histogram', - }), - ordered: {}, - makeLabel(aggConfig) { - return aggConfig.getFieldDisplayName(); - }, - createFilter: createFilterHistogram, - decorateAggConfig() { - let autoBounds: AutoBounds; - - return { - setAutoBounds: { - configurable: true, - value(newValue: AutoBounds) { - autoBounds = newValue; + new BucketAggType( + { + name: BUCKET_TYPES.HISTOGRAM, + title: i18n.translate('data.search.aggs.buckets.histogramTitle', { + defaultMessage: 'Histogram', + }), + ordered: {}, + makeLabel(aggConfig) { + return aggConfig.getFieldDisplayName(); + }, + createFilter: createFilterHistogram, + decorateAggConfig() { + let autoBounds: AutoBounds; + + return { + setAutoBounds: { + configurable: true, + value(newValue: AutoBounds) { + autoBounds = newValue; + }, }, - }, - getAutoBounds: { - configurable: true, - value() { - return autoBounds; + getAutoBounds: { + configurable: true, + value() { + return autoBounds; + }, }, - }, - }; - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.NUMBER, - }, - { - /* - * This parameter can be set if you want the auto scaled interval to always - * be a multiple of a specific base. - */ - name: 'intervalBase', - default: null, - write: () => {}, + }; }, - { - name: 'interval', - modifyAggConfigOnSearchRequestStart( - aggConfig: IBucketHistogramAggConfig, - searchSource: any, - options: any - ) { - const field = aggConfig.getField(); - const aggBody = field.scripted - ? { script: { source: field.script, lang: field.lang } } - : { field: field.name }; - - const childSearchSource = searchSource - .createChild() - .setField('size', 0) - .setField('aggs', { - maxAgg: { - max: aggBody, - }, - minAgg: { - min: aggBody, - }, - }); - - return childSearchSource - .fetch(options) - .then((resp: any) => { - aggConfig.setAutoBounds({ - min: get(resp, 'aggregations.minAgg.value'), - max: get(resp, 'aggregations.maxAgg.value'), - }); - }) - .catch((e: Error) => { - if (e.name === 'AbortError') return; - notifications.toasts.addWarning( - i18n.translate('data.search.aggs.histogram.missingMaxMinValuesWarning', { - defaultMessage: - 'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.', - }) - ); - }); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + }, + { + /* + * This parameter can be set if you want the auto scaled interval to always + * be a multiple of a specific base. + */ + name: 'intervalBase', + default: null, + write: () => {}, }, - write(aggConfig, output) { - let interval = parseFloat(aggConfig.params.interval); - if (interval <= 0) { - interval = 1; - } - const autoBounds = aggConfig.getAutoBounds(); - - // ensure interval does not create too many buckets and crash browser - if (autoBounds) { - const range = autoBounds.max - autoBounds.min; - const bars = range / interval; - - if (bars > uiSettings.get('histogram:maxBars')) { - const minInterval = range / uiSettings.get('histogram:maxBars'); - - // Round interval by order of magnitude to provide clean intervals - // Always round interval up so there will always be less buckets than histogram:maxBars - const orderOfMagnitude = Math.pow(10, Math.floor(Math.log10(minInterval))); - let roundInterval = orderOfMagnitude; - - while (roundInterval < minInterval) { - roundInterval += orderOfMagnitude; + { + name: 'interval', + modifyAggConfigOnSearchRequestStart( + aggConfig: IBucketHistogramAggConfig, + searchSource: any, + options: any + ) { + const field = aggConfig.getField(); + const aggBody = field.scripted + ? { script: { source: field.script, lang: field.lang } } + : { field: field.name }; + + const childSearchSource = searchSource + .createChild() + .setField('size', 0) + .setField('aggs', { + maxAgg: { + max: aggBody, + }, + minAgg: { + min: aggBody, + }, + }); + + return childSearchSource + .fetch(options) + .then((resp: any) => { + aggConfig.setAutoBounds({ + min: get(resp, 'aggregations.minAgg.value'), + max: get(resp, 'aggregations.maxAgg.value'), + }); + }) + .catch((e: Error) => { + if (e.name === 'AbortError') return; + getInternalStartServices().notifications.toasts.addWarning( + i18n.translate('data.search.aggs.histogram.missingMaxMinValuesWarning', { + defaultMessage: + 'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.', + }) + ); + }); + }, + write(aggConfig, output) { + let interval = parseFloat(aggConfig.params.interval); + if (interval <= 0) { + interval = 1; + } + const autoBounds = aggConfig.getAutoBounds(); + + // ensure interval does not create too many buckets and crash browser + if (autoBounds) { + const range = autoBounds.max - autoBounds.min; + const bars = range / interval; + + if (bars > uiSettings.get('histogram:maxBars')) { + const minInterval = range / uiSettings.get('histogram:maxBars'); + + // Round interval by order of magnitude to provide clean intervals + // Always round interval up so there will always be less buckets than histogram:maxBars + const orderOfMagnitude = Math.pow(10, Math.floor(Math.log10(minInterval))); + let roundInterval = orderOfMagnitude; + + while (roundInterval < minInterval) { + roundInterval += orderOfMagnitude; + } + interval = roundInterval; } - interval = roundInterval; } - } - const base = aggConfig.params.intervalBase; - - if (base) { - if (interval < base) { - // In case the specified interval is below the base, just increase it to it's base - interval = base; - } else if (interval % base !== 0) { - // In case the interval is not a multiple of the base round it to the next base - interval = Math.round(interval / base) * base; + const base = aggConfig.params.intervalBase; + + if (base) { + if (interval < base) { + // In case the specified interval is below the base, just increase it to it's base + interval = base; + } else if (interval % base !== 0) { + // In case the interval is not a multiple of the base round it to the next base + interval = Math.round(interval / base) * base; + } } - } - output.params.interval = interval; + output.params.interval = interval; + }, }, - }, - { - name: 'min_doc_count', - default: false, - write(aggConfig, output) { - if (aggConfig.params.min_doc_count) { - output.params.min_doc_count = 0; - } else { - output.params.min_doc_count = 1; - } + { + name: 'min_doc_count', + default: false, + write(aggConfig, output) { + if (aggConfig.params.min_doc_count) { + output.params.min_doc_count = 0; + } else { + output.params.min_doc_count = 1; + } + }, }, - }, - { - name: 'has_extended_bounds', - default: false, - write: () => {}, - }, - { - name: 'extended_bounds', - default: { - min: '', - max: '', + { + name: 'has_extended_bounds', + default: false, + write: () => {}, }, - write(aggConfig, output) { - const { min, max } = aggConfig.params.extended_bounds; + { + name: 'extended_bounds', + default: { + min: '', + max: '', + }, + write(aggConfig, output) { + const { min, max } = aggConfig.params.extended_bounds; - if (aggConfig.params.has_extended_bounds && (min || min === 0) && (max || max === 0)) { - output.params.extended_bounds = { min, max }; - } + if (aggConfig.params.has_extended_bounds && (min || min === 0) && (max || max === 0)) { + output.params.extended_bounds = { min, max }; + } + }, + shouldShow: (aggConfig: IBucketAggConfig) => aggConfig.params.has_extended_bounds, }, - shouldShow: (aggConfig: IBucketAggConfig) => aggConfig.params.has_extended_bounds, - }, - ], - }); + ], + }, + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/ip_range.ts b/src/plugins/data/public/search/aggs/buckets/ip_range.ts index da6866d40a52f..bde347d6e673d 100644 --- a/src/plugins/data/public/search/aggs/buckets/ip_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/ip_range.ts @@ -19,77 +19,85 @@ import { noop, map, omit, isNull } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { createFilterIpRange } from './create_filter/ip_range'; import { IpRangeKey, convertIPRangeToString } from './lib/ip_range'; import { KBN_FIELD_TYPES, FieldFormat, TEXT_CONTEXT_TYPE } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; const ipRangeTitle = i18n.translate('data.search.aggs.buckets.ipRangeTitle', { defaultMessage: 'IPv4 Range', }); -export const ipRangeBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.IP_RANGE, - title: ipRangeTitle, - createFilter: createFilterIpRange, - getKey(bucket, key, agg): IpRangeKey { - if (agg.params.ipRangeType === 'mask') { - return { type: 'mask', mask: key }; - } - return { type: 'range', from: bucket.from, to: bucket.to }; - }, - getFormat(agg) { - const fieldFormatsService = getFieldFormats(); - const formatter = agg.fieldOwnFormatter( - TEXT_CONTEXT_TYPE, - fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.IP) - ); - const IpRangeFormat = FieldFormat.from(function(range: IpRangeKey) { - return convertIPRangeToString(range, formatter); - }); - return new IpRangeFormat(); - }, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.buckets.ipRangeLabel', { - defaultMessage: '{fieldName} IP ranges', - values: { - fieldName: aggConfig.getFieldDisplayName(), - }, - }); - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.IP, - }, - { - name: 'ipRangeType', - default: 'fromTo', - write: noop, - }, +export interface IpRangeBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getIpRangeBucketAgg = ({ getInternalStartServices }: IpRangeBucketAggDependencies) => + new BucketAggType( { - name: 'ranges', - default: { - fromTo: [ - { from: '0.0.0.0', to: '127.255.255.255' }, - { from: '128.0.0.0', to: '191.255.255.255' }, - ], - mask: [{ mask: '0.0.0.0/1' }, { mask: '128.0.0.0/2' }], + name: BUCKET_TYPES.IP_RANGE, + title: ipRangeTitle, + createFilter: createFilterIpRange, + getKey(bucket, key, agg): IpRangeKey { + if (agg.params.ipRangeType === 'mask') { + return { type: 'mask', mask: key }; + } + return { type: 'range', from: bucket.from, to: bucket.to }; + }, + getFormat(agg) { + const { fieldFormats } = getInternalStartServices(); + const formatter = agg.fieldOwnFormatter( + TEXT_CONTEXT_TYPE, + fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.IP) + ); + const IpRangeFormat = FieldFormat.from(function(range: IpRangeKey) { + return convertIPRangeToString(range, formatter); + }); + return new IpRangeFormat(); + }, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.buckets.ipRangeLabel', { + defaultMessage: '{fieldName} IP ranges', + values: { + fieldName: aggConfig.getFieldDisplayName(), + }, + }); }, - write(aggConfig, output) { - const ipRangeType = aggConfig.params.ipRangeType; - let ranges = aggConfig.params.ranges[ipRangeType]; + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.IP, + }, + { + name: 'ipRangeType', + default: 'fromTo', + write: noop, + }, + { + name: 'ranges', + default: { + fromTo: [ + { from: '0.0.0.0', to: '127.255.255.255' }, + { from: '128.0.0.0', to: '191.255.255.255' }, + ], + mask: [{ mask: '0.0.0.0/1' }, { mask: '128.0.0.0/2' }], + }, + write(aggConfig, output) { + const ipRangeType = aggConfig.params.ipRangeType; + let ranges = aggConfig.params.ranges[ipRangeType]; - if (ipRangeType === 'fromTo') { - ranges = map(ranges, (range: any) => omit(range, isNull)); - } + if (ipRangeType === 'fromTo') { + ranges = map(ranges, (range: any) => omit(range, isNull)); + } - output.params.ranges = ranges; - }, + output.params.ranges = ranges; + }, + }, + ], }, - ], -}); + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts b/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts index d94477b588f8d..0beeb1c372275 100644 --- a/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts +++ b/src/plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts @@ -18,7 +18,7 @@ */ import { isString, isObject } from 'lodash'; -import { IBucketAggConfig, BucketAggType, BucketAggParam } from './_bucket_agg_type'; +import { IBucketAggConfig, BucketAggType, BucketAggParam } from './bucket_agg_type'; import { IAggConfig } from '../agg_config'; export const isType = (type: string) => { diff --git a/src/plugins/data/public/search/aggs/buckets/range.test.ts b/src/plugins/data/public/search/aggs/buckets/range.test.ts index bf3711543ae88..a1f0ab6a2326a 100644 --- a/src/plugins/data/public/search/aggs/buckets/range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/range.test.ts @@ -17,11 +17,13 @@ * under the License. */ -import { rangeBucketAgg } from './range'; +import { getRangeBucketAgg, RangeBucketAggDependencies } from './range'; import { AggConfigs } from '../agg_configs'; import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; import { FieldFormatsGetConfigFn, NumberFormat } from '../../../../common'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; const buckets = [ { @@ -44,7 +46,16 @@ const buckets = [ ]; describe('Range Agg', () => { + let aggTypesDependencies: RangeBucketAggDependencies; + beforeEach(() => { + aggTypesDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + mockDataServices(); }); @@ -84,15 +95,14 @@ describe('Range Agg', () => { }, }, ], - { typesRegistry: mockAggTypesRegistry([rangeBucketAgg]) } + { typesRegistry: mockAggTypesRegistry([getRangeBucketAgg(aggTypesDependencies)]) } ); }; describe('formating', () => { - it('formats bucket keys properly', () => { + test('formats bucket keys properly', () => { const aggConfigs = getAggConfigs(); const agg = aggConfigs.aggs[0]; - const format = (val: any) => agg.fieldFormatter()(agg.getKey(val)); expect(format(buckets[0])).toBe('≥ -∞ and < 1 KB'); diff --git a/src/plugins/data/public/search/aggs/buckets/range.ts b/src/plugins/data/public/search/aggs/buckets/range.ts index 036a0d4c1e8da..2c1303814a88a 100644 --- a/src/plugins/data/public/search/aggs/buckets/range.ts +++ b/src/plugins/data/public/search/aggs/buckets/range.ts @@ -18,11 +18,12 @@ */ import { i18n } from '@kbn/i18n'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; import { FieldFormat, KBN_FIELD_TYPES } from '../../../../common'; import { RangeKey } from './range_key'; import { createFilterRange } from './create_filter/range'; import { BUCKET_TYPES } from './bucket_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; const keyCaches = new WeakMap(); const formats = new WeakMap(); @@ -31,76 +32,84 @@ const rangeTitle = i18n.translate('data.search.aggs.buckets.rangeTitle', { defaultMessage: 'Range', }); -export const rangeBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.RANGE, - title: rangeTitle, - createFilter: createFilterRange, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.aggTypesLabel', { - defaultMessage: '{fieldName} ranges', - values: { - fieldName: aggConfig.getFieldDisplayName(), +export interface RangeBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getRangeBucketAgg = ({ getInternalStartServices }: RangeBucketAggDependencies) => + new BucketAggType( + { + name: BUCKET_TYPES.RANGE, + title: rangeTitle, + createFilter: createFilterRange, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.aggTypesLabel', { + defaultMessage: '{fieldName} ranges', + values: { + fieldName: aggConfig.getFieldDisplayName(), + }, + }); }, - }); - }, - getKey(bucket, key, agg) { - let keys = keyCaches.get(agg); + getKey(bucket, key, agg) { + let keys = keyCaches.get(agg); - if (!keys) { - keys = new Map(); - keyCaches.set(agg, keys); - } + if (!keys) { + keys = new Map(); + keyCaches.set(agg, keys); + } - const id = RangeKey.idBucket(bucket); + const id = RangeKey.idBucket(bucket); - key = keys.get(id); - if (!key) { - key = new RangeKey(bucket); - keys.set(id, key); - } + key = keys.get(id); + if (!key) { + key = new RangeKey(bucket); + keys.set(id, key); + } - return key; - }, - getFormat(agg) { - let aggFormat = formats.get(agg); - if (aggFormat) return aggFormat; + return key; + }, + getFormat(agg) { + let aggFormat = formats.get(agg); + if (aggFormat) return aggFormat; - const RangeFormat = FieldFormat.from((range: any) => { - const format = agg.fieldOwnFormatter(); - const gte = '\u2265'; - const lt = '\u003c'; - return i18n.translate('data.search.aggs.aggTypes.rangesFormatMessage', { - defaultMessage: '{gte} {from} and {lt} {to}', - values: { - gte, - from: format(range.gte), - lt, - to: format(range.lt), - }, - }); - }); + const RangeFormat = FieldFormat.from((range: any) => { + const format = agg.fieldOwnFormatter(); + const gte = '\u2265'; + const lt = '\u003c'; + return i18n.translate('data.search.aggs.aggTypes.rangesFormatMessage', { + defaultMessage: '{gte} {from} and {lt} {to}', + values: { + gte, + from: format(range.gte), + lt, + to: format(range.lt), + }, + }); + }); - aggFormat = new RangeFormat(); + aggFormat = new RangeFormat(); - formats.set(agg, aggFormat); - return aggFormat; - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER], - }, - { - name: 'ranges', - default: [ - { from: 0, to: 1000 }, - { from: 1000, to: 2000 }, - ], - write(aggConfig, output) { - output.params.ranges = aggConfig.params.ranges; - output.params.keyed = true; + formats.set(agg, aggFormat); + return aggFormat; }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [KBN_FIELD_TYPES.NUMBER], + }, + { + name: 'ranges', + default: [ + { from: 0, to: 1000 }, + { from: 1000, to: 2000 }, + ], + write(aggConfig, output) { + output.params.ranges = aggConfig.params.ranges; + output.params.keyed = true; + }, + }, + ], }, - ], -}); + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts index 1c221126c35e0..761d0ced6a114 100644 --- a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts @@ -20,12 +20,27 @@ import { AggConfigs, IAggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { significantTermsBucketAgg } from './significant_terms'; -import { IBucketAggConfig } from './_bucket_agg_type'; +import { + getSignificantTermsBucketAgg, + SignificantTermsBucketAggDependencies, +} from './significant_terms'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('Significant Terms Agg', () => { describe('order agg editor UI', () => { describe('convert include/exclude from old format', () => { + let aggTypesDependencies: SignificantTermsBucketAggDependencies; + + beforeEach(() => { + aggTypesDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + }); + const getAggConfigs = (params: Record = {}) => { const indexPattern = { id: '1234', @@ -51,7 +66,11 @@ describe('Significant Terms Agg', () => { params, }, ], - { typesRegistry: mockAggTypesRegistry([significantTermsBucketAgg]) } + { + typesRegistry: mockAggTypesRegistry([ + getSignificantTermsBucketAgg(aggTypesDependencies), + ]), + } ); }; @@ -64,19 +83,19 @@ describe('Significant Terms Agg', () => { expect(params.exclude).toBe('400'); }; - it('should generate correct label', () => { + test('should generate correct label', () => { const aggConfigs = getAggConfigs({ size: 'SIZE', field: { name: 'FIELD', }, }); - const label = significantTermsBucketAgg.makeLabel(aggConfigs.aggs[0] as IBucketAggConfig); + const label = aggConfigs.aggs[0].makeLabel(); expect(label).toBe('Top SIZE unusual terms in FIELD'); }); - it('should doesnt do anything with string type', () => { + test('should doesnt do anything with string type', () => { const aggConfigs = getAggConfigs({ include: '404', exclude: '400', @@ -89,7 +108,7 @@ describe('Significant Terms Agg', () => { testSerializeAndWrite(aggConfigs); }); - it('should converts object to string type', () => { + test('should converts object to string type', () => { const aggConfigs = getAggConfigs({ include: { pattern: '404', diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms.ts b/src/plugins/data/public/search/aggs/buckets/significant_terms.ts index f12ebe58e2de2..49d797f3afbc9 100644 --- a/src/plugins/data/public/search/aggs/buckets/significant_terms.ts +++ b/src/plugins/data/public/search/aggs/buckets/significant_terms.ts @@ -18,59 +18,72 @@ */ import { i18n } from '@kbn/i18n'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType } from './bucket_agg_type'; import { createFilterTerms } from './create_filter/terms'; import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format'; import { BUCKET_TYPES } from './bucket_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const significantTermsTitle = i18n.translate('data.search.aggs.buckets.significantTermsTitle', { defaultMessage: 'Significant Terms', }); -export const significantTermsBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.SIGNIFICANT_TERMS, - title: significantTermsTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.buckets.significantTermsLabel', { - defaultMessage: 'Top {size} unusual terms in {fieldName}', - values: { - size: aggConfig.params.size, - fieldName: aggConfig.getFieldDisplayName(), - }, - }); - }, - createFilter: createFilterTerms, - params: [ - { - name: 'field', - type: 'field', - scriptable: false, - filterFieldTypes: KBN_FIELD_TYPES.STRING, - }, - { - name: 'size', - default: '', - }, +export interface SignificantTermsBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getSignificantTermsBucketAgg = ({ + getInternalStartServices, +}: SignificantTermsBucketAggDependencies) => + new BucketAggType( { - name: 'exclude', - displayName: i18n.translate('data.search.aggs.buckets.significantTerms.excludeLabel', { - defaultMessage: 'Exclude', - }), - type: 'string', - advanced: true, - shouldShow: isStringType, - ...migrateIncludeExcludeFormat, + name: BUCKET_TYPES.SIGNIFICANT_TERMS, + title: significantTermsTitle, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.buckets.significantTermsLabel', { + defaultMessage: 'Top {size} unusual terms in {fieldName}', + values: { + size: aggConfig.params.size, + fieldName: aggConfig.getFieldDisplayName(), + }, + }); + }, + createFilter: createFilterTerms, + params: [ + { + name: 'field', + type: 'field', + scriptable: false, + filterFieldTypes: KBN_FIELD_TYPES.STRING, + }, + { + name: 'size', + default: '', + }, + { + name: 'exclude', + displayName: i18n.translate('data.search.aggs.buckets.significantTerms.excludeLabel', { + defaultMessage: 'Exclude', + }), + type: 'string', + advanced: true, + shouldShow: isStringType, + ...migrateIncludeExcludeFormat, + }, + { + name: 'include', + displayName: i18n.translate('data.search.aggs.buckets.significantTerms.includeLabel', { + defaultMessage: 'Include', + }), + type: 'string', + advanced: true, + shouldShow: isStringType, + ...migrateIncludeExcludeFormat, + }, + ], }, { - name: 'include', - displayName: i18n.translate('data.search.aggs.buckets.significantTerms.includeLabel', { - defaultMessage: 'Include', - }), - type: 'string', - advanced: true, - shouldShow: isStringType, - ...migrateIncludeExcludeFormat, - }, - ], -}); + getInternalStartServices, + } + ); diff --git a/src/plugins/data/public/search/aggs/buckets/terms.test.ts b/src/plugins/data/public/search/aggs/buckets/terms.test.ts index 280d78f6620bd..5afe7d0b0c35c 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.test.ts @@ -51,7 +51,7 @@ describe('Terms Agg', () => { ); }; - it('converts object to string type', function() { + test('converts object to string type', () => { const aggConfigs = getAggConfigs({ include: { pattern: '404', diff --git a/src/plugins/data/public/search/aggs/buckets/terms.ts b/src/plugins/data/public/search/aggs/buckets/terms.ts index 813c657934a76..5baa38af0e8d6 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.ts @@ -19,9 +19,8 @@ import { noop } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { BucketAggType } from './_bucket_agg_type'; +import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { IBucketAggConfig } from './_bucket_agg_type'; import { createFilterTerms } from './create_filter/terms'; import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format'; import { IAggConfigs } from '../agg_configs'; @@ -36,6 +35,7 @@ import { mergeOtherBucketAggResponse, updateMissingBucket, } from './_terms_other_bucket_helper'; +import { GetInternalStartServicesFn } from '../../../types'; export const termsAggFilter = [ '!top_hits', @@ -56,220 +56,230 @@ const termsTitle = i18n.translate('data.search.aggs.buckets.termsTitle', { defaultMessage: 'Terms', }); -export const termsBucketAgg = new BucketAggType({ - name: BUCKET_TYPES.TERMS, - title: termsTitle, - makeLabel(agg) { - const params = agg.params; - return agg.getFieldDisplayName() + ': ' + params.order.text; - }, - getFormat(bucket): IFieldFormat { - return { - getConverterFor: (type: FieldFormatsContentType) => { - return (val: any) => { - if (val === '__other__') { - return bucket.params.otherBucketLabel; - } - if (val === '__missing__') { - return bucket.params.missingBucketLabel; - } +export interface TermsBucketAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} - return bucket.params.field.format.convert(val, type); - }; +export const getTermsBucketAgg = ({ getInternalStartServices }: TermsBucketAggDependencies) => + new BucketAggType( + { + name: BUCKET_TYPES.TERMS, + title: termsTitle, + makeLabel(agg) { + const params = agg.params; + return agg.getFieldDisplayName() + ': ' + params.order.text; }, - } as IFieldFormat; - }, - createFilter: createFilterTerms, - postFlightRequest: async ( - resp: any, - aggConfigs: IAggConfigs, - aggConfig: IBucketAggConfig, - searchSource: ISearchSource, - inspectorAdapters: Adapters, - abortSignal?: AbortSignal - ) => { - if (!resp.aggregations) return resp; - const nestedSearchSource = searchSource.createChild(); - if (aggConfig.params.otherBucket) { - const filterAgg = buildOtherBucketAgg(aggConfigs, aggConfig, resp); - if (!filterAgg) return resp; + getFormat(bucket): IFieldFormat { + return { + getConverterFor: (type: FieldFormatsContentType) => { + return (val: any) => { + if (val === '__other__') { + return bucket.params.otherBucketLabel; + } + if (val === '__missing__') { + return bucket.params.missingBucketLabel; + } - nestedSearchSource.setField('aggs', filterAgg); + return bucket.params.field.format.convert(val, type); + }; + }, + } as IFieldFormat; + }, + createFilter: createFilterTerms, + postFlightRequest: async ( + resp: any, + aggConfigs: IAggConfigs, + aggConfig: IBucketAggConfig, + searchSource: ISearchSource, + inspectorAdapters: Adapters, + abortSignal?: AbortSignal + ) => { + if (!resp.aggregations) return resp; + const nestedSearchSource = searchSource.createChild(); + if (aggConfig.params.otherBucket) { + const filterAgg = buildOtherBucketAgg(aggConfigs, aggConfig, resp); + if (!filterAgg) return resp; - const request = inspectorAdapters.requests.start( - i18n.translate('data.search.aggs.buckets.terms.otherBucketTitle', { - defaultMessage: 'Other bucket', - }), - { - description: i18n.translate('data.search.aggs.buckets.terms.otherBucketDescription', { - defaultMessage: - 'This request counts the number of documents that fall ' + - 'outside the criterion of the data buckets.', - }), - } - ); - nestedSearchSource.getSearchRequestBody().then((body: string) => { - request.json(body); - }); - request.stats(getRequestInspectorStats(nestedSearchSource)); + nestedSearchSource.setField('aggs', filterAgg); - const response = await nestedSearchSource.fetch({ abortSignal }); - request.stats(getResponseInspectorStats(nestedSearchSource, response)).ok({ json: response }); - resp = mergeOtherBucketAggResponse(aggConfigs, resp, response, aggConfig, filterAgg()); - } - if (aggConfig.params.missingBucket) { - resp = updateMissingBucket(resp, aggConfigs, aggConfig); - } - return resp; - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: [ - KBN_FIELD_TYPES.NUMBER, - KBN_FIELD_TYPES.BOOLEAN, - KBN_FIELD_TYPES.DATE, - KBN_FIELD_TYPES.IP, - KBN_FIELD_TYPES.STRING, - ], - }, - { - name: 'orderBy', - write: noop, // prevent default write, it's handled by orderAgg - }, - { - name: 'orderAgg', - type: 'agg', - allowedAggs: termsAggFilter, - default: null, - makeAgg(termsAgg, state) { - state = state || {}; - state.schema = 'orderAgg'; - const orderAgg = termsAgg.aggConfigs.createAggConfig(state, { - addToAggConfigs: false, - }); - orderAgg.id = termsAgg.id + '-orderAgg'; + const request = inspectorAdapters.requests.start( + i18n.translate('data.search.aggs.buckets.terms.otherBucketTitle', { + defaultMessage: 'Other bucket', + }), + { + description: i18n.translate('data.search.aggs.buckets.terms.otherBucketDescription', { + defaultMessage: + 'This request counts the number of documents that fall ' + + 'outside the criterion of the data buckets.', + }), + } + ); + nestedSearchSource.getSearchRequestBody().then((body: string) => { + request.json(body); + }); + request.stats(getRequestInspectorStats(nestedSearchSource)); - return orderAgg; + const response = await nestedSearchSource.fetch({ abortSignal }); + request + .stats(getResponseInspectorStats(nestedSearchSource, response)) + .ok({ json: response }); + resp = mergeOtherBucketAggResponse(aggConfigs, resp, response, aggConfig, filterAgg()); + } + if (aggConfig.params.missingBucket) { + resp = updateMissingBucket(resp, aggConfigs, aggConfig); + } + return resp; }, - write(agg, output, aggs) { - const dir = agg.params.order.value; - const order: Record = (output.params.order = {}); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [ + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.BOOLEAN, + KBN_FIELD_TYPES.DATE, + KBN_FIELD_TYPES.IP, + KBN_FIELD_TYPES.STRING, + ], + }, + { + name: 'orderBy', + write: noop, // prevent default write, it's handled by orderAgg + }, + { + name: 'orderAgg', + type: 'agg', + allowedAggs: termsAggFilter, + default: null, + makeAgg(termsAgg, state) { + state = state || {}; + state.schema = 'orderAgg'; + const orderAgg = termsAgg.aggConfigs.createAggConfig(state, { + addToAggConfigs: false, + }); + orderAgg.id = termsAgg.id + '-orderAgg'; - let orderAgg = agg.params.orderAgg || aggs!.getResponseAggById(agg.params.orderBy); + return orderAgg; + }, + write(agg, output, aggs) { + const dir = agg.params.order.value; + const order: Record = (output.params.order = {}); - // TODO: This works around an Elasticsearch bug the always casts terms agg scripts to strings - // thus causing issues with filtering. This probably causes other issues since float might not - // be able to contain the number on the elasticsearch side - if (output.params.script) { - output.params.value_type = - agg.getField().type === 'number' ? 'float' : agg.getField().type; - } + let orderAgg = agg.params.orderAgg || aggs!.getResponseAggById(agg.params.orderBy); - if (agg.params.missingBucket && agg.params.field.type === 'string') { - output.params.missing = '__missing__'; - } + // TODO: This works around an Elasticsearch bug the always casts terms agg scripts to strings + // thus causing issues with filtering. This probably causes other issues since float might not + // be able to contain the number on the elasticsearch side + if (output.params.script) { + output.params.value_type = + agg.getField().type === 'number' ? 'float' : agg.getField().type; + } - if (!orderAgg) { - order[agg.params.orderBy || '_count'] = dir; - return; - } + if (agg.params.missingBucket && agg.params.field.type === 'string') { + output.params.missing = '__missing__'; + } - if (orderAgg.type.name === 'count') { - order._count = dir; - return; - } + if (!orderAgg) { + order[agg.params.orderBy || '_count'] = dir; + return; + } - const orderAggId = orderAgg.id; + if (orderAgg.type.name === 'count') { + order._count = dir; + return; + } - if (orderAgg.parentId && aggs) { - orderAgg = aggs.byId(orderAgg.parentId); - } + const orderAggId = orderAgg.id; - output.subAggs = (output.subAggs || []).concat(orderAgg); - order[orderAggId] = dir; - }, - }, - { - name: 'order', - type: 'optioned', - default: 'desc', - options: [ + if (orderAgg.parentId && aggs) { + orderAgg = aggs.byId(orderAgg.parentId); + } + + output.subAggs = (output.subAggs || []).concat(orderAgg); + order[orderAggId] = dir; + }, + }, { - text: i18n.translate('data.search.aggs.buckets.terms.orderDescendingTitle', { - defaultMessage: 'Descending', + name: 'order', + type: 'optioned', + default: 'desc', + options: [ + { + text: i18n.translate('data.search.aggs.buckets.terms.orderDescendingTitle', { + defaultMessage: 'Descending', + }), + value: 'desc', + }, + { + text: i18n.translate('data.search.aggs.buckets.terms.orderAscendingTitle', { + defaultMessage: 'Ascending', + }), + value: 'asc', + }, + ], + write: noop, // prevent default write, it's handled by orderAgg + }, + { + name: 'size', + default: 5, + }, + { + name: 'otherBucket', + default: false, + write: noop, + }, + { + name: 'otherBucketLabel', + type: 'string', + default: i18n.translate('data.search.aggs.buckets.terms.otherBucketLabel', { + defaultMessage: 'Other', + }), + displayName: i18n.translate('data.search.aggs.otherBucket.labelForOtherBucketLabel', { + defaultMessage: 'Label for other bucket', }), - value: 'desc', + shouldShow: agg => agg.getParam('otherBucket'), + write: noop, }, { - text: i18n.translate('data.search.aggs.buckets.terms.orderAscendingTitle', { - defaultMessage: 'Ascending', + name: 'missingBucket', + default: false, + write: noop, + }, + { + name: 'missingBucketLabel', + default: i18n.translate('data.search.aggs.buckets.terms.missingBucketLabel', { + defaultMessage: 'Missing', + description: `Default label used in charts when documents are missing a field. + Visible when you create a chart with a terms aggregation and enable "Show missing values"`, + }), + type: 'string', + displayName: i18n.translate('data.search.aggs.otherBucket.labelForMissingValuesLabel', { + defaultMessage: 'Label for missing values', }), - value: 'asc', + shouldShow: agg => agg.getParam('missingBucket'), + write: noop, + }, + { + name: 'exclude', + displayName: i18n.translate('data.search.aggs.buckets.terms.excludeLabel', { + defaultMessage: 'Exclude', + }), + type: 'string', + advanced: true, + shouldShow: isStringType, + ...migrateIncludeExcludeFormat, + }, + { + name: 'include', + displayName: i18n.translate('data.search.aggs.buckets.terms.includeLabel', { + defaultMessage: 'Include', + }), + type: 'string', + advanced: true, + shouldShow: isStringType, + ...migrateIncludeExcludeFormat, }, ], - write: noop, // prevent default write, it's handled by orderAgg - }, - { - name: 'size', - default: 5, - }, - { - name: 'otherBucket', - default: false, - write: noop, - }, - { - name: 'otherBucketLabel', - type: 'string', - default: i18n.translate('data.search.aggs.buckets.terms.otherBucketLabel', { - defaultMessage: 'Other', - }), - displayName: i18n.translate('data.search.aggs.otherBucket.labelForOtherBucketLabel', { - defaultMessage: 'Label for other bucket', - }), - shouldShow: agg => agg.getParam('otherBucket'), - write: noop, - }, - { - name: 'missingBucket', - default: false, - write: noop, }, - { - name: 'missingBucketLabel', - default: i18n.translate('data.search.aggs.buckets.terms.missingBucketLabel', { - defaultMessage: 'Missing', - description: `Default label used in charts when documents are missing a field. - Visible when you create a chart with a terms aggregation and enable "Show missing values"`, - }), - type: 'string', - displayName: i18n.translate('data.search.aggs.otherBucket.labelForMissingValuesLabel', { - defaultMessage: 'Label for missing values', - }), - shouldShow: agg => agg.getParam('missingBucket'), - write: noop, - }, - { - name: 'exclude', - displayName: i18n.translate('data.search.aggs.buckets.terms.excludeLabel', { - defaultMessage: 'Exclude', - }), - type: 'string', - advanced: true, - shouldShow: isStringType, - ...migrateIncludeExcludeFormat, - }, - { - name: 'include', - displayName: i18n.translate('data.search.aggs.buckets.terms.includeLabel', { - defaultMessage: 'Include', - }), - type: 'string', - advanced: true, - shouldShow: isStringType, - ...migrateIncludeExcludeFormat, - }, - ], -}); + { getInternalStartServices } + ); diff --git a/src/plugins/data/public/search/aggs/index.test.ts b/src/plugins/data/public/search/aggs/index.test.ts index 8c0e47763c295..419c3fdab1caf 100644 --- a/src/plugins/data/public/search/aggs/index.test.ts +++ b/src/plugins/data/public/search/aggs/index.test.ts @@ -20,16 +20,22 @@ import { coreMock } from '../../../../../../src/core/public/mocks'; import { getAggTypes } from './index'; -import { isBucketAggType } from './buckets/_bucket_agg_type'; +import { isBucketAggType } from './buckets/bucket_agg_type'; import { isMetricAggType } from './metrics/metric_agg_type'; import { QueryStart } from '../../query'; +import { FieldFormatsStart } from '../../field_formats'; describe('AggTypesComponent', () => { - const core = coreMock.createSetup(); + const coreSetup = coreMock.createSetup(); + const coreStart = coreMock.createSetup(); + const aggTypes = getAggTypes({ - uiSettings: core.uiSettings, - notifications: core.notifications, + uiSettings: coreSetup.uiSettings, query: {} as QueryStart, + getInternalStartServices: () => ({ + notifications: coreStart.notifications, + fieldFormats: {} as FieldFormatsStart, + }), }); const { buckets, metrics } = aggTypes; diff --git a/src/plugins/data/public/search/aggs/metrics/avg.ts b/src/plugins/data/public/search/aggs/metrics/avg.ts index 008dede3e1985..d53ce8d3fc489 100644 --- a/src/plugins/data/public/search/aggs/metrics/avg.ts +++ b/src/plugins/data/public/search/aggs/metrics/avg.ts @@ -21,25 +21,37 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const averageTitle = i18n.translate('data.search.aggs.metrics.averageTitle', { defaultMessage: 'Average', }); -export const avgMetricAgg = new MetricAggType({ - name: METRIC_TYPES.AVG, - title: averageTitle, - makeLabel: aggConfig => { - return i18n.translate('data.search.aggs.metrics.averageLabel', { - defaultMessage: 'Average {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - params: [ +export interface AvgMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getAvgMetricAgg = ({ getInternalStartServices }: AvgMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + name: METRIC_TYPES.AVG, + title: averageTitle, + makeLabel: aggConfig => { + return i18n.translate('data.search.aggs.metrics.averageLabel', { + defaultMessage: 'Average {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts b/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts index 11bb559274729..2c32ebc671539 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts @@ -23,6 +23,11 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface BucketAvgMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const overallAverageLabel = i18n.translate('data.search.aggs.metrics.overallAverageLabel', { defaultMessage: 'overall average', @@ -32,25 +37,34 @@ const averageBucketTitle = i18n.translate('data.search.aggs.metrics.averageBucke defaultMessage: 'Average Bucket', }); -export const bucketAvgMetricAgg = new MetricAggType({ - name: METRIC_TYPES.AVG_BUCKET, - title: averageBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallAverageLabel), - subtype: siblingPipelineAggHelper.subtype, - params: [...siblingPipelineAggHelper.params()], - getFormat: siblingPipelineAggHelper.getFormat, - getValue(agg, bucket) { - const customMetric = agg.getParam('customMetric'); - const customBucket = agg.getParam('customBucket'); - const scaleMetrics = customMetric.type && customMetric.type.isScalable(); +export const getBucketAvgMetricAgg = ({ + getInternalStartServices, +}: BucketAvgMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.AVG_BUCKET, + title: averageBucketTitle, + makeLabel: agg => makeNestedLabel(agg, overallAverageLabel), + subtype: siblingPipelineAggHelper.subtype, + params: [...siblingPipelineAggHelper.params()], + getFormat: siblingPipelineAggHelper.getFormat, + getValue(agg, bucket) { + const customMetric = agg.getParam('customMetric'); + const customBucket = agg.getParam('customBucket'); + const scaleMetrics = customMetric.type && customMetric.type.isScalable(); - let value = bucket[agg.id] && bucket[agg.id].value; + let value = bucket[agg.id] && bucket[agg.id].value; - if (scaleMetrics && customBucket.type.name === 'date_histogram') { - const aggInfo = customBucket.write(); + if (scaleMetrics && customBucket.type.name === 'date_histogram') { + const aggInfo = customBucket.write(); - value *= get(aggInfo, 'bucketInterval.scale', 1); + value *= get(aggInfo, 'bucketInterval.scale', 1); + } + return value; + }, + }, + { + getInternalStartServices, } - return value; - }, -}); + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_max.ts b/src/plugins/data/public/search/aggs/metrics/bucket_max.ts index 0668a9bcf57a8..1e57a2dd8e38e 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_max.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_max.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface BucketMaxMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const overallMaxLabel = i18n.translate('data.search.aggs.metrics.overallMaxLabel', { defaultMessage: 'overall max', @@ -31,11 +36,20 @@ const maxBucketTitle = i18n.translate('data.search.aggs.metrics.maxBucketTitle', defaultMessage: 'Max Bucket', }); -export const bucketMaxMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MAX_BUCKET, - title: maxBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallMaxLabel), - subtype: siblingPipelineAggHelper.subtype, - params: [...siblingPipelineAggHelper.params()], - getFormat: siblingPipelineAggHelper.getFormat, -}); +export const getBucketMaxMetricAgg = ({ + getInternalStartServices, +}: BucketMaxMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.MAX_BUCKET, + title: maxBucketTitle, + makeLabel: agg => makeNestedLabel(agg, overallMaxLabel), + subtype: siblingPipelineAggHelper.subtype, + params: [...siblingPipelineAggHelper.params()], + getFormat: siblingPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_min.ts b/src/plugins/data/public/search/aggs/metrics/bucket_min.ts index 8f728cb5e7e42..0484af23a7141 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_min.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_min.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface BucketMinMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const overallMinLabel = i18n.translate('data.search.aggs.metrics.overallMinLabel', { defaultMessage: 'overall min', @@ -31,11 +36,20 @@ const minBucketTitle = i18n.translate('data.search.aggs.metrics.minBucketTitle', defaultMessage: 'Min Bucket', }); -export const bucketMinMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MIN_BUCKET, - title: minBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallMinLabel), - subtype: siblingPipelineAggHelper.subtype, - params: [...siblingPipelineAggHelper.params()], - getFormat: siblingPipelineAggHelper.getFormat, -}); +export const getBucketMinMetricAgg = ({ + getInternalStartServices, +}: BucketMinMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.MIN_BUCKET, + title: minBucketTitle, + makeLabel: agg => makeNestedLabel(agg, overallMinLabel), + subtype: siblingPipelineAggHelper.subtype, + params: [...siblingPipelineAggHelper.params()], + getFormat: siblingPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts b/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts index 1f9392c5bec35..0a4d29a18a980 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface BucketSumMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const overallSumLabel = i18n.translate('data.search.aggs.metrics.overallSumLabel', { defaultMessage: 'overall sum', @@ -31,11 +36,20 @@ const sumBucketTitle = i18n.translate('data.search.aggs.metrics.sumBucketTitle', defaultMessage: 'Sum Bucket', }); -export const bucketSumMetricAgg = new MetricAggType({ - name: METRIC_TYPES.SUM_BUCKET, - title: sumBucketTitle, - makeLabel: agg => makeNestedLabel(agg, overallSumLabel), - subtype: siblingPipelineAggHelper.subtype, - params: [...siblingPipelineAggHelper.params()], - getFormat: siblingPipelineAggHelper.getFormat, -}); +export const getBucketSumMetricAgg = ({ + getInternalStartServices, +}: BucketSumMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.SUM_BUCKET, + title: sumBucketTitle, + makeLabel: agg => makeNestedLabel(agg, overallSumLabel), + subtype: siblingPipelineAggHelper.subtype, + params: [...siblingPipelineAggHelper.params()], + getFormat: siblingPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/cardinality.ts b/src/plugins/data/public/search/aggs/metrics/cardinality.ts index 88cdf3175665e..10b6b5aff1abd 100644 --- a/src/plugins/data/public/search/aggs/metrics/cardinality.ts +++ b/src/plugins/data/public/search/aggs/metrics/cardinality.ts @@ -18,36 +18,48 @@ */ import { i18n } from '@kbn/i18n'; -import { MetricAggType } from './metric_agg_type'; +import { MetricAggType, IMetricAggConfig } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; const uniqueCountTitle = i18n.translate('data.search.aggs.metrics.uniqueCountTitle', { defaultMessage: 'Unique Count', }); -export const cardinalityMetricAgg = new MetricAggType({ - name: METRIC_TYPES.CARDINALITY, - title: uniqueCountTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.metrics.uniqueCountLabel', { - defaultMessage: 'Unique count of {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - getFormat() { - const fieldFormatsService = getFieldFormats(); +export interface CardinalityMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} - return fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); - }, - params: [ +export const getCardinalityMetricAgg = ({ + getInternalStartServices, +}: CardinalityMetricAggDependencies) => + new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter( - type => type !== KBN_FIELD_TYPES.HISTOGRAM - ), + name: METRIC_TYPES.CARDINALITY, + title: uniqueCountTitle, + makeLabel(aggConfig: IMetricAggConfig) { + return i18n.translate('data.search.aggs.metrics.uniqueCountLabel', { + defaultMessage: 'Unique count of {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + getFormat() { + const { fieldFormats } = getInternalStartServices(); + + return fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter( + type => type !== KBN_FIELD_TYPES.HISTOGRAM + ), + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); diff --git a/src/plugins/data/public/search/aggs/metrics/count.ts b/src/plugins/data/public/search/aggs/metrics/count.ts index 3ec1e18d66ab9..bd0b83798c7db 100644 --- a/src/plugins/data/public/search/aggs/metrics/count.ts +++ b/src/plugins/data/public/search/aggs/metrics/count.ts @@ -21,28 +21,38 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; -export const countMetricAgg = new MetricAggType({ - name: METRIC_TYPES.COUNT, - title: i18n.translate('data.search.aggs.metrics.countTitle', { - defaultMessage: 'Count', - }), - hasNoDsl: true, - makeLabel() { - return i18n.translate('data.search.aggs.metrics.countLabel', { - defaultMessage: 'Count', - }); - }, - getFormat() { - const fieldFormatsService = getFieldFormats(); +export interface CountMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} - return fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); - }, - getValue(agg, bucket) { - return bucket.doc_count; - }, - isScalable() { - return true; - }, -}); +export const getCountMetricAgg = ({ getInternalStartServices }: CountMetricAggDependencies) => + new MetricAggType( + { + name: METRIC_TYPES.COUNT, + title: i18n.translate('data.search.aggs.metrics.countTitle', { + defaultMessage: 'Count', + }), + hasNoDsl: true, + makeLabel() { + return i18n.translate('data.search.aggs.metrics.countLabel', { + defaultMessage: 'Count', + }); + }, + getFormat() { + const { fieldFormats } = getInternalStartServices(); + + return fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); + }, + getValue(agg, bucket) { + return bucket.doc_count; + }, + isScalable() { + return true; + }, + }, + { + getInternalStartServices, + } + ); diff --git a/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts b/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts index a5d02459900bb..8ca922e144a1f 100644 --- a/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts +++ b/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface CumulativeSumMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const cumulativeSumLabel = i18n.translate('data.search.aggs.metrics.cumulativeSumLabel', { defaultMessage: 'cumulative sum', @@ -31,11 +36,20 @@ const cumulativeSumTitle = i18n.translate('data.search.aggs.metrics.cumulativeSu defaultMessage: 'Cumulative Sum', }); -export const cumulativeSumMetricAgg = new MetricAggType({ - name: METRIC_TYPES.CUMULATIVE_SUM, - title: cumulativeSumTitle, - subtype: parentPipelineAggHelper.subtype, - makeLabel: agg => makeNestedLabel(agg, cumulativeSumLabel), - params: [...parentPipelineAggHelper.params()], - getFormat: parentPipelineAggHelper.getFormat, -}); +export const getCumulativeSumMetricAgg = ({ + getInternalStartServices, +}: CumulativeSumMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.CUMULATIVE_SUM, + title: cumulativeSumTitle, + subtype: parentPipelineAggHelper.subtype, + makeLabel: agg => makeNestedLabel(agg, cumulativeSumLabel), + params: [...parentPipelineAggHelper.params()], + getFormat: parentPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/derivative.ts b/src/plugins/data/public/search/aggs/metrics/derivative.ts index 1169a527b0668..5752a72c846aa 100644 --- a/src/plugins/data/public/search/aggs/metrics/derivative.ts +++ b/src/plugins/data/public/search/aggs/metrics/derivative.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface DerivativeMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const derivativeLabel = i18n.translate('data.search.aggs.metrics.derivativeLabel', { defaultMessage: 'derivative', @@ -31,13 +36,22 @@ const derivativeTitle = i18n.translate('data.search.aggs.metrics.derivativeTitle defaultMessage: 'Derivative', }); -export const derivativeMetricAgg = new MetricAggType({ - name: METRIC_TYPES.DERIVATIVE, - title: derivativeTitle, - subtype: parentPipelineAggHelper.subtype, - makeLabel(agg) { - return makeNestedLabel(agg, derivativeLabel); - }, - params: [...parentPipelineAggHelper.params()], - getFormat: parentPipelineAggHelper.getFormat, -}); +export const getDerivativeMetricAgg = ({ + getInternalStartServices, +}: DerivativeMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.DERIVATIVE, + title: derivativeTitle, + subtype: parentPipelineAggHelper.subtype, + makeLabel(agg) { + return makeNestedLabel(agg, derivativeLabel); + }, + params: [...parentPipelineAggHelper.params()], + getFormat: parentPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/geo_bounds.ts b/src/plugins/data/public/search/aggs/metrics/geo_bounds.ts index 8a9f66f4b22a8..00927ebba56bf 100644 --- a/src/plugins/data/public/search/aggs/metrics/geo_bounds.ts +++ b/src/plugins/data/public/search/aggs/metrics/geo_bounds.ts @@ -21,6 +21,11 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface GeoBoundsMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const geoBoundsTitle = i18n.translate('data.search.aggs.metrics.geoBoundsTitle', { defaultMessage: 'Geo Bounds', @@ -30,15 +35,24 @@ const geoBoundsLabel = i18n.translate('data.search.aggs.metrics.geoBoundsLabel', defaultMessage: 'Geo Bounds', }); -export const geoBoundsMetricAgg = new MetricAggType({ - name: METRIC_TYPES.GEO_BOUNDS, - title: geoBoundsTitle, - makeLabel: () => geoBoundsLabel, - params: [ +export const getGeoBoundsMetricAgg = ({ + getInternalStartServices, +}: GeoBoundsMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + name: METRIC_TYPES.GEO_BOUNDS, + title: geoBoundsTitle, + makeLabel: () => geoBoundsLabel, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/geo_centroid.ts b/src/plugins/data/public/search/aggs/metrics/geo_centroid.ts index a4e4413843bdd..a4b084f794a5d 100644 --- a/src/plugins/data/public/search/aggs/metrics/geo_centroid.ts +++ b/src/plugins/data/public/search/aggs/metrics/geo_centroid.ts @@ -21,6 +21,11 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface GeoCentroidMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const geoCentroidTitle = i18n.translate('data.search.aggs.metrics.geoCentroidTitle', { defaultMessage: 'Geo Centroid', @@ -30,18 +35,27 @@ const geoCentroidLabel = i18n.translate('data.search.aggs.metrics.geoCentroidLab defaultMessage: 'Geo Centroid', }); -export const geoCentroidMetricAgg = new MetricAggType({ - name: METRIC_TYPES.GEO_CENTROID, - title: geoCentroidTitle, - makeLabel: () => geoCentroidLabel, - params: [ +export const getGeoCentroidMetricAgg = ({ + getInternalStartServices, +}: GeoCentroidMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + name: METRIC_TYPES.GEO_CENTROID, + title: geoCentroidTitle, + makeLabel: () => geoCentroidLabel, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.GEO_POINT, + }, + ], + getValue(agg, bucket) { + return bucket[agg.id] && bucket[agg.id].location; + }, }, - ], - getValue(agg, bucket) { - return bucket[agg.id] && bucket[agg.id].location; - }, -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/max.ts b/src/plugins/data/public/search/aggs/metrics/max.ts index 0cfb7be699a95..88e8b485cb73f 100644 --- a/src/plugins/data/public/search/aggs/metrics/max.ts +++ b/src/plugins/data/public/search/aggs/metrics/max.ts @@ -21,25 +21,37 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const maxTitle = i18n.translate('data.search.aggs.metrics.maxTitle', { defaultMessage: 'Max', }); -export const maxMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MAX, - title: maxTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.metrics.maxLabel', { - defaultMessage: 'Max {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - params: [ +export interface MaxMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getMaxMetricAgg = ({ getInternalStartServices }: MaxMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE], + name: METRIC_TYPES.MAX, + title: maxTitle, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.metrics.maxLabel', { + defaultMessage: 'Max {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE], + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/median.test.ts b/src/plugins/data/public/search/aggs/metrics/median.test.ts index ad55837ec9a30..f80c46026f50a 100644 --- a/src/plugins/data/public/search/aggs/metrics/median.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/median.test.ts @@ -17,16 +17,24 @@ * under the License. */ -import { medianMetricAgg } from './median'; +import { getMedianMetricAgg, MedianMetricAggDependencies } from './median'; import { AggConfigs, IAggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('AggTypeMetricMedianProvider class', () => { let aggConfigs: IAggConfigs; + const aggTypesDependencies: MedianMetricAggDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; beforeEach(() => { - const typesRegistry = mockAggTypesRegistry([medianMetricAgg]); + const typesRegistry = mockAggTypesRegistry([getMedianMetricAgg(aggTypesDependencies)]); const field = { name: 'bytes', }; diff --git a/src/plugins/data/public/search/aggs/metrics/median.ts b/src/plugins/data/public/search/aggs/metrics/median.ts index faa0694cd5312..a398f017602b0 100644 --- a/src/plugins/data/public/search/aggs/metrics/median.ts +++ b/src/plugins/data/public/search/aggs/metrics/median.ts @@ -21,33 +21,49 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const medianTitle = i18n.translate('data.search.aggs.metrics.medianTitle', { defaultMessage: 'Median', }); -export const medianMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MEDIAN, - dslName: 'percentiles', - title: medianTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.metrics.medianLabel', { - defaultMessage: 'Median {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - params: [ +export interface MedianMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getMedianMetricAgg = ({ getInternalStartServices }: MedianMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE, KBN_FIELD_TYPES.HISTOGRAM], - write(agg, output) { - output.params.field = agg.getParam('field').name; - output.params.percents = [50]; + name: METRIC_TYPES.MEDIAN, + dslName: 'percentiles', + title: medianTitle, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.metrics.medianLabel', { + defaultMessage: 'Median {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [ + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.DATE, + KBN_FIELD_TYPES.HISTOGRAM, + ], + write(agg, output) { + output.params.field = agg.getParam('field').name; + output.params.percents = [50]; + }, + }, + ], + getValue(agg, bucket) { + return bucket[agg.id].values['50.0']; }, }, - ], - getValue(agg, bucket) { - return bucket[agg.id].values['50.0']; - }, -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts b/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts index 05c4cb3de4bdf..bb16cba1bee62 100644 --- a/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts +++ b/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts @@ -23,8 +23,8 @@ import { AggParamType } from '../param_types/agg'; import { AggConfig } from '../agg_config'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; import { FieldTypes } from '../param_types'; +import { GetInternalStartServicesFn } from '../../../types'; export interface IMetricAggConfig extends AggConfig { type: InstanceType; @@ -44,6 +44,10 @@ interface MetricAggTypeConfig subtype?: string; } +interface MetricAggTypeDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + // TODO need to make a more explicit interface for this export type IMetricAggType = MetricAggType; @@ -57,8 +61,11 @@ export class MetricAggType {}; - constructor(config: MetricAggTypeConfig) { - super(config); + constructor( + config: MetricAggTypeConfig, + dependencies: MetricAggTypeDependencies + ) { + super(config, dependencies); this.getValue = config.getValue || @@ -78,11 +85,9 @@ export class MetricAggType { - const fieldFormatsService = getFieldFormats(); + const { fieldFormats } = dependencies.getInternalStartServices(); const field = agg.getField(); - return field - ? field.format - : fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); + return field ? field.format : fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); }); this.subtype = diff --git a/src/plugins/data/public/search/aggs/metrics/min.ts b/src/plugins/data/public/search/aggs/metrics/min.ts index 0a9abf1edcd04..aae16f357186c 100644 --- a/src/plugins/data/public/search/aggs/metrics/min.ts +++ b/src/plugins/data/public/search/aggs/metrics/min.ts @@ -21,25 +21,37 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const minTitle = i18n.translate('data.search.aggs.metrics.minTitle', { defaultMessage: 'Min', }); -export const minMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MIN, - title: minTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.metrics.minLabel', { - defaultMessage: 'Min {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - params: [ +export interface MinMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getMinMetricAgg = ({ getInternalStartServices }: MinMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE], + name: METRIC_TYPES.MIN, + title: minTitle, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.metrics.minLabel', { + defaultMessage: 'Min {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE], + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/moving_avg.ts b/src/plugins/data/public/search/aggs/metrics/moving_avg.ts index cb733507858bc..94b9b1d8cd487 100644 --- a/src/plugins/data/public/search/aggs/metrics/moving_avg.ts +++ b/src/plugins/data/public/search/aggs/metrics/moving_avg.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface MovingAvgMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const movingAvgTitle = i18n.translate('data.search.aggs.metrics.movingAvgTitle', { defaultMessage: 'Moving Avg', @@ -31,34 +36,43 @@ const movingAvgLabel = i18n.translate('data.search.aggs.metrics.movingAvgLabel', defaultMessage: 'moving avg', }); -export const movingAvgMetricAgg = new MetricAggType({ - name: METRIC_TYPES.MOVING_FN, - dslName: 'moving_fn', - title: movingAvgTitle, - subtype: parentPipelineAggHelper.subtype, - makeLabel: agg => makeNestedLabel(agg, movingAvgLabel), - params: [ - ...parentPipelineAggHelper.params(), +export const getMovingAvgMetricAgg = ({ + getInternalStartServices, +}: MovingAvgMetricAggDependencies) => { + return new MetricAggType( { - name: 'window', - default: 5, + name: METRIC_TYPES.MOVING_FN, + dslName: 'moving_fn', + title: movingAvgTitle, + subtype: parentPipelineAggHelper.subtype, + makeLabel: agg => makeNestedLabel(agg, movingAvgLabel), + params: [ + ...parentPipelineAggHelper.params(), + { + name: 'window', + default: 5, + }, + { + name: 'script', + default: 'MovingFunctions.unweightedAvg(values)', + }, + ], + getValue(agg, bucket) { + /** + * The previous implementation using `moving_avg` did not + * return any bucket in case there are no documents or empty window. + * The `moving_fn` aggregation returns buckets with the value null if the + * window is empty or doesn't return any value if the sibiling metric + * is null. Since our generic MetricAggType.getValue implementation + * would return the value 0 for null buckets, we need a specific + * implementation here, that preserves the null value. + */ + return bucket[agg.id] ? bucket[agg.id].value : null; + }, + getFormat: parentPipelineAggHelper.getFormat, }, { - name: 'script', - default: 'MovingFunctions.unweightedAvg(values)', - }, - ], - getValue(agg, bucket) { - /** - * The previous implementation using `moving_avg` did not - * return any bucket in case there are no documents or empty window. - * The `moving_fn` aggregation returns buckets with the value null if the - * window is empty or doesn't return any value if the sibiling metric - * is null. Since our generic MetricAggType.getValue implementation - * would return the value 0 for null buckets, we need a specific - * implementation here, that preserves the null value. - */ - return bucket[agg.id] ? bucket[agg.id].value : null; - }, - getFormat: parentPipelineAggHelper.getFormat, -}); + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts b/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts index 02e63f653f94f..af983a50f6c23 100644 --- a/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts @@ -17,26 +17,47 @@ * under the License. */ -import { derivativeMetricAgg } from './derivative'; -import { cumulativeSumMetricAgg } from './cumulative_sum'; -import { movingAvgMetricAgg } from './moving_avg'; -import { serialDiffMetricAgg } from './serial_diff'; +import { getDerivativeMetricAgg } from './derivative'; +import { getCumulativeSumMetricAgg } from './cumulative_sum'; +import { getMovingAvgMetricAgg } from './moving_avg'; +import { getSerialDiffMetricAgg } from './serial_diff'; import { AggConfigs } from '../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; +import { mockAggTypesRegistry } from '../test_helpers'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { GetInternalStartServicesFn } from '../../../types'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('parent pipeline aggs', function() { - beforeEach(() => { - mockDataServices(); + const getInternalStartServices: GetInternalStartServicesFn = () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), }); const typesRegistry = mockAggTypesRegistry(); const metrics = [ - { name: 'derivative', title: 'Derivative', provider: derivativeMetricAgg }, - { name: 'cumulative_sum', title: 'Cumulative Sum', provider: cumulativeSumMetricAgg }, - { name: 'moving_avg', title: 'Moving Avg', provider: movingAvgMetricAgg, dslName: 'moving_fn' }, - { name: 'serial_diff', title: 'Serial Diff', provider: serialDiffMetricAgg }, + { + name: 'derivative', + title: 'Derivative', + provider: getDerivativeMetricAgg({ getInternalStartServices }), + }, + { + name: 'cumulative_sum', + title: 'Cumulative Sum', + provider: getCumulativeSumMetricAgg({ getInternalStartServices }), + }, + { + name: 'moving_avg', + title: 'Moving Avg', + provider: getMovingAvgMetricAgg({ getInternalStartServices }), + dslName: 'moving_fn', + }, + { + name: 'serial_diff', + title: 'Serial Diff', + provider: getSerialDiffMetricAgg({ getInternalStartServices }), + }, ]; metrics.forEach(metric => { diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts index 628f1cd204ee5..2944fc8c11b23 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts @@ -17,18 +17,28 @@ * under the License. */ -import { IPercentileRanksAggConfig, percentileRanksMetricAgg } from './percentile_ranks'; +import { + IPercentileRanksAggConfig, + getPercentileRanksMetricAgg, + PercentileRanksMetricAggDependencies, +} from './percentile_ranks'; import { AggConfigs, IAggConfigs } from '../agg_configs'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; +import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('AggTypesMetricsPercentileRanksProvider class', function() { let aggConfigs: IAggConfigs; + const aggTypesDependencies: PercentileRanksMetricAggDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; beforeEach(() => { - mockDataServices(); - - const typesRegistry = mockAggTypesRegistry([percentileRanksMetricAgg]); + const typesRegistry = mockAggTypesRegistry([getPercentileRanksMetricAgg(aggTypesDependencies)]); const field = { name: 'bytes', }; @@ -65,7 +75,7 @@ describe('AggTypesMetricsPercentileRanksProvider class', function() { }); it('uses the custom label if it is set', function() { - const responseAggs: any = percentileRanksMetricAgg.getResponseAggs( + const responseAggs: any = getPercentileRanksMetricAgg(aggTypesDependencies).getResponseAggs( aggConfigs.aggs[0] as IPercentileRanksAggConfig ); diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts index 7dc0f70ea7b80..0d79665ff9c4e 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts @@ -23,68 +23,86 @@ import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_respons import { getPercentileValue } from './percentiles_get_value'; import { METRIC_TYPES } from './metric_agg_types'; import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES } from '../../../../common'; -import { getFieldFormats } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; // required by the values editor export type IPercentileRanksAggConfig = IResponseAggConfig; -const valueProps = { - makeLabel(this: IPercentileRanksAggConfig) { - const fieldFormatsService = getFieldFormats(); - const field = this.getField(); - const format = - (field && field.format) || fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); - const customLabel = this.getParam('customLabel'); - const label = customLabel || this.getFieldDisplayName(); +export interface PercentileRanksMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} - return i18n.translate('data.search.aggs.metrics.percentileRanks.valuePropsLabel', { - defaultMessage: 'Percentile rank {format} of "{label}"', - values: { format: format.convert(this.key, 'text'), label }, - }); - }, -}; +const getValueProps = (getInternalStartServices: GetInternalStartServicesFn) => { + return { + makeLabel(this: IPercentileRanksAggConfig) { + const { fieldFormats } = getInternalStartServices(); + const field = this.getField(); + const format = + (field && field.format) || fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); + const customLabel = this.getParam('customLabel'); + const label = customLabel || this.getFieldDisplayName(); -export const percentileRanksMetricAgg = new MetricAggType({ - name: METRIC_TYPES.PERCENTILE_RANKS, - title: i18n.translate('data.search.aggs.metrics.percentileRanksTitle', { - defaultMessage: 'Percentile Ranks', - }), - makeLabel(agg) { - return i18n.translate('data.search.aggs.metrics.percentileRanksLabel', { - defaultMessage: 'Percentile ranks of {field}', - values: { field: agg.getFieldDisplayName() }, - }); - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.HISTOGRAM], - }, - { - name: 'values', - default: [], + return i18n.translate('data.search.aggs.metrics.percentileRanks.valuePropsLabel', { + defaultMessage: 'Percentile rank {format} of "{label}"', + values: { format: format.convert(this.key, 'text'), label }, + }); }, + }; +}; + +export const getPercentileRanksMetricAgg = ({ + getInternalStartServices, +}: PercentileRanksMetricAggDependencies) => { + return new MetricAggType( { - write(agg, output) { - output.params.keyed = false; + name: METRIC_TYPES.PERCENTILE_RANKS, + title: i18n.translate('data.search.aggs.metrics.percentileRanksTitle', { + defaultMessage: 'Percentile Ranks', + }), + makeLabel(agg) { + return i18n.translate('data.search.aggs.metrics.percentileRanksLabel', { + defaultMessage: 'Percentile ranks of {field}', + values: { field: agg.getFieldDisplayName() }, + }); }, - }, - ], - getResponseAggs(agg) { - const ValueAggConfig = getResponseAggConfigClass(agg, valueProps); - const values = agg.getParam('values'); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.HISTOGRAM], + }, + { + name: 'values', + default: [], + }, + { + write(agg, output) { + output.params.keyed = false; + }, + }, + ], + getResponseAggs(agg) { + const ValueAggConfig = getResponseAggConfigClass( + agg, + getValueProps(getInternalStartServices) + ); + const values = agg.getParam('values'); - return values.map((value: any) => new ValueAggConfig(value)); - }, - getFormat() { - const fieldFormatsService = getFieldFormats(); - return ( - fieldFormatsService.getInstance(FIELD_FORMAT_IDS.PERCENT) || - fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.NUMBER) - ); - }, - getValue(agg, bucket) { - return getPercentileValue(agg, bucket) / 100; - }, -}); + return values.map((value: any) => new ValueAggConfig(value)); + }, + getFormat() { + const { fieldFormats } = getInternalStartServices(); + return ( + fieldFormats.getInstance(FIELD_FORMAT_IDS.PERCENT) || + fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER) + ); + }, + getValue(agg, bucket) { + return getPercentileValue(agg, bucket) / 100; + }, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts b/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts index e077bc0f8c773..33bd42df74cc7 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts @@ -17,16 +17,28 @@ * under the License. */ -import { IPercentileAggConfig, percentilesMetricAgg } from './percentiles'; +import { + IPercentileAggConfig, + getPercentilesMetricAgg, + PercentilesMetricAggDependencies, +} from './percentiles'; import { AggConfigs, IAggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('AggTypesMetricsPercentilesProvider class', () => { let aggConfigs: IAggConfigs; + const aggTypesDependencies: PercentilesMetricAggDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; beforeEach(() => { - const typesRegistry = mockAggTypesRegistry([percentilesMetricAgg]); + const typesRegistry = mockAggTypesRegistry([getPercentilesMetricAgg(aggTypesDependencies)]); const field = { name: 'bytes', }; @@ -63,7 +75,7 @@ describe('AggTypesMetricsPercentilesProvider class', () => { }); it('uses the custom label if it is set', () => { - const responseAggs: any = percentilesMetricAgg.getResponseAggs( + const responseAggs: any = getPercentilesMetricAgg(aggTypesDependencies).getResponseAggs( aggConfigs.aggs[0] as IPercentileAggConfig ); diff --git a/src/plugins/data/public/search/aggs/metrics/percentiles.ts b/src/plugins/data/public/search/aggs/metrics/percentiles.ts index a39d68248d608..040a52588dd94 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentiles.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentiles.ts @@ -24,9 +24,14 @@ import { KBN_FIELD_TYPES } from '../../../../common'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { getPercentileValue } from './percentiles_get_value'; import { ordinalSuffix } from './lib/ordinal_suffix'; +import { GetInternalStartServicesFn } from '../../../types'; export type IPercentileAggConfig = IResponseAggConfig; +export interface PercentilesMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + const valueProps = { makeLabel(this: IPercentileAggConfig) { const customLabel = this.getParam('customLabel'); @@ -39,38 +44,51 @@ const valueProps = { }, }; -export const percentilesMetricAgg = new MetricAggType({ - name: METRIC_TYPES.PERCENTILES, - title: i18n.translate('data.search.aggs.metrics.percentilesTitle', { - defaultMessage: 'Percentiles', - }), - makeLabel(agg) { - return i18n.translate('data.search.aggs.metrics.percentilesLabel', { - defaultMessage: 'Percentiles of {field}', - values: { field: agg.getFieldDisplayName() }, - }); - }, - params: [ - { - name: 'field', - type: 'field', - filterFieldTypes: [KBN_FIELD_TYPES.NUMBER, KBN_FIELD_TYPES.DATE, KBN_FIELD_TYPES.HISTOGRAM], - }, - { - name: 'percents', - default: [1, 5, 25, 50, 75, 95, 99], - }, +export const getPercentilesMetricAgg = ({ + getInternalStartServices, +}: PercentilesMetricAggDependencies) => { + return new MetricAggType( { - write(agg, output) { - output.params.keyed = false; + name: METRIC_TYPES.PERCENTILES, + title: i18n.translate('data.search.aggs.metrics.percentilesTitle', { + defaultMessage: 'Percentiles', + }), + makeLabel(agg) { + return i18n.translate('data.search.aggs.metrics.percentilesLabel', { + defaultMessage: 'Percentiles of {field}', + values: { field: agg.getFieldDisplayName() }, + }); }, - }, - ], - getResponseAggs(agg) { - const ValueAggConfig = getResponseAggConfigClass(agg, valueProps); + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: [ + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.DATE, + KBN_FIELD_TYPES.HISTOGRAM, + ], + }, + { + name: 'percents', + default: [1, 5, 25, 50, 75, 95, 99], + }, + { + write(agg, output) { + output.params.keyed = false; + }, + }, + ], + getResponseAggs(agg) { + const ValueAggConfig = getResponseAggConfigClass(agg, valueProps); - return agg.getParam('percents').map((percent: any) => new ValueAggConfig(percent)); - }, + return agg.getParam('percents').map((percent: any) => new ValueAggConfig(percent)); + }, - getValue: getPercentileValue, -}); + getValue: getPercentileValue, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/serial_diff.ts b/src/plugins/data/public/search/aggs/metrics/serial_diff.ts index 5af6e1952d135..2b1498560f862 100644 --- a/src/plugins/data/public/search/aggs/metrics/serial_diff.ts +++ b/src/plugins/data/public/search/aggs/metrics/serial_diff.ts @@ -22,6 +22,11 @@ import { MetricAggType } from './metric_agg_type'; import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface SerialDiffMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const serialDiffTitle = i18n.translate('data.search.aggs.metrics.serialDiffTitle', { defaultMessage: 'Serial Diff', @@ -31,11 +36,20 @@ const serialDiffLabel = i18n.translate('data.search.aggs.metrics.serialDiffLabel defaultMessage: 'serial diff', }); -export const serialDiffMetricAgg = new MetricAggType({ - name: METRIC_TYPES.SERIAL_DIFF, - title: serialDiffTitle, - subtype: parentPipelineAggHelper.subtype, - makeLabel: agg => makeNestedLabel(agg, serialDiffLabel), - params: [...parentPipelineAggHelper.params()], - getFormat: parentPipelineAggHelper.getFormat, -}); +export const getSerialDiffMetricAgg = ({ + getInternalStartServices, +}: SerialDiffMetricAggDependencies) => { + return new MetricAggType( + { + name: METRIC_TYPES.SERIAL_DIFF, + title: serialDiffTitle, + subtype: parentPipelineAggHelper.subtype, + makeLabel: agg => makeNestedLabel(agg, serialDiffLabel), + params: [...parentPipelineAggHelper.params()], + getFormat: parentPipelineAggHelper.getFormat, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts b/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts index 8389ed8262ce5..ab480fe44227e 100644 --- a/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts @@ -17,27 +17,47 @@ * under the License. */ -import { bucketSumMetricAgg } from './bucket_sum'; -import { bucketAvgMetricAgg } from './bucket_avg'; -import { bucketMinMetricAgg } from './bucket_min'; -import { bucketMaxMetricAgg } from './bucket_max'; +import { getBucketSumMetricAgg } from './bucket_sum'; +import { getBucketAvgMetricAgg } from './bucket_avg'; +import { getBucketMinMetricAgg } from './bucket_min'; +import { getBucketMaxMetricAgg } from './bucket_max'; import { AggConfigs } from '../agg_configs'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; -import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; +import { mockAggTypesRegistry } from '../test_helpers'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { GetInternalStartServicesFn } from '../../../types'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('sibling pipeline aggs', () => { - beforeEach(() => { - mockDataServices(); + const getInternalStartServices: GetInternalStartServicesFn = () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), }); const typesRegistry = mockAggTypesRegistry(); const metrics = [ - { name: 'sum_bucket', title: 'Overall Sum', provider: bucketSumMetricAgg }, - { name: 'avg_bucket', title: 'Overall Average', provider: bucketAvgMetricAgg }, - { name: 'min_bucket', title: 'Overall Min', provider: bucketMinMetricAgg }, - { name: 'max_bucket', title: 'Overall Max', provider: bucketMaxMetricAgg }, + { + name: 'sum_bucket', + title: 'Overall Sum', + provider: getBucketSumMetricAgg({ getInternalStartServices }), + }, + { + name: 'avg_bucket', + title: 'Overall Average', + provider: getBucketAvgMetricAgg({ getInternalStartServices }), + }, + { + name: 'min_bucket', + title: 'Overall Min', + provider: getBucketMinMetricAgg({ getInternalStartServices }), + }, + { + name: 'max_bucket', + title: 'Overall Max', + provider: getBucketMaxMetricAgg({ getInternalStartServices }), + }, ]; metrics.forEach(metric => { diff --git a/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts b/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts index 0679831b1e6ac..6bbff3009cc11 100644 --- a/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts @@ -17,13 +17,25 @@ * under the License. */ -import { IStdDevAggConfig, stdDeviationMetricAgg } from './std_deviation'; +import { + IStdDevAggConfig, + getStdDeviationMetricAgg, + StdDeviationMetricAggDependencies, +} from './std_deviation'; import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { METRIC_TYPES } from './metric_agg_types'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('AggTypeMetricStandardDeviationProvider class', () => { - const typesRegistry = mockAggTypesRegistry([stdDeviationMetricAgg]); + const aggTypesDependencies: StdDeviationMetricAggDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + const typesRegistry = mockAggTypesRegistry([getStdDeviationMetricAgg(aggTypesDependencies)]); const getAggConfigs = (customLabel?: string) => { const field = { name: 'memory', @@ -58,7 +70,7 @@ describe('AggTypeMetricStandardDeviationProvider class', () => { it('uses the custom label if it is set', () => { const aggConfigs = getAggConfigs('custom label'); - const responseAggs: any = stdDeviationMetricAgg.getResponseAggs( + const responseAggs: any = getStdDeviationMetricAgg(aggTypesDependencies).getResponseAggs( aggConfigs.aggs[0] as IStdDevAggConfig ); @@ -72,7 +84,7 @@ describe('AggTypeMetricStandardDeviationProvider class', () => { it('uses the default labels if custom label is not set', () => { const aggConfigs = getAggConfigs(); - const responseAggs: any = stdDeviationMetricAgg.getResponseAggs( + const responseAggs: any = getStdDeviationMetricAgg(aggTypesDependencies).getResponseAggs( aggConfigs.aggs[0] as IStdDevAggConfig ); diff --git a/src/plugins/data/public/search/aggs/metrics/std_deviation.ts b/src/plugins/data/public/search/aggs/metrics/std_deviation.ts index 5e069e317e052..e972132542ceb 100644 --- a/src/plugins/data/public/search/aggs/metrics/std_deviation.ts +++ b/src/plugins/data/public/search/aggs/metrics/std_deviation.ts @@ -23,6 +23,7 @@ import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; interface ValProp { valProp: string[]; @@ -34,6 +35,10 @@ export interface IStdDevAggConfig extends IResponseAggConfig { valProp: () => ValProp; } +export interface StdDeviationMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + const responseAggConfigProps = { valProp(this: IStdDevAggConfig) { const customLabel = this.getParam('customLabel'); @@ -75,33 +80,42 @@ const responseAggConfigProps = { }, }; -export const stdDeviationMetricAgg = new MetricAggType({ - name: METRIC_TYPES.STD_DEV, - dslName: 'extended_stats', - title: i18n.translate('data.search.aggs.metrics.standardDeviationTitle', { - defaultMessage: 'Standard Deviation', - }), - makeLabel(agg) { - return i18n.translate('data.search.aggs.metrics.standardDeviationLabel', { - defaultMessage: 'Standard Deviation of {field}', - values: { field: agg.getFieldDisplayName() }, - }); - }, - params: [ +export const getStdDeviationMetricAgg = ({ + getInternalStartServices, +}: StdDeviationMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.NUMBER, - }, - ], + name: METRIC_TYPES.STD_DEV, + dslName: 'extended_stats', + title: i18n.translate('data.search.aggs.metrics.standardDeviationTitle', { + defaultMessage: 'Standard Deviation', + }), + makeLabel(agg) { + return i18n.translate('data.search.aggs.metrics.standardDeviationLabel', { + defaultMessage: 'Standard Deviation of {field}', + values: { field: agg.getFieldDisplayName() }, + }); + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + }, + ], - getResponseAggs(agg) { - const ValueAggConfig = getResponseAggConfigClass(agg, responseAggConfigProps); + getResponseAggs(agg) { + const ValueAggConfig = getResponseAggConfigClass(agg, responseAggConfigProps); - return [new ValueAggConfig('std_lower'), new ValueAggConfig('std_upper')]; - }, + return [new ValueAggConfig('std_lower'), new ValueAggConfig('std_upper')]; + }, - getValue(agg, bucket) { - return get(bucket[agg.parentId], agg.valProp()); - }, -}); + getValue(agg, bucket) { + return get(bucket[agg.parentId], agg.valProp()); + }, + }, + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/sum.ts b/src/plugins/data/public/search/aggs/metrics/sum.ts index ffb117dda0839..545c6d6a4939e 100644 --- a/src/plugins/data/public/search/aggs/metrics/sum.ts +++ b/src/plugins/data/public/search/aggs/metrics/sum.ts @@ -21,28 +21,40 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; const sumTitle = i18n.translate('data.search.aggs.metrics.sumTitle', { defaultMessage: 'Sum', }); -export const sumMetricAgg = new MetricAggType({ - name: METRIC_TYPES.SUM, - title: sumTitle, - makeLabel(aggConfig) { - return i18n.translate('data.search.aggs.metrics.sumLabel', { - defaultMessage: 'Sum of {field}', - values: { field: aggConfig.getFieldDisplayName() }, - }); - }, - isScalable() { - return true; - }, - params: [ +export interface SumMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + +export const getSumMetricAgg = ({ getInternalStartServices }: SumMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + name: METRIC_TYPES.SUM, + title: sumTitle, + makeLabel(aggConfig) { + return i18n.translate('data.search.aggs.metrics.sumLabel', { + defaultMessage: 'Sum of {field}', + values: { field: aggConfig.getFieldDisplayName() }, + }); + }, + isScalable() { + return true; + }, + params: [ + { + name: 'field', + type: 'field', + filterFieldTypes: KBN_FIELD_TYPES.NUMBER, + }, + ], }, - ], -}); + { + getInternalStartServices, + } + ); +}; diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts index c65a714f26247..8294ad09bae22 100644 --- a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts @@ -18,15 +18,23 @@ */ import { dropRight, last } from 'lodash'; -import { topHitMetricAgg } from './top_hit'; +import { getTopHitMetricAgg, TopHitMetricAggDependencies } from './top_hit'; import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { IMetricAggConfig } from './metric_agg_type'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('Top hit metric', () => { let aggDsl: Record; let aggConfig: IMetricAggConfig; + const aggTypesDependencies: TopHitMetricAggDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; const init = ({ fieldName = 'field', @@ -36,7 +44,7 @@ describe('Top hit metric', () => { fieldType = KBN_FIELD_TYPES.NUMBER, size = 1, }: any) => { - const typesRegistry = mockAggTypesRegistry([topHitMetricAgg]); + const typesRegistry = mockAggTypesRegistry([getTopHitMetricAgg(aggTypesDependencies)]); const field = { name: fieldName, displayName: fieldName, @@ -91,7 +99,7 @@ describe('Top hit metric', () => { it('should return a label prefixed with Last if sorting in descending order', () => { init({ fieldName: 'bytes' }); - expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('Last bytes'); + expect(getTopHitMetricAgg(aggTypesDependencies).makeLabel(aggConfig)).toEqual('Last bytes'); }); it('should return a label prefixed with First if sorting in ascending order', () => { @@ -99,7 +107,7 @@ describe('Top hit metric', () => { fieldName: 'bytes', sortOrder: 'asc', }); - expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('First bytes'); + expect(getTopHitMetricAgg(aggTypesDependencies).makeLabel(aggConfig)).toEqual('First bytes'); }); it('should request the _source field', () => { @@ -140,7 +148,7 @@ describe('Top hit metric', () => { }; init({ fieldName: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe(null); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toBe(null); }); // it('should return undefined if the field does not appear in the source', () => { @@ -159,7 +167,7 @@ describe('Top hit metric', () => { }; init({ fieldName: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe(undefined); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toBe(undefined); }); it('should return the field value from the top hit', () => { @@ -178,7 +186,7 @@ describe('Top hit metric', () => { }; init({ fieldName: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe('aaa'); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toBe('aaa'); }); it('should return the object if the field value is an object', () => { @@ -200,7 +208,9 @@ describe('Top hit metric', () => { init({ fieldName: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual({ label: 'aaa' }); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toEqual({ + label: 'aaa', + }); }); it('should return an array if the field has more than one values', () => { @@ -219,7 +229,10 @@ describe('Top hit metric', () => { }; init({ fieldName: '@tags' }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual(['aaa', 'bbb']); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toEqual([ + 'aaa', + 'bbb', + ]); }); it('should return undefined if the field is not in the source nor in the doc_values field', () => { @@ -241,7 +254,7 @@ describe('Top hit metric', () => { }; init({ fieldName: 'machine.os.raw', readFromDocValues: true }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toBe(undefined); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toBe(undefined); }); describe('Multivalued field and first/last X docs', () => { @@ -250,7 +263,9 @@ describe('Top hit metric', () => { fieldName: 'bytes', size: 2, }); - expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('Last 2 bytes'); + expect(getTopHitMetricAgg(aggTypesDependencies).makeLabel(aggConfig)).toEqual( + 'Last 2 bytes' + ); }); it('should return a label prefixed with First X docs if sorting in ascending order', () => { @@ -259,7 +274,9 @@ describe('Top hit metric', () => { size: 2, sortOrder: 'asc', }); - expect(topHitMetricAgg.makeLabel(aggConfig)).toEqual('First 2 bytes'); + expect(getTopHitMetricAgg(aggTypesDependencies).makeLabel(aggConfig)).toEqual( + 'First 2 bytes' + ); }); [ @@ -334,7 +351,9 @@ describe('Top hit metric', () => { }; init({ fieldName: 'bytes', aggregate: agg.type }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual(agg.result); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toEqual( + agg.result + ); }); it(`should return the result of the ${agg.type} aggregation over the last X docs - ${agg.description}`, () => { @@ -358,7 +377,9 @@ describe('Top hit metric', () => { }; init({ fieldName: 'bytes', aggregate: agg.type }); - expect(topHitMetricAgg.getValue(aggConfig, bucket)).toEqual(agg.result); + expect(getTopHitMetricAgg(aggTypesDependencies).getValue(aggConfig, bucket)).toEqual( + agg.result + ); }); }); }); diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.ts b/src/plugins/data/public/search/aggs/metrics/top_hit.ts index d0c668c577e62..15da2b485aee7 100644 --- a/src/plugins/data/public/search/aggs/metrics/top_hit.ts +++ b/src/plugins/data/public/search/aggs/metrics/top_hit.ts @@ -22,6 +22,11 @@ import { i18n } from '@kbn/i18n'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { KBN_FIELD_TYPES } from '../../../../common'; +import { GetInternalStartServicesFn } from '../../../types'; + +export interface TopHitMetricAggDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} const isNumericFieldSelected = (agg: IMetricAggConfig) => { const field = agg.getParam('field'); @@ -29,214 +34,225 @@ const isNumericFieldSelected = (agg: IMetricAggConfig) => { return field && field.type && field.type === KBN_FIELD_TYPES.NUMBER; }; -export const topHitMetricAgg = new MetricAggType({ - name: METRIC_TYPES.TOP_HITS, - title: i18n.translate('data.search.aggs.metrics.topHitTitle', { - defaultMessage: 'Top Hit', - }), - makeLabel(aggConfig) { - const lastPrefixLabel = i18n.translate('data.search.aggs.metrics.topHit.lastPrefixLabel', { - defaultMessage: 'Last', - }); - const firstPrefixLabel = i18n.translate('data.search.aggs.metrics.topHit.firstPrefixLabel', { - defaultMessage: 'First', - }); - - let prefix = - aggConfig.getParam('sortOrder').value === 'desc' ? lastPrefixLabel : firstPrefixLabel; - - const size = aggConfig.getParam('size'); - - if (size !== 1) { - prefix += ` ${size}`; - } - - const field = aggConfig.getParam('field'); - - return `${prefix} ${field ? field.displayName : ''}`; - }, - params: [ +export const getTopHitMetricAgg = ({ getInternalStartServices }: TopHitMetricAggDependencies) => { + return new MetricAggType( { - name: 'field', - type: 'field', - onlyAggregatable: false, - filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter( - type => type !== KBN_FIELD_TYPES.HISTOGRAM - ), - write(agg, output) { - const field = agg.getParam('field'); - output.params = {}; - - if (field.scripted) { - output.params.script_fields = { - [field.name]: { - script: { - source: field.script, - lang: field.lang, - }, - }, - }; - } else { - if (field.readFromDocValues) { - // always format date fields as date_time to avoid - // displaying unformatted dates like epoch_millis - // or other not-accepted momentjs formats - const format = field.type === KBN_FIELD_TYPES.DATE ? 'date_time' : 'use_field_mapping'; - output.params.docvalue_fields = [{ field: field.name, format }]; + name: METRIC_TYPES.TOP_HITS, + title: i18n.translate('data.search.aggs.metrics.topHitTitle', { + defaultMessage: 'Top Hit', + }), + makeLabel(aggConfig) { + const lastPrefixLabel = i18n.translate('data.search.aggs.metrics.topHit.lastPrefixLabel', { + defaultMessage: 'Last', + }); + const firstPrefixLabel = i18n.translate( + 'data.search.aggs.metrics.topHit.firstPrefixLabel', + { + defaultMessage: 'First', } - output.params._source = field.name === '_source' ? true : field.name; + ); + + let prefix = + aggConfig.getParam('sortOrder').value === 'desc' ? lastPrefixLabel : firstPrefixLabel; + + const size = aggConfig.getParam('size'); + + if (size !== 1) { + prefix += ` ${size}`; } + + const field = aggConfig.getParam('field'); + + return `${prefix} ${field ? field.displayName : ''}`; }, - }, - { - name: 'aggregate', - type: 'optioned', - options: [ + params: [ { - text: i18n.translate('data.search.aggs.metrics.topHit.minLabel', { - defaultMessage: 'Min', - }), - isCompatible: isNumericFieldSelected, - disabled: true, - value: 'min', - }, - { - text: i18n.translate('data.search.aggs.metrics.topHit.maxLabel', { - defaultMessage: 'Max', - }), - isCompatible: isNumericFieldSelected, - disabled: true, - value: 'max', + name: 'field', + type: 'field', + onlyAggregatable: false, + filterFieldTypes: Object.values(KBN_FIELD_TYPES).filter( + type => type !== KBN_FIELD_TYPES.HISTOGRAM + ), + write(agg, output) { + const field = agg.getParam('field'); + output.params = {}; + + if (field.scripted) { + output.params.script_fields = { + [field.name]: { + script: { + source: field.script, + lang: field.lang, + }, + }, + }; + } else { + if (field.readFromDocValues) { + // always format date fields as date_time to avoid + // displaying unformatted dates like epoch_millis + // or other not-accepted momentjs formats + const format = + field.type === KBN_FIELD_TYPES.DATE ? 'date_time' : 'use_field_mapping'; + output.params.docvalue_fields = [{ field: field.name, format }]; + } + output.params._source = field.name === '_source' ? true : field.name; + } + }, }, { - text: i18n.translate('data.search.aggs.metrics.topHit.sumLabel', { - defaultMessage: 'Sum', - }), - isCompatible: isNumericFieldSelected, - disabled: true, - value: 'sum', + name: 'aggregate', + type: 'optioned', + options: [ + { + text: i18n.translate('data.search.aggs.metrics.topHit.minLabel', { + defaultMessage: 'Min', + }), + isCompatible: isNumericFieldSelected, + disabled: true, + value: 'min', + }, + { + text: i18n.translate('data.search.aggs.metrics.topHit.maxLabel', { + defaultMessage: 'Max', + }), + isCompatible: isNumericFieldSelected, + disabled: true, + value: 'max', + }, + { + text: i18n.translate('data.search.aggs.metrics.topHit.sumLabel', { + defaultMessage: 'Sum', + }), + isCompatible: isNumericFieldSelected, + disabled: true, + value: 'sum', + }, + { + text: i18n.translate('data.search.aggs.metrics.topHit.averageLabel', { + defaultMessage: 'Average', + }), + isCompatible: isNumericFieldSelected, + disabled: true, + value: 'average', + }, + { + text: i18n.translate('data.search.aggs.metrics.topHit.concatenateLabel', { + defaultMessage: 'Concatenate', + }), + isCompatible(aggConfig: IMetricAggConfig) { + return _.get(aggConfig.params, 'field.filterFieldTypes', '*') === '*'; + }, + disabled: true, + value: 'concat', + }, + ], + write: _.noop, }, { - text: i18n.translate('data.search.aggs.metrics.topHit.averageLabel', { - defaultMessage: 'Average', - }), - isCompatible: isNumericFieldSelected, - disabled: true, - value: 'average', + name: 'size', + default: 1, }, { - text: i18n.translate('data.search.aggs.metrics.topHit.concatenateLabel', { - defaultMessage: 'Concatenate', - }), - isCompatible(aggConfig: IMetricAggConfig) { - return _.get(aggConfig.params, 'field.filterFieldTypes', '*') === '*'; + name: 'sortField', + type: 'field', + filterFieldTypes: [ + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.DATE, + KBN_FIELD_TYPES.IP, + KBN_FIELD_TYPES.STRING, + ], + default(agg: IMetricAggConfig) { + return agg.getIndexPattern().timeFieldName; }, - disabled: true, - value: 'concat', - }, - ], - write: _.noop, - }, - { - name: 'size', - default: 1, - }, - { - name: 'sortField', - type: 'field', - filterFieldTypes: [ - KBN_FIELD_TYPES.NUMBER, - KBN_FIELD_TYPES.DATE, - KBN_FIELD_TYPES.IP, - KBN_FIELD_TYPES.STRING, - ], - default(agg: IMetricAggConfig) { - return agg.getIndexPattern().timeFieldName; - }, - write: _.noop, // prevent default write, it is handled below - }, - { - name: 'sortOrder', - type: 'optioned', - default: 'desc', - options: [ - { - text: i18n.translate('data.search.aggs.metrics.topHit.descendingLabel', { - defaultMessage: 'Descending', - }), - value: 'desc', + write: _.noop, // prevent default write, it is handled below }, { - text: i18n.translate('data.search.aggs.metrics.topHit.ascendingLabel', { - defaultMessage: 'Ascending', - }), - value: 'asc', - }, - ], - write(agg, output) { - const sortField = agg.params.sortField; - const sortOrder = agg.params.sortOrder; - - if (sortField.scripted) { - output.params.sort = [ + name: 'sortOrder', + type: 'optioned', + default: 'desc', + options: [ { - _script: { - script: { - source: sortField.script, - lang: sortField.lang, - }, - type: sortField.type, - order: sortOrder.value, - }, + text: i18n.translate('data.search.aggs.metrics.topHit.descendingLabel', { + defaultMessage: 'Descending', + }), + value: 'desc', }, - ]; - } else { - output.params.sort = [ { - [sortField.name]: { - order: sortOrder.value, - }, + text: i18n.translate('data.search.aggs.metrics.topHit.ascendingLabel', { + defaultMessage: 'Ascending', + }), + value: 'asc', }, - ]; + ], + write(agg, output) { + const sortField = agg.params.sortField; + const sortOrder = agg.params.sortOrder; + + if (sortField.scripted) { + output.params.sort = [ + { + _script: { + script: { + source: sortField.script, + lang: sortField.lang, + }, + type: sortField.type, + order: sortOrder.value, + }, + }, + ]; + } else { + output.params.sort = [ + { + [sortField.name]: { + order: sortOrder.value, + }, + }, + ]; + } + }, + }, + ], + getValue(agg, bucket) { + const hits: any[] = _.get(bucket, `${agg.id}.hits.hits`); + if (!hits || !hits.length) { + return null; } - }, - }, - ], - getValue(agg, bucket) { - const hits: any[] = _.get(bucket, `${agg.id}.hits.hits`); - if (!hits || !hits.length) { - return null; - } - const path = agg.getParam('field').name; + const path = agg.getParam('field').name; - let values = _.flatten( - hits.map(hit => - path === '_source' ? hit._source : agg.getIndexPattern().flattenHit(hit, true)[path] - ) - ); + let values = _.flatten( + hits.map(hit => + path === '_source' ? hit._source : agg.getIndexPattern().flattenHit(hit, true)[path] + ) + ); - if (values.length === 1) { - values = values[0]; - } + if (values.length === 1) { + values = values[0]; + } + + if (Array.isArray(values)) { + if (!_.compact(values).length) { + return null; + } + + const aggregate = agg.getParam('aggregate'); - if (Array.isArray(values)) { - if (!_.compact(values).length) { - return null; - } - - const aggregate = agg.getParam('aggregate'); - - switch (aggregate.value) { - case 'max': - return _.max(values); - case 'min': - return _.min(values); - case 'sum': - return _.sum(values); - case 'average': - return _.sum(values) / values.length; - } + switch (aggregate.value) { + case 'max': + return _.max(values); + case 'min': + return _.min(values); + case 'sum': + return _.sum(values); + case 'average': + return _.sum(values) / values.length; + } + } + return values; + }, + }, + { + getInternalStartServices, } - return values; - }, -}); + ); +}; diff --git a/src/plugins/data/public/search/aggs/param_types/field.test.ts b/src/plugins/data/public/search/aggs/param_types/field.test.ts index 0182471392910..ea7931130b84a 100644 --- a/src/plugins/data/public/search/aggs/param_types/field.test.ts +++ b/src/plugins/data/public/search/aggs/param_types/field.test.ts @@ -18,11 +18,20 @@ */ import { BaseParamType } from './base'; -import { FieldParamType } from './field'; +import { FieldParamType, FieldParamTypeDependencies } from './field'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../../../../common'; import { IAggConfig } from '../agg_config'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; +import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('Field', () => { + const fieldParamTypeDependencies: FieldParamTypeDependencies = { + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), + }; + const indexPattern = { id: '1234', title: 'logstash-*', @@ -52,10 +61,13 @@ describe('Field', () => { describe('constructor', () => { it('it is an instance of BaseParamType', () => { - const aggParam = new FieldParamType({ - name: 'field', - type: 'field', - }); + const aggParam = new FieldParamType( + { + name: 'field', + type: 'field', + }, + fieldParamTypeDependencies + ); expect(aggParam instanceof BaseParamType).toBeTruthy(); }); @@ -63,10 +75,13 @@ describe('Field', () => { describe('getAvailableFields', () => { it('should return only aggregatable fields by default', () => { - const aggParam = new FieldParamType({ - name: 'field', - type: 'field', - }); + const aggParam = new FieldParamType( + { + name: 'field', + type: 'field', + }, + fieldParamTypeDependencies + ); const fields = aggParam.getAvailableFields(agg); @@ -78,10 +93,13 @@ describe('Field', () => { }); it('should return all fields if onlyAggregatable is false', () => { - const aggParam = new FieldParamType({ - name: 'field', - type: 'field', - }); + const aggParam = new FieldParamType( + { + name: 'field', + type: 'field', + }, + fieldParamTypeDependencies + ); aggParam.onlyAggregatable = false; @@ -91,10 +109,13 @@ describe('Field', () => { }); it('should return all fields if filterFieldTypes was not specified', () => { - const aggParam = new FieldParamType({ - name: 'field', - type: 'field', - }); + const aggParam = new FieldParamType( + { + name: 'field', + type: 'field', + }, + fieldParamTypeDependencies + ); indexPattern.fields[1].aggregatable = true; diff --git a/src/plugins/data/public/search/aggs/param_types/field.ts b/src/plugins/data/public/search/aggs/param_types/field.ts index 34b77e14a3a71..4d67f41905c5a 100644 --- a/src/plugins/data/public/search/aggs/param_types/field.ts +++ b/src/plugins/data/public/search/aggs/param_types/field.ts @@ -24,7 +24,7 @@ import { BaseParamType } from './base'; import { propFilter } from '../filter'; import { isNestedField, KBN_FIELD_TYPES } from '../../../../common'; import { Field as IndexPatternField } from '../../../index_patterns'; -import { getNotifications } from '../../../../public/services'; +import { GetInternalStartServicesFn } from '../../../types'; const filterByType = propFilter('type'); @@ -32,13 +32,20 @@ export type FieldTypes = KBN_FIELD_TYPES | KBN_FIELD_TYPES[] | '*'; // TODO need to make a more explicit interface for this export type IFieldParamType = FieldParamType; +export interface FieldParamTypeDependencies { + getInternalStartServices: GetInternalStartServicesFn; +} + export class FieldParamType extends BaseParamType { required = true; scriptable = true; filterFieldTypes: FieldTypes; onlyAggregatable: boolean; - constructor(config: Record) { + constructor( + config: Record, + { getInternalStartServices }: FieldParamTypeDependencies + ) { super(config); this.filterFieldTypes = config.filterFieldTypes || '*'; @@ -87,7 +94,7 @@ export class FieldParamType extends BaseParamType { // @ts-ignore const validField = this.getAvailableFields(aggConfig).find((f: any) => f.name === fieldName); if (!validField) { - getNotifications().toasts.addDanger( + getInternalStartServices().notifications.toasts.addDanger( i18n.translate( 'data.search.aggs.paramTypes.field.invalidSavedFieldParameterErrorMessage', { diff --git a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts index 57d27b7da6313..2383affa2a8c5 100644 --- a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts +++ b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts @@ -18,12 +18,13 @@ */ // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { coreMock, notificationServiceMock } from '../../../../../../../src/core/public/mocks'; import { AggTypesRegistry, AggTypesRegistryStart } from '../agg_types_registry'; import { getAggTypes } from '../agg_types'; -import { BucketAggType } from '../buckets/_bucket_agg_type'; +import { BucketAggType } from '../buckets/bucket_agg_type'; import { MetricAggType } from '../metrics/metric_agg_type'; import { queryServiceMock } from '../../../query/mocks'; +import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; /** * Testing utility which creates a new instance of AggTypesRegistry, @@ -55,8 +56,11 @@ export function mockAggTypesRegistry | MetricAggTyp const core = coreMock.createSetup(); const aggTypes = getAggTypes({ uiSettings: core.uiSettings, - notifications: core.notifications, query: queryServiceMock.createSetupContract(), + getInternalStartServices: () => ({ + fieldFormats: fieldFormatsServiceMock.createStartContract(), + notifications: notificationServiceMock.createStartContract(), + }), }); aggTypes.buckets.forEach(type => registrySetup.registerBucket(type)); diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index dc1c99f76d59a..42f31ef450d28 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -26,6 +26,7 @@ import { getEsClient, LegacyApiCaller } from './es_client'; import { ES_SEARCH_STRATEGY, DEFAULT_SEARCH_STRATEGY } from '../../common/search'; import { esSearchStrategyProvider } from './es_search/es_search_strategy'; import { QuerySetup } from '../query/query_service'; +import { GetInternalStartServicesFn } from '../types'; import { SearchInterceptor } from './search_interceptor'; import { getAggTypes, @@ -44,6 +45,7 @@ import { interface SearchServiceSetupDependencies { packageInfo: PackageInfo; query: QuerySetup; + getInternalStartServices: GetInternalStartServicesFn; } /** @@ -81,7 +83,7 @@ export class SearchService implements Plugin { public setup( core: CoreSetup, - { packageInfo, query }: SearchServiceSetupDependencies + { packageInfo, query, getInternalStartServices }: SearchServiceSetupDependencies ): ISearchSetup { this.esClient = getEsClient(core.injectedMetadata, core.http, packageInfo); this.registerSearchStrategyProvider(SYNC_SEARCH_STRATEGY, syncSearchStrategyProvider); @@ -91,7 +93,7 @@ export class SearchService implements Plugin { const aggTypes = getAggTypes({ query, uiSettings: core.uiSettings, - notifications: core.notifications, + getInternalStartServices, }); aggTypes.buckets.forEach(b => aggTypesSetup.registerBucket(b)); diff --git a/src/plugins/data/public/types.ts b/src/plugins/data/public/types.ts index 45160cbf30179..e24e01d241278 100644 --- a/src/plugins/data/public/types.ts +++ b/src/plugins/data/public/types.ts @@ -71,3 +71,12 @@ export interface IDataPluginServices extends Partial { storage: IStorageWrapper; data: DataPublicPluginStart; } + +/** @internal **/ +export interface InternalStartServices { + fieldFormats: FieldFormatsStart; + notifications: CoreStart['notifications']; +} + +/** @internal **/ +export type GetInternalStartServicesFn = () => InternalStartServices; diff --git a/src/plugins/data/server/index_patterns/routes.ts b/src/plugins/data/server/index_patterns/routes.ts index 8f017a73083ec..8b9fa28c77165 100644 --- a/src/plugins/data/server/index_patterns/routes.ts +++ b/src/plugins/data/server/index_patterns/routes.ts @@ -70,7 +70,22 @@ export function registerRoutes(http: HttpServiceSetup) { }, }); } catch (error) { - return response.notFound(); + if ( + typeof error === 'object' && + !!error?.isBoom && + !!error?.output?.payload && + typeof error?.output?.payload === 'object' + ) { + const payload = error?.output?.payload; + return response.notFound({ + body: { + message: payload.message, + attributes: payload, + }, + }); + } else { + return response.notFound(); + } } } ); diff --git a/src/plugins/data/server/saved_objects/search_migrations.test.ts b/src/plugins/data/server/saved_objects/search_migrations.test.ts index 7fdf2e14aefed..f9b4af7d6d2bf 100644 --- a/src/plugins/data/server/saved_objects/search_migrations.test.ts +++ b/src/plugins/data/server/saved_objects/search_migrations.test.ts @@ -23,6 +23,38 @@ import { searchSavedObjectTypeMigrations } from './search_migrations'; const savedObjectMigrationContext = (null as unknown) as SavedObjectMigrationContext; describe('migration search', () => { + describe('6.7.2', () => { + const migrationFn = searchSavedObjectTypeMigrations['6.7.2']; + + it('should migrate obsolete match_all query', () => { + const migratedDoc = migrationFn( + { + type: 'search', + attributes: { + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + query: { + match_all: {}, + }, + }), + }, + }, + }, + savedObjectMigrationContext + ); + const migratedSearchSource = JSON.parse( + migratedDoc.attributes.kibanaSavedObjectMeta.searchSourceJSON + ); + + expect(migratedSearchSource).toEqual({ + query: { + query: '', + language: 'kuery', + }, + }); + }); + }); + describe('7.0.0', () => { const migrationFn = searchSavedObjectTypeMigrations['7.0.0']; diff --git a/src/plugins/data/server/saved_objects/search_migrations.ts b/src/plugins/data/server/saved_objects/search_migrations.ts index db545e52ce170..45fa5e11e2a3d 100644 --- a/src/plugins/data/server/saved_objects/search_migrations.ts +++ b/src/plugins/data/server/saved_objects/search_migrations.ts @@ -19,6 +19,41 @@ import { flow, get } from 'lodash'; import { SavedObjectMigrationFn } from 'kibana/server'; +import { DEFAULT_QUERY_LANGUAGE } from '../../common'; + +const migrateMatchAllQuery: SavedObjectMigrationFn = doc => { + const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); + + if (searchSourceJSON) { + let searchSource: any; + + try { + searchSource = JSON.parse(searchSourceJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + + if (searchSource.query?.match_all) { + return { + ...doc, + attributes: { + ...doc.attributes, + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + ...searchSource, + query: { + query: '', + language: DEFAULT_QUERY_LANGUAGE, + }, + }), + }, + }, + }; + } + } + + return doc; +}; const migrateIndexPattern: SavedObjectMigrationFn = doc => { const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); @@ -87,6 +122,7 @@ const migrateSearchSortToNestedArray: SavedObjectMigrationFn = doc => { }; export const searchSavedObjectTypeMigrations = { + '6.7.2': flow(migrateMatchAllQuery), '7.0.0': flow(setNewReferences), '7.4.0': flow(migrateSearchSortToNestedArray), }; diff --git a/src/plugins/discover/public/services.ts b/src/plugins/discover/public/services.ts index 3a28759d82b71..37e2144800ea1 100644 --- a/src/plugins/discover/public/services.ts +++ b/src/plugins/discover/public/services.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createGetterSetter } from '../../kibana_utils/common'; +import { createGetterSetter } from '../../kibana_utils/public'; import { DocViewsRegistry } from './doc_views/doc_views_registry'; export const [getDocViewsRegistry, setDocViewsRegistry] = createGetterSetter( diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 23275fbe8e8f0..3f671b520fb2e 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -23,23 +23,24 @@ import { PluginInitializerContext } from 'src/core/public'; import { EmbeddablePublicPlugin } from './plugin'; export { - Adapters, ACTION_ADD_PANEL, - AddPanelAction, ACTION_APPLY_FILTER, + ACTION_EDIT_PANEL, + Adapters, + AddPanelAction, Container, ContainerInput, ContainerOutput, CONTEXT_MENU_TRIGGER, contextMenuTrigger, - ACTION_EDIT_PANEL, + defaultEmbeddableFactoryProvider, EditPanelAction, Embeddable, EmbeddableChildPanel, EmbeddableChildPanelProps, EmbeddableContext, - EmbeddableFactoryDefinition, EmbeddableFactory, + EmbeddableFactoryDefinition, EmbeddableFactoryNotFoundError, EmbeddableFactoryRenderer, EmbeddableInput, diff --git a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx index 9e47da5cea032..2a0ffd723850b 100644 --- a/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/containers/embeddable_child_panel.test.tsx @@ -29,7 +29,7 @@ import { ContactCardEmbeddable, } from '../test_samples/embeddables/contact_card/contact_card_embeddable'; // eslint-disable-next-line -import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; +import { inspectorPluginMock } from '../../../../inspector/public/mocks'; import { mount } from 'enzyme'; import { embeddablePluginMock } from '../../mocks'; diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx index e3a6a3359132c..3c5374d6c124c 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.tsx @@ -22,12 +22,7 @@ import { Adapters, ViewMode } from '../types'; import { IContainer } from '../containers'; import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable'; import { TriggerContextMapping } from '../ui_actions'; -import { EmbeddableActionStorage } from './embeddable_action_storage'; -import { - UiActionsDynamicActionManager, - UiActionsStart, -} from '../../../../../plugins/ui_actions/public'; -import { EmbeddableContext } from '../triggers'; +import { UiActionsStart } from '../../../../../plugins/ui_actions/public'; function getPanelTitle(input: EmbeddableInput, output: EmbeddableOutput) { return input.hidePanelTitles ? '' : input.title === undefined ? output.defaultTitle : input.title; @@ -60,28 +55,9 @@ export abstract class Embeddable< // to update input when the parent changes. private parentSubscription?: Rx.Subscription; - private storageSubscription?: Rx.Subscription; - // TODO: Rename to destroyed. private destoyed: boolean = false; - private storage = new EmbeddableActionStorage((this as unknown) as Embeddable); - - private cachedDynamicActions?: UiActionsDynamicActionManager; - public get dynamicActions(): UiActionsDynamicActionManager | undefined { - if (!this.params.uiActions) return undefined; - if (!this.cachedDynamicActions) { - this.cachedDynamicActions = new UiActionsDynamicActionManager({ - isCompatible: async (context: unknown) => - (context as EmbeddableContext).embeddable.runtimeId === this.runtimeId, - storage: this.storage, - uiActions: this.params.uiActions, - }); - } - - return this.cachedDynamicActions; - } - constructor( input: TEmbeddableInput, output: TEmbeddableOutput, @@ -111,18 +87,6 @@ export abstract class Embeddable< this.onResetInput(newInput); }); } - - if (this.dynamicActions) { - this.dynamicActions.start().catch(error => { - /* eslint-disable */ - console.log('Failed to start embeddable dynamic actions', this); - console.error(error); - /* eslint-enable */ - }); - this.storageSubscription = this.input$.subscribe(() => { - this.storage.reload$.next(); - }); - } } public getIsContainer(): this is IContainer { @@ -202,19 +166,6 @@ export abstract class Embeddable< public destroy(): void { this.destoyed = true; - if (this.dynamicActions) { - this.dynamicActions.stop().catch(error => { - /* eslint-disable */ - console.log('Failed to stop embeddable dynamic actions', this); - console.error(error); - /* eslint-enable */ - }); - } - - if (this.storageSubscription) { - this.storageSubscription.unsubscribe(); - } - this.input$.complete(); this.output$.complete(); diff --git a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx new file mode 100644 index 0000000000000..715827a72c61b --- /dev/null +++ b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.test.tsx @@ -0,0 +1,59 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { ErrorEmbeddable } from './error_embeddable'; +import { EmbeddableRoot } from './embeddable_root'; +import { mount } from 'enzyme'; + +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); +}); + +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); +}); diff --git a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx index 2c2c47775369d..cdbe7af98a4f4 100644 --- a/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/error_embeddable.tsx @@ -20,6 +20,7 @@ import { EuiText, EuiIcon, EuiSpacer } from '@elastic/eui'; import React from 'react'; import ReactDOM from 'react-dom'; +import { Markdown } from '../../../../kibana_react/public'; import { Embeddable } from './embeddable'; import { EmbeddableInput, EmbeddableOutput, IEmbeddable } from './i_embeddable'; import { IContainer } from '../containers'; @@ -53,7 +54,11 @@ export class ErrorEmbeddable extends Embeddable - {title} + , dom diff --git a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts index 9a4452aceba00..e5d8e6e1b7de9 100644 --- a/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts +++ b/src/plugins/embeddable/public/lib/embeddables/i_embeddable.ts @@ -18,7 +18,6 @@ */ import { Observable } from 'rxjs'; -import { UiActionsDynamicActionManager } from '../../../../../plugins/ui_actions/public'; import { Adapters } from '../types'; import { IContainer } from '../containers/i_container'; import { ViewMode } from '../types'; @@ -92,9 +91,9 @@ export interface IEmbeddable< readonly runtimeId?: number; /** - * Default implementation of dynamic action API for embeddables. + * Extra abilities added to Embeddable by `*_enhanced` plugins. */ - dynamicActions?: UiActionsDynamicActionManager; + enhancements?: object; /** * A functional representation of the isContainer variable, but helpful for typescript to diff --git a/src/plugins/embeddable/public/lib/embeddables/with_subscription.tsx b/src/plugins/embeddable/public/lib/embeddables/with_subscription.tsx index 47b8001961cf5..9bc5889715c76 100644 --- a/src/plugins/embeddable/public/lib/embeddables/with_subscription.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/with_subscription.tsx @@ -23,18 +23,19 @@ import { IEmbeddable, EmbeddableInput, EmbeddableOutput } from './i_embeddable'; export const withEmbeddableSubscription = < I extends EmbeddableInput, O extends EmbeddableOutput, - E extends IEmbeddable = IEmbeddable + E extends IEmbeddable = IEmbeddable, + ExtraProps = {} >( - WrappedComponent: React.ComponentType<{ input: I; output: O; embeddable: E }> -): React.ComponentType<{ embeddable: E }> => + WrappedComponent: React.ComponentType<{ input: I; output: O; embeddable: E } & ExtraProps> +): React.ComponentType<{ embeddable: E } & ExtraProps> => class WithEmbeddableSubscription extends React.Component< - { embeddable: E }, + { embeddable: E } & ExtraProps, { input: I; output: O } > { private subscription?: Rx.Subscription; private mounted: boolean = false; - constructor(props: { embeddable: E }) { + constructor(props: { embeddable: E } & ExtraProps) { super(props); this.state = { input: this.props.embeddable.getInput(), @@ -71,6 +72,7 @@ export const withEmbeddableSubscription = < input={this.state.input} output={this.state.output} embeddable={this.props.embeddable} + {...this.props} /> ); } diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index c588f9613ed9d..c0934451d9df4 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -25,7 +25,7 @@ import { nextTick } from 'test_utils/enzyme_helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { I18nProvider } from '@kbn/i18n/react'; import { CONTEXT_MENU_TRIGGER } from '../triggers'; -import { Action, UiActionsStart, ActionType } from 'src/plugins/ui_actions/public'; +import { Action, UiActionsStart, ActionType } from '../../../../ui_actions/public'; import { Trigger, ViewMode } from '../types'; import { isErrorEmbeddable } from '../embeddables'; import { EmbeddablePanel } from './embeddable_panel'; @@ -41,7 +41,7 @@ import { ContactCardEmbeddableOutput, } from '../test_samples/embeddables/contact_card/contact_card_embeddable'; // eslint-disable-next-line -import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; +import { inspectorPluginMock } from '../../../../inspector/public/mocks'; import { EuiBadge } from '@elastic/eui'; import { embeddablePluginMock } from '../../mocks'; diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index c6537f2d94994..d4d23874cfb19 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -203,13 +203,15 @@ export class EmbeddablePanel extends React.Component { this.props.embeddable.render(this.embeddableRoot.current); } - const dynamicActions = this.props.embeddable.dynamicActions; + const dynamicActions = (this.props.embeddable.enhancements as any)?.dynamicActions; if (dynamicActions) { this.setState({ eventCount: dynamicActions.state.get().events.length }); - this.eventCountSubscription = dynamicActions.state.state$.subscribe(({ events }) => { - if (!this.mounted) return; - this.setState({ eventCount: events.length }); - }); + this.eventCountSubscription = dynamicActions.state.state$.subscribe( + ({ events }: { events: unknown[] }) => { + if (!this.mounted) return; + this.setState({ eventCount: events.length }); + } + ); } } diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx index 06c47bd1bcad8..50d8bcef8506c 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx @@ -29,7 +29,7 @@ import { EuiTitle, } from '@elastic/eui'; -import { EmbeddableStart } from 'src/plugins/embeddable/public/plugin'; +import { EmbeddableStart } from 'src/plugins/embeddable/public'; import { IContainer } from '../../../../containers'; import { EmbeddableFactoryNotFoundError } from '../../../../errors'; import { SavedObjectFinderCreateNew } from './saved_object_finder_create_new'; diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx index ee31127cb5a40..491eaad9faefa 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx @@ -27,7 +27,7 @@ import { ContactCardEmbeddable, } from '../../../test_samples'; // eslint-disable-next-line -import { inspectorPluginMock } from 'src/plugins/inspector/public/mocks'; +import { inspectorPluginMock } from '../../../../../../../plugins/inspector/public/mocks'; import { EmbeddableOutput, isErrorEmbeddable, ErrorEmbeddable } from '../../../embeddables'; import { of } from '../../../../tests/helpers'; import { esFilters } from '../../../../../../../plugins/data/public'; diff --git a/src/plugins/embeddable/public/mocks.ts b/src/plugins/embeddable/public/mocks.ts index 2ee05d8316ace..65b15f3a7614f 100644 --- a/src/plugins/embeddable/public/mocks.ts +++ b/src/plugins/embeddable/public/mocks.ts @@ -16,11 +16,12 @@ * specific language governing permissions and limitations * under the License. */ - import { EmbeddableStart, EmbeddableSetup } from '.'; import { EmbeddablePublicPlugin } from './plugin'; import { coreMock } from '../../../core/public/mocks'; +// eslint-disable-next-line +import { inspectorPluginMock } from '../../inspector/public/mocks'; // eslint-disable-next-line import { uiActionsPluginMock } from '../../ui_actions/public/mocks'; @@ -39,6 +40,7 @@ const createStartContract = (): Start => { const startContract: Start = { getEmbeddableFactories: jest.fn(), getEmbeddableFactory: jest.fn(), + EmbeddablePanel: jest.fn(), }; return startContract; }; @@ -48,7 +50,11 @@ const createInstance = () => { const setup = plugin.setup(coreMock.createSetup(), { uiActions: uiActionsPluginMock.createSetupContract(), }); - const doStart = () => plugin.start(coreMock.createStart()); + const doStart = () => + plugin.start(coreMock.createStart(), { + uiActions: uiActionsPluginMock.createStartContract(), + inspector: inspectorPluginMock.createStartContract(), + }); return { plugin, setup, diff --git a/src/plugins/embeddable/public/plugin.ts b/src/plugins/embeddable/public/plugin.tsx similarity index 76% rename from src/plugins/embeddable/public/plugin.ts rename to src/plugins/embeddable/public/plugin.tsx index a483f90f76dde..01fbf52c80182 100644 --- a/src/plugins/embeddable/public/plugin.ts +++ b/src/plugins/embeddable/public/plugin.tsx @@ -16,7 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { UiActionsSetup } from 'src/plugins/ui_actions/public'; +import React from 'react'; +import { getSavedObjectFinder } from '../../saved_objects/public'; +import { UiActionsSetup, UiActionsStart } from '../../ui_actions/public'; +import { Start as InspectorStart } from '../../inspector/public'; import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/public'; import { EmbeddableFactoryRegistry, EmbeddableFactoryProvider } from './types'; import { bootstrap } from './bootstrap'; @@ -26,6 +29,7 @@ import { EmbeddableOutput, defaultEmbeddableFactoryProvider, IEmbeddable, + EmbeddablePanel, } from './lib'; import { EmbeddableFactoryDefinition } from './lib/embeddables/embeddable_factory_definition'; @@ -33,6 +37,11 @@ export interface EmbeddableSetupDependencies { uiActions: UiActionsSetup; } +export interface EmbeddableStartDependencies { + uiActions: UiActionsStart; + inspector: InspectorStart; +} + export interface EmbeddableSetup { registerEmbeddableFactory: ( id: string, @@ -50,6 +59,7 @@ export interface EmbeddableStart { embeddableFactoryId: string ) => EmbeddableFactory | undefined; getEmbeddableFactories: () => IterableIterator; + EmbeddablePanel: React.FC<{ embeddable: IEmbeddable; hideHeader?: boolean }>; } export class EmbeddablePublicPlugin implements Plugin { @@ -78,7 +88,10 @@ export class EmbeddablePublicPlugin implements Plugin { this.embeddableFactories.set( def.type, @@ -89,15 +102,36 @@ export class EmbeddablePublicPlugin implements Plugin { - this.ensureFactoriesExist(); - return this.embeddableFactories.values(); - }, + getEmbeddableFactories: this.getEmbeddableFactories, + EmbeddablePanel: ({ + embeddable, + hideHeader, + }: { + embeddable: IEmbeddable; + hideHeader?: boolean; + }) => ( + + ), }; } public stop() {} + private getEmbeddableFactories = () => { + this.ensureFactoriesExist(); + return this.embeddableFactories.values(); + }; + private registerEmbeddableFactory = ( embeddableFactoryId: string, factory: EmbeddableFactoryDefinition @@ -130,11 +164,11 @@ export class EmbeddablePublicPlugin implements Plugin { this.embeddableFactoryDefinitions.forEach(def => this.ensureFactoryExists(def.type)); - } + }; - private ensureFactoryExists(type: string) { + private ensureFactoryExists = (type: string) => { if (!this.embeddableFactories.get(type)) { const def = this.embeddableFactoryDefinitions.get(type); if (!def) return; @@ -145,5 +179,5 @@ export class EmbeddablePublicPlugin implements Plugin { diff --git a/src/plugins/embeddable/public/tests/test_plugin.ts b/src/plugins/embeddable/public/tests/test_plugin.ts index e199ef193aa1c..e13a906e30338 100644 --- a/src/plugins/embeddable/public/tests/test_plugin.ts +++ b/src/plugins/embeddable/public/tests/test_plugin.ts @@ -18,9 +18,11 @@ */ import { CoreSetup, CoreStart } from 'src/core/public'; +import { UiActionsStart } from '../../../ui_actions/public'; // eslint-disable-next-line -import { uiActionsPluginMock } from 'src/plugins/ui_actions/public/mocks'; -import { UiActionsStart } from 'src/plugins/ui_actions/public'; +import { uiActionsPluginMock } from '../../../ui_actions/public/mocks'; +// eslint-disable-next-line +import { inspectorPluginMock } from '../../../inspector/public/mocks'; import { coreMock } from '../../../../core/public/mocks'; import { EmbeddablePublicPlugin, EmbeddableSetup, EmbeddableStart } from '../plugin'; @@ -48,7 +50,10 @@ export const testPlugin = ( coreStart, setup, doStart: (anotherCoreStart: CoreStart = coreStart) => { - const start = plugin.start(anotherCoreStart); + const start = plugin.start(anotherCoreStart, { + uiActions: uiActionsPluginMock.createStartContract(), + inspector: inspectorPluginMock.createStartContract(), + }); return start; }, uiActions: uiActions.doStart(coreStart), diff --git a/src/plugins/es_ui_shared/kibana.json b/src/plugins/es_ui_shared/kibana.json new file mode 100644 index 0000000000000..5a3db3b344090 --- /dev/null +++ b/src/plugins/es_ui_shared/kibana.json @@ -0,0 +1,5 @@ +{ + "id": "esUiShared", + "version": "kibana", + "ui": true +} diff --git a/src/plugins/es_ui_shared/public/index.ts b/src/plugins/es_ui_shared/public/index.ts index 5935c7cc38d03..6db6248f4c68f 100644 --- a/src/plugins/es_ui_shared/public/index.ts +++ b/src/plugins/es_ui_shared/public/index.ts @@ -19,6 +19,10 @@ export { JsonEditor, OnJsonEditorUpdateHandler } from './components/json_editor'; +export { SectionLoading } from './components/section_loading'; + +export { CronEditor, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR } from './components/cron_editor'; + export { SendRequestConfig, SendRequestResponse, @@ -31,3 +35,11 @@ export { export { indices } from './indices'; export { useUIAceKeyboardMode } from './use_ui_ace_keyboard_mode'; + +/** dummy plugin, we just want esUiShared to have its own bundle */ +export function plugin() { + return new (class EsUiSharedPlugin { + setup() {} + start() {} + })(); +} diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts index 80cddb513b20a..aa09fe26c29ca 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts @@ -431,6 +431,7 @@ export const useField = ( errors, form, isPristine, + isValid: errors.length === 0, isValidating, isValidated, isChangingValue, diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts index 2ee68eb4d7a1a..4742f7ca976b6 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts @@ -129,8 +129,7 @@ export function useForm( }, [] as string[]); }; - const isFieldValid = (field: FieldHook) => - field.getErrorsMessages() === null && !field.isValidating; + const isFieldValid = (field: FieldHook) => field.isValid && !field.isValidating; const updateFormValidity = () => { const fieldsArray = fieldsToArray(); @@ -167,14 +166,24 @@ export function useForm( }; const validateAllFields = async (): Promise => { - const fieldsToValidate = fieldsToArray().filter(field => !field.isValidated); + const fieldsArray = fieldsToArray(); + const fieldsToValidate = fieldsArray.filter(field => !field.isValidated); + + let isFormValid: boolean | undefined = isValid; if (fieldsToValidate.length === 0) { - // Nothing left to validate, all fields are already validated. - return isValid!; + if (isFormValid === undefined) { + // We should never enter this condition as the form validity is updated each time + // a field is validated. But sometimes, during tests it does not happen and we need + // to wait the next tick (hooks lifecycle being tricky) to make sure the "isValid" state is updated. + // In order to avoid this unintentional behaviour, we add this if condition here. + isFormValid = fieldsArray.every(isFieldValid); + setIsValid(isFormValid); + } + return isFormValid; } - const { isFormValid } = await validateFields(fieldsToValidate.map(field => field.path)); + ({ isFormValid } = await validateFields(fieldsToValidate.map(field => field.path))); return isFormValid!; }; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts index 8dc1e59b40c34..4b69be1278209 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts @@ -94,6 +94,7 @@ export interface FieldHook { readonly type: string; readonly value: unknown; readonly errors: ValidationError[]; + readonly isValid: boolean; readonly isPristine: boolean; readonly isValidating: boolean; readonly isValidated: boolean; diff --git a/src/plugins/home/public/application/components/__snapshots__/synopsis.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/synopsis.test.js.snap index 594d67d9c8eb0..d757d6a8b7305 100644 --- a/src/plugins/home/public/application/components/__snapshots__/synopsis.test.js.snap +++ b/src/plugins/home/public/application/components/__snapshots__/synopsis.test.js.snap @@ -9,8 +9,8 @@ exports[`props iconType 1`] = ` href="link_to_item" icon={ } diff --git a/src/plugins/home/public/application/components/synopsis.js b/src/plugins/home/public/application/components/synopsis.js index f43c377b4e5b9..b6fa85db2bfe9 100644 --- a/src/plugins/home/public/application/components/synopsis.js +++ b/src/plugins/home/public/application/components/synopsis.js @@ -35,9 +35,9 @@ export function Synopsis({ }) { let optionalImg; if (iconUrl) { - optionalImg = ; + optionalImg = ; } else if (iconType) { - optionalImg = ; + optionalImg = ; } const classes = classNames('homSynopsis__card', { diff --git a/src/plugins/home/public/application/components/tutorial/__snapshots__/introduction.test.js.snap b/src/plugins/home/public/application/components/tutorial/__snapshots__/introduction.test.js.snap index b35545787e4a4..410d29a42cac9 100644 --- a/src/plugins/home/public/application/components/tutorial/__snapshots__/introduction.test.js.snap +++ b/src/plugins/home/public/application/components/tutorial/__snapshots__/introduction.test.js.snap @@ -15,7 +15,6 @@ exports[`props exportedFieldsUrl 1`] = ` >

Great tutorial -  

@@ -56,6 +55,7 @@ exports[`props iconType 1`] = ` > @@ -67,7 +67,6 @@ exports[`props iconType 1`] = ` >

Great tutorial -  

@@ -97,7 +96,7 @@ exports[`props isBeta 1`] = ` >

Great tutorial -   +   @@ -130,7 +129,6 @@ exports[`props previewUrl 1`] = ` >

Great tutorial -  

@@ -169,7 +167,6 @@ exports[`render 1`] = ` >

Great tutorial -  

diff --git a/src/plugins/home/public/application/components/tutorial/introduction.js b/src/plugins/home/public/application/components/tutorial/introduction.js index bc5f30622f1a5..c36d150c42d3d 100644 --- a/src/plugins/home/public/application/components/tutorial/introduction.js +++ b/src/plugins/home/public/application/components/tutorial/introduction.js @@ -76,7 +76,7 @@ function IntroductionUI({ if (iconType) { icon = ( - + ); } @@ -99,8 +99,13 @@ function IntroductionUI({

- {title}   - {betaBadge} + {title} + {betaBadge && ( + <> +   + {betaBadge} + + )}

diff --git a/src/plugins/index_pattern_management/kibana.json b/src/plugins/index_pattern_management/kibana.json new file mode 100644 index 0000000000000..d5397a11184aa --- /dev/null +++ b/src/plugins/index_pattern_management/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "indexPatternManagement", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": [] +} diff --git a/src/legacy/core_plugins/management/public/np_ready/index.ts b/src/plugins/index_pattern_management/public/index.ts similarity index 83% rename from src/legacy/core_plugins/management/public/np_ready/index.ts rename to src/plugins/index_pattern_management/public/index.ts index bae0f1d3e23cd..da482c0c51f0a 100644 --- a/src/legacy/core_plugins/management/public/np_ready/index.ts +++ b/src/plugins/index_pattern_management/public/index.ts @@ -29,14 +29,11 @@ * either types, or static code. */ import { PluginInitializerContext } from 'src/core/public'; -import { ManagementPlugin } from './plugin'; -export { ManagementSetup, ManagementStart } from './plugin'; +import { IndexPatternManagementPlugin } from './plugin'; +export { IndexPatternManagementSetup, IndexPatternManagementStart } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { - return new ManagementPlugin(initializerContext); + return new IndexPatternManagementPlugin(initializerContext); } -export { - IndexPatternCreationConfig, - IndexPatternListConfig, -} from './services/index_pattern_management'; +export { IndexPatternCreationConfig, IndexPatternListConfig } from './service'; diff --git a/src/legacy/core_plugins/management/public/np_ready/mocks.ts b/src/plugins/index_pattern_management/public/mocks.ts similarity index 57% rename from src/legacy/core_plugins/management/public/np_ready/mocks.ts rename to src/plugins/index_pattern_management/public/mocks.ts index ae0be98de63f3..bc97f46c302e3 100644 --- a/src/legacy/core_plugins/management/public/np_ready/mocks.ts +++ b/src/plugins/index_pattern_management/public/mocks.ts @@ -18,42 +18,38 @@ */ import { PluginInitializerContext } from 'src/core/public'; -import { coreMock } from '../../../../../core/public/mocks'; +import { coreMock } from '../../../core/public/mocks'; import { - ManagementSetup, - ManagementStart, - ManagementPlugin, - ManagementPluginSetupDependencies, + IndexPatternManagementSetup, + IndexPatternManagementStart, + IndexPatternManagementPlugin, } from './plugin'; -const createSetupContract = (): ManagementSetup => ({ - indexPattern: { - creation: { - add: jest.fn(), - getType: jest.fn(), - getIndexPatternCreationOptions: jest.fn(), - } as any, - list: { - add: jest.fn(), - getIndexPatternTags: jest.fn(), - getFieldInfo: jest.fn(), - areScriptedFieldsEnabled: jest.fn(), - } as any, - }, +const createSetupContract = (): IndexPatternManagementSetup => ({ + creation: { + addCreationConfig: jest.fn(), + } as any, + list: { + addListConfig: jest.fn(), + } as any, }); -const createStartContract = (): ManagementStart => ({}); +const createStartContract = (): IndexPatternManagementStart => ({ + creation: { + getType: jest.fn(), + getIndexPatternCreationOptions: jest.fn(), + } as any, + list: { + getIndexPatternTags: jest.fn(), + getFieldInfo: jest.fn(), + areScriptedFieldsEnabled: jest.fn(), + } as any, +}); const createInstance = async () => { - const plugin = new ManagementPlugin({} as PluginInitializerContext); + const plugin = new IndexPatternManagementPlugin({} as PluginInitializerContext); - const setup = plugin.setup(coreMock.createSetup(), ({ - home: { - featureCatalogue: { - register: jest.fn(), - }, - }, - } as unknown) as ManagementPluginSetupDependencies); + const setup = plugin.setup(coreMock.createSetup()); const doStart = () => plugin.start(coreMock.createStart(), {}); return { diff --git a/src/legacy/core_plugins/management/public/np_ready/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts similarity index 60% rename from src/legacy/core_plugins/management/public/np_ready/plugin.ts rename to src/plugins/index_pattern_management/public/plugin.ts index 2a8ef10c817cc..93bb0ead1df4a 100644 --- a/src/legacy/core_plugins/management/public/np_ready/plugin.ts +++ b/src/plugins/index_pattern_management/public/plugin.ts @@ -17,43 +17,40 @@ * under the License. */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; -import { HomePublicPluginSetup } from 'src/plugins/home/public'; -import { IndexPatternManagementService, IndexPatternManagementSetup } from './services'; +import { + IndexPatternManagementService, + IndexPatternManagementServiceSetup, + IndexPatternManagementServiceStart, +} from './service'; -export interface ManagementPluginSetupDependencies { - home: HomePublicPluginSetup; -} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface IndexPatternManagementSetupDependencies {} // eslint-disable-next-line @typescript-eslint/no-empty-interface -interface ManagementPluginStartDependencies {} +export interface IndexPatternManagementStartDependencies {} -export interface ManagementSetup { - indexPattern: IndexPatternManagementSetup; -} +export type IndexPatternManagementSetup = IndexPatternManagementServiceSetup; -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ManagementStart {} +export type IndexPatternManagementStart = IndexPatternManagementServiceStart; -export class ManagementPlugin +export class IndexPatternManagementPlugin implements Plugin< - ManagementSetup, - ManagementStart, - ManagementPluginSetupDependencies, - ManagementPluginStartDependencies + IndexPatternManagementSetup, + IndexPatternManagementStart, + IndexPatternManagementSetupDependencies, + IndexPatternManagementStartDependencies > { private readonly indexPattern = new IndexPatternManagementService(); constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup, { home }: ManagementPluginSetupDependencies) { - return { - indexPattern: this.indexPattern.setup({ httpClient: core.http, home }), - }; + public setup(core: CoreSetup) { + return this.indexPattern.setup({ httpClient: core.http }); } - public start(core: CoreStart, plugins: ManagementPluginStartDependencies) { - return {}; + public start(core: CoreStart, plugins: IndexPatternManagementStartDependencies) { + return this.indexPattern.start(); } public stop() { diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts b/src/plugins/index_pattern_management/public/service/creation/config.ts similarity index 88% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts rename to src/plugins/index_pattern_management/public/service/creation/config.ts index 5714fa3338962..29ab0ebfc3d5f 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/config.ts +++ b/src/plugins/index_pattern_management/public/service/creation/config.ts @@ -18,20 +18,20 @@ */ import { i18n } from '@kbn/i18n'; -import { MatchedIndex } from '../../../../../../kibana/public/management/sections/index_patterns/create_index_pattern_wizard/types'; +import { MatchedIndex } from '../../../../../legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/types'; const indexPatternTypeName = i18n.translate( - 'management.editIndexPattern.createIndex.defaultTypeName', + 'indexPatternManagement.editIndexPattern.createIndex.defaultTypeName', { defaultMessage: 'index pattern' } ); const indexPatternButtonText = i18n.translate( - 'management.editIndexPattern.createIndex.defaultButtonText', + 'indexPatternManagement.editIndexPattern.createIndex.defaultButtonText', { defaultMessage: 'Standard index pattern' } ); const indexPatternButtonDescription = i18n.translate( - 'management.editIndexPattern.createIndex.defaultButtonDescription', + 'indexPatternManagement.editIndexPattern.createIndex.defaultButtonDescription', { defaultMessage: 'Perform full aggregations against any data' } ); diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/index.ts b/src/plugins/index_pattern_management/public/service/creation/index.ts similarity index 100% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/index.ts rename to src/plugins/index_pattern_management/public/service/creation/index.ts diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts b/src/plugins/index_pattern_management/public/service/creation/manager.ts similarity index 79% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts rename to src/plugins/index_pattern_management/public/service/creation/manager.ts index e7fa13409ab04..32b3e7ee7a133 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/creation/manager.ts +++ b/src/plugins/index_pattern_management/public/service/creation/manager.ts @@ -17,23 +17,25 @@ * under the License. */ -import { HttpSetup } from '../../../../../../../../core/public'; +import { HttpSetup } from '../../../../../core/public'; import { IndexPatternCreationConfig, UrlHandler, IndexPatternCreationOption } from './config'; export class IndexPatternCreationManager { private configs: IndexPatternCreationConfig[]; - constructor(private readonly httpClient: HttpSetup) { + constructor() { this.configs = []; } - public add(Config: typeof IndexPatternCreationConfig) { - const config = new Config({ httpClient: this.httpClient }); + public addCreationConfig = (httpClient: HttpSetup) => ( + Config: typeof IndexPatternCreationConfig + ) => { + const config = new Config({ httpClient }); if (this.configs.findIndex(c => c.key === config.key) !== -1) { throw new Error(`${config.key} exists in IndexPatternCreationManager.`); } this.configs.push(config); - } + }; public getType(key: string | undefined): IndexPatternCreationConfig | null { if (key) { @@ -58,4 +60,13 @@ export class IndexPatternCreationManager { ); return options; } + + setup = (httpClient: HttpSetup) => ({ + addCreationConfig: this.addCreationConfig(httpClient).bind(this), + }); + + start = () => ({ + getType: this.getType.bind(this), + getIndexPatternCreationOptions: this.getIndexPatternCreationOptions.bind(this), + }); } diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index.ts b/src/plugins/index_pattern_management/public/service/index.ts similarity index 100% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index.ts rename to src/plugins/index_pattern_management/public/service/index.ts diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts b/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts similarity index 51% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts rename to src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts index 2b6f008dd928a..4780fa00ed468 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/index_pattern_management_service.ts +++ b/src/plugins/index_pattern_management/public/service/index_pattern_management_service.ts @@ -17,18 +17,12 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -import { - FeatureCatalogueCategory, - HomePublicPluginSetup, -} from '../../../../../../../plugins/home/public'; -import { HttpSetup } from '../../../../../../../core/public'; +import { HttpSetup } from '../../../../core/public'; import { IndexPatternCreationManager, IndexPatternCreationConfig } from './creation'; import { IndexPatternListManager, IndexPatternListConfig } from './list'; interface SetupDependencies { httpClient: HttpSetup; - home: HomePublicPluginSetup; } /** @@ -37,31 +31,29 @@ interface SetupDependencies { * @internal */ export class IndexPatternManagementService { - public setup({ httpClient, home }: SetupDependencies) { - const creation = new IndexPatternCreationManager(httpClient); - const list = new IndexPatternListManager(); + indexPatternCreationManager: IndexPatternCreationManager; + indexPatternListConfig: IndexPatternListManager; - creation.add(IndexPatternCreationConfig); - list.add(IndexPatternListConfig); + constructor() { + this.indexPatternCreationManager = new IndexPatternCreationManager(); + this.indexPatternListConfig = new IndexPatternListManager(); + } + + public setup({ httpClient }: SetupDependencies) { + const creationManagerSetup = this.indexPatternCreationManager.setup(httpClient); + creationManagerSetup.addCreationConfig(IndexPatternCreationConfig); + this.indexPatternListConfig.setup().addListConfig(IndexPatternListConfig); - home.featureCatalogue.register({ - id: 'index_patterns', - title: i18n.translate('management.indexPatternHeader', { - defaultMessage: 'Index Patterns', - }), - description: i18n.translate('management.indexPatternLabel', { - defaultMessage: - 'Manage the index patterns that help retrieve your data from Elasticsearch.', - }), - icon: 'indexPatternApp', - path: '/app/kibana#/management/kibana/index_patterns', - showOnHomePage: true, - category: FeatureCatalogueCategory.ADMIN, - }); + return { + creation: creationManagerSetup, + list: this.indexPatternListConfig.setup(), + }; + } + public start() { return { - creation, - list, + creation: this.indexPatternCreationManager.start(), + list: this.indexPatternListConfig.start(), }; } @@ -71,4 +63,5 @@ export class IndexPatternManagementService { } /** @internal */ -export type IndexPatternManagementSetup = ReturnType; +export type IndexPatternManagementServiceSetup = ReturnType; +export type IndexPatternManagementServiceStart = ReturnType; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts b/src/plugins/index_pattern_management/public/service/list/config.ts similarity index 87% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts rename to src/plugins/index_pattern_management/public/service/list/config.ts index dd4d77a681171..87c246e8913e5 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/config.ts +++ b/src/plugins/index_pattern_management/public/service/list/config.ts @@ -33,9 +33,12 @@ export class IndexPatternListConfig { ? [ { key: 'default', - name: i18n.translate('management.editIndexPattern.list.defaultIndexPatternListName', { - defaultMessage: 'Default', - }), + name: i18n.translate( + 'indexPatternManagement.editIndexPattern.list.defaultIndexPatternListName', + { + defaultMessage: 'Default', + } + ), }, ] : []; diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/index.ts b/src/plugins/index_pattern_management/public/service/list/index.ts similarity index 100% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/index.ts rename to src/plugins/index_pattern_management/public/service/list/index.ts diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.ts b/src/plugins/index_pattern_management/public/service/list/manager.ts similarity index 75% rename from src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.ts rename to src/plugins/index_pattern_management/public/service/list/manager.ts index 73ca33ae914a9..3a2910a222cd7 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/index_pattern_management/list/manager.ts +++ b/src/plugins/index_pattern_management/public/service/list/manager.ts @@ -27,7 +27,7 @@ export class IndexPatternListManager { this.configs = []; } - public add(Config: typeof IndexPatternListConfig) { + private addListConfig(Config: typeof IndexPatternListConfig) { const config = new Config(); if (this.configs.findIndex(c => c.key === config.key) !== -1) { throw new Error(`${config.key} exists in IndexPatternListManager.`); @@ -35,7 +35,7 @@ export class IndexPatternListManager { this.configs.push(config); } - public getIndexPatternTags(indexPattern: IIndexPattern, isDefault: boolean) { + private getIndexPatternTags(indexPattern: IIndexPattern, isDefault: boolean) { return this.configs.reduce((tags: IndexPatternTag[], config) => { return config.getIndexPatternTags ? tags.concat(config.getIndexPatternTags(indexPattern, isDefault)) @@ -43,15 +43,25 @@ export class IndexPatternListManager { }, []); } - public getFieldInfo(indexPattern: IIndexPattern, field: IFieldType): string[] { + private getFieldInfo(indexPattern: IIndexPattern, field: IFieldType): string[] { return this.configs.reduce((info: string[], config) => { return config.getFieldInfo ? info.concat(config.getFieldInfo(indexPattern, field)) : info; }, []); } - public areScriptedFieldsEnabled(indexPattern: IIndexPattern): boolean { + private areScriptedFieldsEnabled(indexPattern: IIndexPattern): boolean { return this.configs.every(config => { return config.areScriptedFieldsEnabled ? config.areScriptedFieldsEnabled(indexPattern) : true; }); } + + setup = () => ({ + addListConfig: this.addListConfig.bind(this), + }); + + start = () => ({ + getIndexPatternTags: this.getIndexPatternTags.bind(this), + getFieldInfo: this.getFieldInfo.bind(this), + areScriptedFieldsEnabled: this.areScriptedFieldsEnabled.bind(this), + }); } diff --git a/src/plugins/kibana_react/kibana.json b/src/plugins/kibana_react/kibana.json new file mode 100644 index 0000000000000..0add1bee84ae0 --- /dev/null +++ b/src/plugins/kibana_react/kibana.json @@ -0,0 +1,5 @@ +{ + "id": "kibanaReact", + "version": "kibana", + "ui": true +} diff --git a/src/plugins/kibana_react/public/adapters/react_to_ui_component.ts b/src/plugins/kibana_react/public/adapters/react_to_ui_component.ts index b4007b30cf8ca..21bba92ada4c1 100644 --- a/src/plugins/kibana_react/public/adapters/react_to_ui_component.ts +++ b/src/plugins/kibana_react/public/adapters/react_to_ui_component.ts @@ -19,7 +19,7 @@ import { ComponentType, createElement as h } from 'react'; import { render as renderReact, unmountComponentAtNode } from 'react-dom'; -import { UiComponent, UiComponentInstance } from '../../../kibana_utils/common'; +import { UiComponent, UiComponentInstance } from '../../../kibana_utils/public'; /** * Transform a React component into a `UiComponent`. diff --git a/src/plugins/kibana_react/public/adapters/ui_to_react_component.test.tsx b/src/plugins/kibana_react/public/adapters/ui_to_react_component.test.tsx index 939d372b9997f..aefbd66e50fcf 100644 --- a/src/plugins/kibana_react/public/adapters/ui_to_react_component.test.tsx +++ b/src/plugins/kibana_react/public/adapters/ui_to_react_component.test.tsx @@ -19,7 +19,7 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { UiComponent } from '../../../kibana_utils/common'; +import { UiComponent } from '../../../kibana_utils/public'; import { uiToReactComponent } from './ui_to_react_component'; import { reactToUiComponent } from './react_to_ui_component'; diff --git a/src/plugins/kibana_react/public/adapters/ui_to_react_component.ts b/src/plugins/kibana_react/public/adapters/ui_to_react_component.ts index 9b34880cf4fe3..ee99ea3672672 100644 --- a/src/plugins/kibana_react/public/adapters/ui_to_react_component.ts +++ b/src/plugins/kibana_react/public/adapters/ui_to_react_component.ts @@ -18,7 +18,7 @@ */ import { FC, createElement as h, useRef, useLayoutEffect, useMemo } from 'react'; -import { UiComponent, UiComponentInstance } from '../../../kibana_utils/common'; +import { UiComponent, UiComponentInstance } from '../../../kibana_utils/public'; /** * Transforms `UiComponent` into a React component. diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts index e1689e38dbfe0..9ad9f14ac5659 100644 --- a/src/plugins/kibana_react/public/index.ts +++ b/src/plugins/kibana_react/public/index.ts @@ -31,3 +31,11 @@ export { Markdown, MarkdownSimple } from './markdown'; export { reactToUiComponent, uiToReactComponent } from './adapters'; export { useUrlTracker } from './use_url_tracker'; export { toMountPoint } from './util'; + +/** dummy plugin, we just want kibanaReact to have its own bundle */ +export function plugin() { + return new (class KibanaReactPlugin { + setup() {} + start() {} + })(); +} diff --git a/src/plugins/kibana_utils/kibana.json b/src/plugins/kibana_utils/kibana.json new file mode 100644 index 0000000000000..6fa39d82d1021 --- /dev/null +++ b/src/plugins/kibana_utils/kibana.json @@ -0,0 +1,5 @@ +{ + "id": "kibanaUtils", + "version": "kibana", + "ui": true +} diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 1876e688c989a..2f139050e994a 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -19,7 +19,6 @@ export { calculateObjectHash, - createGetterSetter, defer, Defer, Get, @@ -31,6 +30,8 @@ export { UiComponent, UiComponentInstance, url, + createGetterSetter, + defaultFeedbackMessage, } from '../common'; export * from './core'; export * from './errors'; @@ -75,3 +76,11 @@ export { } from './state_sync'; export { removeQueryParam, redirectWhenMissing, ensureDefaultIndexPattern } from './history'; export { applyDiff } from './state_management/utils/diff_object'; + +/** dummy plugin, we just want kibanaUtils to have its own bundle */ +export function plugin() { + return new (class KibanaUtilsPlugin { + setup() {} + start() {} + })(); +} diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts index b778535a2d428..af8811b1969e6 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_tracker.ts @@ -115,7 +115,6 @@ export function createKbnUrlTracker({ */ shouldTrackUrlUpdate?: (pathname: string) => boolean; }): KbnUrlTracker { - const historyInstance = history || createHashHistory(); const storageInstance = storage || sessionStorage; // local state storing current listeners and active url @@ -159,6 +158,7 @@ export function createKbnUrlTracker({ function onMountApp() { unsubscribe(); + const historyInstance = history || createHashHistory(); // track current hash when within app unsubscribeURLHistory = historyInstance.listen(location => { if (shouldTrackUrlUpdate(location.pathname)) { diff --git a/src/plugins/share/public/services/short_url_redirect_app.ts b/src/plugins/share/public/services/short_url_redirect_app.ts index 6f72b711f6602..5326aa3a21dc5 100644 --- a/src/plugins/share/public/services/short_url_redirect_app.ts +++ b/src/plugins/share/public/services/short_url_redirect_app.ts @@ -19,7 +19,6 @@ import { CoreSetup } from 'kibana/public'; import { getUrlIdFromGotoRoute, getUrlPath, GOTO_PREFIX } from '../../common/short_url_routes'; -import { hashUrl } from '../../../kibana_utils/public'; export const createShortUrlRedirectApp = (core: CoreSetup, location: Location) => ({ id: 'short_url_redirect', @@ -35,6 +34,7 @@ export const createShortUrlRedirectApp = (core: CoreSetup, location: Location) = const response = await core.http.get<{ url: string }>(getUrlPath(urlId)); const redirectUrl = response.url; + const { hashUrl } = await import('../../../kibana_utils/public'); const hashedUrl = hashUrl(redirectUrl); const url = core.http.basePath.prepend(hashedUrl); diff --git a/src/plugins/telemetry/common/constants.ts b/src/plugins/telemetry/common/constants.ts index babd009143c5e..fd32862896528 100644 --- a/src/plugins/telemetry/common/constants.ts +++ b/src/plugins/telemetry/common/constants.ts @@ -80,3 +80,14 @@ export const APPLICATION_USAGE_TYPE = 'application_usage'; * The type name used within the Monitoring index to publish management stats. */ export const KIBANA_STACK_MANAGEMENT_STATS_TYPE = 'stack_management'; + +/** + * The type name used to publish Kibana usage stats. + * NOTE: this string shows as-is in the stats API as a field name for the kibana usage stats + */ +export const KIBANA_USAGE_TYPE = 'kibana'; + +/** + * The type name used to publish Kibana usage stats in the formatted as bulk. + */ +export const KIBANA_STATS_TYPE = 'kibana_stats'; diff --git a/src/plugins/telemetry/server/collectors/index.ts b/src/plugins/telemetry/server/collectors/index.ts index 6eeda080bb3ab..a874f8dd6202c 100644 --- a/src/plugins/telemetry/server/collectors/index.ts +++ b/src/plugins/telemetry/server/collectors/index.ts @@ -22,3 +22,5 @@ export { registerUiMetricUsageCollector } from './ui_metric'; export { registerTelemetryPluginUsageCollector } from './telemetry_plugin'; export { registerManagementUsageCollector } from './management'; export { registerApplicationUsageCollector } from './application_usage'; +export { registerKibanaUsageCollector } from './kibana'; +export { registerOpsStatsCollector } from './ops_stats'; diff --git a/src/plugins/telemetry/server/collectors/kibana/get_saved_object_counts.test.ts b/src/plugins/telemetry/server/collectors/kibana/get_saved_object_counts.test.ts new file mode 100644 index 0000000000000..a7681e1766427 --- /dev/null +++ b/src/plugins/telemetry/server/collectors/kibana/get_saved_object_counts.test.ts @@ -0,0 +1,61 @@ +/* + * 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 { getSavedObjectsCounts } from './get_saved_object_counts'; + +describe('getSavedObjectsCounts', () => { + test('Get all the saved objects equal to 0 because no results were found', async () => { + const callCluster = jest.fn(() => ({})); + + const results = await getSavedObjectsCounts(callCluster as any, '.kibana'); + expect(results).toStrictEqual({ + dashboard: { total: 0 }, + visualization: { total: 0 }, + search: { total: 0 }, + index_pattern: { total: 0 }, + graph_workspace: { total: 0 }, + timelion_sheet: { total: 0 }, + }); + }); + + test('Merge the zeros with the results', async () => { + const callCluster = jest.fn(() => ({ + aggregations: { + types: { + buckets: [ + { key: 'dashboard', doc_count: 1 }, + { key: 'timelion-sheet', doc_count: 2 }, + { key: 'index-pattern', value: 2 }, // Malformed on purpose + { key: 'graph_workspace', doc_count: 3 }, // already snake_cased + ], + }, + }, + })); + + const results = await getSavedObjectsCounts(callCluster as any, '.kibana'); + expect(results).toStrictEqual({ + dashboard: { total: 1 }, + visualization: { total: 0 }, + search: { total: 0 }, + index_pattern: { total: 0 }, + graph_workspace: { total: 3 }, + timelion_sheet: { total: 2 }, + }); + }); +}); diff --git a/src/plugins/telemetry/server/collectors/kibana/get_saved_object_counts.ts b/src/plugins/telemetry/server/collectors/kibana/get_saved_object_counts.ts new file mode 100644 index 0000000000000..804c8b0ed2026 --- /dev/null +++ b/src/plugins/telemetry/server/collectors/kibana/get_saved_object_counts.ts @@ -0,0 +1,82 @@ +/* + * 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. + */ + +/** + * Moved from /x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_kibana_usage_collector.ts + * + * The PR https://github.com/elastic/kibana/pull/62665 proved what the issue https://github.com/elastic/kibana/issues/58249 + * was claiming: the structure and payload for common telemetry bits differs between Monitoring and OSS/X-Pack collections. + * + * Unifying this logic from Monitoring that makes sense to have in OSS here and we will import it on the monitoring side to reuse it. + */ + +import { snakeCase } from 'lodash'; +import { APICaller } from 'kibana/server'; + +const TYPES = [ + 'dashboard', + 'visualization', + 'search', + 'index-pattern', + 'graph-workspace', + 'timelion-sheet', +]; + +export interface KibanaSavedObjectCounts { + [pluginName: string]: { + total: number; + }; +} + +export async function getSavedObjectsCounts( + callCluster: APICaller, + kibanaIndex: string // Typically '.kibana'. We might need a way to obtain it from the SavedObjects client (or the SavedObjects client to provide a way to run aggregations?) +): Promise { + const savedObjectCountSearchParams = { + index: kibanaIndex, + ignoreUnavailable: true, + filterPath: 'aggregations.types.buckets', + body: { + size: 0, + query: { + terms: { type: TYPES }, + }, + aggs: { + types: { + terms: { field: 'type', size: TYPES.length }, + }, + }, + }, + }; + const resp = await callCluster('search', savedObjectCountSearchParams); + const buckets: Array<{ key: string; doc_count: number }> = + resp.aggregations?.types?.buckets || []; + + // Initialise the object with all zeros for all the types + const allZeros: KibanaSavedObjectCounts = TYPES.reduce( + (acc, type) => ({ ...acc, [snakeCase(type)]: { total: 0 } }), + {} + ); + + // Add the doc_count from each bucket + return buckets.reduce( + (acc, { key, doc_count: total }) => (total ? { ...acc, [snakeCase(key)]: { total } } : acc), + allZeros + ); +} diff --git a/src/plugins/telemetry/server/collectors/kibana/index.test.ts b/src/plugins/telemetry/server/collectors/kibana/index.test.ts new file mode 100644 index 0000000000000..91ede686ded3d --- /dev/null +++ b/src/plugins/telemetry/server/collectors/kibana/index.test.ts @@ -0,0 +1,76 @@ +/* + * 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 { UsageCollectionSetup } from '../../../../../plugins/usage_collection/server'; +import { pluginInitializerContextConfigMock } from '../../../../../core/server/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { CollectorOptions } from '../../../../../plugins/usage_collection/server/collector/collector'; + +import { registerKibanaUsageCollector } from './'; + +describe('telemetry_kibana', () => { + let collector: CollectorOptions; + + const usageCollectionMock: jest.Mocked = { + makeUsageCollector: jest.fn().mockImplementation(config => (collector = config)), + registerCollector: jest.fn(), + } as any; + + const legacyConfig$ = pluginInitializerContextConfigMock({}).legacy.globalConfig$; + const callCluster = jest.fn().mockImplementation(() => ({})); + + beforeAll(() => registerKibanaUsageCollector(usageCollectionMock, legacyConfig$)); + afterAll(() => jest.clearAllTimers()); + + test('registered collector is set', () => { + expect(collector).not.toBeUndefined(); + expect(collector.type).toBe('kibana'); + }); + + test('fetch', async () => { + expect(await collector.fetch(callCluster)).toStrictEqual({ + index: '.kibana-tests', + dashboard: { total: 0 }, + visualization: { total: 0 }, + search: { total: 0 }, + index_pattern: { total: 0 }, + graph_workspace: { total: 0 }, + timelion_sheet: { total: 0 }, + }); + }); + + test('formatForBulkUpload', async () => { + const resultFromFetch = { + index: '.kibana-tests', + dashboard: { total: 0 }, + visualization: { total: 0 }, + search: { total: 0 }, + index_pattern: { total: 0 }, + graph_workspace: { total: 0 }, + timelion_sheet: { total: 0 }, + }; + + expect(collector.formatForBulkUpload!(resultFromFetch)).toStrictEqual({ + type: 'kibana_stats', + payload: { + usage: resultFromFetch, + }, + }); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/test_utils/index.ts b/src/plugins/telemetry/server/collectors/kibana/index.ts similarity index 91% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/test_utils/index.ts rename to src/plugins/telemetry/server/collectors/kibana/index.ts index a9a306da7f1a2..695d972346b8a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/test_utils/index.ts +++ b/src/plugins/telemetry/server/collectors/kibana/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { getSavedDashboardMock } from './get_saved_dashboard_mock'; +export { registerKibanaUsageCollector } from './kibana_usage_collector'; diff --git a/src/plugins/telemetry/server/collectors/kibana/kibana_usage_collector.ts b/src/plugins/telemetry/server/collectors/kibana/kibana_usage_collector.ts new file mode 100644 index 0000000000000..ccf6f7b1033c9 --- /dev/null +++ b/src/plugins/telemetry/server/collectors/kibana/kibana_usage_collector.ts @@ -0,0 +1,65 @@ +/* + * 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 } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { SharedGlobalConfig } from 'kibana/server'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { KIBANA_STATS_TYPE, KIBANA_USAGE_TYPE } from '../../../common/constants'; +import { getSavedObjectsCounts } from './get_saved_object_counts'; + +export function getKibanaUsageCollector( + usageCollection: UsageCollectionSetup, + legacyConfig$: Observable +) { + return usageCollection.makeUsageCollector({ + type: KIBANA_USAGE_TYPE, + isReady: () => true, + async fetch(callCluster) { + const { + kibana: { index }, + } = await legacyConfig$.pipe(take(1)).toPromise(); + return { + index, + ...(await getSavedObjectsCounts(callCluster, index)), + }; + }, + + /* + * Format the response data into a model for internal upload + * 1. Make this data part of the "kibana_stats" type + * 2. Organize the payload in the usage namespace of the data payload (usage.index, etc) + */ + formatForBulkUpload: result => { + return { + type: KIBANA_STATS_TYPE, + payload: { + usage: result, + }, + }; + }, + }); +} + +export function registerKibanaUsageCollector( + usageCollection: UsageCollectionSetup, + legacyConfig$: Observable +) { + usageCollection.registerCollector(getKibanaUsageCollector(usageCollection, legacyConfig$)); +} diff --git a/src/plugins/telemetry/server/collectors/ops_stats/__snapshots__/index.test.ts.snap b/src/plugins/telemetry/server/collectors/ops_stats/__snapshots__/index.test.ts.snap new file mode 100644 index 0000000000000..678237ffb6ea2 --- /dev/null +++ b/src/plugins/telemetry/server/collectors/ops_stats/__snapshots__/index.test.ts.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`telemetry_ops_stats should return something when there is a metric 1`] = ` +Object { + "concurrent_connections": 20, + "os": Object { + "load": Object { + "15m": 3, + "1m": 0.5, + "5m": 1, + }, + "memory": Object { + "free_in_bytes": 10, + "total_in_bytes": 10, + "used_in_bytes": 10, + }, + "platform": "darwin", + "platformRelease": "test", + "uptime_in_millis": 1000, + }, + "process": Object { + "event_loop_delay": 10, + "memory": Object { + "heap": Object { + "size_limit": 0, + "total_in_bytes": 0, + "used_in_bytes": 0, + }, + "resident_set_size_in_bytes": 0, + }, + "uptime_in_millis": 1000, + }, + "requests": Object { + "disconnects": 10, + "total": 100, + }, + "response_times": Object { + "average": 100, + "max": 200, + }, + "timestamp": Any, +} +`; diff --git a/src/plugins/telemetry/server/collectors/ops_stats/index.test.ts b/src/plugins/telemetry/server/collectors/ops_stats/index.test.ts new file mode 100644 index 0000000000000..92e0e40776eb8 --- /dev/null +++ b/src/plugins/telemetry/server/collectors/ops_stats/index.test.ts @@ -0,0 +1,132 @@ +/* + * 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 { Subject } from 'rxjs'; +import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/server'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { CollectorOptions } from '../../../../../plugins/usage_collection/server/collector/collector'; + +import { registerOpsStatsCollector } from './'; +import { OpsMetrics } from '../../../../../core/server'; + +describe('telemetry_ops_stats', () => { + let collector: CollectorOptions; + + const usageCollectionMock: jest.Mocked = { + makeStatsCollector: jest.fn().mockImplementation(config => (collector = config)), + registerCollector: jest.fn(), + } as any; + + const metrics$ = new Subject(); + const callCluster = jest.fn(); + + const metric: OpsMetrics = { + process: { + memory: { + heap: { + total_in_bytes: 0, + used_in_bytes: 0, + size_limit: 0, + }, + resident_set_size_in_bytes: 0, + }, + event_loop_delay: 10, + pid: 10, + uptime_in_millis: 1000, + }, + os: { + platform: 'darwin', + platformRelease: 'test', + load: { + '1m': 0.5, + '5m': 1, + '15m': 3, + }, + memory: { + total_in_bytes: 10, + free_in_bytes: 10, + used_in_bytes: 10, + }, + uptime_in_millis: 1000, + }, + response_times: { avg_in_millis: 100, max_in_millis: 200 }, + requests: { + disconnects: 10, + total: 100, + statusCodes: { 200: 100 }, + }, + concurrent_connections: 20, + }; + + beforeAll(() => registerOpsStatsCollector(usageCollectionMock, metrics$)); + afterAll(() => jest.clearAllTimers()); + + test('registered collector is set', () => { + expect(collector).not.toBeUndefined(); + expect(collector.type).toBe('kibana_stats'); + }); + + test('isReady should return false because no metrics have been provided yet', () => { + expect(collector.isReady()).toBe(false); + }); + + test('should return something when there is a metric', async () => { + metrics$.next(metric); + expect(collector.isReady()).toBe(true); + expect(await collector.fetch(callCluster)).toMatchSnapshot({ + concurrent_connections: 20, + os: { + load: { + '15m': 3, + '1m': 0.5, + '5m': 1, + }, + memory: { + free_in_bytes: 10, + total_in_bytes: 10, + used_in_bytes: 10, + }, + platform: 'darwin', + platformRelease: 'test', + uptime_in_millis: 1000, + }, + process: { + event_loop_delay: 10, + memory: { + heap: { + size_limit: 0, + total_in_bytes: 0, + used_in_bytes: 0, + }, + resident_set_size_in_bytes: 0, + }, + uptime_in_millis: 1000, + }, + requests: { + disconnects: 10, + total: 100, + }, + response_times: { + average: 100, + max: 200, + }, + timestamp: expect.any(String), + }); + }); +}); diff --git a/src/legacy/core_plugins/management/public/np_ready/services/index.ts b/src/plugins/telemetry/server/collectors/ops_stats/index.ts similarity index 92% rename from src/legacy/core_plugins/management/public/np_ready/services/index.ts rename to src/plugins/telemetry/server/collectors/ops_stats/index.ts index 9df010223542b..443a25749d200 100644 --- a/src/legacy/core_plugins/management/public/np_ready/services/index.ts +++ b/src/plugins/telemetry/server/collectors/ops_stats/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from './index_pattern_management'; +export { registerOpsStatsCollector } from './ops_stats_collector'; diff --git a/src/plugins/telemetry/server/collectors/ops_stats/ops_stats_collector.ts b/src/plugins/telemetry/server/collectors/ops_stats/ops_stats_collector.ts new file mode 100644 index 0000000000000..4967e20006ddd --- /dev/null +++ b/src/plugins/telemetry/server/collectors/ops_stats/ops_stats_collector.ts @@ -0,0 +1,71 @@ +/* + * 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 } from 'rxjs'; +import { cloneDeep } from 'lodash'; +import moment from 'moment'; +import { OpsMetrics } from 'kibana/server'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { KIBANA_STATS_TYPE } from '../../../common/constants'; + +interface OpsStatsMetrics extends Omit { + timestamp: string; + response_times: { + average: number; + max: number; + }; +} + +/** + * Initialize a collector for Kibana Ops Stats + */ +export function getOpsStatsCollector( + usageCollection: UsageCollectionSetup, + metrics$: Observable +) { + let lastMetrics: OpsStatsMetrics | null = null; + metrics$.subscribe(_metrics => { + const metrics = cloneDeep(_metrics); + // Ensure we only include the same data that Metricbeat collection would get + delete metrics.process.pid; + const responseTimes = { + average: metrics.response_times.avg_in_millis, + max: metrics.response_times.max_in_millis, + }; + delete metrics.requests.statusCodes; + lastMetrics = { + ...metrics, + response_times: responseTimes, + timestamp: moment.utc().toISOString(), + }; + }); + + return usageCollection.makeStatsCollector({ + type: KIBANA_STATS_TYPE, + isReady: () => !!lastMetrics, + fetch: () => lastMetrics, + }); +} + +export function registerOpsStatsCollector( + usageCollection: UsageCollectionSetup, + metrics$: Observable +) { + usageCollection.registerCollector(getOpsStatsCollector(usageCollection, metrics$)); +} diff --git a/src/plugins/telemetry/server/plugin.ts b/src/plugins/telemetry/server/plugin.ts index 77036b4ea7ddc..1df6a665e4d76 100644 --- a/src/plugins/telemetry/server/plugin.ts +++ b/src/plugins/telemetry/server/plugin.ts @@ -32,6 +32,8 @@ import { SavedObjectsClient, Plugin, Logger, + SharedGlobalConfig, + MetricsServiceSetup, } from '../../../core/server'; import { registerRoutes } from './routes'; import { registerCollection } from './telemetry_collection'; @@ -41,6 +43,8 @@ import { registerTelemetryPluginUsageCollector, registerManagementUsageCollector, registerApplicationUsageCollector, + registerKibanaUsageCollector, + registerOpsStatsCollector, } from './collectors'; import { TelemetryConfigType } from './config'; import { FetcherTask } from './fetcher'; @@ -61,6 +65,7 @@ export class TelemetryPlugin implements Plugin { private readonly logger: Logger; private readonly currentKibanaVersion: string; private readonly config$: Observable; + private readonly legacyConfig$: Observable; private readonly isDev: boolean; private readonly fetcherTask: FetcherTask; private savedObjectsClient?: ISavedObjectsRepository; @@ -71,6 +76,7 @@ export class TelemetryPlugin implements Plugin { this.isDev = initializerContext.env.mode.dev; this.currentKibanaVersion = initializerContext.env.packageInfo.version; this.config$ = initializerContext.config.create(); + this.legacyConfig$ = initializerContext.config.legacy.globalConfig$; this.fetcherTask = new FetcherTask({ ...initializerContext, logger: this.logger, @@ -78,15 +84,15 @@ export class TelemetryPlugin implements Plugin { } public async setup( - core: CoreSetup, + { elasticsearch, http, savedObjects, metrics }: CoreSetup, { usageCollection, telemetryCollectionManager }: TelemetryPluginsSetup ) { const currentKibanaVersion = this.currentKibanaVersion; const config$ = this.config$; const isDev = this.isDev; - registerCollection(telemetryCollectionManager, core.elasticsearch.dataClient); - const router = core.http.createRouter(); + registerCollection(telemetryCollectionManager, elasticsearch.dataClient); + const router = http.createRouter(); registerRoutes({ config$, @@ -96,8 +102,8 @@ export class TelemetryPlugin implements Plugin { telemetryCollectionManager, }); - this.registerMappings(opts => core.savedObjects.registerType(opts)); - this.registerUsageCollectors(usageCollection, opts => core.savedObjects.registerType(opts)); + this.registerMappings(opts => savedObjects.registerType(opts)); + this.registerUsageCollectors(usageCollection, metrics, opts => savedObjects.registerType(opts)); } public async start(core: CoreStart, { telemetryCollectionManager }: TelemetryPluginsStart) { @@ -153,11 +159,14 @@ export class TelemetryPlugin implements Plugin { private registerUsageCollectors( usageCollection: UsageCollectionSetup, + metrics: MetricsServiceSetup, registerType: SavedObjectsRegisterType ) { const getSavedObjectsClient = () => this.savedObjectsClient; const getUiSettingsClient = () => this.uiSettingsClient; + registerOpsStatsCollector(usageCollection, metrics.getOpsMetrics$()); + registerKibanaUsageCollector(usageCollection, this.legacyConfig$); registerTelemetryPluginUsageCollector(usageCollection, { currentKibanaVersion: this.currentKibanaVersion, config$: this.config$, diff --git a/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts b/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts index 86c6731e11d37..a17f1b8232a22 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts @@ -55,6 +55,10 @@ export function handleKibanaStats( ...kibanaStats.os, }; const formattedOsStats = Object.entries(os).reduce((acc, [key, value]) => { + if (typeof value !== 'string') { + // There are new fields reported now from the "os" property like "load", "memory", etc. They are objects. + return acc; + } return { ...acc, [`${key}s`]: [{ [key]: value, count: 1 }], diff --git a/src/plugins/ui_actions/public/actions/action.ts b/src/plugins/ui_actions/public/actions/action.ts index 15f1d6dd79289..f5dbbc9f923ac 100644 --- a/src/plugins/ui_actions/public/actions/action.ts +++ b/src/plugins/ui_actions/public/actions/action.ts @@ -17,7 +17,7 @@ * under the License. */ -import { UiComponent } from 'src/plugins/kibana_utils/common'; +import { UiComponent } from 'src/plugins/kibana_utils/public'; import { ActionType, ActionContextMapping } from '../types'; import { Presentable } from '../util/presentable'; diff --git a/src/plugins/ui_actions/public/actions/index.ts b/src/plugins/ui_actions/public/actions/index.ts index 0ddba197aced6..0b0e60b3cf75c 100644 --- a/src/plugins/ui_actions/public/actions/index.ts +++ b/src/plugins/ui_actions/public/actions/index.ts @@ -23,6 +23,4 @@ export * from './action_factory_definition'; export * from './action_factory'; export * from './create_action'; export * from './incompatible_action_error'; -export * from './dynamic_action_storage'; -export * from './dynamic_action_manager'; export * from './types'; diff --git a/src/plugins/ui_actions/public/actions/types.ts b/src/plugins/ui_actions/public/actions/types.ts index 465f091e45ef1..d29e97eea532f 100644 --- a/src/plugins/ui_actions/public/actions/types.ts +++ b/src/plugins/ui_actions/public/actions/types.ts @@ -22,3 +22,9 @@ export interface SerializedAction { readonly name: string; readonly config: Config; } + +export interface SerializedEvent { + eventId: string; + triggers: string[]; + action: SerializedAction; +} diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index 9265d35bad9a9..4f00ac4a26fc3 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -31,11 +31,7 @@ export { ActionDefinition as UiActionsActionDefinition, ActionFactoryDefinition as UiActionsActionFactoryDefinition, ActionInternal as UiActionsActionInternal, - ActionStorage as UiActionsActionStorage, - AbstractActionStorage as UiActionsAbstractActionStorage, createAction, - DynamicActionManager, - DynamicActionManagerState, IncompatibleActionError, SerializedAction as UiActionsSerializedAction, SerializedEvent as UiActionsSerializedEvent, @@ -57,4 +53,4 @@ export { applyFilterTrigger, } from './triggers'; export { TriggerContextMapping, TriggerId, ActionContextMapping, ActionType } from './types'; -export { ActionByType, DynamicActionManager as UiActionsDynamicActionManager } from './actions'; +export { ActionByType } from './actions'; diff --git a/src/plugins/ui_actions/public/util/presentable.ts b/src/plugins/ui_actions/public/util/presentable.ts index 945fd2065ce78..57dc99b10dd0a 100644 --- a/src/plugins/ui_actions/public/util/presentable.ts +++ b/src/plugins/ui_actions/public/util/presentable.ts @@ -17,7 +17,7 @@ * under the License. */ -import { UiComponent } from 'src/plugins/kibana_utils/common'; +import { UiComponent } from 'src/plugins/kibana_utils/public'; /** * Represents something that can be displayed to user in UI. diff --git a/src/plugins/vis_default_editor/README.md b/src/plugins/vis_default_editor/README.md new file mode 100644 index 0000000000000..8607dfe486ace --- /dev/null +++ b/src/plugins/vis_default_editor/README.md @@ -0,0 +1,16 @@ +# Visualization Deafult Editor plugin + +The default editor is used in most primary visualizations, e.x. `Area`, `Data table`, `Pie`, etc. +It acts as a container for a particular visualization and options tabs. Contains the default "Data" tab in `public/components/sidebar/data_tab.tsx`. +The plugin exposes the static `DefaultEditorController` class to consume. + +```ts +import { DefaultEditorController } from '../../vis_default_editor/public'; + +const editor = new DefaultEditorController( + element, + vis, + eventEmitter, + embeddableHandler +); +``` \ No newline at end of file diff --git a/src/legacy/core_plugins/vis_default_editor/public/_agg.scss b/src/plugins/vis_default_editor/public/_agg.scss similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/_agg.scss rename to src/plugins/vis_default_editor/public/_agg.scss diff --git a/src/legacy/core_plugins/vis_default_editor/public/_agg_params.scss b/src/plugins/vis_default_editor/public/_agg_params.scss similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/_agg_params.scss rename to src/plugins/vis_default_editor/public/_agg_params.scss diff --git a/src/legacy/core_plugins/vis_default_editor/public/_default.scss b/src/plugins/vis_default_editor/public/_default.scss similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/_default.scss rename to src/plugins/vis_default_editor/public/_default.scss diff --git a/src/legacy/core_plugins/vis_default_editor/public/_sidebar.scss b/src/plugins/vis_default_editor/public/_sidebar.scss similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/_sidebar.scss rename to src/plugins/vis_default_editor/public/_sidebar.scss diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap b/src/plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap rename to src/plugins/vis_default_editor/public/components/__snapshots__/agg.test.tsx.snap diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/__snapshots__/agg_group.test.tsx.snap b/src/plugins/vis_default_editor/public/components/__snapshots__/agg_group.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/__snapshots__/agg_group.test.tsx.snap rename to src/plugins/vis_default_editor/public/components/__snapshots__/agg_group.test.tsx.snap diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg.test.tsx b/src/plugins/vis_default_editor/public/components/agg.test.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg.test.tsx rename to src/plugins/vis_default_editor/public/components/agg.test.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg.tsx b/src/plugins/vis_default_editor/public/components/agg.tsx similarity index 98% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg.tsx rename to src/plugins/vis_default_editor/public/components/agg.tsx index 83fbf70c9099e..c7e3e609490f9 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg.tsx +++ b/src/plugins/vis_default_editor/public/components/agg.tsx @@ -28,14 +28,13 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { IAggConfig } from 'src/plugins/data/public'; +import { IAggConfig, TimeRange } from 'src/plugins/data/public'; import { DefaultEditorAggParams } from './agg_params'; import { DefaultEditorAggCommonProps } from './agg_common_props'; import { AGGS_ACTION_KEYS, AggsAction } from './agg_group_state'; import { RowsOrColumnsControl } from './controls/rows_or_columns'; import { RadiusRatioOptionControl } from './controls/radius_ratio_option'; import { getSchemaByName } from '../schemas'; -import { TimeRange } from '../../../../../plugins/data/public'; import { buildAggDescription } from './agg_params_helper'; export interface DefaultEditorAggProps extends DefaultEditorAggCommonProps { diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_add.tsx b/src/plugins/vis_default_editor/public/components/agg_add.tsx similarity index 98% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_add.tsx rename to src/plugins/vis_default_editor/public/components/agg_add.tsx index 9df4ea58e0f07..f2c2f8b4d0b94 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_add.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_add.tsx @@ -29,7 +29,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { IAggConfig, AggGroupNames } from '../../../../../plugins/data/public'; +import { IAggConfig, AggGroupNames } from '../../../data/public'; import { Schema } from '../schemas'; interface DefaultEditorAggAddProps { diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_common_props.ts b/src/plugins/vis_default_editor/public/components/agg_common_props.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_common_props.ts rename to src/plugins/vis_default_editor/public/components/agg_common_props.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.test.tsx b/src/plugins/vis_default_editor/public/components/agg_group.test.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_group.test.tsx rename to src/plugins/vis_default_editor/public/components/agg_group.test.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.tsx b/src/plugins/vis_default_editor/public/components/agg_group.tsx similarity index 97% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_group.tsx rename to src/plugins/vis_default_editor/public/components/agg_group.tsx index 792595fd421f6..ecbc41f28003c 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_group.tsx @@ -30,7 +30,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { AggGroupNames, search, IAggConfig } from '../../../../../plugins/data/public'; +import { AggGroupNames, search, IAggConfig, TimeRange } from '../../../data/public'; import { DefaultEditorAgg } from './agg'; import { DefaultEditorAggAdd } from './agg_add'; import { AddSchema, ReorderAggs, DefaultEditorAggCommonProps } from './agg_common_props'; @@ -42,7 +42,6 @@ import { } from './agg_group_helper'; import { aggGroupReducer, initAggsState, AGGS_ACTION_KEYS } from './agg_group_state'; import { Schema } from '../schemas'; -import { TimeRange } from '../../../../../plugins/data/public'; export interface DefaultEditorAggGroupProps extends DefaultEditorAggCommonProps { schemas: Schema[]; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group_helper.test.ts b/src/plugins/vis_default_editor/public/components/agg_group_helper.test.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_group_helper.test.ts rename to src/plugins/vis_default_editor/public/components/agg_group_helper.test.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group_helper.tsx b/src/plugins/vis_default_editor/public/components/agg_group_helper.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_group_helper.tsx rename to src/plugins/vis_default_editor/public/components/agg_group_helper.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group_state.tsx b/src/plugins/vis_default_editor/public/components/agg_group_state.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_group_state.tsx rename to src/plugins/vis_default_editor/public/components/agg_group_state.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_param.tsx b/src/plugins/vis_default_editor/public/components/agg_param.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_param.tsx rename to src/plugins/vis_default_editor/public/components/agg_param.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_param_props.ts b/src/plugins/vis_default_editor/public/components/agg_param_props.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_param_props.ts rename to src/plugins/vis_default_editor/public/components/agg_param_props.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.test.tsx b/src/plugins/vis_default_editor/public/components/agg_params.test.tsx similarity index 96% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_params.test.tsx rename to src/plugins/vis_default_editor/public/components/agg_params.test.tsx index 1c49ebf40640e..cac1b0851b92d 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.test.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_params.test.tsx @@ -25,8 +25,8 @@ import { DefaultEditorAggParams as PureDefaultEditorAggParams, DefaultEditorAggParamsProps, } from './agg_params'; -import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; -import { dataPluginMock } from '../../../../../plugins/data/public/mocks'; +import { KibanaContextProvider } from '../../../kibana_react/public'; +import { dataPluginMock } from '../../../data/public/mocks'; import { EditorVisState } from './sidebar/state/reducers'; const mockEditorConfig = { diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.tsx b/src/plugins/vis_default_editor/public/components/agg_params.tsx similarity index 98% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_params.tsx rename to src/plugins/vis_default_editor/public/components/agg_params.tsx index b1555b76500d0..3674e39b558d2 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_params.tsx @@ -22,7 +22,7 @@ import { EuiForm, EuiAccordion, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import useUnmount from 'react-use/lib/useUnmount'; -import { IAggConfig, IndexPattern, AggGroupNames } from '../../../../../plugins/data/public'; +import { IAggConfig, IndexPattern, AggGroupNames } from '../../../data/public'; import { DefaultEditorAggSelect } from './agg_select'; import { DefaultEditorAggParam } from './agg_param'; @@ -40,7 +40,7 @@ import { import { DefaultEditorCommonProps } from './agg_common_props'; import { EditorParamConfig, TimeIntervalParam, FixedParam, getEditorConfig } from './utils'; import { Schema, getSchemaByName } from '../schemas'; -import { useKibana } from '../../../../../plugins/kibana_react/public'; +import { useKibana } from '../../../kibana_react/public'; import { VisDefaultEditorKibanaServices } from '../types'; const FIXED_VALUE_PROP = 'fixedValue'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.test.ts b/src/plugins/vis_default_editor/public/components/agg_params_helper.test.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.test.ts rename to src/plugins/vis_default_editor/public/components/agg_params_helper.test.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.ts b/src/plugins/vis_default_editor/public/components/agg_params_helper.ts similarity index 99% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.ts rename to src/plugins/vis_default_editor/public/components/agg_params_helper.ts index 073cb7d5ac66c..a32bd76bafa5a 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.ts +++ b/src/plugins/vis_default_editor/public/components/agg_params_helper.ts @@ -34,7 +34,7 @@ import { AggParamEditorProps } from './agg_param_props'; import { aggParamsMap } from './agg_params_map'; import { EditorConfig } from './utils'; import { Schema, getSchemaByName } from '../schemas'; -import { search } from '../../../../../plugins/data/public'; +import { search } from '../../../data/public'; import { EditorVisState } from './sidebar/state/reducers'; interface ParamInstanceBase { diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_map.ts b/src/plugins/vis_default_editor/public/components/agg_params_map.ts similarity index 96% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_params_map.ts rename to src/plugins/vis_default_editor/public/components/agg_params_map.ts index 4517313b6fd6e..5af3cfc5b0928 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_map.ts +++ b/src/plugins/vis_default_editor/public/components/agg_params_map.ts @@ -18,12 +18,7 @@ */ import * as controls from './controls'; -import { - AggGroupNames, - BUCKET_TYPES, - METRIC_TYPES, - search, -} from '../../../../../plugins/data/public'; +import { AggGroupNames, BUCKET_TYPES, METRIC_TYPES, search } from '../../../data/public'; import { wrapWithInlineComp } from './controls/utils'; const { siblingPipelineType, parentPipelineType } = search.aggs; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_state.ts b/src/plugins/vis_default_editor/public/components/agg_params_state.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_params_state.ts rename to src/plugins/vis_default_editor/public/components/agg_params_state.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_select.tsx b/src/plugins/vis_default_editor/public/components/agg_select.tsx similarity index 98% rename from src/legacy/core_plugins/vis_default_editor/public/components/agg_select.tsx rename to src/plugins/vis_default_editor/public/components/agg_select.tsx index 7ee432946f3c8..6cb76b18e24a6 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_select.tsx +++ b/src/plugins/vis_default_editor/public/components/agg_select.tsx @@ -24,7 +24,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { IAggType, IndexPattern } from 'src/plugins/data/public'; -import { useKibana } from '../../../../../plugins/kibana_react/public'; +import { useKibana } from '../../../kibana_react/public'; import { ComboBoxGroupedOptions } from '../utils'; import { AGG_TYPE_ACTION_KEYS, AggTypeAction } from './agg_params_state'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/__snapshots__/extended_bounds.test.tsx.snap b/src/plugins/vis_default_editor/public/components/controls/__snapshots__/extended_bounds.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/__snapshots__/extended_bounds.test.tsx.snap rename to src/plugins/vis_default_editor/public/components/controls/__snapshots__/extended_bounds.test.tsx.snap diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/__snapshots__/metric_agg.test.tsx.snap b/src/plugins/vis_default_editor/public/components/controls/__snapshots__/metric_agg.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/__snapshots__/metric_agg.test.tsx.snap rename to src/plugins/vis_default_editor/public/components/controls/__snapshots__/metric_agg.test.tsx.snap diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/__snapshots__/size.test.tsx.snap b/src/plugins/vis_default_editor/public/components/controls/__snapshots__/size.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/__snapshots__/size.test.tsx.snap rename to src/plugins/vis_default_editor/public/components/controls/__snapshots__/size.test.tsx.snap diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/__snapshots__/top_aggregate.test.tsx.snap b/src/plugins/vis_default_editor/public/components/controls/__snapshots__/top_aggregate.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/__snapshots__/top_aggregate.test.tsx.snap rename to src/plugins/vis_default_editor/public/components/controls/__snapshots__/top_aggregate.test.tsx.snap diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_control_props.tsx b/src/plugins/vis_default_editor/public/components/controls/agg_control_props.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_control_props.tsx rename to src/plugins/vis_default_editor/public/components/controls/agg_control_props.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_utils.test.tsx b/src/plugins/vis_default_editor/public/components/controls/agg_utils.test.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_utils.test.tsx rename to src/plugins/vis_default_editor/public/components/controls/agg_utils.test.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/auto_precision.tsx b/src/plugins/vis_default_editor/public/components/controls/auto_precision.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/auto_precision.tsx rename to src/plugins/vis_default_editor/public/components/controls/auto_precision.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/from_to_list.tsx b/src/plugins/vis_default_editor/public/components/controls/components/from_to_list.tsx similarity index 68% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/components/from_to_list.tsx rename to src/plugins/vis_default_editor/public/components/controls/components/from_to_list.tsx index e52b2c85b63fa..b874459a8e7d3 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/from_to_list.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/components/from_to_list.tsx @@ -17,11 +17,11 @@ * under the License. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { EuiFieldText, EuiFlexItem, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { Ipv4Address } from '../../../../../../../plugins/kibana_utils/public'; +import { Ipv4Address } from '../../../../../kibana_utils/public'; import { InputList, InputListConfig, InputModel, InputObject, InputItem } from './input_list'; const EMPTY_STRING = ''; @@ -44,38 +44,42 @@ interface FromToListProps { setValidity(isValid: boolean): void; } -function FromToList({ showValidation, onBlur, ...rest }: FromToListProps) { - const fromToListConfig: InputListConfig = { - defaultValue: { - from: { value: '0.0.0.0', model: '0.0.0.0', isInvalid: false }, - to: { value: '255.255.255.255', model: '255.255.255.255', isInvalid: false }, +const defaultConfig = { + defaultValue: { + from: { value: '0.0.0.0', model: '0.0.0.0', isInvalid: false }, + to: { value: '255.255.255.255', model: '255.255.255.255', isInvalid: false }, + }, + validateClass: Ipv4Address, + getModelValue: (item: FromToObject = {}) => ({ + from: { + value: item.from || EMPTY_STRING, + model: item.from || EMPTY_STRING, + isInvalid: false, }, - validateClass: Ipv4Address, - getModelValue: (item: FromToObject = {}) => ({ - from: { - value: item.from || EMPTY_STRING, - model: item.from || EMPTY_STRING, - isInvalid: false, - }, - to: { value: item.to || EMPTY_STRING, model: item.to || EMPTY_STRING, isInvalid: false }, + to: { value: item.to || EMPTY_STRING, model: item.to || EMPTY_STRING, isInvalid: false }, + }), + getRemoveBtnAriaLabel: (item: FromToModel) => + i18n.translate('visDefaultEditor.controls.ipRanges.removeRangeAriaLabel', { + defaultMessage: 'Remove the range of {from} to {to}', + values: { from: item.from.value || '*', to: item.to.value || '*' }, }), - getRemoveBtnAriaLabel: (item: FromToModel) => - i18n.translate('visDefaultEditor.controls.ipRanges.removeRangeAriaLabel', { - defaultMessage: 'Remove the range of {from} to {to}', - values: { from: item.from.value || '*', to: item.to.value || '*' }, - }), - onChangeFn: ({ from, to }: FromToModel) => { - const result: FromToObject = {}; - if (from.model) { - result.from = from.model; - } - if (to.model) { - result.to = to.model; - } - return result; - }, - hasInvalidValuesFn: ({ from, to }: FromToModel) => from.isInvalid || to.isInvalid, - renderInputRow: (item: FromToModel, index, onChangeValue) => ( + onChangeFn: ({ from, to }: FromToModel) => { + const result: FromToObject = {}; + if (from.model) { + result.from = from.model; + } + if (to.model) { + result.to = to.model; + } + return result; + }, + hasInvalidValuesFn: ({ from, to }: FromToModel) => from.isInvalid || to.isInvalid, + modelNames: ['from', 'to'], +}; + +function FromToList({ showValidation, onBlur, ...rest }: FromToListProps) { + const renderInputRow = useCallback( + (item: FromToModel, index, onChangeValue) => ( <> ), - modelNames: ['from', 'to'], + [onBlur, showValidation] + ); + const fromToListConfig: InputListConfig = { + ...defaultConfig, + renderInputRow, }; return ; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/input_list.tsx b/src/plugins/vis_default_editor/public/components/controls/components/input_list.tsx similarity index 75% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/components/input_list.tsx rename to src/plugins/vis_default_editor/public/components/controls/components/input_list.tsx index cc80d0073c904..639b69cd3d33c 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/input_list.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/components/input_list.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { useState, useEffect, Fragment } from 'react'; +import React, { useState, useEffect, Fragment, useCallback } from 'react'; import { isEmpty, isEqual, mapValues, omit, pick } from 'lodash'; import { EuiButtonIcon, @@ -70,7 +70,10 @@ interface InputListProps { } const generateId = htmlIdGenerator(); -const validateValue = (inputValue: string | undefined, config: InputListConfig) => { +const validateValue = ( + inputValue: string | undefined, + InputObject: InputListConfig['validateClass'] +) => { const result = { model: inputValue || '', isInvalid: false, @@ -80,7 +83,6 @@ const validateValue = (inputValue: string | undefined, config: InputListConfig) return result; } try { - const InputObject = config.validateClass; result.model = new InputObject(inputValue).toString(); result.isInvalid = false; return result; @@ -91,47 +93,60 @@ const validateValue = (inputValue: string | undefined, config: InputListConfig) }; function InputList({ config, list, onChange, setValidity }: InputListProps) { + const { defaultValue, getModelValue, modelNames, onChangeFn, validateClass } = config; const [models, setModels] = useState(() => list.map( item => ({ id: generateId(), - ...config.getModelValue(item), + ...getModelValue(item), } as InputModel) ) ); const hasInvalidValues = models.some(config.hasInvalidValuesFn); - const updateValues = (modelList: InputModel[]) => { - setModels(modelList); - onChange(modelList.map(config.onChangeFn)); - }; - const onChangeValue = (index: number, value: string, modelName: string) => { - const { model, isInvalid } = validateValue(value, config); - updateValues( - models.map((range, arrayIndex) => - arrayIndex === index - ? { - ...range, - [modelName]: { - value, - model, - isInvalid, - }, - } - : range - ) - ); - }; - const onDelete = (id: string) => updateValues(models.filter(model => model.id !== id)); - const onAdd = () => - updateValues([ - ...models, - { - id: generateId(), - ...config.getModelValue(), - } as InputModel, - ]); + const updateValues = useCallback( + (modelList: InputModel[]) => { + setModels(modelList); + onChange(modelList.map(onChangeFn)); + }, + [onChangeFn, onChange] + ); + const onChangeValue = useCallback( + (index: number, value: string, modelName: string) => { + const { model, isInvalid } = validateValue(value, validateClass); + updateValues( + models.map((range, arrayIndex) => + arrayIndex === index + ? { + ...range, + [modelName]: { + value, + model, + isInvalid, + }, + } + : range + ) + ); + }, + [models, updateValues, validateClass] + ); + const onDelete = useCallback( + (id: string) => updateValues(models.filter(model => model.id !== id)), + [models, updateValues] + ); + const onAdd = useCallback( + () => + updateValues([ + ...models, + { + id: generateId(), + ...getModelValue(), + } as InputModel, + ]), + [getModelValue, models, updateValues] + ); useEffect(() => { // resposible for setting up an initial value when there is no default value @@ -139,15 +154,15 @@ function InputList({ config, list, onChange, setValidity }: InputListProps) { updateValues([ { id: generateId(), - ...config.defaultValue, + ...defaultValue, } as InputModel, ]); } - }, []); + }, [defaultValue, list.length, updateValues]); useEffect(() => { setValidity(!hasInvalidValues); - }, [hasInvalidValues]); + }, [hasInvalidValues, setValidity]); useEffect(() => { // responsible for discarding changes @@ -155,7 +170,7 @@ function InputList({ config, list, onChange, setValidity }: InputListProps) { list.length !== models.length || list.some((item, index) => { // make model to be the same shape as stored value - const model: InputObject = mapValues(pick(models[index], config.modelNames), 'model'); + const model: InputObject = mapValues(pick(models[index], modelNames), 'model'); // we need to skip empty values since they are not stored in saved object return !isEqual(item, omit(model, isEmpty)); @@ -166,12 +181,12 @@ function InputList({ config, list, onChange, setValidity }: InputListProps) { item => ({ id: generateId(), - ...config.getModelValue(item), + ...getModelValue(item), } as InputModel) ) ); } - }, [list]); + }, [getModelValue, list, modelNames, models]); return ( <> diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/mask_list.tsx b/src/plugins/vis_default_editor/public/components/controls/components/mask_list.tsx similarity index 62% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/components/mask_list.tsx rename to src/plugins/vis_default_editor/public/components/controls/components/mask_list.tsx index f6edecbbcbd70..560213fc08ff0 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/mask_list.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/components/mask_list.tsx @@ -17,12 +17,12 @@ * under the License. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { EuiFieldText, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { InputList, InputListConfig, InputObject, InputModel, InputItem } from './input_list'; -import { search } from '../../../../../../../plugins/data/public'; +import { search } from '../../../../../data/public'; const EMPTY_STRING = ''; @@ -42,36 +42,40 @@ interface MaskListProps { setValidity(isValid: boolean): void; } -function MaskList({ showValidation, onBlur, ...rest }: MaskListProps) { - const maskListConfig: InputListConfig = { - defaultValue: { - mask: { model: '0.0.0.0/1', value: '0.0.0.0/1', isInvalid: false }, +const defaultConfig = { + defaultValue: { + mask: { model: '0.0.0.0/1', value: '0.0.0.0/1', isInvalid: false }, + }, + validateClass: search.aggs.CidrMask, + getModelValue: (item: MaskObject = {}) => ({ + mask: { + model: item.mask || EMPTY_STRING, + value: item.mask || EMPTY_STRING, + isInvalid: false, }, - validateClass: search.aggs.CidrMask, - getModelValue: (item: MaskObject = {}) => ({ - mask: { - model: item.mask || EMPTY_STRING, - value: item.mask || EMPTY_STRING, - isInvalid: false, - }, - }), - getRemoveBtnAriaLabel: (item: MaskModel) => - item.mask.value - ? i18n.translate('visDefaultEditor.controls.ipRanges.removeCidrMaskButtonAriaLabel', { - defaultMessage: 'Remove the CIDR mask value of {mask}', - values: { mask: item.mask.value }, - }) - : i18n.translate('visDefaultEditor.controls.ipRanges.removeEmptyCidrMaskButtonAriaLabel', { - defaultMessage: 'Remove the CIDR mask default value', - }), - onChangeFn: ({ mask }: MaskModel) => { - if (mask.model) { - return { mask: mask.model }; - } - return {}; - }, - hasInvalidValuesFn: ({ mask }) => mask.isInvalid, - renderInputRow: ({ mask }: MaskModel, index, onChangeValue) => ( + }), + getRemoveBtnAriaLabel: (item: MaskModel) => + item.mask.value + ? i18n.translate('visDefaultEditor.controls.ipRanges.removeCidrMaskButtonAriaLabel', { + defaultMessage: 'Remove the CIDR mask value of {mask}', + values: { mask: item.mask.value }, + }) + : i18n.translate('visDefaultEditor.controls.ipRanges.removeEmptyCidrMaskButtonAriaLabel', { + defaultMessage: 'Remove the CIDR mask default value', + }), + onChangeFn: ({ mask }: MaskModel) => { + if (mask.model) { + return { mask: mask.model }; + } + return {}; + }, + hasInvalidValuesFn: ({ mask }: MaskModel) => mask.isInvalid, + modelNames: 'mask', +}; + +function MaskList({ showValidation, onBlur, ...rest }: MaskListProps) { + const renderInputRow = useCallback( + ({ mask }: MaskModel, index, onChangeValue) => ( ), - modelNames: 'mask', + [onBlur, showValidation] + ); + const maskListConfig: InputListConfig = { + ...defaultConfig, + renderInputRow, }; return ; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/__snapshots__/number_list.test.tsx.snap b/src/plugins/vis_default_editor/public/components/controls/components/number_list/__snapshots__/number_list.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/__snapshots__/number_list.test.tsx.snap rename to src/plugins/vis_default_editor/public/components/controls/components/number_list/__snapshots__/number_list.test.tsx.snap diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/__snapshots__/number_row.test.tsx.snap b/src/plugins/vis_default_editor/public/components/controls/components/number_list/__snapshots__/number_row.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/__snapshots__/number_row.test.tsx.snap rename to src/plugins/vis_default_editor/public/components/controls/components/number_list/__snapshots__/number_row.test.tsx.snap diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/index.ts b/src/plugins/vis_default_editor/public/components/controls/components/number_list/index.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/index.ts rename to src/plugins/vis_default_editor/public/components/controls/components/number_list/index.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/number_list.test.tsx b/src/plugins/vis_default_editor/public/components/controls/components/number_list/number_list.test.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/number_list.test.tsx rename to src/plugins/vis_default_editor/public/components/controls/components/number_list/number_list.test.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/number_list.tsx b/src/plugins/vis_default_editor/public/components/controls/components/number_list/number_list.tsx similarity index 98% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/number_list.tsx rename to src/plugins/vis_default_editor/public/components/controls/components/number_list/number_list.tsx index a43c66c2e08cc..b4683e47979ce 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/number_list.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/components/number_list/number_list.tsx @@ -79,7 +79,7 @@ function NumberList({ if (!numberArray.length) { onChange([models[0].value as number]); } - }, []); + }, [models, numberArray.length, onChange]); const isValid = !hasInvalidValues(models); useValidation(setValidity, isValid); @@ -109,7 +109,7 @@ function NumberList({ }) ); }, - [numberRange, models, onUpdate] + [models, onUpdate] ); // Add an item to the end of the list diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/number_row.test.tsx b/src/plugins/vis_default_editor/public/components/controls/components/number_list/number_row.test.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/number_row.test.tsx rename to src/plugins/vis_default_editor/public/components/controls/components/number_list/number_row.test.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/number_row.tsx b/src/plugins/vis_default_editor/public/components/controls/components/number_list/number_row.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/number_row.tsx rename to src/plugins/vis_default_editor/public/components/controls/components/number_list/number_row.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/range.test.ts b/src/plugins/vis_default_editor/public/components/controls/components/number_list/range.test.ts similarity index 98% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/range.test.ts rename to src/plugins/vis_default_editor/public/components/controls/components/number_list/range.test.ts index e9090e5b38ef7..a19034d3c8e92 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/range.test.ts +++ b/src/plugins/vis_default_editor/public/components/controls/components/number_list/range.test.ts @@ -108,7 +108,6 @@ describe('Range parsing utility', () => { }; forOwn(tests, (spec, str: any) => { - // eslint-disable-next-line jest/valid-describe describe(str, () => { const range = parseRange(str); diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/range.ts b/src/plugins/vis_default_editor/public/components/controls/components/number_list/range.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/range.ts rename to src/plugins/vis_default_editor/public/components/controls/components/number_list/range.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/utils.test.ts b/src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.test.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/utils.test.ts rename to src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.test.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts b/src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts rename to src/plugins/vis_default_editor/public/components/controls/components/number_list/utils.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/date_ranges.test.tsx b/src/plugins/vis_default_editor/public/components/controls/date_ranges.test.tsx similarity index 95% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/date_ranges.test.tsx rename to src/plugins/vis_default_editor/public/components/controls/date_ranges.test.tsx index 6b1a4dca7b84f..b844fdfb82256 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/date_ranges.test.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/date_ranges.test.tsx @@ -20,8 +20,8 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { DateRangesParamEditor } from './date_ranges'; -import { KibanaContextProvider } from '../../../../../../plugins/kibana_react/public'; -import { docLinksServiceMock } from '../../../../../../core/public/mocks'; +import { KibanaContextProvider } from '../../../../kibana_react/public'; +import { docLinksServiceMock } from '../../../../../core/public/mocks'; describe('DateRangesParamEditor component', () => { let setValue: jest.Mock; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/date_ranges.tsx b/src/plugins/vis_default_editor/public/components/controls/date_ranges.tsx similarity index 90% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/date_ranges.tsx rename to src/plugins/vis_default_editor/public/components/controls/date_ranges.tsx index 15e864bfd026d..57f4c7d04019b 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/date_ranges.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/date_ranges.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { Fragment, useState, useEffect } from 'react'; +import React, { Fragment, useState, useEffect, useCallback } from 'react'; import { htmlIdGenerator, EuiButtonIcon, @@ -36,8 +36,9 @@ import dateMath from '@elastic/datemath'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { isEqual, omit } from 'lodash'; +import { useMount } from 'react-use'; -import { useKibana } from '../../../../../../plugins/kibana_react/public'; +import { useKibana } from '../../../../kibana_react/public'; import { AggParamEditorProps } from '../agg_param_props'; const FROM_PLACEHOLDER = '\u2212\u221E'; @@ -72,12 +73,26 @@ function DateRangesParamEditor({ ({ from, to }) => (!from && !to) || !validateDateMath(from) || !validateDateMath(to) ); - // set up an initial range when there is no default range - useEffect(() => { + const updateRanges = useCallback( + (rangeValues: DateRangeValuesModel[]) => { + // do not set internal id parameter into saved object + setValue(rangeValues.map(range => omit(range, 'id'))); + setRanges(rangeValues); + }, + [setValue] + ); + + const onAddRange = useCallback(() => updateRanges([...ranges, { id: generateId() }]), [ + ranges, + updateRanges, + ]); + + useMount(() => { + // set up an initial range when there is no default range if (!value.length) { onAddRange(); } - }, []); + }); useEffect(() => { // responsible for discarding changes @@ -87,18 +102,12 @@ function DateRangesParamEditor({ ) { setRanges(value.map(range => ({ ...range, id: generateId() }))); } - }, [value]); + }, [ranges, value]); useEffect(() => { setValidity(!hasInvalidRange); - }, [hasInvalidRange]); - - const updateRanges = (rangeValues: DateRangeValuesModel[]) => { - // do not set internal id parameter into saved object - setValue(rangeValues.map(range => omit(range, 'id'))); - setRanges(rangeValues); - }; - const onAddRange = () => updateRanges([...ranges, { id: generateId() }]); + }, [hasInvalidRange, setValidity]); + const onRemoveRange = (id: string) => updateRanges(ranges.filter(range => range.id !== id)); const onChangeRange = (id: string, key: string, newValue: string) => updateRanges( diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/drop_partials.tsx b/src/plugins/vis_default_editor/public/components/controls/drop_partials.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/drop_partials.tsx rename to src/plugins/vis_default_editor/public/components/controls/drop_partials.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/extended_bounds.test.tsx b/src/plugins/vis_default_editor/public/components/controls/extended_bounds.test.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/extended_bounds.test.tsx rename to src/plugins/vis_default_editor/public/components/controls/extended_bounds.test.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/extended_bounds.tsx b/src/plugins/vis_default_editor/public/components/controls/extended_bounds.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/extended_bounds.tsx rename to src/plugins/vis_default_editor/public/components/controls/extended_bounds.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/field.test.tsx b/src/plugins/vis_default_editor/public/components/controls/field.test.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/field.test.tsx rename to src/plugins/vis_default_editor/public/components/controls/field.test.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/field.tsx b/src/plugins/vis_default_editor/public/components/controls/field.tsx similarity index 97% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/field.tsx rename to src/plugins/vis_default_editor/public/components/controls/field.tsx index fc1cbb51b70a7..42086004a12dc 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/field.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/field.tsx @@ -18,7 +18,8 @@ */ import { get } from 'lodash'; -import React, { useEffect, useState, useCallback } from 'react'; +import React, { useState, useCallback } from 'react'; +import { useMount } from 'react-use'; import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -84,8 +85,7 @@ function FieldParamEditor({ const showErrorMessage = (showValidation || !indexedFields.length) && !isValid; useValidation(setValidity, isValid); - - useEffect(() => { + useMount(() => { // set field if only one available if (indexedFields.length !== 1) { return; @@ -98,7 +98,7 @@ function FieldParamEditor({ } else if (indexedField.options.length === 1) { setValue(indexedField.options[0].target); } - }, []); + }); const onSearchChange = useCallback(searchValue => setIsDirty(Boolean(searchValue)), []); diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/filter.tsx b/src/plugins/vis_default_editor/public/components/controls/filter.tsx similarity index 99% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/filter.tsx rename to src/plugins/vis_default_editor/public/components/controls/filter.tsx index e2e7c2895093e..16aaff47f49c3 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/filter.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/filter.tsx @@ -21,7 +21,7 @@ import React, { useState } from 'react'; import { EuiForm, EuiButtonIcon, EuiFieldText, EuiFormRow, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { IAggConfig, Query, QueryStringInput } from '../../../../../../plugins/data/public'; +import { IAggConfig, Query, QueryStringInput } from '../../../../data/public'; interface FilterRowProps { id: string; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/filters.tsx b/src/plugins/vis_default_editor/public/components/controls/filters.tsx similarity index 96% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/filters.tsx rename to src/plugins/vis_default_editor/public/components/controls/filters.tsx index be4c62ab08aa2..d4e698f655c5e 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/filters.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/filters.tsx @@ -21,9 +21,10 @@ import React, { useState, useEffect } from 'react'; import { omit, isEqual } from 'lodash'; import { htmlIdGenerator, EuiButton, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { useMount } from 'react-use'; import { Query } from 'src/plugins/data/public'; -import { useKibana } from '../../../../../../plugins/kibana_react/public'; +import { useKibana } from '../../../../kibana_react/public'; import { FilterRow } from './filter'; import { AggParamEditorProps } from '../agg_param_props'; @@ -40,10 +41,10 @@ function FiltersParamEditor({ agg, value = [], setValue }: AggParamEditorProps ({ ...filter, id: generateId() })) ); - useEffect(() => { + useMount(() => { // set parsed values into model after initialization setValue(filters.map(filter => omit({ ...filter, input: filter.input }, 'id'))); - }, []); + }); useEffect(() => { // responsible for discarding changes @@ -53,7 +54,7 @@ function FiltersParamEditor({ agg, value = [], setValue }: AggParamEditorProps ({ ...filter, id: generateId() }))); } - }, [value]); + }, [filters, value]); const updateFilters = (updatedFilters: FilterValue[]) => { // do not set internal id parameter into saved object diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx b/src/plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx similarity index 73% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx rename to src/plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx index 90b7cb03b7a5b..a316a087c8bcb 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/has_extended_bounds.tsx @@ -17,22 +17,32 @@ * under the License. */ -import React, { useEffect } from 'react'; +import React, { useEffect, useRef } from 'react'; import { i18n } from '@kbn/i18n'; -import { search } from '../../../../../../plugins/data/public'; +import { search } from '../../../../data/public'; import { SwitchParamEditor } from './switch'; import { AggParamEditorProps } from '../agg_param_props'; const { isType } = search.aggs; function HasExtendedBoundsParamEditor(props: AggParamEditorProps) { + const { agg, setValue, value } = props; + const minDocCount = useRef(agg.params.min_doc_count); + useEffect(() => { - props.setValue(props.value && props.agg.params.min_doc_count); - }, [props.agg.params.min_doc_count]); + if (minDocCount.current !== agg.params.min_doc_count) { + // The "Extend bounds" param is only enabled when "Show empty buckets" is turned on. + // So if "Show empty buckets" is changed, "Extend bounds" should reflect changes + minDocCount.current = agg.params.min_doc_count; + + setValue(value && agg.params.min_doc_count); + } + }, [agg.params.min_doc_count, setValue, value]); return ( ) { !props.agg.params.min_doc_count || !(isType('number')(props.agg) || isType('date')(props.agg)) } - {...props} /> ); } diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/index.ts b/src/plugins/vis_default_editor/public/components/controls/index.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/index.ts rename to src/plugins/vis_default_editor/public/components/controls/index.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/ip_range_type.tsx b/src/plugins/vis_default_editor/public/components/controls/ip_range_type.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/ip_range_type.tsx rename to src/plugins/vis_default_editor/public/components/controls/ip_range_type.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/ip_ranges.tsx b/src/plugins/vis_default_editor/public/components/controls/ip_ranges.tsx similarity index 79% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/ip_ranges.tsx rename to src/plugins/vis_default_editor/public/components/controls/ip_ranges.tsx index c4b90649aaaae..5ffa8088a9546 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/ip_ranges.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/ip_ranges.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { EuiFormRow } from '@elastic/eui'; import { FromToList, FromToObject } from './components/from_to_list'; @@ -38,12 +38,22 @@ function IpRangesParamEditor({ setValidity, showValidation, }: AggParamEditorProps) { - const handleChange = (modelName: IpRangeTypes, items: Array) => { - setValue({ - ...value, - [modelName]: items, - }); - }; + const handleMaskListChange = useCallback( + (items: MaskObject[]) => + setValue({ + ...value, + [IpRangeTypes.MASK]: items, + }), + [setValue, value] + ); + const handleFromToListChange = useCallback( + (items: FromToObject[]) => + setValue({ + ...value, + [IpRangeTypes.FROM_TO]: items, + }), + [setValue, value] + ); return ( @@ -52,7 +62,7 @@ function IpRangesParamEditor({ list={value.mask} showValidation={showValidation} onBlur={setTouched} - onChange={items => handleChange(IpRangeTypes.MASK, items)} + onChange={handleMaskListChange} setValidity={setValidity} /> ) : ( @@ -60,7 +70,7 @@ function IpRangesParamEditor({ list={value.fromTo} showValidation={showValidation} onBlur={setTouched} - onChange={items => handleChange(IpRangeTypes.FROM_TO, items)} + onChange={handleFromToListChange} setValidity={setValidity} /> )} diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/is_filtered_by_collar.tsx b/src/plugins/vis_default_editor/public/components/controls/is_filtered_by_collar.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/is_filtered_by_collar.tsx rename to src/plugins/vis_default_editor/public/components/controls/is_filtered_by_collar.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/metric_agg.test.tsx b/src/plugins/vis_default_editor/public/components/controls/metric_agg.test.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/metric_agg.test.tsx rename to src/plugins/vis_default_editor/public/components/controls/metric_agg.test.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/metric_agg.tsx b/src/plugins/vis_default_editor/public/components/controls/metric_agg.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/metric_agg.tsx rename to src/plugins/vis_default_editor/public/components/controls/metric_agg.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/min_doc_count.tsx b/src/plugins/vis_default_editor/public/components/controls/min_doc_count.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/min_doc_count.tsx rename to src/plugins/vis_default_editor/public/components/controls/min_doc_count.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/missing_bucket.tsx b/src/plugins/vis_default_editor/public/components/controls/missing_bucket.tsx similarity index 93% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/missing_bucket.tsx rename to src/plugins/vis_default_editor/public/components/controls/missing_bucket.tsx index 7010f0d53e569..7d4d2230bd766 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/missing_bucket.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/missing_bucket.tsx @@ -21,20 +21,22 @@ import React, { useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { SwitchParamEditor } from './switch'; -import { search } from '../../../../../../plugins/data/public'; +import { search } from '../../../../data/public'; import { AggParamEditorProps } from '../agg_param_props'; function MissingBucketParamEditor(props: AggParamEditorProps) { const fieldTypeIsNotString = !search.aggs.isStringType(props.agg); + const { setValue } = props; useEffect(() => { if (fieldTypeIsNotString) { - props.setValue(false); + setValue(false); } - }, [fieldTypeIsNotString]); + }, [fieldTypeIsNotString, setValue]); return ( ) { } )} disabled={fieldTypeIsNotString} - {...props} /> ); } diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/number_interval.tsx b/src/plugins/vis_default_editor/public/components/controls/number_interval.tsx similarity index 99% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/number_interval.tsx rename to src/plugins/vis_default_editor/public/components/controls/number_interval.tsx index 6ab5ee2d260a1..02bf680734526 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/number_interval.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/number_interval.tsx @@ -61,7 +61,7 @@ function NumberIntervalParamEditor({ useEffect(() => { setValidity(isValid); - }, [isValid]); + }, [isValid, setValidity]); const onChange = useCallback( ({ target }: React.ChangeEvent) => diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/order.tsx b/src/plugins/vis_default_editor/public/components/controls/order.tsx similarity index 98% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/order.tsx rename to src/plugins/vis_default_editor/public/components/controls/order.tsx index 8f63662d928c1..e609bf9adf790 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/order.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/order.tsx @@ -39,7 +39,7 @@ function OrderParamEditor({ useEffect(() => { setValidity(isValid); - }, [isValid]); + }, [isValid, setValidity]); // @ts-ignore return ( diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/order_agg.test.tsx b/src/plugins/vis_default_editor/public/components/controls/order_agg.test.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/order_agg.test.tsx rename to src/plugins/vis_default_editor/public/components/controls/order_agg.test.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/order_agg.tsx b/src/plugins/vis_default_editor/public/components/controls/order_agg.tsx similarity index 97% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/order_agg.tsx rename to src/plugins/vis_default_editor/public/components/controls/order_agg.tsx index 41672bc192fab..c5a35cbbd7ab1 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/order_agg.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/order_agg.tsx @@ -20,7 +20,7 @@ import React, { useEffect } from 'react'; import { EuiSpacer } from '@elastic/eui'; -import { AggParamType, IAggConfig, AggGroupNames } from '../../../../../../plugins/data/public'; +import { AggParamType, IAggConfig, AggGroupNames } from '../../../../data/public'; import { useSubAggParamsHandlers } from './utils'; import { AggParamEditorProps } from '../agg_param_props'; import { DefaultEditorAggParams } from '../agg_params'; @@ -47,7 +47,7 @@ function OrderAggParamEditor({ if (orderBy !== 'custom' && value) { setValue(undefined); } - }, [orderBy]); + }, [agg, aggParam, orderBy, setValue, value]); const { onAggTypeChange, setAggParamValue } = useSubAggParamsHandlers( agg, diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/order_by.tsx b/src/plugins/vis_default_editor/public/components/controls/order_by.tsx similarity index 94% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/order_by.tsx rename to src/plugins/vis_default_editor/public/components/controls/order_by.tsx index 9f1aaa54a8ca3..47b12f4340d42 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/order_by.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/order_by.tsx @@ -17,9 +17,10 @@ * under the License. */ -import React, { useEffect } from 'react'; +import React from 'react'; import { EuiFormRow, EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useMount } from 'react-use'; import { isCompatibleAggregation, @@ -28,7 +29,7 @@ import { useValidation, } from './utils'; import { AggParamEditorProps } from '../agg_param_props'; -import { search } from '../../../../../../plugins/data/public'; +import { search } from '../../../../data/public'; const { termsAggFilter } = search.aggs; const DEFAULT_VALUE = '_key'; @@ -58,8 +59,7 @@ function OrderByParamEditor({ const isValid = !!value; useValidation(setValidity, isValid); - - useEffect(() => { + useMount(() => { // setup the initial value of orderBy if (!value) { let respAgg = { id: DEFAULT_VALUE }; @@ -70,7 +70,7 @@ function OrderByParamEditor({ setValue(respAgg.id); } - }, []); + }); useFallbackMetric(setValue, termsAggFilter, metricAggs, value, DEFAULT_VALUE); diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/other_bucket.tsx b/src/plugins/vis_default_editor/public/components/controls/other_bucket.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/other_bucket.tsx rename to src/plugins/vis_default_editor/public/components/controls/other_bucket.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/percentile_ranks.tsx b/src/plugins/vis_default_editor/public/components/controls/percentile_ranks.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/percentile_ranks.tsx rename to src/plugins/vis_default_editor/public/components/controls/percentile_ranks.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/percentiles.test.tsx b/src/plugins/vis_default_editor/public/components/controls/percentiles.test.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/percentiles.test.tsx rename to src/plugins/vis_default_editor/public/components/controls/percentiles.test.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/percentiles.tsx b/src/plugins/vis_default_editor/public/components/controls/percentiles.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/percentiles.tsx rename to src/plugins/vis_default_editor/public/components/controls/percentiles.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/precision.tsx b/src/plugins/vis_default_editor/public/components/controls/precision.tsx similarity index 95% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/precision.tsx rename to src/plugins/vis_default_editor/public/components/controls/precision.tsx index df71e72ee6c61..ad2011513b171 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/precision.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/precision.tsx @@ -22,7 +22,7 @@ import React from 'react'; import { EuiRange, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useKibana } from '../../../../../../plugins/kibana_react/public'; +import { useKibana } from '../../../../kibana_react/public'; import { AggParamEditorProps } from '../agg_param_props'; function PrecisionParamEditor({ agg, value, setValue }: AggParamEditorProps) { diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/radius_ratio_option.tsx b/src/plugins/vis_default_editor/public/components/controls/radius_ratio_option.tsx similarity index 95% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/radius_ratio_option.tsx rename to src/plugins/vis_default_editor/public/components/controls/radius_ratio_option.tsx index c64b079e4f802..86c4431b6d5ed 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/radius_ratio_option.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/radius_ratio_option.tsx @@ -17,10 +17,11 @@ * under the License. */ -import React, { useEffect, useCallback } from 'react'; +import React, { useCallback } from 'react'; import { EuiFormRow, EuiIconTip, EuiRange, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { useMount } from 'react-use'; import { AggControlProps } from './agg_control_props'; @@ -44,11 +45,11 @@ function RadiusRatioOptionControl({ editorStateParams, setStateParamValue }: Agg ); - useEffect(() => { + useMount(() => { if (!editorStateParams.radiusRatio) { setStateParamValue(PARAM_NAME, DEFAULT_VALUE); } - }, []); + }); const onChange = useCallback( (e: React.ChangeEvent | React.MouseEvent) => diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/range_control.tsx b/src/plugins/vis_default_editor/public/components/controls/range_control.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/range_control.tsx rename to src/plugins/vis_default_editor/public/components/controls/range_control.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/ranges.tsx b/src/plugins/vis_default_editor/public/components/controls/ranges.tsx similarity index 91% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/ranges.tsx rename to src/plugins/vis_default_editor/public/components/controls/ranges.tsx index 27de9dfe68ee0..5c6438b400408 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/ranges.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/ranges.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React, { Fragment, useState, useEffect } from 'react'; +import React, { Fragment, useCallback, useState, useEffect } from 'react'; import { htmlIdGenerator, EuiButtonIcon, @@ -76,13 +76,44 @@ function RangesParamEditor({ validateRange, }: RangesParamEditorProps) { const [ranges, setRanges] = useState(() => value.map(range => ({ ...range, id: generateId() }))); + const updateRanges = useCallback( + (rangeValues: RangeValuesModel[]) => { + // do not set internal id parameter into saved object + setValue(rangeValues.map(range => omit(range, 'id'))); + setRanges(rangeValues); + + if (setTouched) { + setTouched(true); + } + }, + [setTouched, setValue] + ); + const onAddRange = useCallback( + () => + addRangeValues + ? updateRanges([...ranges, { ...addRangeValues(), id: generateId() }]) + : updateRanges([...ranges, { id: generateId() }]), + [addRangeValues, ranges, updateRanges] + ); + const onRemoveRange = (id: string) => updateRanges(ranges.filter(range => range.id !== id)); + const onChangeRange = (id: string, key: string, newValue: string) => + updateRanges( + ranges.map(range => + range.id === id + ? { + ...range, + [key]: newValue === '' ? undefined : parseFloat(newValue), + } + : range + ) + ); // set up an initial range when there is no default range useEffect(() => { if (!value.length) { onAddRange(); } - }, []); + }, [onAddRange, value.length]); useEffect(() => { // responsible for discarding changes @@ -92,33 +123,7 @@ function RangesParamEditor({ ) { setRanges(value.map(range => ({ ...range, id: generateId() }))); } - }, [value]); - - const updateRanges = (rangeValues: RangeValuesModel[]) => { - // do not set internal id parameter into saved object - setValue(rangeValues.map(range => omit(range, 'id'))); - setRanges(rangeValues); - - if (setTouched) { - setTouched(true); - } - }; - const onAddRange = () => - addRangeValues - ? updateRanges([...ranges, { ...addRangeValues(), id: generateId() }]) - : updateRanges([...ranges, { id: generateId() }]); - const onRemoveRange = (id: string) => updateRanges(ranges.filter(range => range.id !== id)); - const onChangeRange = (id: string, key: string, newValue: string) => - updateRanges( - ranges.map(range => - range.id === id - ? { - ...range, - [key]: newValue === '' ? undefined : parseFloat(newValue), - } - : range - ) - ); + }, [ranges, value]); const hasInvalidRange = validateRange && diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/raw_json.tsx b/src/plugins/vis_default_editor/public/components/controls/raw_json.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/raw_json.tsx rename to src/plugins/vis_default_editor/public/components/controls/raw_json.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/rows_or_columns.tsx b/src/plugins/vis_default_editor/public/components/controls/rows_or_columns.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/rows_or_columns.tsx rename to src/plugins/vis_default_editor/public/components/controls/rows_or_columns.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/scale_metrics.tsx b/src/plugins/vis_default_editor/public/components/controls/scale_metrics.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/scale_metrics.tsx rename to src/plugins/vis_default_editor/public/components/controls/scale_metrics.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/size.test.tsx b/src/plugins/vis_default_editor/public/components/controls/size.test.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/size.test.tsx rename to src/plugins/vis_default_editor/public/components/controls/size.test.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/size.tsx b/src/plugins/vis_default_editor/public/components/controls/size.tsx similarity index 98% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/size.tsx rename to src/plugins/vis_default_editor/public/components/controls/size.tsx index 824ec4aeb253c..9f55eb9212dee 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/size.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/size.tsx @@ -48,7 +48,7 @@ function SizeParamEditor({ useEffect(() => { setValidity(isValid); - }, [isValid]); + }, [isValid, setValidity]); return ( { setValidity(isValid); - }, [isValid]); + }, [isValid, setValidity]); const onChange = useCallback(ev => setValue(ev.target.value), [setValue]); diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/sub_agg.tsx b/src/plugins/vis_default_editor/public/components/controls/sub_agg.tsx similarity index 97% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/sub_agg.tsx rename to src/plugins/vis_default_editor/public/components/controls/sub_agg.tsx index c9f53a68b3e83..ee0fbd8532ce9 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/sub_agg.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/sub_agg.tsx @@ -20,7 +20,7 @@ import React, { useEffect } from 'react'; import { EuiSpacer } from '@elastic/eui'; -import { AggParamType, IAggConfig, AggGroupNames } from '../../../../../../plugins/data/public'; +import { AggParamType, IAggConfig, AggGroupNames } from '../../../../data/public'; import { useSubAggParamsHandlers } from './utils'; import { AggParamEditorProps } from '../agg_param_props'; import { DefaultEditorAggParams } from '../agg_params'; @@ -29,7 +29,6 @@ function SubAggParamEditor({ agg, aggParam, formIsTouched, - value, metricAggs, state, setValue, @@ -44,7 +43,7 @@ function SubAggParamEditor({ } else if (!agg.params.customMetric) { setValue(aggParam.makeAgg(agg)); } - }, [value, metricAggs]); + }, [metricAggs, agg, setValue, aggParam]); const { onAggTypeChange, setAggParamValue } = useSubAggParamsHandlers( agg, diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/sub_metric.tsx b/src/plugins/vis_default_editor/public/components/controls/sub_metric.tsx similarity index 96% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/sub_metric.tsx rename to src/plugins/vis_default_editor/public/components/controls/sub_metric.tsx index ead3f8bb00623..361eeba9abdbf 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/sub_metric.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/sub_metric.tsx @@ -17,11 +17,12 @@ * under the License. */ -import React, { useEffect } from 'react'; +import React from 'react'; import { EuiFormLabel, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { useMount } from 'react-use'; -import { AggParamType, IAggConfig, AggGroupNames } from '../../../../../../plugins/data/public'; +import { AggParamType, IAggConfig, AggGroupNames } from '../../../../data/public'; import { useSubAggParamsHandlers } from './utils'; import { AggParamEditorProps } from '../agg_param_props'; import { DefaultEditorAggParams } from '../agg_params'; @@ -48,13 +49,13 @@ function SubMetricParamEditor({ const aggTitle = type === 'customMetric' ? metricTitle : bucketTitle; const aggGroup = type === 'customMetric' ? AggGroupNames.Metrics : AggGroupNames.Buckets; - useEffect(() => { + useMount(() => { if (agg.params[type]) { setValue(agg.params[type]); } else { setValue(aggParam.makeAgg(agg)); } - }, []); + }); const { onAggTypeChange, setAggParamValue } = useSubAggParamsHandlers( agg, diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/switch.tsx b/src/plugins/vis_default_editor/public/components/controls/switch.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/switch.tsx rename to src/plugins/vis_default_editor/public/components/controls/switch.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/test_utils.ts b/src/plugins/vis_default_editor/public/components/controls/test_utils.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/test_utils.ts rename to src/plugins/vis_default_editor/public/components/controls/test_utils.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/time_interval.tsx b/src/plugins/vis_default_editor/public/components/controls/time_interval.tsx similarity index 98% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/time_interval.tsx rename to src/plugins/vis_default_editor/public/components/controls/time_interval.tsx index 971a62faf7d7c..4af41f67bc524 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/time_interval.tsx +++ b/src/plugins/vis_default_editor/public/components/controls/time_interval.tsx @@ -23,7 +23,7 @@ import { EuiFormRow, EuiIconTip, EuiComboBox, EuiComboBoxOptionOption } from '@e import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { search, AggParamOption } from '../../../../../../plugins/data/public'; +import { search, AggParamOption } from '../../../../data/public'; import { AggParamEditorProps } from '../agg_param_props'; const { parseEsInterval, InvalidEsCalendarIntervalError } = search.aggs; @@ -161,7 +161,7 @@ function TimeIntervalParamEditor({ useEffect(() => { setValidity(isValid); - }, [isValid]); + }, [isValid, setValidity]); return ( { setValidity(isValid); - }, [isValid]); + }, [isValid, setValidity]); useEffect(() => { if (isFirstRun.current) { @@ -102,7 +102,7 @@ export function TopAggregateParamEditor({ if (filteredOptions.length === 1) { setValue(aggParam.options.find(opt => opt.value === filteredOptions[0].value)); } - }, [fieldType]); + }, [aggParam.options, fieldType, filteredOptions, setValue, value]); const handleChange = (event: React.ChangeEvent) => { if (event.target.value === emptyValue.value) { diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/top_field.tsx b/src/plugins/vis_default_editor/public/components/controls/top_field.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/top_field.tsx rename to src/plugins/vis_default_editor/public/components/controls/top_field.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/top_size.tsx b/src/plugins/vis_default_editor/public/components/controls/top_size.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/top_size.tsx rename to src/plugins/vis_default_editor/public/components/controls/top_size.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/top_sort_field.tsx b/src/plugins/vis_default_editor/public/components/controls/top_sort_field.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/top_sort_field.tsx rename to src/plugins/vis_default_editor/public/components/controls/top_sort_field.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/use_geocentroid.tsx b/src/plugins/vis_default_editor/public/components/controls/use_geocentroid.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/use_geocentroid.tsx rename to src/plugins/vis_default_editor/public/components/controls/use_geocentroid.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts b/src/plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts rename to src/plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/index.ts b/src/plugins/vis_default_editor/public/components/controls/utils/index.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/index.ts rename to src/plugins/vis_default_editor/public/components/controls/utils/index.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/inline_comp_wrapper.tsx b/src/plugins/vis_default_editor/public/components/controls/utils/inline_comp_wrapper.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/inline_comp_wrapper.tsx rename to src/plugins/vis_default_editor/public/components/controls/utils/inline_comp_wrapper.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/strings/comma_separated_list.test.ts b/src/plugins/vis_default_editor/public/components/controls/utils/strings/comma_separated_list.test.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/strings/comma_separated_list.test.ts rename to src/plugins/vis_default_editor/public/components/controls/utils/strings/comma_separated_list.test.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/strings/comma_separated_list.ts b/src/plugins/vis_default_editor/public/components/controls/utils/strings/comma_separated_list.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/strings/comma_separated_list.ts rename to src/plugins/vis_default_editor/public/components/controls/utils/strings/comma_separated_list.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/strings/index.ts b/src/plugins/vis_default_editor/public/components/controls/utils/strings/index.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/strings/index.ts rename to src/plugins/vis_default_editor/public/components/controls/utils/strings/index.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/strings/prose.test.ts b/src/plugins/vis_default_editor/public/components/controls/utils/strings/prose.test.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/strings/prose.test.ts rename to src/plugins/vis_default_editor/public/components/controls/utils/strings/prose.test.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/strings/prose.ts b/src/plugins/vis_default_editor/public/components/controls/utils/strings/prose.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/strings/prose.ts rename to src/plugins/vis_default_editor/public/components/controls/utils/strings/prose.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/use_handlers.ts b/src/plugins/vis_default_editor/public/components/controls/utils/use_handlers.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/use_handlers.ts rename to src/plugins/vis_default_editor/public/components/controls/utils/use_handlers.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/controls.tsx b/src/plugins/vis_default_editor/public/components/sidebar/controls.tsx similarity index 98% rename from src/legacy/core_plugins/vis_default_editor/public/components/sidebar/controls.tsx rename to src/plugins/vis_default_editor/public/components/sidebar/controls.tsx index 18b445b4a26db..db9d7d9e3316a 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/controls.tsx +++ b/src/plugins/vis_default_editor/public/components/sidebar/controls.tsx @@ -30,7 +30,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { useDebounce } from 'react-use'; -import { Vis } from '../../../../../../plugins/visualizations/public'; +import { Vis } from 'src/plugins/visualizations/public'; import { discardChanges, EditorAction } from './state'; interface DefaultEditorControlsProps { diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/data_tab.tsx b/src/plugins/vis_default_editor/public/components/sidebar/data_tab.tsx similarity index 97% rename from src/legacy/core_plugins/vis_default_editor/public/components/sidebar/data_tab.tsx rename to src/plugins/vis_default_editor/public/components/sidebar/data_tab.tsx index 0c967723db8e7..0466c64541e23 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/data_tab.tsx +++ b/src/plugins/vis_default_editor/public/components/sidebar/data_tab.tsx @@ -26,7 +26,8 @@ import { IAggConfig, IMetricAggType, search, -} from '../../../../../../plugins/data/public'; + TimeRange, +} from '../../../../data/public'; import { DefaultEditorAggGroup } from '../agg_group'; import { EditorAction, @@ -39,7 +40,6 @@ import { } from './state'; import { AddSchema, ReorderAggs, DefaultEditorAggCommonProps } from '../agg_common_props'; import { ISchemas } from '../../schemas'; -import { TimeRange } from '../../../../../../plugins/data/public'; import { EditorVisState } from './state/reducers'; export interface DefaultEditorDataTabProps { diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/index.ts b/src/plugins/vis_default_editor/public/components/sidebar/index.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/sidebar/index.ts rename to src/plugins/vis_default_editor/public/components/sidebar/index.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/navbar.tsx b/src/plugins/vis_default_editor/public/components/sidebar/navbar.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/sidebar/navbar.tsx rename to src/plugins/vis_default_editor/public/components/sidebar/navbar.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar.tsx b/src/plugins/vis_default_editor/public/components/sidebar/sidebar.tsx similarity index 96% rename from src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar.tsx rename to src/plugins/vis_default_editor/public/components/sidebar/sidebar.tsx index b24486a12fd24..9dfeae1815d1a 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar.tsx +++ b/src/plugins/vis_default_editor/public/components/sidebar/sidebar.tsx @@ -23,16 +23,15 @@ import { i18n } from '@kbn/i18n'; import { keyCodes, EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { EventEmitter } from 'events'; -import { Vis } from 'src/plugins/visualizations/public'; +import { Vis, PersistedState } from 'src/plugins/visualizations/public'; +import { SavedSearch } from 'src/plugins/discover/public'; +import { TimeRange } from 'src/plugins/data/public'; import { DefaultEditorNavBar, OptionTab } from './navbar'; import { DefaultEditorControls } from './controls'; import { setStateParamValue, useEditorReducer, useEditorFormState, discardChanges } from './state'; import { DefaultEditorAggCommonProps } from '../agg_common_props'; import { SidebarTitle } from './sidebar_title'; -import { PersistedState } from '../../../../../../plugins/visualizations/public'; -import { SavedSearch } from '../../../../../../plugins/discover/public'; import { Schema } from '../../schemas'; -import { TimeRange } from '../../../../../../plugins/data/public'; interface DefaultEditorSideBarProps { isCollapsed: boolean; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx b/src/plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx similarity index 97% rename from src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx rename to src/plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx index fb63a598a4fae..c9f83e5b474a6 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx +++ b/src/plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx @@ -35,8 +35,8 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { Vis } from '../../../../../../plugins/visualizations/public'; -import { SavedSearch } from '../../../../../../plugins/discover/public'; +import { Vis } from 'src/plugins/visualizations/public'; +import { SavedSearch } from 'src/plugins/discover/public'; interface LinkedSearchProps { savedSearch: SavedSearch; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/actions.ts b/src/plugins/vis_default_editor/public/components/sidebar/state/actions.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/actions.ts rename to src/plugins/vis_default_editor/public/components/sidebar/state/actions.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/constants.ts b/src/plugins/vis_default_editor/public/components/sidebar/state/constants.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/constants.ts rename to src/plugins/vis_default_editor/public/components/sidebar/state/constants.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/editor_form_state.ts b/src/plugins/vis_default_editor/public/components/sidebar/state/editor_form_state.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/editor_form_state.ts rename to src/plugins/vis_default_editor/public/components/sidebar/state/editor_form_state.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/index.ts b/src/plugins/vis_default_editor/public/components/sidebar/state/index.ts similarity index 96% rename from src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/index.ts rename to src/plugins/vis_default_editor/public/components/sidebar/state/index.ts index d39d6d07b32d2..1bfa100cbd0f7 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/index.ts +++ b/src/plugins/vis_default_editor/public/components/sidebar/state/index.ts @@ -24,7 +24,7 @@ import { Vis } from 'src/plugins/visualizations/public'; import { createEditorStateReducer, initEditorState, EditorVisState } from './reducers'; import { EditorStateActionTypes } from './constants'; import { EditorAction } from './actions'; -import { useKibana } from '../../../../../../../plugins/kibana_react/public'; +import { useKibana } from '../../../../../kibana_react/public'; import { VisDefaultEditorKibanaServices } from '../../../types'; export * from './editor_form_state'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts b/src/plugins/vis_default_editor/public/components/sidebar/state/reducers.ts similarity index 99% rename from src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts rename to src/plugins/vis_default_editor/public/components/sidebar/state/reducers.ts index b9f89cebd8bf3..4e7a2904584da 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts +++ b/src/plugins/vis_default_editor/public/components/sidebar/state/reducers.ts @@ -20,7 +20,7 @@ import { cloneDeep } from 'lodash'; import { Vis } from 'src/plugins/visualizations/public'; -import { AggGroupNames, DataPublicPluginStart } from '../../../../../../../plugins/data/public'; +import { AggGroupNames, DataPublicPluginStart } from '../../../../../data/public'; import { EditorStateActionTypes } from './constants'; import { getEnabledMetricAggsCount } from '../../agg_group_helper'; import { EditorAction } from './actions'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/utils/editor_config.ts b/src/plugins/vis_default_editor/public/components/utils/editor_config.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/utils/editor_config.ts rename to src/plugins/vis_default_editor/public/components/utils/editor_config.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/utils/index.ts b/src/plugins/vis_default_editor/public/components/utils/index.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/components/utils/index.ts rename to src/plugins/vis_default_editor/public/components/utils/index.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/default_editor.tsx b/src/plugins/vis_default_editor/public/default_editor.tsx similarity index 94% rename from src/legacy/core_plugins/vis_default_editor/public/default_editor.tsx rename to src/plugins/vis_default_editor/public/default_editor.tsx index 899b9c1b5fd6e..f1963b94dcf95 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/default_editor.tsx +++ b/src/plugins/vis_default_editor/public/default_editor.tsx @@ -19,8 +19,8 @@ import React, { useEffect, useRef, useState, useCallback } from 'react'; -import { EditorRenderProps } from '../../kibana/public/visualize/np_ready/types'; -import { PanelsContainer, Panel } from '../../../../plugins/kibana_react/public'; +import { EditorRenderProps } from 'src/legacy/core_plugins/kibana/public/visualize/np_ready/types'; +import { PanelsContainer, Panel } from '../../kibana_react/public'; import './vis_type_agg_filter'; import { DefaultEditorSideBar } from './components/sidebar'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx b/src/plugins/vis_default_editor/public/default_editor_controller.tsx similarity index 92% rename from src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx rename to src/plugins/vis_default_editor/public/default_editor_controller.tsx index 58e67b5064da5..798da09f8e30b 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/default_editor_controller.tsx +++ b/src/plugins/vis_default_editor/public/default_editor_controller.tsx @@ -23,9 +23,9 @@ import { i18n } from '@kbn/i18n'; import { EventEmitter } from 'events'; import { EditorRenderProps } from 'src/legacy/core_plugins/kibana/public/visualize/np_ready/types'; -import { Vis, VisualizeEmbeddableContract } from '../../../../plugins/visualizations/public'; -import { Storage } from '../../../../plugins/kibana_utils/public'; -import { KibanaContextProvider } from '../../../../plugins/kibana_react/public'; +import { Vis, VisualizeEmbeddableContract } from 'src/plugins/visualizations/public'; +import { Storage } from '../../kibana_utils/public'; +import { KibanaContextProvider } from '../../kibana_react/public'; import { DefaultEditor } from './default_editor'; import { DefaultEditorDataTab, OptionTab } from './components/sidebar'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/editor_size.ts b/src/plugins/vis_default_editor/public/editor_size.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/editor_size.ts rename to src/plugins/vis_default_editor/public/editor_size.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/index.scss b/src/plugins/vis_default_editor/public/index.scss similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/index.scss rename to src/plugins/vis_default_editor/public/index.scss diff --git a/src/legacy/core_plugins/vis_default_editor/public/index.ts b/src/plugins/vis_default_editor/public/index.ts similarity index 97% rename from src/legacy/core_plugins/vis_default_editor/public/index.ts rename to src/plugins/vis_default_editor/public/index.ts index 156d50f451b57..6c58c6df26b00 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/index.ts +++ b/src/plugins/vis_default_editor/public/index.ts @@ -17,6 +17,8 @@ * under the License. */ +import './index.scss'; + export { DefaultEditorController } from './default_editor_controller'; export { useValidation } from './components/controls/utils'; export { RangesParamEditor, RangeValues } from './components/controls/ranges'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/schemas.ts b/src/plugins/vis_default_editor/public/schemas.ts similarity index 96% rename from src/legacy/core_plugins/vis_default_editor/public/schemas.ts rename to src/plugins/vis_default_editor/public/schemas.ts index 4e632da44afc0..05ba5fa9c9419 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/schemas.ts +++ b/src/plugins/vis_default_editor/public/schemas.ts @@ -21,7 +21,7 @@ import _, { defaults } from 'lodash'; import { Optional } from '@kbn/utility-types'; -import { AggGroupNames, AggParam, IAggGroupNames } from '../../../../plugins/data/public'; +import { AggGroupNames, AggParam, IAggGroupNames } from '../../data/public'; export interface ISchemas { [AggGroupNames.Buckets]: Schema[]; diff --git a/src/legacy/core_plugins/vis_default_editor/public/types.ts b/src/plugins/vis_default_editor/public/types.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/types.ts rename to src/plugins/vis_default_editor/public/types.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/utils.test.ts b/src/plugins/vis_default_editor/public/utils.test.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/utils.test.ts rename to src/plugins/vis_default_editor/public/utils.test.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/utils.ts b/src/plugins/vis_default_editor/public/utils.ts similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/utils.ts rename to src/plugins/vis_default_editor/public/utils.ts diff --git a/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx b/src/plugins/vis_default_editor/public/vis_options_props.tsx similarity index 100% rename from src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx rename to src/plugins/vis_default_editor/public/vis_options_props.tsx diff --git a/src/legacy/core_plugins/vis_default_editor/public/vis_type_agg_filter.ts b/src/plugins/vis_default_editor/public/vis_type_agg_filter.ts similarity index 97% rename from src/legacy/core_plugins/vis_default_editor/public/vis_type_agg_filter.ts rename to src/plugins/vis_default_editor/public/vis_type_agg_filter.ts index 3ff212c43e6e8..bf5661f42a9f5 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/vis_type_agg_filter.ts +++ b/src/plugins/vis_default_editor/public/vis_type_agg_filter.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { IAggType, IAggConfig, IndexPattern, search } from '../../../../plugins/data/public'; +import { IAggType, IAggConfig, IndexPattern, search } from '../../data/public'; const { aggTypeFilters, propFilter } = search.aggs; const filterByName = propFilter('name'); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/np_core.test.mocks.ts b/src/plugins/vis_type_timeseries/server/validation_telemetry/saved_object_type.ts similarity index 60% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/np_core.test.mocks.ts rename to src/plugins/vis_type_timeseries/server/validation_telemetry/saved_object_type.ts index e1d9cfac95268..77b49e824334f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/np_core.test.mocks.ts +++ b/src/plugins/vis_type_timeseries/server/validation_telemetry/saved_object_type.ts @@ -17,25 +17,29 @@ * under the License. */ -let modalContents: React.Component; +import { flow } from 'lodash'; +import { SavedObjectMigrationFn, SavedObjectsType } from 'kibana/server'; -export const getModalContents = () => modalContents; - -jest.mock('ui/new_platform'); - -jest.doMock('ui/metadata', () => ({ - metadata: { - branch: 'my-metadata-branch', - version: 'my-metadata-version', +const resetCount: SavedObjectMigrationFn = doc => ({ + ...doc, + attributes: { + ...doc.attributes, + failedRequests: 0, }, -})); +}); -jest.doMock('ui/capabilities', () => ({ - uiCapabilities: { - visualize: { - save: true, +export const tsvbTelemetrySavedObjectType: SavedObjectsType = { + name: 'tsvb-validation-telemetry', + hidden: false, + namespaceAgnostic: true, + mappings: { + properties: { + failedRequests: { + type: 'long', + }, }, }, -})); - -jest.doMock('ui/chrome', () => ({ getKibanaVersion: () => '6.3.0', setVisible: () => {} })); + migrations: { + '7.7.0': flow(resetCount), + }, +}; diff --git a/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts index e49664265b8bb..779d9441df2fd 100644 --- a/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts +++ b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts @@ -19,6 +19,7 @@ import { APICaller, CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; import { UsageCollectionSetup } from '../../../usage_collection/server'; +import { tsvbTelemetrySavedObjectType } from './saved_object_type'; export interface ValidationTelemetryServiceSetup { logFailedValidation: () => void; @@ -36,6 +37,7 @@ export class ValidationTelemetryService implements Plugin { this.kibanaIndex = config.kibana.index; }); diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts index 02c114bad4e72..26f8278cd3d43 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.test.ts @@ -150,6 +150,32 @@ describe('migration visualization', () => { expect(aggs[3]).not.toHaveProperty('params.customBucket.params.time_zone'); expect(aggs[2]).not.toHaveProperty('params.time_zone'); }); + + it('should migrate obsolete match_all query', () => { + const migratedDoc = migrate({ + ...doc, + attributes: { + ...doc.attributes, + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + query: { + match_all: {}, + }, + }), + }, + }, + }); + const migratedSearchSource = JSON.parse( + migratedDoc.attributes.kibanaSavedObjectMeta.searchSourceJSON + ); + + expect(migratedSearchSource).toEqual({ + query: { + query: '', + language: 'kuery', + }, + }); + }); }); }); @@ -181,13 +207,13 @@ describe('migration visualization', () => { }, }); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "visState": "{}", - }, - "references": Array [], -} -`); + Object { + "attributes": Object { + "visState": "{}", + }, + "references": Array [], + } + `); }); it('skips errors when searchSourceJSON is null', () => { @@ -205,25 +231,25 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": null, - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": null, + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", + } + `); }); it('skips errors when searchSourceJSON is undefined', () => { @@ -241,25 +267,25 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": undefined, - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": undefined, + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", + } + `); }); it('skips error when searchSourceJSON is not a string', () => { @@ -276,25 +302,25 @@ Object { }; expect(migrate(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": 123, - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": 123, + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", + } + `); }); it('skips error when searchSourceJSON is invalid json', () => { @@ -311,25 +337,25 @@ Object { }; expect(migrate(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{abc123}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{abc123}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", + } + `); }); it('skips error when "index" and "filter" is missing from searchSourceJSON', () => { @@ -347,25 +373,25 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", + } + `); }); it('extracts "index" attribute from doc', () => { @@ -383,30 +409,30 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "pattern*", - "name": "kibanaSavedObjectMeta.searchSourceJSON.index", - "type": "index-pattern", - }, - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "pattern*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + }, + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", + } + `); }); it('extracts index patterns from the filter', () => { @@ -431,30 +457,30 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true,\\"filter\\":[{\\"meta\\":{\\"foo\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "my-index", - "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "type": "index-pattern", - }, - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true,\\"filter\\":[{\\"meta\\":{\\"foo\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "my-index", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern", + }, + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + "type": "visualization", + } + `); }); it('extracts index patterns from controls', () => { @@ -482,22 +508,22 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "foo": true, - "visState": "{\\"bar\\":false,\\"params\\":{\\"controls\\":[{\\"bar\\":true,\\"indexPatternRefName\\":\\"control_0_index_pattern\\"},{\\"foo\\":true}]}}", - }, - "id": "1", - "references": Array [ - Object { - "id": "pattern*", - "name": "control_0_index_pattern", - "type": "index-pattern", - }, - ], - "type": "visualization", -} -`); + Object { + "attributes": Object { + "foo": true, + "visState": "{\\"bar\\":false,\\"params\\":{\\"controls\\":[{\\"bar\\":true,\\"indexPatternRefName\\":\\"control_0_index_pattern\\"},{\\"foo\\":true}]}}", + }, + "id": "1", + "references": Array [ + Object { + "id": "pattern*", + "name": "control_0_index_pattern", + "type": "index-pattern", + }, + ], + "type": "visualization", + } + `); }); it('skips extracting savedSearchId when missing', () => { @@ -513,17 +539,17 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{}", - }, - "visState": "{}", - }, - "id": "1", - "references": Array [], -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{}", + }, + "visState": "{}", + }, + "id": "1", + "references": Array [], + } + `); }); it('extract savedSearchId from doc', () => { @@ -540,24 +566,24 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{}", + }, + "savedSearchRefName": "search_0", + "visState": "{}", + }, + "id": "1", + "references": Array [ + Object { + "id": "123", + "name": "search_0", + "type": "search", + }, + ], + } + `); }); it('delete savedSearchId when empty string in doc', () => { @@ -574,17 +600,17 @@ Object { const migratedDoc = migrate(doc); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{}", - }, - "visState": "{}", - }, - "id": "1", - "references": Array [], -} -`); + Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{}", + }, + "visState": "{}", + }, + "id": "1", + "references": Array [], + } + `); }); it('should return a new object if vis is table and has multiple split aggs', () => { @@ -904,12 +930,12 @@ Object { }, }); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "visState": "{\\"type\\":\\"gauge\\",\\"params\\":{\\"gauge\\":{\\"alignment\\":\\"horizontal\\"}}}", - }, -} -`); + Object { + "attributes": Object { + "visState": "{\\"type\\":\\"gauge\\",\\"params\\":{\\"gauge\\":{\\"alignment\\":\\"horizontal\\"}}}", + }, + } + `); }); it('migrates type = gauge verticalSplit: false to alignment: horizontal', () => { @@ -920,12 +946,12 @@ Object { }); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "visState": "{\\"type\\":\\"gauge\\",\\"params\\":{\\"gauge\\":{\\"alignment\\":\\"vertical\\"}}}", - }, -} -`); + Object { + "attributes": Object { + "visState": "{\\"type\\":\\"gauge\\",\\"params\\":{\\"gauge\\":{\\"alignment\\":\\"vertical\\"}}}", + }, + } + `); }); it('doesnt migrate type = gauge containing invalid visState object, adds message to log', () => { @@ -936,18 +962,18 @@ Object { }); expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "visState": "{\\"type\\":\\"gauge\\"}", - }, -} -`); + Object { + "attributes": Object { + "visState": "{\\"type\\":\\"gauge\\"}", + }, + } + `); expect(logMsgArr).toMatchInlineSnapshot(` -Array [ - "Exception @ migrateGaugeVerticalSplitToAlignment! TypeError: Cannot read property 'gauge' of undefined", - "Exception @ migrateGaugeVerticalSplitToAlignment! Payload: {\\"type\\":\\"gauge\\"}", -] -`); + Array [ + "Exception @ migrateGaugeVerticalSplitToAlignment! TypeError: Cannot read property 'gauge' of undefined", + "Exception @ migrateGaugeVerticalSplitToAlignment! Payload: {\\"type\\":\\"gauge\\"}", + ] + `); }); describe('filters agg query migration', () => { @@ -1353,4 +1379,85 @@ Array [ expect(timeSeriesParams.series[0].split_filters[0].filter.language).toEqual('lucene'); }); }); + + describe('7.7.0 tsvb opperator typo migration', () => { + const migrate = (doc: any) => + visualizationSavedObjectTypeMigrations['7.7.0']( + doc as Parameters[0], + savedObjectMigrationContext + ); + const generateDoc = (params: any) => ({ + attributes: { + title: 'My Vis', + description: 'This is my super cool vis.', + visState: JSON.stringify({ params }), + uiStateJSON: '{}', + version: 1, + kibanaSavedObjectMeta: { + searchSourceJSON: '{}', + }, + }, + }); + + it('should remove the misspelled opperator key if it exists', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene', + }, + series: [], + gauge_color_rules: [ + { + value: 0, + id: '020e3d50-75a6-11ea-8f61-71579ff7f64d', + gauge: 'rgba(69,39,217,1)', + opperator: 'lt', + }, + ], + }; + const timeSeriesDoc = generateDoc(params); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const migratedParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + + expect(migratedParams.gauge_color_rules[0]).toMatchInlineSnapshot(` + Object { + "gauge": "rgba(69,39,217,1)", + "id": "020e3d50-75a6-11ea-8f61-71579ff7f64d", + "opperator": "lt", + "value": 0, + } + `); + }); + + it('should not change color rules with the correct spelling', () => { + const params = { + type: 'timeseries', + filter: { + query: 'bytes:>1000', + language: 'lucene', + }, + series: [], + gauge_color_rules: [ + { + value: 0, + id: '020e3d50-75a6-11ea-8f61-71579ff7f64d', + gauge: 'rgba(69,39,217,1)', + opperator: 'lt', + }, + { + value: 0, + id: '020e3d50-75a6-11ea-8f61-71579ff7f64d', + gauge: 'rgba(69,39,217,1)', + operator: 'lt', + }, + ], + }; + const timeSeriesDoc = generateDoc(params); + const migratedtimeSeriesDoc = migrate(timeSeriesDoc); + const migratedParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; + + expect(migratedParams.gauge_color_rules[1]).toEqual(params.gauge_color_rules[1]); + }); + }); }); diff --git a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts index 9ee355cbb23cf..80783e41863ea 100644 --- a/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts +++ b/src/plugins/visualizations/server/saved_objects/visualization_migrations.ts @@ -19,6 +19,7 @@ import { SavedObjectMigrationFn } from 'kibana/server'; import { cloneDeep, get, omit, has, flow } from 'lodash'; +import { DEFAULT_QUERY_LANGUAGE } from '../../../data/common'; const migrateIndexPattern: SavedObjectMigrationFn = doc => { const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); @@ -98,6 +99,38 @@ const migratePercentileRankAggregation: SavedObjectMigrationFn = doc => { return doc; }; +// [TSVB] Remove stale opperator key +const migrateOperatorKeyTypo: SavedObjectMigrationFn = doc => { + const visStateJSON = get(doc, 'attributes.visState'); + let visState; + + if (visStateJSON) { + try { + visState = JSON.parse(visStateJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + if (visState && visState.type === 'metrics') { + const gaugeColorRules: any[] = get(visState, 'params.gauge_color_rules') || []; + + gaugeColorRules.forEach(colorRule => { + if (colorRule.opperator) { + delete colorRule.opperator; + } + }); + + return { + ...doc, + attributes: { + ...doc.attributes, + visState: JSON.stringify(visState), + }, + }; + } + } + return doc; +}; + // Migrate date histogram aggregation (remove customInterval) const migrateDateHistogramAggregation: SavedObjectMigrationFn = doc => { const visStateJSON = get(doc, 'attributes.visState'); @@ -539,6 +572,40 @@ const migrateTableSplits: SavedObjectMigrationFn = doc => { } }; +const migrateMatchAllQuery: SavedObjectMigrationFn = doc => { + const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); + + if (searchSourceJSON) { + let searchSource: any; + + try { + searchSource = JSON.parse(searchSourceJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + } + + if (searchSource.query?.match_all) { + return { + ...doc, + attributes: { + ...doc.attributes, + kibanaSavedObjectMeta: { + searchSourceJSON: JSON.stringify({ + ...searchSource, + query: { + query: '', + language: DEFAULT_QUERY_LANGUAGE, + }, + }), + }, + }, + }; + } + } + + return doc; +}; + export const visualizationSavedObjectTypeMigrations = { /** * We need to have this migration twice, once with a version prior to 7.0.0 once with a version @@ -550,7 +617,7 @@ export const visualizationSavedObjectTypeMigrations = { * in that version. So we apply this twice, once with 6.7.2 and once with 7.0.1 while the backport to 6.7 * only contained the 6.7.2 migration and not the 7.0.1 migration. */ - '6.7.2': flow(removeDateHistogramTimeZones), + '6.7.2': flow(migrateMatchAllQuery, removeDateHistogramTimeZones), '7.0.0': flow( addDocReferences, migrateIndexPattern, @@ -571,4 +638,5 @@ export const visualizationSavedObjectTypeMigrations = { ), '7.3.1': flow(migrateFiltersAggQueryStringQueries), '7.4.2': flow(transformSplitFiltersStringToQueryObject), + '7.7.0': flow(migrateOperatorKeyTypo), }; diff --git a/tasks/config/run.js b/tasks/config/run.js index dca0f69c35668..fc2ece3c72097 100644 --- a/tasks/config/run.js +++ b/tasks/config/run.js @@ -238,6 +238,8 @@ module.exports = function(grunt) { 'test/server_integration/http/ssl/config.js', '--config', 'test/server_integration/http/ssl_redirect/config.js', + '--config', + 'test/server_integration/http/cache/config.js', '--bail', '--debug', '--kibana-install-dir', diff --git a/test/examples/embeddables/list_container.ts b/test/examples/embeddables/list_container.ts index b1b91ad2c37f1..9e93d479471e8 100644 --- a/test/examples/embeddables/list_container.ts +++ b/test/examples/embeddables/list_container.ts @@ -57,13 +57,12 @@ export default function({ getService }: PluginFunctionalProviderContext) { expect(text).to.eql(['HELLO WORLD!']); }); - it('searchable container filters multi-task children', async () => { + it('searchable container finds matches in multi-task children', async () => { await testSubjects.setValue('filterTodos', 'earth'); + await testSubjects.click('checkMatchingTodos'); + await testSubjects.click('deleteCheckedTodos'); - await retry.try(async () => { - const tasks = await testSubjects.getVisibleTextAll('multiTaskTodoTask'); - expect(tasks).to.eql(['Watch planet earth']); - }); + await testSubjects.missingOrFail('multiTaskTodoTask'); }); }); } diff --git a/test/functional/apps/dashboard/dashboard_state.js b/test/functional/apps/dashboard/dashboard_state.js index a643a9ee40aa2..024ca0d6dc9e7 100644 --- a/test/functional/apps/dashboard/dashboard_state.js +++ b/test/functional/apps/dashboard/dashboard_state.js @@ -21,10 +21,7 @@ import expect from '@kbn/expect'; import { PIE_CHART_VIS_NAME, AREA_CHART_VIS_NAME } from '../../page_objects/dashboard_page'; -// eslint-disable-next-line -import { - DEFAULT_PANEL_WIDTH -} from '../../../../src/plugins/dashboard/public/embeddable/dashboard_constants'; +import { DEFAULT_PANEL_WIDTH } from '../../../../src/plugins/dashboard/public/application/embeddable/dashboard_constants'; export default function({ getService, getPageObjects }) { const PageObjects = getPageObjects([ diff --git a/test/functional/apps/discover/_discover.js b/test/functional/apps/discover/_discover.js index 850b2773b5025..4f357e2993b30 100644 --- a/test/functional/apps/discover/_discover.js +++ b/test/functional/apps/discover/_discover.js @@ -44,7 +44,6 @@ export default function({ getService, getPageObjects }) { }); describe('query', function() { - this.tags(['skipFirefox']); const queryName1 = 'Query # 1'; it('should show correct time range string by timepicker', async function() { @@ -100,9 +99,10 @@ export default function({ getService, getPageObjects }) { const newDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); expect(Math.round(newDurationHours)).to.be(25); const rowData = await PageObjects.discover.getDocTableField(1); + log.debug(`The first timestamp value in doc table: ${rowData}`); expect(Date.parse(rowData)).to.be.within( - Date.parse('Sep 20, 2015 @ 22:00:00.000'), - Date.parse('Sep 20, 2015 @ 23:30:00.000') + Date.parse('Sep 20, 2015 @ 21:30:00.000'), + Date.parse('Sep 20, 2015 @ 23:00:00.000') ); }); diff --git a/test/functional/apps/management/_index_pattern_create_delete.js b/test/functional/apps/management/_index_pattern_create_delete.js index a74620b696d1b..616e2297b2f51 100644 --- a/test/functional/apps/management/_index_pattern_create_delete.js +++ b/test/functional/apps/management/_index_pattern_create_delete.js @@ -42,7 +42,7 @@ export default function({ getService, getPageObjects }) { describe('special character handling', () => { it('should handle special charaters in template input', async () => { - await PageObjects.settings.clickOptionalAddNewButton(); + await PageObjects.settings.clickAddNewIndexPatternButton(); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.settings.setIndexPatternField({ indexPatternName: '❤️', diff --git a/test/functional/apps/management/_mgmt_import_saved_objects.js b/test/functional/apps/management/_mgmt_import_saved_objects.js index 53b7e7062ee2d..2f9d9f9bfb178 100644 --- a/test/functional/apps/management/_mgmt_import_saved_objects.js +++ b/test/functional/apps/management/_mgmt_import_saved_objects.js @@ -35,6 +35,7 @@ export default function({ getService, getPageObjects }) { afterEach(async function() { await esArchiver.unload('discover'); + await esArchiver.load('empty_kibana'); }); it('should import saved objects mgmt', async function() { diff --git a/test/functional/apps/visualize/_vertical_bar_chart.js b/test/functional/apps/visualize/_vertical_bar_chart.js index d5f4c45f8bdbc..04969a07eef6f 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart.js +++ b/test/functional/apps/visualize/_vertical_bar_chart.js @@ -48,10 +48,32 @@ export default function({ getService, getPageObjects }) { await PageObjects.visEditor.selectXAxisPosition('left'); await PageObjects.visEditor.clickGo(); - const leftLabels = await PageObjects.visChart.getXAxisLabels(); + // the getYAxisLabels helper always returns the labels on the left axis + const leftLabels = await PageObjects.visChart.getYAxisLabels(); log.debug(`${leftLabels.length} tick labels on left x axis`); expect(leftLabels.length).to.be.greaterThan(bottomLabels.length * (2 / 3)); }); + + it('should not filter out first label after rotation of the chart', async function() { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVerticalBarChart(); + await PageObjects.visualize.clickNewSearch(); + await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.visEditor.clickBucket('X-axis'); + await PageObjects.visEditor.selectAggregation('Date Range'); + await PageObjects.visEditor.selectField('@timestamp'); + await PageObjects.visEditor.clickGo(); + const bottomLabels = await PageObjects.visChart.getXAxisLabels(); + expect(bottomLabels.length).to.be(1); + + await PageObjects.visEditor.clickMetricsAndAxes(); + await PageObjects.visEditor.selectXAxisPosition('left'); + await PageObjects.visEditor.clickGo(); + + // the getYAxisLabels helper always returns the labels on the left axis + const leftLabels = await PageObjects.visChart.getYAxisLabels(); + expect(leftLabels.length).to.be(1); + }); }); describe('bar charts range on x axis', () => { diff --git a/test/functional/config.edge.js b/test/functional/config.edge.js new file mode 100644 index 0000000000000..ed68b41e8c89a --- /dev/null +++ b/test/functional/config.edge.js @@ -0,0 +1,34 @@ +/* + * 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 async function({ readConfigFile }) { + const defaultConfig = await readConfigFile(require.resolve('./config')); + + return { + ...defaultConfig.getAll(), + + browser: { + type: 'msedge', + }, + + junit: { + reportName: 'MS Chromium Edge UI Functional Tests', + }, + }; +} diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index de4917ef2b1b3..f06baeb7a4167 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -43,42 +43,10 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo appConfig: {}; ensureCurrentUrl: boolean; shouldLoginIfPrompted: boolean; - shouldAcceptAlert: boolean; useActualUrl: boolean; } class CommonPage { - /** - * Navigates the browser window to provided URL - * @param url URL - * @param shouldAcceptAlert pass 'true' if browser alert should be accepted - */ - private static async navigateToUrlAndHandleAlert(url: string, shouldAcceptAlert: boolean) { - log.debug('Navigate to: ' + url); - try { - await browser.get(url); - } catch (navigationError) { - log.debug('Error navigating to url'); - const alert = await browser.getAlert(); - if (alert && alert.accept) { - if (shouldAcceptAlert) { - log.debug('Should accept alert'); - try { - await alert.accept(); - } catch (alertException) { - log.debug('Error accepting alert'); - throw alertException; - } - } else { - log.debug('Will not accept alert'); - throw navigationError; - } - } else { - throw navigationError; - } - } - } - /** * Returns Kibana host URL */ @@ -127,13 +95,7 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo } private async navigate(navigateProps: NavigateProps) { - const { - appConfig, - ensureCurrentUrl, - shouldLoginIfPrompted, - shouldAcceptAlert, - useActualUrl, - } = navigateProps; + const { appConfig, ensureCurrentUrl, shouldLoginIfPrompted, useActualUrl } = navigateProps; const appUrl = getUrl.noAuth(config.get('servers.kibana'), appConfig); await retry.try(async () => { @@ -141,7 +103,11 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo log.debug(`navigateToActualUrl ${appUrl}`); await browser.get(appUrl); } else { - await CommonPage.navigateToUrlAndHandleAlert(appUrl, shouldAcceptAlert); + log.debug(`navigateToUrl ${appUrl}`); + await browser.get(appUrl); + // accept alert if it pops up + const alert = await browser.getAlert(); + await alert?.accept(); } const currentUrl = shouldLoginIfPrompted @@ -167,7 +133,6 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo basePath = '', ensureCurrentUrl = true, shouldLoginIfPrompted = true, - shouldAcceptAlert = true, useActualUrl = false, } = {} ) { @@ -180,7 +145,6 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo appConfig, ensureCurrentUrl, shouldLoginIfPrompted, - shouldAcceptAlert, useActualUrl, }); } @@ -200,7 +164,6 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo basePath = '', ensureCurrentUrl = true, shouldLoginIfPrompted = true, - shouldAcceptAlert = true, useActualUrl = true, } = {} ) { @@ -214,7 +177,6 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo appConfig, ensureCurrentUrl, shouldLoginIfPrompted, - shouldAcceptAlert, useActualUrl, }); } @@ -228,18 +190,12 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo async navigateToActualUrl( appName: string, hash?: string, - { - basePath = '', - ensureCurrentUrl = true, - shouldLoginIfPrompted = true, - shouldAcceptAlert = true, - } = {} + { basePath = '', ensureCurrentUrl = true, shouldLoginIfPrompted = true } = {} ) { await this.navigateToUrl(appName, hash, { basePath, ensureCurrentUrl, shouldLoginIfPrompted, - shouldAcceptAlert, useActualUrl: true, }); } @@ -252,7 +208,7 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo async navigateToApp( appName: string, - { basePath = '', shouldLoginIfPrompted = true, shouldAcceptAlert = true, hash = '' } = {} + { basePath = '', shouldLoginIfPrompted = true, hash = '' } = {} ) { let appUrl: string; if (config.has(['apps', appName])) { @@ -274,7 +230,11 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo await retry.tryForTime(defaultTryTimeout * 2, async () => { let lastUrl = await retry.try(async () => { // since we're using hash URLs, always reload first to force re-render - await CommonPage.navigateToUrlAndHandleAlert(appUrl, shouldAcceptAlert); + log.debug('navigate to: ' + appUrl); + await browser.get(appUrl); + // accept alert if it pops up + const alert = await browser.getAlert(); + await alert?.accept(); await this.sleep(700); log.debug('returned from get, calling refresh'); await browser.refresh(); diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index 0f01097cf50dc..a20d7ae9a5372 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -17,7 +17,7 @@ * under the License. */ -import { DashboardConstants } from '../../../src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_constants'; +import { DashboardConstants } from '../../../src/plugins/dashboard/public/dashboard_constants'; export const PIE_CHART_VIS_NAME = 'Visualization PieChart'; export const AREA_CHART_VIS_NAME = 'Visualization漢字 AreaChart'; diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 2377c32a80b5b..00bf87621864a 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -138,7 +138,7 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider await browser .getActions() - .move({ x: 200, y: 20, origin: el._webElement }) + .move({ x: 0, y: 20, origin: el._webElement }) .click() .perform(); } @@ -147,8 +147,8 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider const el = await elasticChart.getCanvas(); await browser.dragAndDrop( - { location: el, offset: { x: 200, y: 20 } }, - { location: el, offset: { x: 400, y: 30 } } + { location: el, offset: { x: -300, y: 20 } }, + { location: el, offset: { x: -100, y: 30 } } ); } diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index 3f6036f58f0a9..6dcd017335c85 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -326,7 +326,7 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider await PageObjects.header.waitUntilLoadingHasFinished(); await this.clickKibanaIndexPatterns(); await PageObjects.header.waitUntilLoadingHasFinished(); - await this.clickOptionalAddNewButton(); + await this.clickAddNewIndexPatternButton(); if (!isStandardIndexPattern) { await this.clickCreateNewRollupButton(); } @@ -356,11 +356,8 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider return await this.getIndexPatternIdFromUrl(); } - // adding a method to check if the create index pattern button is visible when more than 1 index pattern is present - async clickOptionalAddNewButton() { - if (await testSubjects.isDisplayed('createIndexPatternButton')) { - await testSubjects.click('createIndexPatternButton'); - } + async clickAddNewIndexPatternButton() { + await testSubjects.click('createIndexPatternButton'); } async clickCreateNewRollupButton() { diff --git a/test/functional/services/browser.ts b/test/functional/services/browser.ts index 5017947e95d03..13d2365c07191 100644 --- a/test/functional/services/browser.ts +++ b/test/functional/services/browser.ts @@ -47,7 +47,9 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { */ public readonly browserType: string = browserType; - public readonly isChrome: boolean = browserType === Browsers.Chrome; + public readonly isChromium: boolean = [Browsers.Chrome, Browsers.ChromiumEdge].includes( + browserType + ); public readonly isFirefox: boolean = browserType === Browsers.Firefox; diff --git a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts index 157918df874c8..8b57ecd3c8235 100644 --- a/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts +++ b/test/functional/services/lib/web_element_wrapper/web_element_wrapper.ts @@ -55,6 +55,7 @@ export class WebElementWrapper { private driver: WebDriver = this.webDriver.driver; private Keys = Key; public isW3CEnabled: boolean = (this.webDriver.driver as any).executor_.w3c === true; + public isChromium: boolean = [Browsers.Chrome, Browsers.ChromiumEdge].includes(this.browserType); public static create( webElement: WebElement | WebElementWrapper, @@ -63,7 +64,7 @@ export class WebElementWrapper { timeout: number, fixedHeaderHeight: number, logger: ToolingLog, - browserType: string + browserType: Browsers ): WebElementWrapper { if (webElement instanceof WebElementWrapper) { return webElement; @@ -87,7 +88,7 @@ export class WebElementWrapper { private timeout: number, private fixedHeaderHeight: number, private logger: ToolingLog, - private browserType: string + private browserType: Browsers ) {} private async _findWithCustomTimeout( @@ -243,7 +244,7 @@ export class WebElementWrapper { return this.clearValueWithKeyboard(); } await this.retryCall(async function clearValue(wrapper) { - if (wrapper.browserType === Browsers.Chrome || options.withJS) { + if (wrapper.isChromium || options.withJS) { // https://bugs.chromium.org/p/chromedriver/issues/detail?id=2702 await wrapper.driver.executeScript(`arguments[0].value=''`, wrapper._webElement); } else { @@ -275,7 +276,7 @@ export class WebElementWrapper { await delay(100); } } else { - if (this.browserType === Browsers.Chrome) { + if (this.isChromium) { // https://bugs.chromium.org/p/chromedriver/issues/detail?id=30 await this.retryCall(async function clearValueWithKeyboard(wrapper) { await wrapper.driver.executeScript(`arguments[0].select();`, wrapper._webElement); diff --git a/test/functional/services/remote/browsers.ts b/test/functional/services/remote/browsers.ts index 46d81f1737a55..aa6e364d0a09d 100644 --- a/test/functional/services/remote/browsers.ts +++ b/test/functional/services/remote/browsers.ts @@ -21,4 +21,5 @@ export enum Browsers { Chrome = 'chrome', Firefox = 'firefox', InternetExplorer = 'ie', + ChromiumEdge = 'msedge', } diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index e571a1a7e5551..b0724488cb5db 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -64,18 +64,23 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { lifecycle, config.get('browser.logPollingMs') ); + const isW3CEnabled = (driver as any).executor_.w3c; const caps = await driver.getCapabilities(); - const browserVersion = caps.get(isW3CEnabled ? 'browserVersion' : 'version'); + const browserVersion = caps.get( + isW3CEnabled || browserType === Browsers.ChromiumEdge ? 'browserVersion' : 'version' + ); - log.info(`Remote initialized: ${caps.get('browserName')} ${browserVersion}`); + log.info( + `Remote initialized: ${caps.get( + 'browserName' + )} ${browserVersion}, w3c compliance=${isW3CEnabled}, collectingCoverage=${collectCoverage}` + ); - if (browserType === Browsers.Chrome) { + if ([Browsers.Chrome, Browsers.ChromiumEdge].includes(browserType)) { log.info( - `Chromedriver version: ${ - caps.get('chrome').chromedriverVersion - }, w3c=${isW3CEnabled}, codeCoverage=${collectCoverage}` + `${browserType}driver version: ${caps.get(browserType)[`${browserType}driverVersion`]}` ); } diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index 382543822d8ac..fc0b5bbb787c8 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -31,10 +31,12 @@ import { Builder, Capabilities, By, logging, until } from 'selenium-webdriver'; import chrome from 'selenium-webdriver/chrome'; import firefox from 'selenium-webdriver/firefox'; // @ts-ignore internal modules are not typed +import edge from 'selenium-webdriver/edge'; +import { installDriver } from 'ms-chromium-edge-driver'; +// @ts-ignore internal modules are not typed import { Executor } from 'selenium-webdriver/lib/http'; // @ts-ignore internal modules are not typed import { getLogger } from 'selenium-webdriver/lib/logging'; - import { pollForLogEntry$ } from './poll_for_log_entry'; import { createStdoutSocket } from './create_stdout_stream'; import { preventParallelCalls } from './prevent_parallel_calls'; @@ -63,6 +65,7 @@ Executor.prototype.execute = preventParallelCalls( ); let attemptCounter = 0; +let edgePaths: { driverPath: string | undefined; browserPath: string | undefined }; async function attemptToCreateCommand( log: ToolingLog, browserType: Browsers, @@ -74,6 +77,46 @@ async function attemptToCreateCommand( const buildDriverInstance = async () => { switch (browserType) { + case 'msedge': { + if (edgePaths && edgePaths.browserPath && edgePaths.driverPath) { + const edgeOptions = new edge.Options(); + if (headlessBrowser === '1') { + // @ts-ignore internal modules are not typed + edgeOptions.headless(); + } + // @ts-ignore internal modules are not typed + edgeOptions.setEdgeChromium(true); + // @ts-ignore internal modules are not typed + edgeOptions.setBinaryPath(edgePaths.browserPath); + const session = await new Builder() + .forBrowser('MicrosoftEdge') + .setEdgeOptions(edgeOptions) + .setEdgeService(new edge.ServiceBuilder(edgePaths.driverPath)) + .build(); + return { + session, + consoleLog$: pollForLogEntry$( + session, + logging.Type.BROWSER, + logPollingMs, + lifecycle.cleanup.after$ + ).pipe( + takeUntil(lifecycle.cleanup.after$), + map(({ message, level: { name: level } }) => ({ + message: message.replace(/\\n/g, '\n'), + level, + })) + ), + }; + } else { + throw new Error( + `Chromium Edge session requires browser or driver path to be defined: ${JSON.stringify( + edgePaths + )}` + ); + } + } + case 'chrome': { const chromeCapabilities = Capabilities.chrome(); const chromeOptions = [ @@ -107,9 +150,10 @@ async function attemptToCreateCommand( chromeOptions.push('headless', 'disable-gpu', 'remote-debugging-port=9222'); } chromeCapabilities.set('goog:chromeOptions', { - w3c: false, + w3c: true, args: chromeOptions, }); + chromeCapabilities.set('unexpectedAlertBehaviour', 'accept'); chromeCapabilities.set('goog:loggingPrefs', { browser: 'ALL' }); const session = await new Builder() @@ -264,6 +308,11 @@ export async function initWebDriver( log.verbose(entry.message); }); + // download Edge driver only in case of usage + if (browserType === Browsers.ChromiumEdge) { + edgePaths = await installDriver(); + } + return await Promise.race([ (async () => { await delay(2 * MINUTE); diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.tsx b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.tsx index d3f66d708603c..6c430e34d5e29 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.tsx +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { EuiFieldNumber, EuiFormRow } from '@elastic/eui'; -import { VisOptionsProps } from '../../../../../../src/legacy/core_plugins/vis_default_editor/public/vis_options_props'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public/vis_options_props'; interface CounterParams { counter: number; diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx index 54d13efe4d790..2ecde823dc4df 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/app.tsx @@ -18,21 +18,11 @@ */ import { EuiTab } from '@elastic/eui'; import React, { Component } from 'react'; -import { CoreStart } from 'src/core/public'; import { EmbeddableStart } from 'src/plugins/embeddable/public'; -import { UiActionsService } from '../../../../../../../../src/plugins/ui_actions/public'; import { DashboardContainerExample } from './dashboard_container_example'; -import { Start as InspectorStartContract } from '../../../../../../../../src/plugins/inspector/public'; export interface AppProps { - getActions: UiActionsService['getTriggerCompatibleActions']; - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; - overlays: CoreStart['overlays']; - notifications: CoreStart['notifications']; - inspector: InspectorStartContract; - SavedObjectFinder: React.ComponentType; - I18nContext: CoreStart['i18n']['Context']; + embeddableServices: EmbeddableStart; } export class App extends Component { @@ -72,29 +62,17 @@ export class App extends Component { public render() { return ( - -
-
{this.renderTabs()}
- {this.getContentsForTab()} -
-
+
+
{this.renderTabs()}
+ {this.getContentsForTab()} +
); } private getContentsForTab() { switch (this.state.selectedTabId) { case 'dashboardContainer': { - return ( - - ); + return ; } } } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx index fd07416cadbc5..16c2840d6a32e 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/app/dashboard_container_example.tsx @@ -19,32 +19,17 @@ import React from 'react'; import { EuiButton, EuiLoadingChart } from '@elastic/eui'; import { ContainerOutput } from 'src/plugins/embeddable/public'; -import { - ErrorEmbeddable, - ViewMode, - isErrorEmbeddable, - EmbeddablePanel, - EmbeddableStart, -} from '../embeddable_api'; +import { ErrorEmbeddable, ViewMode, isErrorEmbeddable, EmbeddableStart } from '../embeddable_api'; import { DASHBOARD_CONTAINER_TYPE, DashboardContainer, DashboardContainerInput, } from '../../../../../../../../src/plugins/dashboard/public'; -import { CoreStart } from '../../../../../../../../src/core/public'; import { dashboardInput } from './dashboard_input'; -import { Start as InspectorStartContract } from '../../../../../../../../src/plugins/inspector/public'; -import { UiActionsService } from '../../../../../../../../src/plugins/ui_actions/public'; interface Props { - getActions: UiActionsService['getTriggerCompatibleActions']; - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; - overlays: CoreStart['overlays']; - notifications: CoreStart['notifications']; - inspector: InspectorStartContract; - SavedObjectFinder: React.ComponentType; + embeddableServices: EmbeddableStart; } interface State { @@ -67,7 +52,7 @@ export class DashboardContainerExample extends React.Component { public async componentDidMount() { this.mounted = true; - const dashboardFactory = this.props.getEmbeddableFactory< + const dashboardFactory = this.props.embeddableServices.getEmbeddableFactory< DashboardContainerInput, ContainerOutput, DashboardContainer @@ -99,6 +84,7 @@ export class DashboardContainerExample extends React.Component { }; public render() { + const { embeddableServices } = this.props; return (

Dashboard Container

@@ -108,16 +94,7 @@ export class DashboardContainerExample extends React.Component { {!this.state.loaded || !this.container ? ( ) : ( - + )}
); diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx index 8ddb2e1a4803b..b47e84216dd16 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/plugin.tsx @@ -33,7 +33,6 @@ const REACT_ROOT_ID = 'embeddableExplorerRoot'; import { SayHelloAction, createSendMessageAction } from './embeddable_api'; import { App } from './app'; -import { getSavedObjectFinder } from '../../../../../../../src/plugins/saved_objects/public'; import { EmbeddableStart, EmbeddableSetup, @@ -77,19 +76,7 @@ export class EmbeddableExplorerPublicPlugin plugins.__LEGACY.onRenderComplete(() => { const root = document.getElementById(REACT_ROOT_ID); - ReactDOM.render( - , - root - ); + ReactDOM.render(, root); }); } diff --git a/test/scripts/jenkins_visual_regression.sh b/test/scripts/jenkins_visual_regression.sh index 4fdd197147eac..c6fefd45b005d 100755 --- a/test/scripts/jenkins_visual_regression.sh +++ b/test/scripts/jenkins_visual_regression.sh @@ -12,7 +12,7 @@ tar -xzf "$linuxBuild" -C "$installDir" --strip=1 echo " -> running visual regression tests from kibana directory" checks-reporter-with-killswitch "X-Pack visual regression tests" \ - yarn percy exec -t 500 \ + yarn percy exec -t 500 -- -- \ node scripts/functional_tests \ --debug --bail \ --kibana-install-dir "$installDir" \ diff --git a/test/scripts/jenkins_xpack_build_kibana.sh b/test/scripts/jenkins_xpack_build_kibana.sh index 777d98080e407..962d2794f712f 100755 --- a/test/scripts/jenkins_xpack_build_kibana.sh +++ b/test/scripts/jenkins_xpack_build_kibana.sh @@ -7,6 +7,7 @@ echo " -> building kibana platform plugins" node scripts/build_kibana_platform_plugins \ --scan-dir "$XPACK_DIR/test/plugin_functional/plugins" \ --scan-dir "$XPACK_DIR/test/functional_with_es_ssl/fixtures/plugins" \ + --scan-dir "$XPACK_DIR/test/plugin_api_integration/plugins" \ --verbose; # doesn't persist, also set in kibanaPipeline.groovy diff --git a/test/scripts/jenkins_xpack_visual_regression.sh b/test/scripts/jenkins_xpack_visual_regression.sh index 73e92da3bad63..96521ccc8f787 100755 --- a/test/scripts/jenkins_xpack_visual_regression.sh +++ b/test/scripts/jenkins_xpack_visual_regression.sh @@ -14,7 +14,7 @@ tar -xzf "$linuxBuild" -C "$installDir" --strip=1 echo " -> running visual regression tests from x-pack directory" cd "$XPACK_DIR" checks-reporter-with-killswitch "X-Pack visual regression tests" \ - yarn percy exec -t 500 \ + yarn percy exec -t 500 -- -- \ node scripts/functional_tests \ --debug --bail \ --kibana-install-dir "$installDir" \ diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_constants.ts b/test/server_integration/http/cache/config.js similarity index 64% rename from src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_constants.ts rename to test/server_integration/http/cache/config.js index 0820ebd371004..10acf94f0a3cf 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_constants.ts +++ b/test/server_integration/http/cache/config.js @@ -17,16 +17,17 @@ * under the License. */ -export const DashboardConstants = { - ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM: 'addToDashboard', - LANDING_PAGE_PATH: '/dashboards', - CREATE_NEW_DASHBOARD_URL: '/dashboard', - ADD_EMBEDDABLE_ID: 'addEmbeddableId', - ADD_EMBEDDABLE_TYPE: 'addEmbeddableType', - DASHBOARDS_ID: 'dashboards', - DASHBOARD_ID: 'dashboard', -}; +export default async function({ readConfigFile }) { + const httpConfig = await readConfigFile(require.resolve('../../config')); -export function createDashboardEditUrl(id: string) { - return `/dashboard/${id}`; + return { + testFiles: [require.resolve('./')], + services: httpConfig.get('services'), + servers: httpConfig.get('servers'), + junit: { + reportName: 'Http Cache-Control Integration Tests', + }, + esTestCluster: httpConfig.get('esTestCluster'), + kbnTestServer: httpConfig.get('kbnTestServer'), + }; } diff --git a/src/legacy/core_plugins/management/index.ts b/test/server_integration/http/cache/index.js similarity index 50% rename from src/legacy/core_plugins/management/index.ts rename to test/server_integration/http/cache/index.js index 4962c948f842f..3aff35ca12928 100644 --- a/src/legacy/core_plugins/management/index.ts +++ b/test/server_integration/http/cache/index.js @@ -17,21 +17,30 @@ * under the License. */ -import { resolve } from 'path'; -import { Legacy } from '../../../../kibana'; - // eslint-disable-next-line import/no-default-export -export default function ManagementPlugin(kibana: any) { - const config: Legacy.PluginSpecOptions = { - id: 'stack-management', - publicDir: resolve(__dirname, 'public'), - config: (Joi: any) => { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - init: (server: Legacy.Server) => ({}), - }; +export default function({ getService }) { + const supertest = getService('supertest'); + + describe('kibana server cache-control', () => { + it('properly marks responses as private, with directives to disable caching', async () => { + await supertest + .get('/api/status') + .expect('Cache-Control', 'private, no-cache, no-store, must-revalidate') + .expect(200); + }); + + it('allows translation bundles to be cached', async () => { + await supertest + .get('/translations/en.json') + .expect('Cache-Control', 'must-revalidate') + .expect(200); + }); - return new kibana.Plugin(config); + it('allows the bootstrap bundles to be cached', async () => { + await supertest + .get('/bundles/app/any-old-id-works/bootstrap.js') + .expect('Cache-Control', 'must-revalidate') + .expect(200); + }); + }); } diff --git a/typings/elastic__node_crypto.d.ts b/typings/elastic__node_crypto.d.ts deleted file mode 100644 index 8d4b47da96b73..0000000000000 --- a/typings/elastic__node_crypto.d.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -declare module '@elastic/node-crypto'; diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap index 205a303bcf47b..afa0cb51cd108 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap @@ -11,6 +11,12 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` "sortable": false, "width": "96px", }, + Object { + "field": "type", + "name": "Type", + "render": [Function], + "sortable": false, + }, Object { "field": "message", "name": "Error message and culprit", @@ -142,7 +148,28 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` +
+ + Type + +
+ + List should render empty state 1`] = ` List should render empty state 1`] = ` aria-live="polite" aria-sort="descending" className="euiTableHeaderCell" - data-test-subj="tableHeaderCell_occurrenceCount_3" + data-test-subj="tableHeaderCell_occurrenceCount_4" role="columnheader" scope="col" style={ @@ -225,7 +252,7 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` aria-live="polite" aria-sort="none" className="euiTableHeaderCell" - data-test-subj="tableHeaderCell_latestOccurrenceAt_4" + data-test-subj="tableHeaderCell_latestOccurrenceAt_5" role="columnheader" scope="col" style={ @@ -264,7 +291,7 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` > List should render with data 1`] = ` font-family: "Roboto Mono",Consolas,Menlo,Courier,monospace; } +.c2 { + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .c1 { max-width: 100%; white-space: nowrap; @@ -301,7 +335,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` text-overflow: ellipsis; } -.c2 { +.c3 { font-family: "Roboto Mono",Consolas,Menlo,Courier,monospace; font-size: 16px; max-width: 100%; @@ -310,7 +344,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` text-overflow: ellipsis; } -.c3 { +.c4 { font-family: "Roboto Mono",Consolas,Menlo,Courier,monospace; } @@ -324,6 +358,12 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` "sortable": false, "width": "96px", }, + Object { + "field": "type", + "name": "Type", + "render": [Function], + "sortable": false, + }, Object { "field": "message", "name": "Error message and culprit", @@ -486,7 +526,28 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` +
+ + Type + +
+ + List should render with data 1`] = ` List should render with data 1`] = ` aria-live="polite" aria-sort="descending" className="euiTableHeaderCell" - data-test-subj="tableHeaderCell_occurrenceCount_3" + data-test-subj="tableHeaderCell_occurrenceCount_4" role="columnheader" scope="col" style={ @@ -569,7 +630,7 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` aria-live="polite" aria-sort="none" className="euiTableHeaderCell" - data-test-subj="tableHeaderCell_latestOccurrenceAt_4" + data-test-subj="tableHeaderCell_latestOccurrenceAt_5" role="columnheader" scope="col" style={ @@ -642,6 +703,49 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = ` + +
+ Type +
+ + List should render with data 1`] = ` className="" >
List should render with data 1`] = ` serviceName="opbeans-python" > List should render with data 1`] = ` onFocus={[Function]} >
@@ -812,6 +916,49 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
+ +
+ Type +
+
+ List should render with data 1`] = ` className="" >
List should render with data 1`] = ` serviceName="opbeans-python" > List should render with data 1`] = ` onFocus={[Function]} >
@@ -982,6 +1129,49 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
+ +
+ Type +
+
+ List should render with data 1`] = ` className="" >
List should render with data 1`] = ` serviceName="opbeans-python" > List should render with data 1`] = ` onFocus={[Function]} >
@@ -1152,6 +1342,49 @@ exports[`ErrorGroupOverview -> List should render with data 1`] = `
+ +
+ Type +
+
+ List should render with data 1`] = ` className="" >
List should render with data 1`] = ` serviceName="opbeans-python" > List should render with data 1`] = ` onFocus={[Function]} >
diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx index b26833c02fe22..250b9a5d188d0 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx @@ -23,6 +23,8 @@ import { useUrlParams } from '../../../../hooks/useUrlParams'; import { ManagedTable } from '../../../shared/ManagedTable'; import { ErrorDetailLink } from '../../../shared/Links/apm/ErrorDetailLink'; import { TimestampTooltip } from '../../../shared/TimestampTooltip'; +import { ErrorOverviewLink } from '../../../shared/Links/apm/ErrorOverviewLink'; +import { APMQueryParams } from '../../../shared/Links/url_helpers'; const GroupIdLink = styled(ErrorDetailLink)` font-family: ${fontFamilyCode}; @@ -32,6 +34,10 @@ const MessageAndCulpritCell = styled.div` ${truncate('100%')}; `; +const ErrorLink = styled(ErrorOverviewLink)` + ${truncate('100%')}; +`; + const MessageLink = styled(ErrorDetailLink)` font-family: ${fontFamilyCode}; font-size: ${fontSizes.large}; @@ -48,9 +54,8 @@ interface Props { const ErrorGroupList: React.FC = props => { const { items } = props; - const { - urlParams: { serviceName } - } = useUrlParams(); + const { urlParams } = useUrlParams(); + const { serviceName } = urlParams; if (!serviceName) { throw new Error('Service name is required'); @@ -73,6 +78,29 @@ const ErrorGroupList: React.FC = props => { ); } }, + { + name: i18n.translate('xpack.apm.errorsTable.typeColumnLabel', { + defaultMessage: 'Type' + }), + field: 'type', + sortable: false, + render: (type: string, item: ErrorGroupListAPIResponse[0]) => { + return ( + + {type} + + ); + } + }, { name: i18n.translate( 'xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel', @@ -150,7 +178,7 @@ const ErrorGroupList: React.FC = props => { ) } ], - [serviceName] + [serviceName, urlParams] ); return ( diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx index 46754c8c7cb6b..a8d3b843a1f3d 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.stories.tsx @@ -75,27 +75,17 @@ storiesOf('app/ServiceMap/Cytoscape', module) const cy = cytoscape(); const elements = [ { data: { id: 'default' } }, - { data: { id: 'cache', label: 'cache', 'span.type': 'cache' } }, - { data: { id: 'database', label: 'database', 'span.type': 'db' } }, + { data: { id: 'cache', 'span.type': 'cache' } }, + { data: { id: 'database', 'span.type': 'db' } }, { data: { id: 'elasticsearch', - label: 'elasticsearch', 'span.type': 'db', 'span.subtype': 'elasticsearch' } }, - { - data: { id: 'external', label: 'external', 'span.type': 'external' } - }, - { - data: { - id: 'messaging', - label: 'messaging', - 'span.type': 'messaging' - } - }, - + { data: { id: 'external', 'span.type': 'external' } }, + { data: { id: 'messaging', 'span.type': 'messaging' } }, { data: { id: 'dotnet', @@ -119,11 +109,18 @@ storiesOf('app/ServiceMap/Cytoscape', module) }, { data: { - id: 'js-base', - 'service.name': 'js-base service', + id: 'RUM (js-base)', + 'service.name': 'RUM service', 'agent.name': 'js-base' } }, + { + data: { + id: 'RUM (rum-js)', + 'service.name': 'RUM service', + 'agent.name': 'rum-js' + } + }, { data: { id: 'nodejs', @@ -163,7 +160,8 @@ storiesOf('app/ServiceMap/Cytoscape', module) description={
                     agent.name: {node.data('agent.name') || 'undefined'},
-                    span.type: {node.data('span.type') || 'undefined'}
+                    span.type: {node.data('span.type') || 'undefined'},
+                    span.subtype: {node.data('span.subtype') || 'undefined'}
                   
} icon={ @@ -174,7 +172,7 @@ storiesOf('app/ServiceMap/Cytoscape', module) width={80} /> } - title={node.data('label')} + title={node.data('id')} /> ))} diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx index 7bdc6aebbd9a0..e4b656ae8160d 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx @@ -14,8 +14,6 @@ import React, { useState } from 'react'; import { debounce } from 'lodash'; -import { isRumAgentName } from '../../../../../../../plugins/apm/common/agent_name'; -import { AGENT_NAME } from '../../../../../../../plugins/apm/common/elasticsearch_fieldnames'; import { animationOptions, cytoscapeOptions, @@ -96,10 +94,15 @@ function getLayoutOptions( } function selectRoots(cy: cytoscape.Core): string[] { - const nodes = cy.nodes(); - const roots = nodes.roots(); - const rumNodes = nodes.filter(node => isRumAgentName(node.data(AGENT_NAME))); - return rumNodes.union(roots).map(node => node.id()); + const bfs = cy.elements().bfs({ + roots: cy.elements().leaves() + }); + const furthestNodeFromLeaves = bfs.path.last(); + return cy + .elements() + .roots() + .union(furthestNodeFromLeaves) + .map(el => el.id()); } export function Cytoscape({ @@ -124,6 +127,12 @@ export function Cytoscape({ // Trigger a custom "data" event when data changes useEffect(() => { if (cy && elements.length > 0) { + const renderedElements = cy.elements('node,edge'); + const latestElementIds = elements.map(el => el.data.id); + const absentElements = renderedElements.filter( + el => !latestElementIds.includes(el.id()) + ); + cy.remove(absentElements); cy.add(elements); cy.trigger('data'); } @@ -162,15 +171,26 @@ export function Cytoscape({ layout.run(); } }; + let layoutstopDelayTimeout: NodeJS.Timeout; const layoutstopHandler: cytoscape.EventHandler = event => { - event.cy.animate({ - ...animationOptions, - center: { - eles: serviceName - ? event.cy.getElementById(serviceName) - : event.cy.collection() + // This 0ms timer is necessary to prevent a race condition + // between the layout finishing rendering and viewport centering + layoutstopDelayTimeout = setTimeout(() => { + if (serviceName) { + event.cy.animate({ + ...animationOptions, + fit: { + eles: event.cy.elements(), + padding: nodeHeight + }, + center: { + eles: event.cy.getElementById(serviceName) + } + }); + } else { + event.cy.fit(undefined, nodeHeight); } - }); + }, 0); }; // debounce hover tracking so it doesn't spam telemetry with redundant events const trackNodeEdgeHover = debounce( @@ -225,6 +245,7 @@ export function Cytoscape({ cy.removeListener('select', 'node', selectHandler); cy.removeListener('unselect', 'node', unselectHandler); } + clearTimeout(layoutstopDelayTimeout); }; }, [cy, height, serviceName, trackApmEvent, width]); diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts index 0438842f7af10..92f66f698f044 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts @@ -83,7 +83,7 @@ const style: cytoscape.Stylesheet[] = [ style: { 'curve-style': 'taxi', // @ts-ignore - 'taxi-direction': 'rightward', + 'taxi-direction': 'auto', 'line-color': lineColor, 'overlay-opacity': 0, 'target-arrow-color': lineColor, diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons.ts index 4925ffba310b5..dd9b48d312725 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons.ts @@ -5,11 +5,12 @@ */ import cytoscape from 'cytoscape'; +import { isRumAgentName } from '../../../../../../../plugins/apm/common/agent_name'; import { AGENT_NAME, SERVICE_NAME, - SPAN_TYPE, - SPAN_SUBTYPE + SPAN_SUBTYPE, + SPAN_TYPE } from '../../../../../../../plugins/apm/common/elasticsearch_fieldnames'; import databaseIcon from './icons/database.svg'; import defaultIconImport from './icons/default.svg'; @@ -62,7 +63,12 @@ export function iconForNode(node: cytoscape.NodeSingular) { const type = node.data(SPAN_TYPE); if (node.data(SERVICE_NAME)) { - return serviceIcons[node.data(AGENT_NAME) as string]; + const agentName = node.data(AGENT_NAME); + // RUM can have multiple names. Normalize it + const normalizedAgentName = isRumAgentName(agentName) + ? 'js-base' + : agentName; + return serviceIcons[normalizedAgentName]; } else if (isIE11) { return defaultIcon; } else if ( diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingFormRow.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingFormRow.tsx index b1959e4d68aa4..30c772bf5f634 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingFormRow.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingFormRow.tsx @@ -90,6 +90,7 @@ function FormRow({ onChange( setting.key, diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx index 17ec42b3e2016..07af7b0c0e7db 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/Title.tsx @@ -28,7 +28,7 @@ export const Title = () => ( 'xpack.apm.settings.customizeUI.customLink.info', { defaultMessage: - "These links will be shown in the 'Actions' context menu for the transaction detail." + 'These links will be shown in the Actions context menu for transactions.' } )} /> diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/index.tsx index 1cd1298fdd549..350f2185fb3c8 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/CustomizeUI/index.tsx @@ -14,8 +14,8 @@ export const CustomizeUI = () => { <>

- {i18n.translate('xpack.apm.settings.customizeUI', { - defaultMessage: 'Customize UI' + {i18n.translate('xpack.apm.settings.customizeApp', { + defaultMessage: 'Customize app' })}

diff --git a/x-pack/legacy/plugins/apm/public/components/app/Settings/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/Settings/index.tsx index f33bb17decd4e..2bb85876686bf 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Settings/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/Settings/index.tsx @@ -57,8 +57,8 @@ export const Settings: React.FC = props => { isSelected: pathname === '/settings/apm-indices' }, { - name: i18n.translate('xpack.apm.settings.customizeUI', { - defaultMessage: 'Customize UI' + name: i18n.translate('xpack.apm.settings.customizeApp', { + defaultMessage: 'Customize app' }), id: '3', href: getAPMHref('/settings/customize-ui', search), diff --git a/x-pack/legacy/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx index 6d0a2b96092a1..b7e23c2979cb8 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/ErrorRateAlertTrigger/index.tsx @@ -6,6 +6,7 @@ import React from 'react'; import { EuiFieldNumber } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { isFinite } from 'lodash'; import { ForLastExpression } from '../../../../../../../plugins/triggers_actions_ui/public'; import { ALERT_TYPES_CONFIG } from '../../../../../../../plugins/apm/common/alert_types'; import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; @@ -37,15 +38,17 @@ export function ErrorRateAlertTrigger(props: Props) { ...alertParams }; + const threshold = isFinite(params.threshold) ? params.threshold : ''; + const fields = [ setAlertParams('threshold', parseInt(e.target.value, 10)) @@ -58,7 +61,7 @@ export function ErrorRateAlertTrigger(props: Props) { , - setAlertParams('windowSize', windowSize) + setAlertParams('windowSize', windowSize || '') } onChangeWindowUnit={windowUnit => setAlertParams('windowUnit', windowUnit) diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkSection.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkSection.tsx index 52befe37ffdae..bd00bcf600ffe 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkSection.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/CustomLinkSection.tsx @@ -3,14 +3,26 @@ * 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 { EuiLink, EuiText } from '@elastic/eui'; import Mustache from 'mustache'; +import React from 'react'; +import styled from 'styled-components'; import { CustomLink } from '../../../../../../../../plugins/apm/common/custom_link/custom_link_types'; import { Transaction } from '../../../../../../../../plugins/apm/typings/es_schemas/ui/transaction'; -import { - SectionLinks, - SectionLink -} from '../../../../../../../../plugins/observability/public'; +import { px, truncate, units } from '../../../../style/variables'; + +const LinkContainer = styled.li` + margin-top: ${px(units.half)}; + &:first-of-type { + margin-top: 0; + } +`; + +const TruncateText = styled(EuiText)` + font-weight: 500; + line-height: ${px(units.unit)}; + ${truncate(px(units.unit * 25))} +`; export const CustomLinkSection = ({ customLinks, @@ -19,7 +31,7 @@ export const CustomLinkSection = ({ customLinks: CustomLink[]; transaction: Transaction; }) => ( - +
    {customLinks.map(link => { let href = link.url; try { @@ -28,13 +40,12 @@ export const CustomLinkSection = ({ // ignores any error that happens } return ( - + + + {link.label} + + ); })} - +
); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.test.tsx index 2dab8d63f99b2..9d1eeb9a3136d 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.test.tsx @@ -28,7 +28,7 @@ describe('Custom links', () => { ); expectTextsInDocument(component, [ - 'No custom links found. Set up your own custom links i.e. a link to a specific Dashboard or external link.' + 'No custom links found. Set up your own custom links, e.g., a link to a specific Dashboard or external link.' ]); expectTextsNotInDocument(component, ['Create']); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.tsx index b32d8f0d9582c..38b672a181fce 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/CustomLink/index.tsx @@ -55,7 +55,7 @@ export const CustomLink = ({ {i18n.translate('xpack.apm.customLink.empty', { defaultMessage: - 'No custom links found. Set up your own custom links i.e. a link to a specific Dashboard or external link.' + 'No custom links found. Set up your own custom links, e.g., a link to a specific Dashboard or external link.' })} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index 048ed662ec502..e3fbcf8485d54 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -85,7 +85,13 @@ export const TransactionActionMenu: FunctionComponent = ({ urlParams }); + const closePopover = () => { + setIsActionPopoverOpen(false); + setIsCustomLinksPopoverOpen(false); + }; + const toggleCustomLinkFlyout = () => { + closePopover(); setIsCustomLinkFlyoutOpen(isOpen => !isOpen); }; @@ -111,10 +117,7 @@ export const TransactionActionMenu: FunctionComponent = ({ )} { - setIsActionPopoverOpen(false); - setIsCustomLinksPopoverOpen(false); - }} + closePopover={closePopover} isOpen={isActionPopoverOpen} anchorPosition="downRight" button={ diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx index cdc7c30089b4f..077e6535a8b21 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionDurationAlertTrigger/index.tsx @@ -123,7 +123,7 @@ export function TransactionDurationAlertTrigger(props: Props) { , - setAlertParams('windowSize', timeWindowSize) + setAlertParams('windowSize', timeWindowSize || '') } onChangeWindowUnit={timeWindowUnit => setAlertParams('windowUnit', timeWindowUnit) diff --git a/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js b/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js index 85cb6d45c595d..cc74faeac6a96 100644 --- a/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js +++ b/x-pack/legacy/plugins/canvas/.storybook/webpack.config.js @@ -6,6 +6,7 @@ const path = require('path'); const webpack = require('webpack'); +const { stringifyRequest } = require('loader-utils'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const { DLL_OUTPUT, KIBANA_ROOT } = require('./constants'); @@ -73,7 +74,20 @@ module.exports = async ({ config }) => { path: path.resolve(KIBANA_ROOT, 'src/optimize/postcss.config.js'), }, }, - { loader: 'sass-loader' }, + { + loader: 'sass-loader', + options: { + prependData(loaderContext) { + return `@import ${stringifyRequest( + loaderContext, + path.resolve(KIBANA_ROOT, 'src/legacy/ui/public/styles/_styling_constants.scss') + )};\n`; + }, + sassOptions: { + includePaths: [path.resolve(KIBANA_ROOT, 'node_modules')], + }, + }, + }, ], }); @@ -86,8 +100,9 @@ module.exports = async ({ config }) => { loader: 'css-loader', options: { importLoaders: 2, - modules: true, - localIdentName: '[name]__[local]___[hash:base64:5]', + modules: { + localIdentName: '[name]__[local]___[hash:base64:5]', + }, }, }, { @@ -159,7 +174,11 @@ module.exports = async ({ config }) => { // what require() calls it will execute within the bundle JSON.stringify({ type, modules: extensions[type] || [] }), ].join(''); - }) + }), + + // Mock out libs used by a few componets to avoid loading in kibana_legacy and platform + new webpack.NormalModuleReplacementPlugin(/lib\/notify/, path.resolve(__dirname, '../tasks/mocks/uiNotify')), + new webpack.NormalModuleReplacementPlugin(/lib\/download_workpad/, path.resolve(__dirname, '../tasks/mocks/downloadWorkpad')), ); // Tell Webpack about relevant extensions diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/index.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata/demo_rows_types.ts similarity index 78% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/index.js rename to x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata/demo_rows_types.ts index f75d869f4667c..e92dc79fba8c3 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/index.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata/demo_rows_types.ts @@ -4,4 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export { ImportProgress, IMPORT_STATUS } from './import_progress'; +export enum DemoRows { + CI = 'ci', + SHIRTS = 'shirts', +} diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata/get_demo_rows.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata/get_demo_rows.ts index 02f8efcfde95d..58a2354b5cf38 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata/get_demo_rows.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata/get_demo_rows.ts @@ -6,14 +6,10 @@ import { cloneDeep } from 'lodash'; import ci from './ci.json'; +import { DemoRows } from './demo_rows_types'; import shirts from './shirts.json'; import { getFunctionErrors } from '../../../../i18n'; -export enum DemoRows { - CI = 'ci', - SHIRTS = 'shirts', -} - export function getDemoRows(arg: string | null) { if (arg === DemoRows.CI) { return cloneDeep(ci); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts index 826c49d328f21..5cebae5bb669f 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/functions/server/demodata/index.ts @@ -8,7 +8,8 @@ import { sortBy } from 'lodash'; import { ExpressionFunctionDefinition } from 'src/plugins/expressions'; // @ts-ignore unconverted lib file import { queryDatatable } from '../../../../common/lib/datatable/query'; -import { DemoRows, getDemoRows } from './get_demo_rows'; +import { DemoRows } from './demo_rows_types'; +import { getDemoRows } from './get_demo_rows'; import { Filter, Datatable, DatatableColumn, DatatableRow } from '../../../../types'; import { getFunctionHelp } from '../../../../i18n'; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/templates/index.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/templates/index.ts index eba0b47f7dc13..88d2b904e6cb3 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/templates/index.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/templates/index.ts @@ -7,7 +7,7 @@ import { applyTemplateStrings } from '../../i18n/templates'; import darkTemplate from './theme_dark.json'; import lightTemplate from './theme_light.json'; -import pitchTemplate from './pitch_presentation.json'; +// import pitchTemplate from './pitch_presentation.json'; import statusTemplate from './status_report.json'; import summaryTemplate from './summary_report.json'; @@ -15,7 +15,7 @@ import summaryTemplate from './summary_report.json'; export const templateSpecs = applyTemplateStrings([ darkTemplate, lightTemplate, - pitchTemplate, + // pitchTemplate, statusTemplate, summaryTemplate, ]); diff --git a/x-pack/legacy/plugins/canvas/i18n/functions/dict/demodata.ts b/x-pack/legacy/plugins/canvas/i18n/functions/dict/demodata.ts index caedbfdec5be4..35a5b86f752dc 100644 --- a/x-pack/legacy/plugins/canvas/i18n/functions/dict/demodata.ts +++ b/x-pack/legacy/plugins/canvas/i18n/functions/dict/demodata.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { demodata } from '../../../canvas_plugin_src/functions/server/demodata'; import { FunctionHelp } from '../function_help'; import { FunctionFactory } from '../../../types'; -import { DemoRows } from '../../../canvas_plugin_src/functions/server/demodata/get_demo_rows'; +import { DemoRows } from '../../../canvas_plugin_src/functions/server/demodata/demo_rows_types'; export const help: FunctionHelp> = { help: i18n.translate('xpack.canvas.functions.demodataHelpText', { diff --git a/x-pack/legacy/plugins/canvas/i18n/templates/template_strings.ts b/x-pack/legacy/plugins/canvas/i18n/templates/template_strings.ts index 5ab6a908641de..d8e4d51706be9 100644 --- a/x-pack/legacy/plugins/canvas/i18n/templates/template_strings.ts +++ b/x-pack/legacy/plugins/canvas/i18n/templates/template_strings.ts @@ -37,14 +37,6 @@ export const getTemplateStrings = (): TemplateStringDict => ({ defaultMessage: 'Light color themed presentation deck', }), }, - Pitch: { - name: i18n.translate('xpack.canvas.templates.pitchName', { - defaultMessage: 'Pitch', - }), - help: i18n.translate('xpack.canvas.templates.pitchHelp', { - defaultMessage: 'Branded presentation with large photos', - }), - }, Status: { name: i18n.translate('xpack.canvas.templates.statusName', { defaultMessage: 'Status', @@ -62,3 +54,14 @@ export const getTemplateStrings = (): TemplateStringDict => ({ }), }, }); + +export const getUnusedTemplateStrings = (): TemplateStringDict => ({ + Pitch: { + name: i18n.translate('xpack.canvas.templates.pitchName', { + defaultMessage: 'Pitch', + }), + help: i18n.translate('xpack.canvas.templates.pitchHelp', { + defaultMessage: 'Branded presentation with large photos', + }), + }, +}); diff --git a/x-pack/legacy/plugins/canvas/public/application.tsx b/x-pack/legacy/plugins/canvas/public/application.tsx index e26157aadebcb..670583e6542ee 100644 --- a/x-pack/legacy/plugins/canvas/public/application.tsx +++ b/x-pack/legacy/plugins/canvas/public/application.tsx @@ -26,9 +26,24 @@ import { getDocumentationLinks } from './lib/documentation_links'; import { HelpMenu } from './components/help_menu/help_menu'; import { createStore } from './store'; +import { VALUE_CLICK_TRIGGER, ActionByType } from '../../../../../src/plugins/ui_actions/public'; +/* eslint-disable */ +import { ACTION_VALUE_CLICK } from '../../../../../src/plugins/data/public/actions/value_click_action'; +/* eslint-enable */ + import { CapabilitiesStrings } from '../i18n'; const { ReadOnlyBadge: strings } = CapabilitiesStrings; +let restoreAction: ActionByType | undefined; +const emptyAction = { + id: 'empty-action', + type: '', + getDisplayName: () => 'empty action', + getIconType: () => undefined, + isCompatible: async () => true, + execute: async () => undefined, +} as ActionByType; + export const renderApp = ( coreStart: CoreStart, plugins: CanvasStartDeps, @@ -94,13 +109,30 @@ export const initializeCanvas = async ( }, }); + // TODO: We need this to disable the filtering modal from popping up in lens embeds until + // they honor the disableTriggers parameter + const action = startPlugins.uiActions.getAction(ACTION_VALUE_CLICK); + + if (action) { + restoreAction = action; + + startPlugins.uiActions.detachAction(VALUE_CLICK_TRIGGER, action.id); + startPlugins.uiActions.addTriggerAction(VALUE_CLICK_TRIGGER, emptyAction); + } + return canvasStore; }; -export const teardownCanvas = (coreStart: CoreStart) => { +export const teardownCanvas = (coreStart: CoreStart, startPlugins: CanvasStartDeps) => { destroyRegistries(); resetInterpreter(); + startPlugins.uiActions.detachAction(VALUE_CLICK_TRIGGER, emptyAction.id); + if (restoreAction) { + startPlugins.uiActions.addTriggerAction(VALUE_CLICK_TRIGGER, restoreAction); + restoreAction = undefined; + } + coreStart.chrome.setBadge(undefined); coreStart.chrome.setHelpExtension(undefined); }; diff --git a/x-pack/legacy/plugins/canvas/public/legacy.ts b/x-pack/legacy/plugins/canvas/public/legacy.ts index 9bccc958f7263..a6caa1985325e 100644 --- a/x-pack/legacy/plugins/canvas/public/legacy.ts +++ b/x-pack/legacy/plugins/canvas/public/legacy.ts @@ -27,6 +27,7 @@ const shimSetupPlugins: CanvasSetupDeps = { const shimStartPlugins: CanvasStartDeps = { ...npStart.plugins, expressions: npStart.plugins.expressions, + uiActions: npStart.plugins.uiActions, __LEGACY: { // ToDo: Copy directly into canvas absoluteToParsedUrl, diff --git a/x-pack/legacy/plugins/canvas/public/plugin.tsx b/x-pack/legacy/plugins/canvas/public/plugin.tsx index f4a3aed28a0a4..d9e5e6b4b084b 100644 --- a/x-pack/legacy/plugins/canvas/public/plugin.tsx +++ b/x-pack/legacy/plugins/canvas/public/plugin.tsx @@ -10,6 +10,7 @@ import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public'; import { initLoadingIndicator } from './lib/loading_indicator'; import { featureCatalogueEntry } from './feature_catalogue_entry'; import { ExpressionsSetup, ExpressionsStart } from '../../../../../src/plugins/expressions/public'; +import { UiActionsStart } from '../../../../../src/plugins/ui_actions/public'; // @ts-ignore untyped local import { argTypeSpecs } from './expression_types/arg_types'; import { transitions } from './transitions'; @@ -31,6 +32,7 @@ export interface CanvasSetupDeps { export interface CanvasStartDeps { expressions: ExpressionsStart; + uiActions: UiActionsStart; __LEGACY: { absoluteToParsedUrl: (url: string, basePath: string) => any; formatMsg: any; @@ -70,7 +72,7 @@ export class CanvasPlugin return () => { unmount(); - teardownCanvas(coreStart); + teardownCanvas(coreStart, depsStart); }; }, }); diff --git a/x-pack/legacy/plugins/canvas/tasks/mocks/downloadWorkpad.js b/x-pack/legacy/plugins/canvas/tasks/mocks/downloadWorkpad.js new file mode 100644 index 0000000000000..3571448c11aa9 --- /dev/null +++ b/x-pack/legacy/plugins/canvas/tasks/mocks/downloadWorkpad.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export const downloadWorkpad = async workpadId => console.log(`Download workpad ${workpadId}`); + +export const downloadRenderedWorkpad = async renderedWorkpad => + console.log(`Download workpad ${renderedWorkpad.id}`); + +export const downloadRuntime = async basePath => console.log(`Download run time at ${basePath}`); + +export const downloadZippedRuntime = async data => console.log(`Downloading data ${data}`); diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index a51972852c6ca..905c88a6d18a0 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -29,12 +29,14 @@ import _ from 'lodash'; import 'ui/autoload/all'; import 'ui/agg_response'; import 'leaflet'; -import 'plugins/kibana/dashboard/legacy'; import { npStart } from 'ui/new_platform'; import { localApplicationService } from 'plugins/kibana/local_application_service'; import { showAppRedirectNotification } from '../../../../../src/plugins/kibana_legacy/public'; -import { DashboardConstants, createDashboardEditUrl } from 'plugins/kibana/dashboard'; +import { + DashboardConstants, + createDashboardEditUrl, +} from '../../../../../src/plugins/dashboard/public'; npStart.plugins.kibanaLegacy.dashboardConfig.turnHideWriteControlsOn(); diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx index 47fd810bb4c53..42a1fcc055a1e 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_service/service.test.tsx @@ -17,7 +17,6 @@ import { CoreSetup } from 'kibana/public'; jest.mock('ui/new_platform'); // mock away actual dependencies to prevent all of it being loaded -jest.mock('../../../../../../src/legacy/core_plugins/interpreter/public/registries', () => {}); jest.mock('./embeddable/embeddable_factory', () => ({ EmbeddableFactory: class Mock {}, })); diff --git a/x-pack/legacy/plugins/lens/public/helpers/url_helper.test.ts b/x-pack/legacy/plugins/lens/public/helpers/url_helper.test.ts index 9c59c9a96d00f..ef960fb52952b 100644 --- a/x-pack/legacy/plugins/lens/public/helpers/url_helper.test.ts +++ b/x-pack/legacy/plugins/lens/public/helpers/url_helper.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../legacy_imports', () => ({ +jest.mock('../../../../../../src/plugins/dashboard/public', () => ({ DashboardConstants: { ADD_EMBEDDABLE_ID: 'addEmbeddableId', ADD_EMBEDDABLE_TYPE: 'addEmbeddableType', diff --git a/x-pack/legacy/plugins/lens/public/helpers/url_helper.ts b/x-pack/legacy/plugins/lens/public/helpers/url_helper.ts index fca44195b98c4..3495c15118ce7 100644 --- a/x-pack/legacy/plugins/lens/public/helpers/url_helper.ts +++ b/x-pack/legacy/plugins/lens/public/helpers/url_helper.ts @@ -5,7 +5,7 @@ */ import { parseUrl, stringify } from 'query-string'; -import { DashboardConstants } from '../legacy_imports'; +import { DashboardConstants } from '../../../../../../src/plugins/dashboard/public'; type UrlVars = Record; diff --git a/x-pack/legacy/plugins/lens/public/legacy.ts b/x-pack/legacy/plugins/lens/public/legacy.ts index b7d47644c7f31..3b7b6a7a1b510 100644 --- a/x-pack/legacy/plugins/lens/public/legacy.ts +++ b/x-pack/legacy/plugins/lens/public/legacy.ts @@ -5,7 +5,6 @@ */ import { npSetup, npStart } from 'ui/new_platform'; -import { visualizations } from './legacy_imports'; export * from './types'; @@ -14,6 +13,5 @@ import { plugin } from './index'; const pluginInstance = plugin(); pluginInstance.setup(npSetup.core, { ...npSetup.plugins, - __LEGACY: { visualizations }, }); pluginInstance.start(npStart.core, npStart.plugins); diff --git a/x-pack/legacy/plugins/lens/public/legacy_imports.ts b/x-pack/legacy/plugins/lens/public/legacy_imports.ts deleted file mode 100644 index 857443ae0fa1c..0000000000000 --- a/x-pack/legacy/plugins/lens/public/legacy_imports.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 { npSetup } from 'ui/new_platform'; -export const { visualizations } = npSetup.plugins; -export { VisualizationsSetup } from '../../../../../src/plugins/visualizations/public'; -export { DashboardConstants } from '../../../../../src/legacy/core_plugins/kibana/public/dashboard'; diff --git a/x-pack/legacy/plugins/lens/public/plugin.tsx b/x-pack/legacy/plugins/lens/public/plugin.tsx index 45817fdc3c05f..b426a12d07f9b 100644 --- a/x-pack/legacy/plugins/lens/public/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/plugin.tsx @@ -15,7 +15,9 @@ import { AppMountParameters, CoreSetup, CoreStart } from 'src/core/public'; import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/public'; import { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public'; import { ExpressionsSetup, ExpressionsStart } from 'src/plugins/expressions/public'; +import { VisualizationsSetup } from 'src/plugins/visualizations/public'; import { KibanaLegacySetup } from 'src/plugins/kibana_legacy/public'; +import { DashboardConstants } from '../../../../../src/plugins/dashboard/public'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { EditorFrameService } from './editor_frame_service'; import { IndexPatternDatasource } from './indexpattern_datasource'; @@ -37,16 +39,13 @@ import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../../../../plugins/lens/com import { addEmbeddableToDashboardUrl, getUrlVars } from './helpers'; import { EditorFrameStart } from './types'; import { getLensAliasConfig } from './vis_type_alias'; -import { VisualizationsSetup, DashboardConstants } from './legacy_imports'; export interface LensPluginSetupDependencies { kibanaLegacy: KibanaLegacySetup; expressions: ExpressionsSetup; data: DataPublicPluginSetup; embeddable: EmbeddableSetup; - __LEGACY: { - visualizations: VisualizationsSetup; - }; + visualizations: VisualizationsSetup; } export interface LensPluginStartDependencies { @@ -77,13 +76,7 @@ export class LensPlugin { setup( core: CoreSetup, - { - kibanaLegacy, - expressions, - data, - embeddable, - __LEGACY: { visualizations }, - }: LensPluginSetupDependencies + { kibanaLegacy, expressions, data, embeddable, visualizations }: LensPluginSetupDependencies ) { const editorFrameSetupInterface = this.editorFrameService.setup(core, { data, diff --git a/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts b/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts index b4a8ff90c3512..34f8c30b51874 100644 --- a/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts +++ b/x-pack/legacy/plugins/maps/public/actions/map_actions.d.ts @@ -5,59 +5,4 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -import { Filter, Query, TimeRange } from 'src/plugins/data/public'; -import { AnyAction } from 'redux'; -import { LAYER_TYPE } from '../../common/constants'; -import { DataMeta, MapFilters } from '../../common/descriptor_types'; -import { - MapCenterAndZoom, - MapRefreshConfig, -} from '../../../../../plugins/maps/common/descriptor_types'; - -export type SyncContext = { - startLoading(dataId: string, requestToken: symbol, meta: DataMeta): void; - stopLoading(dataId: string, requestToken: symbol, data: unknown, meta: DataMeta): void; - onLoadError(dataId: string, requestToken: symbol, errorMessage: string): void; - updateSourceData(newData: unknown): void; - isRequestStillActive(dataId: string, requestToken: symbol): boolean; - registerCancelCallback(requestToken: symbol, callback: () => void): void; - dataFilters: MapFilters; -}; - -export function updateSourceProp( - layerId: string, - propName: string, - value: unknown, - newLayerType?: LAYER_TYPE -): void; - -export function setGotoWithCenter(config: MapCenterAndZoom): AnyAction; - -export function replaceLayerList(layerList: unknown[]): AnyAction; - -export type QueryGroup = { - filters: Filter[]; - query?: Query; - timeFilters?: TimeRange; - refresh?: boolean; -}; - -export function setQuery(query: QueryGroup): AnyAction; - -export function setRefreshConfig(config: MapRefreshConfig): AnyAction; - -export function disableScrollZoom(): AnyAction; - -export function disableInteractive(): AnyAction; - -export function disableTooltipControl(): AnyAction; - -export function hideToolbarOverlay(): AnyAction; - -export function hideLayerControl(): AnyAction; - -export function hideViewControl(): AnyAction; - -export function setHiddenLayers(hiddenLayerIds: string[]): AnyAction; - -export function addLayerWithoutDataSync(layerDescriptor: unknown): AnyAction; +export * from '../../../../../plugins/maps/public/actions/map_actions'; diff --git a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js index 8fc32aef54770..5e497ff0736b2 100644 --- a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js +++ b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js @@ -4,10 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ import _ from 'lodash'; -import { KibanaTilemapSource } from '../layers/sources/kibana_tilemap_source'; -import { EMSTMSSource } from '../layers/sources/ems_tms_source'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { KibanaTilemapSource } from '../../../../../plugins/maps/public/layers/sources/kibana_tilemap_source'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { EMSTMSSource } from '../../../../../plugins/maps/public/layers/sources/ems_tms_source'; import { getInjectedVarFunc } from '../kibana_services'; -import { getKibanaTileMap } from '../meta'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getKibanaTileMap } from '../../../../../plugins/maps/public/meta'; export function getInitialLayers(layerListJSON, initialLayers = []) { if (layerListJSON) { diff --git a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js index f41ed26b2a05d..5334beaaf714a 100644 --- a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js +++ b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../meta', () => { +jest.mock('../../../../../plugins/maps/public/meta', () => { return {}; }); jest.mock('../kibana_services'); @@ -32,7 +32,7 @@ describe('Saved object has layer list', () => { describe('kibana.yml configured with map.tilemap.url', () => { beforeAll(() => { - require('../meta').getKibanaTileMap = () => { + require('../../../../../plugins/maps/public/meta').getKibanaTileMap = () => { return { url: 'myTileUrl', }; @@ -62,7 +62,7 @@ describe('kibana.yml configured with map.tilemap.url', () => { describe('EMS is enabled', () => { beforeAll(() => { - require('../meta').getKibanaTileMap = () => { + require('../../../../../plugins/maps/public/meta').getKibanaTileMap = () => { return null; }; require('../kibana_services').getInjectedVarFunc = () => key => { @@ -106,7 +106,7 @@ describe('EMS is enabled', () => { describe('EMS is not enabled', () => { beforeAll(() => { - require('../meta').getKibanaTileMap = () => { + require('../../../../../plugins/maps/public/meta').getKibanaTileMap = () => { return null; }; diff --git a/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js b/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js index f846d3d4a617f..990a0613da681 100644 --- a/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js +++ b/x-pack/legacy/plugins/maps/public/angular/services/saved_gis_map.js @@ -17,7 +17,8 @@ import { getFilters, } from '../../selectors/map_selectors'; import { getIsLayerTOCOpen, getOpenTOCDetails } from '../../selectors/ui_selectors'; -import { convertMapExtentToPolygon } from '../../elasticsearch_geo_utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { convertMapExtentToPolygon } from '../../../../../../plugins/maps/public/elasticsearch_geo_utils'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { copyPersistentState } from '../../../../../../plugins/maps/public/reducers/util'; import { extractReferences, injectReferences } from '../../../common/migrations/references'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.d.ts b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.d.ts index 00a9400109dc1..8689d88297171 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.d.ts +++ b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/index.d.ts @@ -6,7 +6,8 @@ import React from 'react'; import { Filter } from 'src/plugins/data/public'; -import { RenderToolTipContent } from '../../layers/tooltips/tooltip_property'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { RenderToolTipContent } from '../../../../../../plugins/maps/public/layers/tooltips/tooltip_property'; export const GisMap: React.ComponentType<{ addFilters: ((filters: Filter[]) => void) | null; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js index 97139103ab7c1..358313b8f5b6d 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/gis_map/view.js @@ -13,7 +13,8 @@ import { LayerPanel } from '../layer_panel/index'; import { AddLayerPanel } from '../layer_addpanel/index'; import { EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui'; import { ExitFullScreenButton } from 'ui/exit_full_screen'; -import { getIndexPatternsFromIds } from '../../index_pattern_util'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getIndexPatternsFromIds } from '../../../../../../plugins/maps/public/index_pattern_util'; import { ES_GEO_FIELD_TYPE } from '../../../common/constants'; import { indexPatterns as indexPatternsUtils } from '../../../../../../../src/plugins/data/public'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js index 762409b256286..cb20d80733c33 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/import_editor/view.js @@ -7,7 +7,8 @@ import React, { Fragment } from 'react'; import { EuiSpacer, EuiPanel, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { uploadLayerWizardConfig } from '../../../layers/sources/client_file_source'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { uploadLayerWizardConfig } from '../../../../../../../plugins/maps/public/layers/sources/client_file_source'; export const ImportEditor = ({ clearSource, isIndexingTriggered, ...props }) => { const editorProperties = getEditorProperties({ isIndexingTriggered, ...props }); diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js index b34a432bec88c..67cc17ebaa224 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_addpanel/source_select/source_select.js @@ -5,7 +5,8 @@ */ import React, { Fragment } from 'react'; -import { getLayerWizards } from '../../../layers/layer_wizard_registry'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getLayerWizards } from '../../../../../../../plugins/maps/public/layers/layer_wizard_registry'; import { EuiTitle, EuiSpacer, EuiCard, EuiIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import _ from 'lodash'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js index f7edcf6e85e25..6c080ace4442a 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js @@ -16,9 +16,11 @@ import { EuiFormHelpText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { SingleFieldSelect } from '../../../../components/single_field_select'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { SingleFieldSelect } from '../../../../../../../../plugins/maps/public/components/single_field_select'; import { FormattedMessage } from '@kbn/i18n/react'; -import { getTermsFields } from '../../../../index_pattern_util'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getTermsFields } from '../../../../../../../../plugins/maps/public/index_pattern_util'; import { getIndexPatternService, getIndexPatternSelectComponent, diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.js index 0944d0e602c2f..c6a79a398f9af 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.js @@ -14,7 +14,8 @@ import { EuiFormErrorText, EuiFormHelpText, } from '@elastic/eui'; -import { MetricsEditor } from '../../../../components/metrics_editor'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { MetricsEditor } from '../../../../../../../../plugins/maps/public/components/metrics_editor'; import { FormattedMessage } from '@kbn/i18n/react'; import { AGG_TYPE } from '../../../../../common/constants'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js index e4e3776c8e92c..d8bf862249448 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/metrics_expression.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../../../../components/metrics_editor', () => ({ +jest.mock('../../../../../../../../plugins/maps/public/components/metric_editor', () => ({ MetricsEditor: () => { return
mockMetricsEditor
; }, diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js index eb23607aa2150..bd27450943638 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.js @@ -8,7 +8,8 @@ import React, { Fragment } from 'react'; import { EuiTitle, EuiPanel, EuiFormRow, EuiFieldText, EuiSpacer } from '@elastic/eui'; -import { ValidatedRange } from '../../../components/validated_range'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ValidatedRange } from '../../../../../../../plugins/maps/public/components/validated_range'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { ValidatedDualRange } from '../../../../../../../../src/plugins/kibana_react/public'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.d.ts b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.d.ts index 6d1d076c723ad..cf4fdc7be70c6 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.d.ts +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/view.d.ts @@ -5,10 +5,4 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ -import { LAYER_TYPE } from '../../../common/constants'; - -export type OnSourceChangeArgs = { - propName: string; - value: unknown; - newLayerType?: LAYER_TYPE; -}; +export * from '../../../../../../plugins/maps/public/connected_components/layer_panel/view'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js b/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js index 416af95581058..7063c50edad6a 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/features_tooltip/feature_geometry_filter_form.js @@ -8,7 +8,8 @@ import React, { Component, Fragment } from 'react'; import { EuiIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { createSpatialFilterWithGeometry } from '../../../elasticsearch_geo_utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { createSpatialFilterWithGeometry } from '../../../../../../../plugins/maps/public/elasticsearch_geo_utils'; import { GEO_JSON_TYPE } from '../../../../common/constants'; import { GeometryFilterForm } from '../../../components/geometry_filter_form'; import { UrlOverflowService } from 'ui/error_url_overflow'; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js index 99abe5d108b5a..df2988d399c5b 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/draw_control/draw_control.js @@ -16,7 +16,8 @@ import { createSpatialFilterWithGeometry, getBoundingBoxGeometry, roundCoordinates, -} from '../../../../elasticsearch_geo_utils'; + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../../plugins/maps/public/elasticsearch_geo_utils'; import { DrawTooltip } from './draw_tooltip'; const mbDrawModes = MapboxDraw.modes; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/utils.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/utils.js index a2850d2bb6c23..a1d1341b7c4f7 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/utils.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/utils.js @@ -5,7 +5,13 @@ */ import _ from 'lodash'; -import { RGBAImage } from './image_utils'; +import { + loadSpriteSheetImageData, + addSpriteSheetToMapFromImageData, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../plugins/maps/public/connected_components/map/mb/utils'; + +export { loadSpriteSheetImageData, addSpriteSheetToMapFromImageData }; export function removeOrphanedSourcesAndLayers(mbMap, layerList) { const mbStyle = mbMap.getStyle(); @@ -95,62 +101,7 @@ export function syncLayerOrderForSingleLayer(mbMap, layerList) { }); } -function getImageData(img) { - const canvas = window.document.createElement('canvas'); - const context = canvas.getContext('2d'); - if (!context) { - throw new Error('failed to create canvas 2d context'); - } - canvas.width = img.width; - canvas.height = img.height; - context.drawImage(img, 0, 0, img.width, img.height); - return context.getImageData(0, 0, img.width, img.height); -} - -export async function loadSpriteSheetImageData(imgUrl) { - return new Promise((resolve, reject) => { - const image = new Image(); - if (isCrossOriginUrl(imgUrl)) { - image.crossOrigin = 'Anonymous'; - } - image.onload = el => { - const imgData = getImageData(el.currentTarget); - resolve(imgData); - }; - image.onerror = e => { - reject(e); - }; - image.src = imgUrl; - }); -} - -export function addSpriteSheetToMapFromImageData(json, imgData, mbMap) { - for (const imageId in json) { - if (!(json.hasOwnProperty(imageId) && !mbMap.hasImage(imageId))) { - continue; - } - const { width, height, x, y, sdf, pixelRatio } = json[imageId]; - if (typeof width !== 'number' || typeof height !== 'number') { - continue; - } - - const data = new RGBAImage({ width, height }); - RGBAImage.copy(imgData, data, { x, y }, { x: 0, y: 0 }, { width, height }); - mbMap.addImage(imageId, data, { pixelRatio, sdf }); - } -} - export async function addSpritesheetToMap(json, imgUrl, mbMap) { const imgData = await loadSpriteSheetImageData(imgUrl); addSpriteSheetToMapFromImageData(json, imgData, mbMap); } - -function isCrossOriginUrl(url) { - const a = window.document.createElement('a'); - a.href = url; - return ( - a.protocol !== window.document.location.protocol || - a.host !== window.document.location.host || - a.port !== window.document.location.port - ); -} diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js index 2995ea039e7a8..fedc1902d80a2 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js @@ -12,7 +12,8 @@ import { removeOrphanedSourcesAndLayers, addSpritesheetToMap, } from './utils'; -import { getGlyphUrl, isRetina } from '../../../meta'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getGlyphUrl, isRetina } from '../../../../../../../plugins/maps/public/meta'; import { DECIMAL_DEGREES_PRECISION, ZOOM_PRECISION } from '../../../../common/constants'; import mapboxgl from 'mapbox-gl/dist/mapbox-gl-csp'; import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker'; @@ -23,7 +24,11 @@ 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 '../../../elasticsearch_geo_utils'; +import { + clampToLatBounds, + clampToLonBounds, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../plugins/maps/public/elasticsearch_geo_utils'; mapboxgl.workerUrl = mbWorkerUrl; mapboxgl.setRTLTextPlugin(mbRtlPlugin); diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/_attribution_control.scss b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/_attribution_control.scss index 9ebaee57fba4d..e319535b4a45c 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/_attribution_control.scss +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/_attribution_control.scss @@ -4,3 +4,7 @@ pointer-events: all; padding-left: $euiSizeM; } + +.mapAttributionControl__fullScreen { + margin-left: $euiSizeXXL * 4; +} diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/index.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/index.js index e73a51ffa2ced..8bad536b39245 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/index.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/index.js @@ -7,10 +7,12 @@ import { connect } from 'react-redux'; import { AttributionControl } from './view'; import { getLayerList } from '../../../selectors/map_selectors'; +import { getIsFullScreen } from '../../../selectors/ui_selectors'; function mapStateToProps(state = {}) { return { layerList: getLayerList(state), + isFullScreen: getIsFullScreen(state), }; } diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.js index 161b5b81c1255..8f11d1b23376c 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/attribution_control/view.js @@ -7,6 +7,7 @@ import React, { Fragment } from 'react'; import _ from 'lodash'; import { EuiText, EuiLink } from '@elastic/eui'; +import classNames from 'classnames'; export class AttributionControl extends React.Component { state = { @@ -86,7 +87,11 @@ export class AttributionControl extends React.Component { return null; } return ( -
+
{this._renderAttributions()} diff --git a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/view_control/view_control.js b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/view_control/view_control.js index 445e5be542ffb..19b60221ead36 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/view_control/view_control.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/widget_overlay/view_control/view_control.js @@ -5,28 +5,33 @@ */ import _ from 'lodash'; -import React from 'react'; +import React, { Fragment } from 'react'; import { EuiText } from '@elastic/eui'; import { DECIMAL_DEGREES_PRECISION } from '../../../../common/constants'; import { FormattedMessage } from '@kbn/i18n/react'; export function ViewControl({ mouseCoordinates, zoom }) { - if (!mouseCoordinates) { - return null; + let latLon; + if (mouseCoordinates) { + latLon = ( + + + + {' '} + {_.round(mouseCoordinates.lat, DECIMAL_DEGREES_PRECISION)},{' '} + + + {' '} + {_.round(mouseCoordinates.lon, DECIMAL_DEGREES_PRECISION)},{' '} + + ); } return (
- - - {' '} - {_.round(mouseCoordinates.lat, DECIMAL_DEGREES_PRECISION)},{' '} - - - {' '} - {_.round(mouseCoordinates.lon, DECIMAL_DEGREES_PRECISION)},{' '} + {latLon} {' '} diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx index 9544e8714f265..bdd2d863e6920 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.tsx @@ -57,7 +57,8 @@ import { } from '../../../../../plugins/maps/public/reducers/non_serializable_instances'; import { getMapCenter, getMapZoom, getHiddenLayerIds } from '../selectors/map_selectors'; import { MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; -import { RenderToolTipContent } from '../layers/tooltips/tooltip_property'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { RenderToolTipContent } from '../../../../../plugins/maps/public/layers/tooltips/tooltip_property'; interface MapEmbeddableConfig { editUrl?: string; diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts index 5a036ed47fb62..5deb3057a449e 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable_factory.ts @@ -27,6 +27,11 @@ import { getInitialLayers } from '../angular/get_initial_layers'; import { mergeInputWithSavedMap } from './merge_input_with_saved_map'; import '../angular/services/gis_map_saved_object_loader'; import { bindSetupCoreAndPlugins, bindStartCoreAndPlugins } from '../plugin'; +// @ts-ignore +import { + bindSetupCoreAndPlugins as bindNpSetupCoreAndPlugins, + bindStartCoreAndPlugins as bindNpStartCoreAndPlugins, +} from '../../../../../plugins/maps/public/plugin'; // eslint-disable-line @kbn/eslint/no-restricted-paths export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { type = MAP_SAVED_OBJECT_TYPE; @@ -40,7 +45,9 @@ export class MapEmbeddableFactory implements EmbeddableFactoryDefinition { constructor() { // Init required services. Necessary while in legacy bindSetupCoreAndPlugins(npSetup.core, npSetup.plugins); + bindNpSetupCoreAndPlugins(npSetup.core, npSetup.plugins); bindStartCoreAndPlugins(npStart.core, npStart.plugins); + bindNpStartCoreAndPlugins(npStart.core, npStart.plugins); } async isEditable() { diff --git a/x-pack/legacy/plugins/maps/public/index.scss b/x-pack/legacy/plugins/maps/public/index.scss index 328b2e576e0e6..b2ac514299d80 100644 --- a/x-pack/legacy/plugins/maps/public/index.scss +++ b/x-pack/legacy/plugins/maps/public/index.scss @@ -14,4 +14,4 @@ @import './mapbox_hacks'; @import './connected_components/index'; @import './components/index'; -@import './layers/index'; +@import '../../../../plugins/maps/public/layers/index'; diff --git a/x-pack/legacy/plugins/maps/public/index.ts b/x-pack/legacy/plugins/maps/public/index.ts index 2d13f005f1a70..b69485e251be4 100644 --- a/x-pack/legacy/plugins/maps/public/index.ts +++ b/x-pack/legacy/plugins/maps/public/index.ts @@ -26,5 +26,9 @@ export const plugin = (initializerContext: PluginInitializerContext) => { return new MapsPlugin(); }; -export { RenderTooltipContentParams, ITooltipProperty } from './layers/tooltips/tooltip_property'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +export { + RenderTooltipContentParams, + ITooltipProperty, +} from '../../../../plugins/maps/public/layers/tooltips/tooltip_property'; export { MapEmbeddable, MapEmbeddableInput } from './embeddable'; diff --git a/x-pack/legacy/plugins/maps/public/kibana_services.js b/x-pack/legacy/plugins/maps/public/kibana_services.js index 3b0f501dc0f60..a6491fe1aa6d4 100644 --- a/x-pack/legacy/plugins/maps/public/kibana_services.js +++ b/x-pack/legacy/plugins/maps/public/kibana_services.js @@ -4,88 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { esFilters, search } from '../../../../../src/plugins/data/public'; -const { getRequestInspectorStats, getResponseInspectorStats } = search; - -export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER; -export { SearchSource } from '../../../../../src/plugins/data/public'; - let indexPatternService; export const setIndexPatternService = dataIndexPatterns => (indexPatternService = dataIndexPatterns); export const getIndexPatternService = () => indexPatternService; -let autocompleteService; -export const setAutocompleteService = dataAutoComplete => (autocompleteService = dataAutoComplete); -export const getAutocompleteService = () => autocompleteService; - -let licenseId; -export const setLicenseId = latestLicenseId => (licenseId = latestLicenseId); -export const getLicenseId = () => { - return licenseId; -}; - let inspector; export const setInspector = newInspector => (inspector = newInspector); export const getInspector = () => { return inspector; }; -let fileUploadPlugin; -export const setFileUpload = fileUpload => (fileUploadPlugin = fileUpload); -export const getFileUploadComponent = () => { - return fileUploadPlugin.JsonUploadAndParse; -}; - let getInjectedVar; export const setInjectedVarFunc = getInjectedVarFunc => (getInjectedVar = getInjectedVarFunc); export const getInjectedVarFunc = () => getInjectedVar; -let uiSettings; -export const setUiSettings = coreUiSettings => (uiSettings = coreUiSettings); -export const getUiSettings = () => uiSettings; - let indexPatternSelectComponent; export const setIndexPatternSelect = indexPatternSelect => (indexPatternSelectComponent = indexPatternSelect); export const getIndexPatternSelectComponent = () => indexPatternSelectComponent; -let coreHttp; -export const setHttp = http => (coreHttp = http); -export const getHttp = () => coreHttp; - let dataTimeFilter; export const setTimeFilter = timeFilter => (dataTimeFilter = timeFilter); export const getTimeFilter = () => dataTimeFilter; - -let toast; -export const setToasts = notificationToast => (toast = notificationToast); -export const getToasts = () => toast; - -export async function fetchSearchSourceAndRecordWithInspector({ - searchSource, - requestId, - requestName, - requestDesc, - inspectorAdapters, - abortSignal, -}) { - const inspectorRequest = inspectorAdapters.requests.start(requestName, { - id: requestId, - description: requestDesc, - }); - let resp; - try { - inspectorRequest.stats(getRequestInspectorStats(searchSource)); - searchSource.getSearchRequestBody().then(body => { - inspectorRequest.json(body); - }); - resp = await searchSource.fetch({ abortSignal }); - inspectorRequest.stats(getResponseInspectorStats(searchSource, resp)).ok({ json: resp }); - } catch (error) { - inspectorRequest.error({ error }); - throw error; - } - - return resp; -} diff --git a/x-pack/legacy/plugins/maps/public/layers/_index.scss b/x-pack/legacy/plugins/maps/public/layers/_index.scss deleted file mode 100644 index a2ce58e0381af..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './styles/index'; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss b/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss deleted file mode 100644 index b5d9113619c76..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss +++ /dev/null @@ -1,4 +0,0 @@ -@import './components/color_gradient'; -@import './vector/components/style_prop_editor'; -@import './vector/components/color/color_stops'; -@import './vector/components/symbol/icon_select'; diff --git a/x-pack/legacy/plugins/maps/public/plugin.ts b/x-pack/legacy/plugins/maps/public/plugin.ts index c08ed6fc6da61..0fa7e1106a6df 100644 --- a/x-pack/legacy/plugins/maps/public/plugin.ts +++ b/x-pack/legacy/plugins/maps/public/plugin.ts @@ -4,9 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import './layers/layer_wizard_registry'; -import './layers/sources/source_registry'; -import './layers/load_layer_wizards'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../plugins/maps/public/layers/layer_wizard_registry'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../plugins/maps/public/layers/sources/source_registry'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import '../../../../plugins/maps/public/layers/load_layer_wizards'; import { Plugin, CoreStart, CoreSetup } from 'src/core/public'; // @ts-ignore @@ -17,20 +20,17 @@ import { Start as InspectorStartContract } from 'src/plugins/inspector/public'; import { MapListing } from './components/map_listing'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { - setLicenseId, setInspector, - setFileUpload, setIndexPatternSelect, - setHttp, setTimeFilter, - setUiSettings, setInjectedVarFunc, - setToasts, setIndexPatternService, - setAutocompleteService, } from './kibana_services'; // @ts-ignore -import { setInjectedVarFunc as npSetInjectedVarFunc } from '../../../../plugins/maps/public/kibana_services'; // eslint-disable-line @kbn/eslint/no-restricted-paths +import { + bindSetupCoreAndPlugins as bindNpSetupCoreAndPlugins, + bindStartCoreAndPlugins as bindNpStartCoreAndPlugins, +} from '../../../../plugins/maps/public/plugin'; // eslint-disable-line @kbn/eslint/no-restricted-paths import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public'; import { LicensingPluginSetup } from '../../../../plugins/licensing/public'; import { featureCatalogueEntry } from './feature_catalogue_entry'; @@ -63,27 +63,17 @@ interface MapsPluginStartDependencies { } export const bindSetupCoreAndPlugins = (core: CoreSetup, plugins: any) => { - const { licensing } = plugins; - const { injectedMetadata, http } = core; - if (licensing) { - licensing.license$.subscribe(({ uid }: { uid: string }) => setLicenseId(uid)); - } + const { injectedMetadata } = core; setInjectedVarFunc(injectedMetadata.getInjectedVar); - setHttp(http); - setUiSettings(core.uiSettings); setInjectedVarFunc(core.injectedMetadata.getInjectedVar); - npSetInjectedVarFunc(core.injectedMetadata.getInjectedVar); - setToasts(core.notifications.toasts); }; export const bindStartCoreAndPlugins = (core: CoreStart, plugins: any) => { - const { file_upload, data, inspector } = plugins; + const { data, inspector } = plugins; setInspector(inspector); - setFileUpload(file_upload); setIndexPatternSelect(data.ui.IndexPatternSelect); setTimeFilter(data.query.timefilter.timefilter); setIndexPatternService(data.indexPatterns); - setAutocompleteService(data.autocomplete); }; /** @internal */ @@ -96,11 +86,13 @@ export class MapsPlugin implements Plugin { }); bindSetupCoreAndPlugins(core, np); + bindNpSetupCoreAndPlugins(core, np); np.home.featureCatalogue.register(featureCatalogueEntry); } public start(core: CoreStart, plugins: MapsPluginStartDependencies) { bindStartCoreAndPlugins(core, plugins); + bindNpStartCoreAndPlugins(core, plugins); } } diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js index 397478cfd1d1b..59346e4c6fb98 100644 --- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js +++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js @@ -6,11 +6,16 @@ import { createSelector } from 'reselect'; import _ from 'lodash'; -import { TileLayer } from '../layers/tile_layer'; -import { VectorTileLayer } from '../layers/vector_tile_layer'; -import { VectorLayer } from '../layers/vector_layer'; -import { HeatmapLayer } from '../layers/heatmap_layer'; -import { BlendedVectorLayer } from '../layers/blended_vector_layer'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { TileLayer } from '../../../../../plugins/maps/public/layers/tile_layer'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { VectorTileLayer } from '../../../../../plugins/maps/public/layers/vector_tile_layer'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { VectorLayer } from '../../../../../plugins/maps/public/layers/vector_layer'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { HeatmapLayer } from '../../../../../plugins/maps/public/layers/heatmap_layer'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { BlendedVectorLayer } from '../../../../../plugins/maps/public/layers/blended_vector_layer'; import { getTimeFilter } from '../kibana_services'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { getInspectorAdapters } from '../../../../../plugins/maps/public/reducers/non_serializable_instances'; @@ -19,8 +24,10 @@ import { TRACKED_LAYER_DESCRIPTOR, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../../plugins/maps/public/reducers/util'; -import { InnerJoin } from '../layers/joins/inner_join'; -import { getSourceByType } from '../layers/sources/source_registry'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { InnerJoin } from '../../../../../plugins/maps/public/layers/joins/inner_join'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getSourceByType } from '../../../../../plugins/maps/public/layers/sources/source_registry'; function createLayerInstance(layerDescriptor, inspectorAdapters) { const source = createSourceInstance(layerDescriptor.sourceDescriptor, inspectorAdapters); diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js index 1a5ab633a569f..77bd29259647c 100644 --- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js +++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.test.js @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../layers/vector_layer', () => {}); -jest.mock('../layers/blended_vector_layer', () => {}); -jest.mock('../layers/heatmap_layer', () => {}); -jest.mock('../layers/vector_tile_layer', () => {}); -jest.mock('../layers/joins/inner_join', () => {}); +jest.mock('../../../../../plugins/maps/public/layers/vector_layer', () => {}); +jest.mock('../../../../../plugins/maps/public/layers/blended_vector_layer', () => {}); +jest.mock('../../../../../plugins/maps/public/layers/heatmap_layer', () => {}); +jest.mock('../../../../../plugins/maps/public/layers/vector_tile_layer', () => {}); +jest.mock('../../../../../plugins/maps/public/layers/joins/inner_join', () => {}); jest.mock('../../../../../plugins/maps/public/reducers/non_serializable_instances', () => ({ getInspectorAdapters: () => { return {}; diff --git a/x-pack/legacy/plugins/maps/server/plugin.js b/x-pack/legacy/plugins/maps/server/plugin.js index 5b52a3eba2f23..25c552433e9f8 100644 --- a/x-pack/legacy/plugins/maps/server/plugin.js +++ b/x-pack/legacy/plugins/maps/server/plugin.js @@ -9,7 +9,6 @@ import { getEcommerceSavedObjects } from './sample_data/ecommerce_saved_objects' import { getFlightsSavedObjects } from './sample_data/flights_saved_objects.js'; import { getWebLogsSavedObjects } from './sample_data/web_logs_saved_objects.js'; import { registerMapsUsageCollector } from './maps_telemetry/collectors/register'; -import { LICENSE_CHECK_STATE } from '../../../../plugins/licensing/server'; import { initRoutes } from './routes'; import { emsBoundariesSpecProvider } from './tutorials/ems'; @@ -52,7 +51,7 @@ export class MapPlugin { licensing.license$.subscribe(license => { const { state } = license.check('maps', 'basic'); - if (state === LICENSE_CHECK_STATE.Valid && !routesInitialized) { + if (state === 'valid' && !routesInitialized) { routesInitialized = true; initRoutes(__LEGACY, license.uid); } diff --git a/x-pack/legacy/plugins/monitoring/common/constants.ts b/x-pack/legacy/plugins/monitoring/common/constants.ts index 9a4030f3eb214..3a4c7b71dcd03 100644 --- a/x-pack/legacy/plugins/monitoring/common/constants.ts +++ b/x-pack/legacy/plugins/monitoring/common/constants.ts @@ -239,11 +239,15 @@ export const ALERT_TYPE_PREFIX = 'monitoring_'; * This is the alert type id for the license expiration alert */ export const ALERT_TYPE_LICENSE_EXPIRATION = `${ALERT_TYPE_PREFIX}alert_type_license_expiration`; +/** + * This is the alert type id for the cluster state alert + */ +export const ALERT_TYPE_CLUSTER_STATE = `${ALERT_TYPE_PREFIX}alert_type_cluster_state`; /** * A listing of all alert types */ -export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION]; +export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION, ALERT_TYPE_CLUSTER_STATE]; /** * Matches the id for the built-in in email action type @@ -254,7 +258,7 @@ export const ALERT_ACTION_TYPE_EMAIL = '.email'; /** * The number of alerts that have been migrated */ -export const NUMBER_OF_MIGRATED_ALERTS = 1; +export const NUMBER_OF_MIGRATED_ALERTS = 2; /** * The advanced settings config name for the email address diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js b/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js index 11fcef73a4b97..95c1af5549198 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/alerts.js @@ -6,10 +6,15 @@ import React from 'react'; import chrome from '../../np_imports/ui/chrome'; -import { capitalize } from 'lodash'; +import { capitalize, get } from 'lodash'; import { formatDateTimeLocal } from '../../../common/formatting'; import { formatTimestampToDuration } from '../../../common'; -import { CALCULATE_DURATION_SINCE, EUI_SORT_DESCENDING } from '../../../common/constants'; +import { + CALCULATE_DURATION_SINCE, + EUI_SORT_DESCENDING, + ALERT_TYPE_LICENSE_EXPIRATION, + ALERT_TYPE_CLUSTER_STATE, +} from '../../../common/constants'; import { mapSeverity } from './map_severity'; import { FormattedAlert } from 'plugins/monitoring/components/alerts/formatted_alert'; import { EuiMonitoringTable } from 'plugins/monitoring/components/table'; @@ -21,6 +26,8 @@ const linkToCategories = { 'elasticsearch/indices': 'Elasticsearch Indices', 'kibana/instances': 'Kibana Instances', 'logstash/instances': 'Logstash Nodes', + [ALERT_TYPE_LICENSE_EXPIRATION]: 'License expiration', + [ALERT_TYPE_CLUSTER_STATE]: 'Cluster state', }; const getColumns = (kbnUrl, scope, timezone) => [ { @@ -94,19 +101,22 @@ const getColumns = (kbnUrl, scope, timezone) => [ }), field: 'message', sortable: true, - render: (message, alert) => ( - { - scope.$evalAsync(() => { - kbnUrl.changePath(target); - }); - }} - /> - ), + render: (_message, alert) => { + const message = get(alert, 'message.text', get(alert, 'message', '')); + return ( + { + scope.$evalAsync(() => { + kbnUrl.changePath(target); + }); + }} + /> + ); + }, }, { name: i18n.translate('xpack.monitoring.alerts.categoryColumnTitle', { @@ -148,8 +158,8 @@ const getColumns = (kbnUrl, scope, timezone) => [ export const Alerts = ({ alerts, angular, sorting, pagination, onTableChange }) => { const alertsFlattened = alerts.map(alert => ({ ...alert, - status: alert.metadata.severity, - category: alert.metadata.link, + status: get(alert, 'metadata.severity', get(alert, 'severity', 0)), + category: get(alert, 'metadata.link', get(alert, 'type', null)), })); const injector = chrome.dangerouslyGetActiveInjector(); diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap index 94d951a94fe29..cb1081c0c14da 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap @@ -25,6 +25,7 @@ exports[`Step1 editing should allow for editing 1`] = ` "actionTypeId": "1abc", "config": Object {}, "id": "1", + "isPreconfigured": false, "name": "Testing", } } diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.tsx index 0933cd22db7c9..eaa474ba177b1 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.tsx +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.tsx @@ -61,7 +61,7 @@ export const AlertsConfiguration: React.FC = ( async function fetchEmailActions() { const kibanaActions = await kfetch({ method: 'GET', - pathname: `/api/action/_find`, + pathname: `/api/action/_getAll`, }); const actions = kibanaActions.data.filter( diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx index 650294c29e9a5..19a1a61d00a42 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx @@ -27,6 +27,7 @@ describe('Step1', () => { actionTypeId: '1abc', name: 'Testing', config: {}, + isPreconfigured: false, }, ]; const selectedEmailActionId = emailActions[0].id; @@ -83,6 +84,7 @@ describe('Step1', () => { actionTypeId: '.email', name: '', config: {}, + isPreconfigured: false, }, ], selectedEmailActionId: NEW_ACTION_ID, diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx index 258a5b68db372..d3cf4b463a2cc 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { kfetch } from 'ui/kfetch'; import { AlertsStatus, AlertsStatusProps } from './status'; -import { ALERT_TYPE_PREFIX } from '../../../common/constants'; +import { ALERT_TYPES } from '../../../common/constants'; import { getSetupModeState } from '../../lib/setup_mode'; import { mockUseEffects } from '../../jest.helpers'; @@ -63,11 +63,7 @@ describe('Status', () => { it('should render a success message if all alerts have been migrated and in setup mode', async () => { (kfetch as jest.Mock).mockReturnValue({ - data: [ - { - alertTypeId: ALERT_TYPE_PREFIX, - }, - ], + data: ALERT_TYPES.map(type => ({ alertTypeId: type })), }); (getSetupModeState as jest.Mock).mockReturnValue({ diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx index 072a98b123452..5f5329bf7fff8 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx @@ -142,7 +142,7 @@ export const AlertsStatus: React.FC = (props: AlertsStatusPro ); } - const allMigrated = kibanaAlerts.length === NUMBER_OF_MIGRATED_ALERTS; + const allMigrated = kibanaAlerts.length >= NUMBER_OF_MIGRATED_ALERTS; if (allMigrated) { if (setupModeEnabled) { return ( diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js index 8455fb8cf3088..d87ff98e79be0 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js @@ -6,14 +6,12 @@ import React, { Fragment } from 'react'; import moment from 'moment-timezone'; -import chrome from '../../../np_imports/ui/chrome'; import { FormattedAlert } from 'plugins/monitoring/components/alerts/formatted_alert'; import { mapSeverity } from 'plugins/monitoring/components/alerts/map_severity'; import { formatTimestampToDuration } from '../../../../common/format_timestamp_to_duration'; import { CALCULATE_DURATION_SINCE, KIBANA_ALERTING_ENABLED, - ALERT_TYPE_LICENSE_EXPIRATION, CALCULATE_DURATION_UNTIL, } from '../../../../common/constants'; import { formatDateTimeLocal } from '../../../../common/formatting'; @@ -31,6 +29,37 @@ import { EuiLink, } from '@elastic/eui'; +function replaceTokens(alert) { + if (!alert.message.tokens) { + return alert.message.text; + } + + let text = alert.message.text; + + for (const token of alert.message.tokens) { + if (token.type === 'time') { + text = text.replace( + token.startToken, + token.isRelative + ? formatTimestampToDuration(alert.expirationTime, CALCULATE_DURATION_UNTIL) + : moment.tz(alert.expirationTime, moment.tz.guess()).format('LLL z') + ); + } else if (token.type === 'link') { + const linkPart = new RegExp(`${token.startToken}(.+?)${token.endToken}`).exec(text); + // TODO: we assume this is at the end, which works for now but will not always work + const nonLinkText = text.replace(linkPart[0], ''); + text = ( + + {nonLinkText} + {linkPart[1]} + + ); + } + } + + return text; +} + export function AlertsPanel({ alerts, changeUrl }) { const goToAlerts = () => changeUrl('/alerts'); @@ -58,9 +87,6 @@ export function AlertsPanel({ alerts, changeUrl }) { severityIcon.iconType = 'check'; } - const injector = chrome.dangerouslyGetActiveInjector(); - const timezone = injector.get('config').get('dateFormat:tz'); - return ( @@ -96,14 +122,7 @@ export function AlertsPanel({ alerts, changeUrl }) { const alertsList = KIBANA_ALERTING_ENABLED ? alerts.map((alert, idx) => { const callOutProps = mapSeverity(alert.severity); - let message = alert.message - // scan message prefix and replace relative times - // \w: Matches any alphanumeric character from the basic Latin alphabet, including the underscore. Equivalent to [A-Za-z0-9_]. - .replace( - '#relative', - formatTimestampToDuration(alert.expirationTime, CALCULATE_DURATION_UNTIL) - ) - .replace('#absolute', moment.tz(alert.expirationTime, moment.tz.guess()).format('LLL z')); + const message = replaceTokens(alert); if (!alert.isFiring) { callOutProps.title = i18n.translate( @@ -118,22 +137,30 @@ export function AlertsPanel({ alerts, changeUrl }) { ); callOutProps.color = 'success'; callOutProps.iconType = 'check'; - } else { - if (alert.type === ALERT_TYPE_LICENSE_EXPIRATION) { - message = ( - - {message} -   - Please update your license - - ); - } } return ( - -

{message}

-
+ + +

{message}

+ +

+ +

+
+
+ +
); }) : alerts.map((item, index) => ( diff --git a/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js b/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js index 7c065a78a8af9..62cc985887e9f 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js @@ -18,25 +18,37 @@ import { Alerts } from '../../components/alerts'; import { MonitoringViewBaseEuiTableController } from '../base_eui_table_controller'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiLink } from '@elastic/eui'; -import { CODE_PATH_ALERTS } from '../../../common/constants'; +import { CODE_PATH_ALERTS, KIBANA_ALERTING_ENABLED } from '../../../common/constants'; function getPageData($injector) { const globalState = $injector.get('globalState'); const $http = $injector.get('$http'); const Private = $injector.get('Private'); - const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/legacy_alerts`; + const url = KIBANA_ALERTING_ENABLED + ? `../api/monitoring/v1/alert_status` + : `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/legacy_alerts`; const timeBounds = timefilter.getBounds(); + const data = { + timeRange: { + min: timeBounds.min.toISOString(), + max: timeBounds.max.toISOString(), + }, + }; + + if (!KIBANA_ALERTING_ENABLED) { + data.ccs = globalState.ccs; + } return $http - .post(url, { - ccs: globalState.ccs, - timeRange: { - min: timeBounds.min.toISOString(), - max: timeBounds.max.toISOString(), - }, + .post(url, data) + .then(response => { + const result = get(response, 'data', []); + if (KIBANA_ALERTING_ENABLED) { + return result.alerts; + } + return result; }) - .then(response => get(response, 'data', [])) .catch(err => { const ajaxErrorHandlers = Private(ajaxErrorHandlersProvider); return ajaxErrorHandlers(err); diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts index 6f415d7ee5ea9..0436f5d5bc843 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { cryptoFactory } from '../../../server/lib/crypto'; -import { CryptoFactory, Logger } from '../../../types'; +import { Logger } from '../../../types'; interface HasEncryptedHeaders { headers?: string; @@ -25,9 +25,16 @@ export const decryptJobHeaders = async < job: JobDocPayloadType; logger: Logger; }): Promise> => { - const crypto: CryptoFactory = cryptoFactory(encryptionKey); try { - const decryptedHeaders: Record = await crypto.decrypt(job.headers); + if (typeof job.headers !== 'string') { + throw new Error( + i18n.translate('xpack.reporting.exportTypes.common.missingJobHeadersErrorMessage', { + defaultMessage: 'Job headers are missing', + }) + ); + } + const crypto = cryptoFactory(encryptionKey); + const decryptedHeaders = (await crypto.decrypt(job.headers)) as Record; return decryptedHeaders; } catch (err) { logger.error(err); diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts index d78d8a8a8010d..3a282eb0b2974 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/server/execute_job.ts @@ -43,9 +43,18 @@ export const executeJobFactory: ExecuteJobFactory { - let decryptedHeaders; try { - decryptedHeaders = await crypto.decrypt(headers); + if (typeof headers !== 'string') { + throw new Error( + i18n.translate( + 'xpack.reporting.exportTypes.csv.executeJob.missingJobHeadersErrorMessage', + { + defaultMessage: 'Job headers are missing', + } + ) + ); + } + return await crypto.decrypt(headers); } catch (err) { logger.error(err); throw new Error( @@ -58,7 +67,6 @@ export const executeJobFactory: ExecuteJobFactory; const serializedEncryptedHeaders = job.headers; try { - decryptedHeaders = await crypto.decrypt(serializedEncryptedHeaders); + if (typeof serializedEncryptedHeaders !== 'string') { + throw new Error( + i18n.translate( + 'xpack.reporting.exportTypes.csv_from_savedobject.executeJob.missingJobHeadersErrorMessage', + { + defaultMessage: 'Job headers are missing', + } + ) + ); + } + decryptedHeaders = (await crypto.decrypt(serializedEncryptedHeaders)) as Record< + string, + unknown + >; } catch (err) { jobLogger.error(err); throw new Error( diff --git a/x-pack/legacy/plugins/reporting/server/lib/crypto.ts b/x-pack/legacy/plugins/reporting/server/lib/crypto.ts index 97876529ecfa7..0394c8ed1fbad 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/crypto.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/crypto.ts @@ -6,6 +6,10 @@ import nodeCrypto from '@elastic/node-crypto'; -export function cryptoFactory(encryptionKey: string | undefined) { +export function cryptoFactory(encryptionKey?: string) { + if (typeof encryptionKey !== 'string') { + throw new Error('Encryption Key required.'); + } + return nodeCrypto({ encryptionKey }); } diff --git a/x-pack/legacy/plugins/reporting/types.d.ts b/x-pack/legacy/plugins/reporting/types.d.ts index 09d53278941c9..7334a859005e0 100644 --- a/x-pack/legacy/plugins/reporting/types.d.ts +++ b/x-pack/legacy/plugins/reporting/types.d.ts @@ -116,10 +116,6 @@ export interface ConditionalHeadersConditions { basePath: string; } -export interface CryptoFactory { - decrypt: (headers?: string) => any; -} - export interface IndexPatternSavedObject { attributes: { fieldFormatMap: string; diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js index 98af94437fa5a..ebc03e40cc2b9 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js @@ -11,7 +11,7 @@ import { WEEK, MONTH, YEAR, -} from '../../../../../../src/plugins/es_ui_shared/public/components/cron_editor'; +} from '../../../../../../src/plugins/es_ui_shared/public'; import { indexPatterns } from '../../../../../../src/plugins/data/public'; import { setHttp } from '../../public/crud_app/services'; import { mockHttpRequest, pageHelpers } from './helpers'; diff --git a/x-pack/legacy/plugins/rollup/kibana.json b/x-pack/legacy/plugins/rollup/kibana.json index 3781d59d8c0f3..3df8bd7c187d5 100644 --- a/x-pack/legacy/plugins/rollup/kibana.json +++ b/x-pack/legacy/plugins/rollup/kibana.json @@ -4,7 +4,8 @@ "requiredPlugins": [ "home", "index_management", - "metrics" + "metrics", + "indexPatternManagement" ], "optionalPlugins": [ "usageCollection" diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js index c3996fe3231b1..5462a46bf59b9 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js @@ -24,8 +24,7 @@ import { EuiTitle, } from '@elastic/eui'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { CronEditor } from '../../../../../../../../../src/plugins/es_ui_shared/public/components/cron_editor'; +import { CronEditor } from '../../../../../../../../../src/plugins/es_ui_shared/public'; import { indexPatterns } from '../../../../../../../../../src/plugins/data/public'; import { indices } from '../../../../shared_imports'; diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js index db77844dcfe35..e1efcfdd24627 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps_config/index.js @@ -8,8 +8,7 @@ import cloneDeep from 'lodash/lang/cloneDeep'; import get from 'lodash/object/get'; import pick from 'lodash/object/pick'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { WEEK } from '../../../../../../../../../src/plugins/es_ui_shared/public/components/cron_editor'; +import { WEEK } from '../../../../../../../../../src/plugins/es_ui_shared/public'; import { validateId } from './validate_id'; import { validateIndexPattern } from './validate_index_pattern'; diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js index f0eb21a219442..f4de2a3098127 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/rollup_index_pattern_creation_config.js @@ -8,7 +8,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { RollupPrompt } from './components/rollup_prompt'; -import { IndexPatternCreationConfig } from '../../../../../../src/legacy/core_plugins/management/public'; +import { IndexPatternCreationConfig } from '../../../../../../src/plugins/index_pattern_management/public'; const rollupIndexPatternTypeName = i18n.translate( 'xpack.rollupJobs.editRollupIndexPattern.createIndex.defaultTypeName', diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js b/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js index fbf2612b83aa8..809a76d1868b2 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_list/rollup_index_pattern_list_config.js @@ -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 { IndexPatternListConfig } from '../../../../../../src/legacy/core_plugins/management/public'; +import { IndexPatternListConfig } from '../../../../../../src/plugins/index_pattern_management/public'; function isRollup(indexPattern) { return ( diff --git a/x-pack/legacy/plugins/rollup/public/legacy.ts b/x-pack/legacy/plugins/rollup/public/legacy.ts index ec530e63408f4..83945110c2c76 100644 --- a/x-pack/legacy/plugins/rollup/public/legacy.ts +++ b/x-pack/legacy/plugins/rollup/public/legacy.ts @@ -6,14 +6,8 @@ import { npSetup, npStart } from 'ui/new_platform'; import { RollupPlugin } from './plugin'; -import { setup as management } from '../../../../../src/legacy/core_plugins/management/public/legacy'; const plugin = new RollupPlugin(); -export const setup = plugin.setup(npSetup.core, { - ...npSetup.plugins, - __LEGACY: { - managementLegacy: management, - }, -}); +export const setup = plugin.setup(npSetup.core, npSetup.plugins); export const start = plugin.start(npStart.core, npStart.plugins); diff --git a/x-pack/legacy/plugins/rollup/public/plugin.ts b/x-pack/legacy/plugins/rollup/public/plugin.ts index c58975419e20f..5782e88c3448b 100644 --- a/x-pack/legacy/plugins/rollup/public/plugin.ts +++ b/x-pack/legacy/plugins/rollup/public/plugin.ts @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; import { PluginsStart } from './legacy_imports'; -import { ManagementSetup as ManagementSetupLegacy } from '../../../../../src/legacy/core_plugins/management/public/np_ready'; import { rollupBadgeExtension, rollupToggleExtension } from './extend_index_management'; // @ts-ignore import { RollupIndexPatternCreationConfig } from './index_pattern_creation/rollup_index_pattern_creation_config'; @@ -26,6 +25,7 @@ import { import { CRUD_APP_BASE_PATH } from './crud_app/constants'; import { ManagementSetup } from '../../../../../src/plugins/management/public'; import { IndexMgmtSetup } from '../../../../plugins/index_management/public'; +import { IndexPatternManagementSetup } from '../../../../../src/plugins/index_pattern_management/public'; import { search } from '../../../../../src/plugins/data/public'; // @ts-ignore import { setEsBaseAndXPackBase, setHttp } from './crud_app/services'; @@ -33,23 +33,16 @@ import { setNotifications, setFatalErrors } from './kibana_services'; import { renderApp } from './application'; export interface RollupPluginSetupDependencies { - __LEGACY: { - managementLegacy: ManagementSetupLegacy; - }; home?: HomePublicPluginSetup; management: ManagementSetup; indexManagement?: IndexMgmtSetup; + indexPatternManagement: IndexPatternManagementSetup; } export class RollupPlugin implements Plugin { setup( core: CoreSetup, - { - __LEGACY: { managementLegacy }, - home, - management, - indexManagement, - }: RollupPluginSetupDependencies + { home, management, indexManagement, indexPatternManagement }: RollupPluginSetupDependencies ) { setFatalErrors(core.fatalErrors); @@ -61,8 +54,8 @@ export class RollupPlugin implements Plugin { const isRollupIndexPatternsEnabled = core.uiSettings.get(CONFIG_ROLLUPS); if (isRollupIndexPatternsEnabled) { - managementLegacy.indexPattern.creation.add(RollupIndexPatternCreationConfig); - managementLegacy.indexPattern.list.add(RollupIndexPatternListConfig); + indexPatternManagement.creation.addCreationConfig(RollupIndexPatternCreationConfig); + indexPatternManagement.list.addListConfig(RollupIndexPatternListConfig); } if (home) { diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index 662fb8fb8ef68..22f1b3beffa35 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -65,8 +65,6 @@ export const INTERNAL_IDENTIFIER = '__internal'; export const INTERNAL_RULE_ID_KEY = `${INTERNAL_IDENTIFIER}_rule_id`; export const INTERNAL_RULE_ALERT_ID_KEY = `${INTERNAL_IDENTIFIER}_rule_alert_id`; export const INTERNAL_IMMUTABLE_KEY = `${INTERNAL_IDENTIFIER}_immutable`; -export const INTERNAL_NOTIFICATION_ID_KEY = `${INTERNAL_IDENTIFIER}_notification_id`; -export const INTERNAL_NOTIFICATION_RULE_ID_KEY = `${INTERNAL_IDENTIFIER}_notification_rule_id`; /** * Detection engine routes diff --git a/x-pack/legacy/plugins/siem/cypress/integration/detections_timeline.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/detections_timeline.spec.ts new file mode 100644 index 0000000000000..2cac6e0f603b9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/integration/detections_timeline.spec.ts @@ -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 { SIGNAL_ID } from '../screens/detections'; +import { PROVIDER_BADGE } from '../screens/timeline'; + +import { + expandFirstSignal, + investigateFirstSignalInTimeline, + waitForSignalsPanelToBeLoaded, +} from '../tasks/detections'; +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { loginAndWaitForPage } from '../tasks/login'; + +import { DETECTIONS } from '../urls/navigation'; + +describe('Detections timeline', () => { + beforeEach(() => { + esArchiverLoad('timeline_signals'); + loginAndWaitForPage(DETECTIONS); + }); + + afterEach(() => { + esArchiverUnload('timeline_signals'); + }); + + it('Investigate signal in default timeline', () => { + waitForSignalsPanelToBeLoaded(); + expandFirstSignal(); + cy.get(SIGNAL_ID) + .first() + .invoke('text') + .then(eventId => { + investigateFirstSignalInTimeline(); + cy.get(PROVIDER_BADGE) + .invoke('text') + .should('eql', `_id: "${eventId}"`); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules.spec.ts new file mode 100644 index 0000000000000..2d2db9e70255b --- /dev/null +++ b/x-pack/legacy/plugins/siem/cypress/integration/signal_detection_rules.spec.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 { + FIFTH_RULE, + FIRST_RULE, + RULE_NAME, + SECOND_RULE, + SEVENTH_RULE, +} from '../screens/signal_detection_rules'; + +import { goToManageSignalDetectionRules } from '../tasks/detections'; +import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver'; +import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; +import { + activateRule, + sortByActivatedRules, + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded, + waitForRuleToBeActivated, +} from '../tasks/signal_detection_rules'; + +import { DETECTIONS } from '../urls/navigation'; + +describe('Signal detection rules', () => { + before(() => { + esArchiverLoad('prebuilt_rules_loaded'); + }); + + after(() => { + esArchiverUnload('prebuilt_rules_loaded'); + }); + + it.skip('Sorts by activated rules', () => { + loginAndWaitForPageWithoutDateRange(DETECTIONS); + goToManageSignalDetectionRules(); + waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded(); + cy.get(RULE_NAME) + .eq(FIFTH_RULE) + .invoke('text') + .then(fifthRuleName => { + activateRule(FIFTH_RULE); + waitForRuleToBeActivated(); + cy.get(RULE_NAME) + .eq(SEVENTH_RULE) + .invoke('text') + .then(seventhRuleName => { + activateRule(SEVENTH_RULE); + waitForRuleToBeActivated(); + sortByActivatedRules(); + + cy.get(RULE_NAME) + .eq(FIRST_RULE) + .should('have.text', fifthRuleName); + cy.get(RULE_NAME) + .eq(SECOND_RULE) + .should('have.text', seventhRuleName); + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.ts b/x-pack/legacy/plugins/siem/cypress/objects/timeline.ts similarity index 58% rename from x-pack/legacy/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.ts rename to x-pack/legacy/plugins/siem/cypress/objects/timeline.ts index 24011c51ddbaa..bca99bfa9266a 100644 --- a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.ts +++ b/x-pack/legacy/plugins/siem/cypress/objects/timeline.ts @@ -3,10 +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 { ESTooltipProperty } from './es_tooltip_property'; -export class ESAggTooltipProperty extends ESTooltipProperty { - isFilterable(): boolean { - return false; - } +interface Timeline { + title: string; + query: string; } diff --git a/x-pack/legacy/plugins/siem/cypress/screens/detections.ts b/x-pack/legacy/plugins/siem/cypress/screens/detections.ts index cb776be8d7b6b..d9ffa5b5a4ab2 100644 --- a/x-pack/legacy/plugins/siem/cypress/screens/detections.ts +++ b/x-pack/legacy/plugins/siem/cypress/screens/detections.ts @@ -6,6 +6,8 @@ export const CLOSED_SIGNALS_BTN = '[data-test-subj="closedSignals"]'; +export const EXPAND_SIGNAL_BTN = '[data-test-subj="expand-event"]'; + export const LOADING_SIGNALS_PANEL = '[data-test-subj="loading-signals-panel"]'; export const MANAGE_SIGNAL_DETECTION_RULES_BTN = '[data-test-subj="manage-signal-detection-rules"]'; @@ -20,8 +22,12 @@ export const OPENED_SIGNALS_BTN = '[data-test-subj="openSignals"]'; export const SELECTED_SIGNALS = '[data-test-subj="selectedSignals"]'; +export const SEND_SIGNAL_TO_TIMELINE_BTN = '[data-test-subj="send-signal-to-timeline-button"]'; + export const SHOWING_SIGNALS = '[data-test-subj="showingSignals"]'; export const SIGNALS = '[data-test-subj="event"]'; +export const SIGNAL_ID = '[data-test-subj="draggable-content-_id"]'; + export const SIGNAL_CHECKBOX = '[data-test-subj="select-event-container"] .euiCheckbox__input'; diff --git a/x-pack/legacy/plugins/siem/cypress/screens/signal_detection_rules.ts b/x-pack/legacy/plugins/siem/cypress/screens/signal_detection_rules.ts index 09fbc2132302c..f74f5c26ddc2e 100644 --- a/x-pack/legacy/plugins/siem/cypress/screens/signal_detection_rules.ts +++ b/x-pack/legacy/plugins/siem/cypress/screens/signal_detection_rules.ts @@ -18,6 +18,10 @@ export const DELETE_RULE_BULK_BTN = '[data-test-subj="deleteRuleBulk"]'; export const ELASTIC_RULES_BTN = '[data-test-subj="show-elastic-rules-filter-button"]'; +export const FIFTH_RULE = 4; + +export const FIRST_RULE = 0; + export const LOAD_PREBUILT_RULES_BTN = '[data-test-subj="load-prebuilt-rules"]'; export const LOADING_INITIAL_PREBUILT_RULES_TABLE = @@ -31,18 +35,26 @@ export const RISK_SCORE = '[data-test-subj="riskScore"]'; export const RELOAD_PREBUILT_RULES_BTN = '[data-test-subj="reloadPrebuiltRulesBtn"]'; +export const SECOND_RULE = 1; + export const RULE_CHECKBOX = '.euiTableRow .euiCheckbox__input'; export const RULE_NAME = '[data-test-subj="ruleName"]'; export const RULE_SWITCH = '[data-test-subj="rule-switch"]'; +export const RULE_SWITCH_LOADER = '[data-test-subj="rule-switch-loader"]'; + export const RULES_TABLE = '[data-test-subj="rules-table"]'; export const RULES_ROW = '.euiTableRow'; +export const SEVENTH_RULE = 6; + export const SEVERITY = '[data-test-subj="severity"]'; export const SHOWING_RULES_TEXT = '[data-test-subj="showingRules"]'; +export const SORT_RULES_BTN = '[data-test-subj="tableHeaderSortButton"]'; + export const THREE_HUNDRED_ROWS = '[data-test-subj="tablePagination-300-rows"]'; diff --git a/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts b/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts index fbce585a70f86..53d8273d9ce6b 100644 --- a/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts +++ b/x-pack/legacy/plugins/siem/cypress/screens/timeline.ts @@ -14,6 +14,8 @@ export const ID_FIELD = '[data-test-subj="timeline"] [data-test-subj="field-name export const ID_TOGGLE_FIELD = '[data-test-subj="toggle-field-_id"]'; +export const PROVIDER_BADGE = '[data-test-subj="providerBadge"]'; + export const SEARCH_OR_FILTER_CONTAINER = '[data-test-subj="timeline-search-or-filter-search-container"]'; diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts b/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts index abea4a887b8ba..c30a178eab489 100644 --- a/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts +++ b/x-pack/legacy/plugins/siem/cypress/tasks/detections.ts @@ -6,11 +6,13 @@ import { CLOSED_SIGNALS_BTN, + EXPAND_SIGNAL_BTN, LOADING_SIGNALS_PANEL, MANAGE_SIGNAL_DETECTION_RULES_BTN, OPEN_CLOSE_SIGNAL_BTN, OPEN_CLOSE_SIGNALS_BTN, OPENED_SIGNALS_BTN, + SEND_SIGNAL_TO_TIMELINE_BTN, SIGNALS, SIGNAL_CHECKBOX, } from '../screens/detections'; @@ -26,6 +28,12 @@ export const closeSignals = () => { cy.get(OPEN_CLOSE_SIGNALS_BTN).click({ force: true }); }; +export const expandFirstSignal = () => { + cy.get(EXPAND_SIGNAL_BTN) + .first() + .click({ force: true }); +}; + export const goToClosedSignals = () => { cy.get(CLOSED_SIGNALS_BTN).click({ force: true }); }; @@ -58,6 +66,12 @@ export const selectNumberOfSignals = (numberOfSignals: number) => { } }; +export const investigateFirstSignalInTimeline = () => { + cy.get(SEND_SIGNAL_TO_TIMELINE_BTN) + .first() + .click({ force: true }); +}; + export const waitForSignals = () => { cy.get(REFRESH_BUTTON) .invoke('text') diff --git a/x-pack/legacy/plugins/siem/cypress/tasks/signal_detection_rules.ts b/x-pack/legacy/plugins/siem/cypress/tasks/signal_detection_rules.ts index cfc490526d84e..a404f1142cba7 100644 --- a/x-pack/legacy/plugins/siem/cypress/tasks/signal_detection_rules.ts +++ b/x-pack/legacy/plugins/siem/cypress/tasks/signal_detection_rules.ts @@ -15,13 +15,22 @@ import { LOADING_INITIAL_PREBUILT_RULES_TABLE, LOADING_SPINNER, PAGINATION_POPOVER_BTN, + RELOAD_PREBUILT_RULES_BTN, RULE_CHECKBOX, RULE_NAME, + RULE_SWITCH, + RULE_SWITCH_LOADER, RULES_TABLE, + SORT_RULES_BTN, THREE_HUNDRED_ROWS, - RELOAD_PREBUILT_RULES_BTN, } from '../screens/signal_detection_rules'; +export const activateRule = (rulePosition: number) => { + cy.get(RULE_SWITCH) + .eq(rulePosition) + .click({ force: true }); +}; + export const changeToThreeHundredRowsPerPage = () => { cy.get(PAGINATION_POPOVER_BTN).click({ force: true }); cy.get(THREE_HUNDRED_ROWS).click(); @@ -71,6 +80,13 @@ export const selectNumberOfRules = (numberOfRules: number) => { } }; +export const sortByActivatedRules = () => { + cy.get(SORT_RULES_BTN).click({ force: true }); + waitForRulesToBeLoaded(); + cy.get(SORT_RULES_BTN).click({ force: true }); + waitForRulesToBeLoaded(); +}; + export const waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded = () => { cy.get(LOADING_INITIAL_PREBUILT_RULES_TABLE).should('exist'); cy.get(LOADING_INITIAL_PREBUILT_RULES_TABLE).should('not.exist'); @@ -81,6 +97,11 @@ export const waitForPrebuiltDetectionRulesToBeLoaded = () => { cy.get(RULES_TABLE).should('exist'); }; +export const waitForRuleToBeActivated = () => { + cy.get(RULE_SWITCH_LOADER).should('exist'); + cy.get(RULE_SWITCH_LOADER).should('not.exist'); +}; + export const waitForRulesToBeLoaded = () => { cy.get(LOADING_SPINNER).should('exist'); cy.get(LOADING_SPINNER).should('not.exist'); diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx index 098de39bbfef5..56fa0d56f3c3a 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx @@ -10,6 +10,16 @@ import React from 'react'; import '../../mock/match_media'; import { HeaderGlobal } from './index'; +jest.mock('react-router-dom', () => ({ + useLocation: () => ({ + pathname: '/app/siem#/hosts/allHosts', + hash: '', + search: '', + state: '', + }), + withRouter: () => jest.fn(), +})); + jest.mock('ui/new_platform'); // Test will fail because we will to need to mock some core services to make the test work @@ -19,6 +29,10 @@ jest.mock('../search_bar', () => ({ })); describe('HeaderGlobal', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + test('it renders', () => { const wrapper = shallow(); diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx index a12fab8a4f5d9..adc2be4f9c365 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.tsx @@ -9,6 +9,7 @@ import { pickBy } from 'lodash/fp'; import React from 'react'; import styled, { css } from 'styled-components'; +import { useLocation } from 'react-router-dom'; import { gutterTimeline } from '../../lib/helpers'; import { navTabs } from '../../pages/home/home_navigations'; import { SiemPageName } from '../../pages/home/types'; @@ -36,63 +37,68 @@ FlexItem.displayName = 'FlexItem'; interface HeaderGlobalProps { hideDetectionEngine?: boolean; } -export const HeaderGlobal = React.memo(({ hideDetectionEngine = false }) => ( - - - - {({ indicesExist }) => ( - <> - - - - - - - +export const HeaderGlobal = React.memo(({ hideDetectionEngine = false }) => { + const currentLocation = useLocation(); - - {indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - key !== SiemPageName.detections, navTabs) - : navTabs - } - /> - ) : ( - key === SiemPageName.overview, navTabs)} - /> - )} - - - - - - - {indicesExistOrDataTemporarilyUnavailable(indicesExist) && ( + return ( + + + + {({ indicesExist }) => ( + <> + + - + + + + + + + {indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + key !== SiemPageName.detections, navTabs) + : navTabs + } + /> + ) : ( + key === SiemPageName.overview, navTabs)} + /> + )} - )} + + - - - {i18n.BUTTON_ADD_DATA} - - - - - - )} - - - -)); + + + {indicesExistOrDataTemporarilyUnavailable(indicesExist) && + currentLocation.pathname.includes(`/${SiemPageName.detections}/`) && ( + + + + )} + + + + {i18n.BUTTON_ADD_DATA} + + + + + + )} + + + + ); +}); HeaderGlobal.displayName = 'HeaderGlobal'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/__snapshots__/popover_description.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/ml_popover/__snapshots__/popover_description.test.tsx.snap index 09e95c5ff59ea..46e61f9e939ee 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/__snapshots__/popover_description.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/__snapshots__/popover_description.test.tsx.snap @@ -5,7 +5,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` size="s" > { iconSide="right" onClick={() => setIsPopoverOpen(!isPopoverOpen)} > - {i18n.ANOMALY_DETECTION} + {i18n.ML_JOB_SETTINGS} } isOpen={isPopoverOpen} @@ -142,14 +142,14 @@ export const MlPopover = React.memo(() => { dispatch({ type: 'refresh' }); }} > - {i18n.ANOMALY_DETECTION} + {i18n.ML_JOB_SETTINGS} } isOpen={isPopoverOpen} closePopover={() => setIsPopoverOpen(!isPopoverOpen)} > - {i18n.ANOMALY_DETECTION_TITLE} + {i18n.ML_JOB_SETTINGS} diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx index 20e8dd2492fef..a491d4b6b769c 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx @@ -14,7 +14,7 @@ export const PopoverDescriptionComponent = () => ( diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts b/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts index 442068dd0e193..613691e55dcfd 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/translations.ts @@ -6,17 +6,10 @@ import { i18n } from '@kbn/i18n'; -export const ANOMALY_DETECTION = i18n.translate( - 'xpack.siem.components.mlPopup.anomalyDetectionButtonLabel', +export const ML_JOB_SETTINGS = i18n.translate( + 'xpack.siem.components.mlPopup.mlJobSettingsButtonLabel', { - defaultMessage: 'Anomaly detection', - } -); - -export const ANOMALY_DETECTION_TITLE = i18n.translate( - 'xpack.siem.components.mlPopup.anomalyDetectionTitle', - { - defaultMessage: 'Anomaly detection settings', + defaultMessage: 'ML job settings', } ); diff --git a/x-pack/legacy/plugins/siem/public/components/toasters/utils.ts b/x-pack/legacy/plugins/siem/public/components/toasters/utils.ts index e624144c9826f..e7cc389d4c06b 100644 --- a/x-pack/legacy/plugins/siem/public/components/toasters/utils.ts +++ b/x-pack/legacy/plugins/siem/public/components/toasters/utils.ts @@ -37,6 +37,30 @@ export const displayErrorToast = ( }); }; +/** + * Displays a warning toast for the provided title and message + * + * @param title warning message to display in toaster and modal + * @param dispatchToaster provided by useStateToaster() + * @param id unique ID if necessary + */ +export const displayWarningToast = ( + title: string, + dispatchToaster: React.Dispatch, + id: string = uuid.v4() +): void => { + const toast: AppToast = { + id, + title, + color: 'warning', + iconType: 'help', + }; + dispatchToaster({ + type: 'addToaster', + toast, + }); +}; + /** * Displays a success toast for the provided title and message * diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts b/x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts index ed47cdc62a1b6..c24081c777a96 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/api.ts @@ -6,7 +6,7 @@ import { isEmpty } from 'lodash/fp'; import { - CasesConnectorsFindResult, + Connector, CasesConfigurePatch, CasesConfigureResponse, CasesConfigureRequest, @@ -18,7 +18,7 @@ import { ApiProps } from '../types'; import { convertToCamelCase, decodeCaseConfigureResponse } from '../utils'; import { CaseConfigure } from './types'; -export const fetchConnectors = async ({ signal }: ApiProps): Promise => { +export const fetchConnectors = async ({ signal }: ApiProps): Promise => { const response = await KibanaServices.get().http.fetch( `${CASES_CONFIGURE_URL}/connectors/_find`, { diff --git a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.tsx b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.tsx index d31dcdbee2a14..30108ecf33874 100644 --- a/x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/case/configure/use_connectors.tsx @@ -31,7 +31,7 @@ export const useConnectors = (): ReturnConnectors => { const res = await fetchConnectors({ signal: abortCtrl.signal }); if (!didCancel) { setLoading(false); - setConnectors(res.data); + setConnectors(res); } } catch (error) { if (!didCancel) { diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts index bc559c5ac4972..f89d21ef1aeb1 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts @@ -70,7 +70,7 @@ const MetaRule = t.intersection([ }), t.partial({ throttle: t.string, - kibanaSiemAppUrl: t.string, + kibana_siem_app_url: t.string, }), ]); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx new file mode 100644 index 0000000000000..135f0f2a7e26d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/__mock__/index.tsx @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + Connector, + CasesConfigurationMapping, +} from '../../../../../containers/case/configure/types'; +import { State } from '../reducer'; +import { ReturnConnectors } from '../../../../../containers/case/configure/use_connectors'; +import { ReturnUseCaseConfigure } from '../../../../../containers/case/configure/use_configure'; +import { createUseKibanaMock } from '../../../../../mock/kibana_react'; + +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { actionTypeRegistryMock } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/action_type_registry.mock'; + +export const connectors: Connector[] = [ + { + id: '123', + actionTypeId: '.servicenow', + name: 'My Connector', + isPreconfigured: false, + config: { + apiUrl: 'https://instance1.service-now.com', + casesConfiguration: { + mapping: [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'description', + target: 'description', + actionType: 'append', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, + ], + }, + }, + }, + { + id: '456', + actionTypeId: '.servicenow', + name: 'My Connector 2', + isPreconfigured: false, + config: { + apiUrl: 'https://instance2.service-now.com', + casesConfiguration: { + mapping: [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'description', + target: 'description', + actionType: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, + ], + }, + }, + }, +]; + +export const mapping: CasesConfigurationMapping[] = [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'description', + target: 'description', + actionType: 'append', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, +]; + +export const searchURL = + '?timerange=(global:(linkTo:!(),timerange:(from:1585487656371,fromStr:now-24h,kind:relative,to:1585574056371,toStr:now)),timeline:(linkTo:!(),timerange:(from:1585227005527,kind:absolute,to:1585313405527)))'; + +export const initialState: State = { + connectorId: 'none', + closureType: 'close-by-user', + mapping: null, + currentConfiguration: { connectorId: 'none', closureType: 'close-by-user' }, +}; + +export const useCaseConfigureResponse: ReturnUseCaseConfigure = { + loading: false, + persistLoading: false, + refetchCaseConfigure: jest.fn(), + persistCaseConfigure: jest.fn(), +}; + +export const useConnectorsResponse: ReturnConnectors = { + loading: false, + connectors, + refetchConnectors: jest.fn(), +}; + +export const kibanaMockImplementationArgs = { + services: { + ...createUseKibanaMock()().services, + triggers_actions_ui: { actionTypeRegistry: actionTypeRegistryMock.create() }, + }, +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.test.tsx new file mode 100644 index 0000000000000..cf52fef94ed17 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.test.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 React from 'react'; +import { ReactWrapper, mount } from 'enzyme'; +import { EuiText } from '@elastic/eui'; + +import { ConfigureCaseButton, ConfigureCaseButtonProps } from './button'; +import { TestProviders } from '../../../../mock'; +import { searchURL } from './__mock__'; + +describe('Configuration button', () => { + let wrapper: ReactWrapper; + const props: ConfigureCaseButtonProps = { + isDisabled: false, + label: 'My label', + msgTooltip: <>, + showToolTip: false, + titleTooltip: '', + urlSearch: searchURL, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders without the tooltip', () => { + expect( + wrapper + .find('[data-test-subj="configure-case-button"]') + .first() + .exists() + ).toBe(true); + + expect( + wrapper + .find('[data-test-subj="configure-case-tooltip"]') + .first() + .exists() + ).toBe(false); + }); + + test('it pass the correct props to the button', () => { + expect( + wrapper + .find('[data-test-subj="configure-case-button"]') + .first() + .props() + ).toMatchObject({ + href: `#/link-to/case/configure${searchURL}`, + iconType: 'controlsHorizontal', + isDisabled: false, + 'aria-label': 'My label', + children: 'My label', + }); + }); + + test('it renders the tooltip', () => { + const msgTooltip = {'My message tooltip'}; + + const newWrapper = mount( + , + { + wrappingComponent: TestProviders, + } + ); + + expect( + newWrapper + .find('[data-test-subj="configure-case-tooltip"]') + .first() + .exists() + ).toBe(true); + + expect( + wrapper + .find('[data-test-subj="configure-case-button"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the tooltip when hovering the button', () => { + const msgTooltip = 'My message tooltip'; + const titleTooltip = 'My title'; + + const newWrapper = mount( + {msgTooltip}} + />, + { + wrappingComponent: TestProviders, + } + ); + + newWrapper + .find('[data-test-subj="configure-case-button"]') + .first() + .simulate('mouseOver'); + + expect(newWrapper.find('.euiToolTipPopover').text()).toBe(`${titleTooltip}${msgTooltip}`); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx index b0bea83148bda..844ffea28415f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/button.tsx @@ -8,7 +8,7 @@ import { EuiButton, EuiToolTip } from '@elastic/eui'; import React, { memo, useMemo } from 'react'; import { getConfigureCasesUrl } from '../../../../components/link_to'; -interface ConfigureCaseButtonProps { +export interface ConfigureCaseButtonProps { label: string; isDisabled: boolean; msgTooltip: JSX.Element; @@ -32,6 +32,7 @@ const ConfigureCaseButtonComponent: React.FC = ({ iconType="controlsHorizontal" isDisabled={isDisabled} aria-label={label} + data-test-subj="configure-case-button" > {label} @@ -39,7 +40,12 @@ const ConfigureCaseButtonComponent: React.FC = ({ [label, isDisabled, urlSearch] ); return showToolTip ? ( - {msgTooltip}

}> + {msgTooltip}

} + data-test-subj="configure-case-tooltip" + > {configureCaseButton}
) : ( diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx new file mode 100644 index 0000000000000..209dce9aedffc --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.test.tsx @@ -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 React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { ClosureOptions, ClosureOptionsProps } from './closure_options'; +import { TestProviders } from '../../../../mock'; +import { ClosureOptionsRadio } from './closure_options_radio'; + +describe('ClosureOptions', () => { + let wrapper: ReactWrapper; + const onChangeClosureType = jest.fn(); + const props: ClosureOptionsProps = { + disabled: false, + closureTypeSelected: 'close-by-user', + onChangeClosureType, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it shows the closure options form group', () => { + expect( + wrapper + .find('[data-test-subj="case-closure-options-form-group"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the closure options form row', () => { + expect( + wrapper + .find('[data-test-subj="case-closure-options-form-row"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows closure options', () => { + expect( + wrapper + .find('[data-test-subj="case-closure-options-radio"]') + .first() + .exists() + ).toBe(true); + }); + + test('it pass the correct props to child', () => { + const closureOptionsRadioComponent = wrapper.find(ClosureOptionsRadio); + expect(closureOptionsRadioComponent.props().disabled).toEqual(false); + expect(closureOptionsRadioComponent.props().closureTypeSelected).toEqual('close-by-user'); + expect(closureOptionsRadioComponent.props().onChangeClosureType).toEqual(onChangeClosureType); + }); + + test('the closure type is changed successfully', () => { + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + + expect(onChangeClosureType).toHaveBeenCalled(); + expect(onChangeClosureType).toHaveBeenCalledWith('close-by-pushing'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx index 9879b9149059a..6fa97818dd0ce 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options.tsx @@ -11,7 +11,7 @@ import { ClosureType } from '../../../../containers/case/configure/types'; import { ClosureOptionsRadio } from './closure_options_radio'; import * as i18n from './translations'; -interface ClosureOptionsProps { +export interface ClosureOptionsProps { closureTypeSelected: ClosureType; disabled: boolean; onChangeClosureType: (newClosureType: ClosureType) => void; @@ -27,12 +27,18 @@ const ClosureOptionsComponent: React.FC = ({ fullWidth title={

{i18n.CASE_CLOSURE_OPTIONS_TITLE}

} description={i18n.CASE_CLOSURE_OPTIONS_DESC} + data-test-subj="case-closure-options-form-group" > - + diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.test.tsx new file mode 100644 index 0000000000000..f2ef2c2d55c28 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.test.tsx @@ -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 React from 'react'; +import { ReactWrapper, mount } from 'enzyme'; + +import { ClosureOptionsRadio, ClosureOptionsRadioComponentProps } from './closure_options_radio'; +import { TestProviders } from '../../../../mock'; + +describe('ClosureOptionsRadio', () => { + let wrapper: ReactWrapper; + const onChangeClosureType = jest.fn(); + const props: ClosureOptionsRadioComponentProps = { + disabled: false, + closureTypeSelected: 'close-by-user', + onChangeClosureType, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders', () => { + expect( + wrapper + .find('[data-test-subj="closure-options-radio-group"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the correct number of radio buttons', () => { + expect(wrapper.find('input[name="closure_options"]')).toHaveLength(2); + }); + + test('it renders close by user radio button', () => { + expect(wrapper.find('input[id="close-by-user"]').exists()).toBeTruthy(); + }); + + test('it renders close by pushing radio button', () => { + expect(wrapper.find('input[id="close-by-pushing"]').exists()).toBeTruthy(); + }); + + test('it disables the close by user radio button', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find('input[id="close-by-user"]').prop('disabled')).toEqual(true); + }); + + test('it disables correctly the close by pushing radio button', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find('input[id="close-by-pushing"]').prop('disabled')).toEqual(true); + }); + + test('it selects the correct radio button', () => { + const newWrapper = mount( + , + { + wrappingComponent: TestProviders, + } + ); + expect(newWrapper.find('input[id="close-by-pushing"]').prop('checked')).toEqual(true); + }); + + test('it calls the onChangeClosureType function', () => { + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + expect(onChangeClosureType).toHaveBeenCalled(); + expect(onChangeClosureType).toHaveBeenCalledWith('close-by-pushing'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx index f32f867b2471d..d2cdb7ecda7ba 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/closure_options_radio.tsx @@ -26,7 +26,7 @@ const radios: ClosureRadios[] = [ }, ]; -interface ClosureOptionsRadioComponentProps { +export interface ClosureOptionsRadioComponentProps { closureTypeSelected: ClosureType; disabled: boolean; onChangeClosureType: (newClosureType: ClosureType) => void; @@ -51,6 +51,7 @@ const ClosureOptionsRadioComponent: React.FC idSelected={closureTypeSelected} onChange={onChangeLocal} name="closure_options" + data-test-subj="closure-options-radio-group" /> ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx new file mode 100644 index 0000000000000..5fb52c374b482 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.test.tsx @@ -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 React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { Connectors, Props } from './connectors'; +import { TestProviders } from '../../../../mock'; +import { ConnectorsDropdown } from './connectors_dropdown'; +import { connectors } from './__mock__'; + +describe('Connectors', () => { + let wrapper: ReactWrapper; + const onChangeConnector = jest.fn(); + const handleShowAddFlyout = jest.fn(); + const props: Props = { + disabled: false, + connectors, + selectedConnector: 'none', + isLoading: false, + onChangeConnector, + handleShowAddFlyout, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it shows the connectors from group', () => { + expect( + wrapper + .find('[data-test-subj="case-connectors-form-group"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the connectors form row', () => { + expect( + wrapper + .find('[data-test-subj="case-connectors-form-row"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the connectors dropdown', () => { + expect( + wrapper + .find('[data-test-subj="case-connectors-dropdown"]') + .first() + .exists() + ).toBe(true); + }); + + test('it pass the correct props to child', () => { + const connectorsDropdownProps = wrapper.find(ConnectorsDropdown).props(); + expect(connectorsDropdownProps).toMatchObject({ + disabled: false, + isLoading: false, + connectors, + selectedConnector: 'none', + onChange: props.onChangeConnector, + }); + }); + + test('the connector is changed successfully', () => { + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click'); + + expect(onChangeConnector).toHaveBeenCalled(); + expect(onChangeConnector).toHaveBeenCalledWith('456'); + }); + + test('the connector is changed successfully to none', () => { + onChangeConnector.mockClear(); + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + newWrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + newWrapper.find('button[data-test-subj="dropdown-connector-no-connector"]').simulate('click'); + + expect(onChangeConnector).toHaveBeenCalled(); + expect(onChangeConnector).toHaveBeenCalledWith('none'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx index 8fb1cfb1aa6cc..de6d5f76cfad0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors.tsx @@ -28,7 +28,7 @@ const EuiFormRowExtended = styled(EuiFormRow)` } `; -interface Props { +export interface Props { connectors: Connector[]; disabled: boolean; isLoading: boolean; @@ -48,7 +48,11 @@ const ConnectorsComponent: React.FC = ({ {i18n.INCIDENT_MANAGEMENT_SYSTEM_LABEL} - + {i18n.ADD_NEW_CONNECTOR} @@ -61,14 +65,20 @@ const ConnectorsComponent: React.FC = ({ fullWidth title={

{i18n.INCIDENT_MANAGEMENT_SYSTEM_TITLE}

} description={i18n.INCIDENT_MANAGEMENT_SYSTEM_DESC} + data-test-subj="case-connectors-form-group" > - + diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx new file mode 100644 index 0000000000000..044108962efc7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.test.tsx @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; +import { EuiSuperSelect } from '@elastic/eui'; + +import { ConnectorsDropdown, Props } from './connectors_dropdown'; +import { TestProviders } from '../../../../mock'; +import { connectors } from './__mock__'; + +describe('ConnectorsDropdown', () => { + let wrapper: ReactWrapper; + const props: Props = { + disabled: false, + connectors, + isLoading: false, + onChange: jest.fn(), + selectedConnector: 'none', + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders', () => { + expect( + wrapper + .find('[data-test-subj="dropdown-connectors"]') + .first() + .exists() + ).toBe(true); + }); + + test('it formats the connectors correctly', () => { + const selectProps = wrapper.find(EuiSuperSelect).props(); + + expect(selectProps.options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + value: 'none', + 'data-test-subj': 'dropdown-connector-no-connector', + }), + expect.objectContaining({ value: '123', 'data-test-subj': 'dropdown-connector-123' }), + expect.objectContaining({ value: '456', 'data-test-subj': 'dropdown-connector-456' }), + ]) + ); + }); + + test('it disables the dropdown', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect( + newWrapper + .find('[data-test-subj="dropdown-connectors"]') + .first() + .prop('disabled') + ).toEqual(true); + }); + + test('it loading correctly', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect( + newWrapper + .find('[data-test-subj="dropdown-connectors"]') + .first() + .prop('isLoading') + ).toEqual(true); + }); + + test('it selects the correct connector', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find('button span').text()).toEqual('My Connector'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx index a0a0ad6cd3e7f..15066e73eee82 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/connectors_dropdown.tsx @@ -12,7 +12,7 @@ import { Connector } from '../../../../containers/case/configure/types'; import { connectors as connectorsDefinition } from '../../../../lib/connectors/config'; import * as i18n from './translations'; -interface Props { +export interface Props { connectors: Connector[]; disabled: boolean; isLoading: boolean; @@ -34,7 +34,7 @@ const noConnectorOption = { {i18n.NO_CONNECTOR} ), - 'data-test-subj': 'no-connector', + 'data-test-subj': 'dropdown-connector-no-connector', }; const ConnectorsDropdownComponent: React.FC = ({ @@ -60,7 +60,7 @@ const ConnectorsDropdownComponent: React.FC = ({ {connector.name} ), - 'data-test-subj': connector.id, + 'data-test-subj': `dropdown-connector-${connector.id}`, }, ], [noConnectorOption] @@ -76,6 +76,7 @@ const ConnectorsDropdownComponent: React.FC = ({ valueOfSelected={selectedConnector} fullWidth onChange={onChange} + data-test-subj="dropdown-connectors" /> ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx new file mode 100644 index 0000000000000..9ab752bb589c0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.test.tsx @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { FieldMapping, FieldMappingProps } from './field_mapping'; +import { mapping } from './__mock__'; +import { FieldMappingRow } from './field_mapping_row'; +import { defaultMapping } from '../../../../lib/connectors/config'; +import { TestProviders } from '../../../../mock'; + +describe('FieldMappingRow', () => { + let wrapper: ReactWrapper; + const onChangeMapping = jest.fn(); + const props: FieldMappingProps = { + disabled: false, + mapping, + onChangeMapping, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders', () => { + expect( + wrapper + .find('[data-test-subj="case-configure-field-mapping-cols"]') + .first() + .exists() + ).toBe(true); + + expect( + wrapper + .find('[data-test-subj="case-configure-field-mapping-row-wrapper"]') + .first() + .exists() + ).toBe(true); + + expect(wrapper.find(FieldMappingRow).length).toEqual(3); + }); + + test('it shows the correct number of FieldMappingRow with default mapping', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find(FieldMappingRow).length).toEqual(3); + }); + + test('it pass the corrects props to mapping row', () => { + const rows = wrapper.find(FieldMappingRow); + rows.forEach((row, index) => { + expect(row.prop('siemField')).toEqual(mapping[index].source); + expect(row.prop('selectedActionType')).toEqual(mapping[index].actionType); + expect(row.prop('selectedThirdParty')).toEqual(mapping[index].target); + }); + }); + + test('it pass the default mapping when mapping is null', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + const rows = newWrapper.find(FieldMappingRow); + rows.forEach((row, index) => { + expect(row.prop('siemField')).toEqual(defaultMapping[index].source); + expect(row.prop('selectedActionType')).toEqual(defaultMapping[index].actionType); + expect(row.prop('selectedThirdParty')).toEqual(defaultMapping[index].target); + }); + }); + + test('it should show zero rows on empty array', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find(FieldMappingRow).length).toEqual(0); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx index 0c0dc14f1c218..2934b1056e29c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping.tsx @@ -18,6 +18,7 @@ import { FieldMappingRow } from './field_mapping_row'; import * as i18n from './translations'; import { defaultMapping } from '../../../../lib/connectors/config'; +import { setActionTypeToMapping, setThirdPartyToMapping } from './utils'; const FieldRowWrapper = styled.div` margin-top: 8px; @@ -28,22 +29,26 @@ const supportedThirdPartyFields: Array> = { value: 'not_mapped', inputDisplay: {i18n.FIELD_MAPPING_FIELD_NOT_MAPPED}, + 'data-test-subj': 'third-party-field-not-mapped', }, { value: 'short_description', inputDisplay: {i18n.FIELD_MAPPING_FIELD_SHORT_DESC}, + 'data-test-subj': 'third-party-field-short-description', }, { value: 'comments', inputDisplay: {i18n.FIELD_MAPPING_FIELD_COMMENTS}, + 'data-test-subj': 'third-party-field-comments', }, { value: 'description', inputDisplay: {i18n.FIELD_MAPPING_FIELD_DESC}, + 'data-test-subj': 'third-party-field-description', }, ]; -interface FieldMappingProps { +export interface FieldMappingProps { disabled: boolean; mapping: CasesConfigurationMapping[] | null; onChangeMapping: (newMapping: CasesConfigurationMapping[]) => void; @@ -57,14 +62,7 @@ const FieldMappingComponent: React.FC = ({ const onChangeActionType = useCallback( (caseField: CaseField, newActionType: ActionType) => { const myMapping = mapping ?? defaultMapping; - const findItemIndex = myMapping.findIndex(item => item.source === caseField); - if (findItemIndex >= 0) { - onChangeMapping([ - ...myMapping.slice(0, findItemIndex), - { ...myMapping[findItemIndex], actionType: newActionType }, - ...myMapping.slice(findItemIndex + 1), - ]); - } + onChangeMapping(setActionTypeToMapping(caseField, newActionType, myMapping)); }, [mapping] ); @@ -72,22 +70,13 @@ const FieldMappingComponent: React.FC = ({ const onChangeThirdParty = useCallback( (caseField: CaseField, newThirdPartyField: ThirdPartyField) => { const myMapping = mapping ?? defaultMapping; - onChangeMapping( - myMapping.map(item => { - if (item.source !== caseField && item.target === newThirdPartyField) { - return { ...item, target: 'not_mapped' }; - } else if (item.source === caseField) { - return { ...item, target: newThirdPartyField }; - } - return item; - }) - ); + onChangeMapping(setThirdPartyToMapping(caseField, newThirdPartyField, myMapping)); }, [mapping] ); return ( <> - + {i18n.FIELD_MAPPING_FIRST_COL} @@ -100,7 +89,7 @@ const FieldMappingComponent: React.FC = ({ - + {(mapping ?? defaultMapping).map(item => ( > = [ + { + value: 'short_description', + inputDisplay: {'Short Description'}, + 'data-test-subj': 'third-party-short-desc', + }, + { + value: 'description', + inputDisplay: {'Description'}, + 'data-test-subj': 'third-party-desc', + }, +]; + +describe('FieldMappingRow', () => { + let wrapper: ReactWrapper; + const onChangeActionType = jest.fn(); + const onChangeThirdParty = jest.fn(); + + const props: RowProps = { + disabled: false, + siemField: 'title', + thirdPartyOptions, + onChangeActionType, + onChangeThirdParty, + selectedActionType: 'nothing', + selectedThirdParty: 'short_description', + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders', () => { + expect( + wrapper + .find('[data-test-subj="case-configure-third-party-select"]') + .first() + .exists() + ).toBe(true); + + expect( + wrapper + .find('[data-test-subj="case-configure-action-type-select"]') + .first() + .exists() + ).toBe(true); + }); + + test('it passes thirdPartyOptions correctly', () => { + const selectProps = wrapper + .find(EuiSuperSelect) + .first() + .props(); + + expect(selectProps.options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + value: 'short_description', + 'data-test-subj': 'third-party-short-desc', + }), + expect.objectContaining({ + value: 'description', + 'data-test-subj': 'third-party-desc', + }), + ]) + ); + }); + + test('it passes the correct actionTypeOptions', () => { + const selectProps = wrapper + .find(EuiSuperSelect) + .at(1) + .props(); + + expect(selectProps.options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + value: 'nothing', + 'data-test-subj': 'edit-update-option-nothing', + }), + expect.objectContaining({ + value: 'overwrite', + 'data-test-subj': 'edit-update-option-overwrite', + }), + expect.objectContaining({ + value: 'append', + 'data-test-subj': 'edit-update-option-append', + }), + ]) + ); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx index 62e43c86af8d9..732a11a58d35a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/field_mapping_row.tsx @@ -21,7 +21,7 @@ import { ThirdPartyField, } from '../../../../containers/case/configure/types'; -interface RowProps { +export interface RowProps { disabled: boolean; siemField: CaseField; thirdPartyOptions: Array>; @@ -77,6 +77,7 @@ const FieldMappingRowComponent: React.FC = ({ options={thirdPartyOptions} valueOfSelected={selectedThirdParty} onChange={onChangeThirdParty.bind(null, siemField)} + data-test-subj={'case-configure-third-party-select'} /> @@ -85,6 +86,7 @@ const FieldMappingRowComponent: React.FC = ({ options={actionTypeOptions} valueOfSelected={selectedActionType} onChange={onChangeActionType.bind(null, siemField)} + data-test-subj={'case-configure-action-type-select'} />
diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx new file mode 100644 index 0000000000000..5ea3f500c0349 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.test.tsx @@ -0,0 +1,748 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { ReactWrapper, mount } from 'enzyme'; + +import { useKibana } from '../../../../lib/kibana'; +import { useConnectors } from '../../../../containers/case/configure/use_connectors'; +import { useCaseConfigure } from '../../../../containers/case/configure/use_configure'; +import { useGetUrlSearch } from '../../../../components/navigation/use_get_url_search'; + +import { + connectors, + searchURL, + useCaseConfigureResponse, + useConnectorsResponse, + kibanaMockImplementationArgs, +} from './__mock__'; + +jest.mock('../../../../lib/kibana'); +jest.mock('../../../../containers/case/configure/use_connectors'); +jest.mock('../../../../containers/case/configure/use_configure'); +jest.mock('../../../../components/navigation/use_get_url_search'); + +const useKibanaMock = useKibana as jest.Mock; +const useConnectorsMock = useConnectors as jest.Mock; +const useCaseConfigureMock = useCaseConfigure as jest.Mock; +const useGetUrlSearchMock = useGetUrlSearch as jest.Mock; + +import { ConfigureCases } from './'; +import { TestProviders } from '../../../../mock'; +import { Connectors } from './connectors'; +import { ClosureOptions } from './closure_options'; +import { Mapping } from './mapping'; +import { + ActionsConnectorsContextProvider, + ConnectorAddFlyout, + ConnectorEditFlyout, +} from '../../../../../../../../plugins/triggers_actions_ui/public'; +import { EuiBottomBar } from '@elastic/eui'; + +describe('rendering', () => { + let wrapper: ReactWrapper; + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse); + useConnectorsMock.mockImplementation(() => ({ ...useConnectorsResponse, connectors: [] })); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders the Connectors', () => { + expect(wrapper.find('[data-test-subj="case-connectors-form-group"]').exists()).toBeTruthy(); + }); + + test('it renders the ClosureType', () => { + expect( + wrapper.find('[data-test-subj="case-closure-options-form-group"]').exists() + ).toBeTruthy(); + }); + + test('it renders the Mapping', () => { + expect(wrapper.find('[data-test-subj="case-mapping-form-group"]').exists()).toBeTruthy(); + }); + + test('it renders the ActionsConnectorsContextProvider', () => { + // Components from triggers_actions_ui do not have a data-test-subj + expect(wrapper.find(ActionsConnectorsContextProvider).exists()).toBeTruthy(); + }); + + test('it renders the ConnectorAddFlyout', () => { + // Components from triggers_actions_ui do not have a data-test-subj + expect(wrapper.find(ConnectorAddFlyout).exists()).toBeTruthy(); + }); + + test('it does NOT render the ConnectorEditFlyout', () => { + // Components from triggers_actions_ui do not have a data-test-subj + expect(wrapper.find(ConnectorEditFlyout).exists()).toBeFalsy(); + }); + + test('it does NOT render the EuiCallOut', () => { + expect(wrapper.find('[data-test-subj="configure-cases-warning-callout"]').exists()).toBeFalsy(); + }); + + test('it does NOT render the EuiBottomBar', () => { + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + }); +}); + +describe('ConfigureCases - Unhappy path', () => { + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation(() => useCaseConfigureResponse); + useConnectorsMock.mockImplementation(() => ({ ...useConnectorsResponse, connectors: [] })); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + }); + + test('it shows the warning callout when configuration is invalid', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('not-id'), []); + return useCaseConfigureResponse; + } + ); + + const wrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + wrapper.find('[data-test-subj="configure-cases-warning-callout"]').exists() + ).toBeTruthy(); + }); +}); + +describe('ConfigureCases - Happy path', () => { + let wrapper: ReactWrapper; + + beforeEach(() => { + jest.resetAllMocks(); + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('123'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return useCaseConfigureResponse; + } + ); + useConnectorsMock.mockImplementation(() => useConnectorsResponse); + useKibanaMock.mockImplementation(() => kibanaMockImplementationArgs); + useGetUrlSearchMock.mockImplementation(() => searchURL); + + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it renders the ConnectorEditFlyout', () => { + expect(wrapper.find(ConnectorEditFlyout).exists()).toBeTruthy(); + }); + + test('it renders with correct props', () => { + // Connector + expect(wrapper.find(Connectors).prop('connectors')).toEqual(connectors); + expect(wrapper.find(Connectors).prop('disabled')).toBe(false); + expect(wrapper.find(Connectors).prop('isLoading')).toBe(false); + expect(wrapper.find(Connectors).prop('selectedConnector')).toBe('123'); + + // ClosureOptions + expect(wrapper.find(ClosureOptions).prop('disabled')).toBe(false); + expect(wrapper.find(ClosureOptions).prop('closureTypeSelected')).toBe('close-by-user'); + + // Mapping + expect(wrapper.find(Mapping).prop('disabled')).toBe(true); + expect(wrapper.find(Mapping).prop('updateConnectorDisabled')).toBe(false); + expect(wrapper.find(Mapping).prop('mapping')).toEqual( + connectors[0].config.casesConfiguration.mapping + ); + + // Flyouts + expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(false); + expect(wrapper.find(ConnectorAddFlyout).prop('actionTypes')).toEqual([ + { + id: '.servicenow', + name: 'ServiceNow', + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'platinum', + }, + ]); + + expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(false); + expect(wrapper.find(ConnectorEditFlyout).prop('initialConnector')).toEqual(connectors[0]); + }); + + test('it disables correctly when the user cannot crud', () => { + const newWrapper = mount(, { + wrappingComponent: TestProviders, + }); + + expect(newWrapper.find(Connectors).prop('disabled')).toBe(true); + expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true); + expect(newWrapper.find(Mapping).prop('disabled')).toBe(true); + expect(newWrapper.find(Mapping).prop('updateConnectorDisabled')).toBe(true); + }); + + test('it disables correctly Connector when loading connectors', () => { + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Connectors).prop('disabled')).toBe(true); + }); + + test('it disables correctly Connector when saving configuration', () => { + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + persistLoading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Connectors).prop('disabled')).toBe(true); + }); + + test('it pass the correct value to isLoading attribute on Connector', () => { + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Connectors).prop('isLoading')).toBe(true); + }); + + test('it set correctly the selected connector', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + return useCaseConfigureResponse; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Connectors).prop('selectedConnector')).toBe('456'); + }); + + test('it show the add flyout when pressing the add connector button', () => { + wrapper.find('button[data-test-subj="case-configure-add-connector-button"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(true); + expect(wrapper.find(EuiBottomBar).exists()).toBeFalsy(); + }); + + test('it disables correctly ClosureOptions when loading connectors', () => { + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true); + }); + + test('it disables correctly ClosureOptions when saving configuration', () => { + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + persistLoading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true); + }); + + test('it disables correctly ClosureOptions when the connector is set to none', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('none'), []); + return useCaseConfigureResponse; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(ClosureOptions).prop('disabled')).toBe(true); + }); + + test('it disables the mapping permanently', () => { + expect(wrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it disables the update connector button when loading the connectors', () => { + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + + expect(wrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it disables the update connector button when loading the configuration', () => { + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + loading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it disables the update connector button when saving the configuration', () => { + useCaseConfigureMock.mockImplementation(() => ({ + ...useCaseConfigureResponse, + persistLoading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it disables the update connector button when the connectorId is invalid', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('not-id'), []); + return useCaseConfigureResponse; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it disables the update connector button when the connectorId is set to none', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('none'), []); + return useCaseConfigureResponse; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect(newWrapper.find(Mapping).prop('disabled')).toBe(true); + }); + + test('it show the edit flyout when pressing the update connector button', () => { + wrapper.find('button[data-test-subj="case-mapping-update-connector-button"]').simulate('click'); + wrapper.update(); + + expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(true); + expect(wrapper.find(EuiBottomBar).exists()).toBeFalsy(); + }); + + test('it sets the mapping of a connector correctly', () => { + expect(wrapper.find(Mapping).prop('mapping')).toEqual( + connectors[0].config.casesConfiguration.mapping + ); + }); + + // TODO: When mapping is enabled the test.todo should be implemented. + test.todo('the mapping is changed successfully when changing the third party'); + test.todo('the mapping is changed successfully when changing the action type'); + + test('it does not shows the action bar when there is no change', () => { + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + }); + + test('it shows the action bar when the connector is changed', () => { + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]') + .first() + .text() + ).toBe('1 unsaved changes'); + }); + + test('it shows the action bar when the closure type is changed', () => { + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]') + .first() + .text() + ).toBe('1 unsaved changes'); + }); + + test('it tracks the changes successfully', () => { + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click'); + wrapper.update(); + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]') + .first() + .text() + ).toBe('2 unsaved changes'); + }); + + test('it tracks and reverts the changes successfully ', () => { + // change settings + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-456"]').simulate('click'); + wrapper.update(); + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + // revert back to initial settings + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.update(); + wrapper.find('button[data-test-subj="dropdown-connector-123"]').simulate('click'); + wrapper.update(); + wrapper.find('input[id="close-by-user"]').simulate('change'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + }); + + test('it close and restores the action bar when the add connector button is pressed', () => { + // Change closure type + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + // Press add connector button + wrapper.find('button[data-test-subj="case-configure-add-connector-button"]').simulate('click'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + + expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(true); + + // Close the add flyout + wrapper.find('button[data-test-subj="euiFlyoutCloseButton"]').simulate('click'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeTruthy(); + + expect(wrapper.find(ConnectorAddFlyout).prop('addFlyoutVisible')).toBe(false); + + expect( + wrapper + .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]') + .first() + .text() + ).toBe('1 unsaved changes'); + }); + + test('it close and restores the action bar when the update connector button is pressed', () => { + // Change closure type + wrapper.find('input[id="close-by-pushing"]').simulate('change'); + wrapper.update(); + + // Press update connector button + wrapper.find('button[data-test-subj="case-mapping-update-connector-button"]').simulate('click'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + + expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(true); + + // Close the edit flyout + wrapper.find('button[data-test-subj="euiFlyoutCloseButton"]').simulate('click'); + wrapper.update(); + + expect( + wrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeTruthy(); + + expect(wrapper.find(ConnectorEditFlyout).prop('editFlyoutVisible')).toBe(false); + + expect( + wrapper + .find('[data-test-subj="case-configure-action-bottom-bar-total-changes"]') + .first() + .text() + ).toBe('1 unsaved changes'); + }); + + test('it disables the buttons of action bar when loading connectors', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return useCaseConfigureResponse; + } + ); + + useConnectorsMock.mockImplementation(() => ({ + ...useConnectorsResponse, + loading: true, + })); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + }); + + test('it disables the buttons of action bar when loading configuration', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return { ...useCaseConfigureResponse, loading: true }; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + }); + + test('it disables the buttons of action bar when saving configuration', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return { ...useCaseConfigureResponse, persistLoading: true }; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .prop('isDisabled') + ).toBe(true); + }); + + test('it shows the loading spinner when saving configuration', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return { ...useCaseConfigureResponse, persistLoading: true }; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]') + .first() + .prop('isLoading') + ).toBe(true); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .prop('isLoading') + ).toBe(true); + }); + + test('it closes the action bar when pressing save', () => { + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return useCaseConfigureResponse; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .simulate('click'); + + newWrapper.update(); + + expect( + newWrapper.find('[data-test-subj="case-configure-action-bottom-bar"]').exists() + ).toBeFalsy(); + }); + + test('it submits the configuration correctly', () => { + const persistCaseConfigure = jest.fn(); + + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-pushing', + }), + [] + ); + return { ...useCaseConfigureResponse, persistCaseConfigure }; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-save-button"]') + .first() + .simulate('click'); + + newWrapper.update(); + + expect(persistCaseConfigure).toHaveBeenCalled(); + expect(persistCaseConfigure).toHaveBeenCalledWith({ + connectorId: '456', + connectorName: 'My Connector 2', + closureType: 'close-by-user', + }); + }); + + test('it has the correct url on cancel button', () => { + const persistCaseConfigure = jest.fn(); + + useCaseConfigureMock.mockImplementation( + ({ setConnector, setClosureType, setCurrentConfiguration }) => { + useEffect(() => setConnector('456'), []); + useEffect(() => setClosureType('close-by-user'), []); + useEffect( + () => + setCurrentConfiguration({ + connectorId: '123', + closureType: 'close-by-user', + }), + [] + ); + return { ...useCaseConfigureResponse, persistCaseConfigure }; + } + ); + + const newWrapper = mount(, { wrappingComponent: TestProviders }); + + expect( + newWrapper + .find('[data-test-subj="case-configure-action-bottom-bar-cancel-button"]') + .first() + .prop('href') + ).toBe(`#/link-to/case${searchURL}`); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx index b8cf5a3880801..241dcef14a145 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/index.tsx @@ -140,6 +140,7 @@ const ConfigureCasesComponent: React.FC = ({ userC setClosureType, setCurrentConfiguration, }); + const { loading: isLoadingConnectors, connectors, refetchConnectors } = useConnectors(); // ActionsConnectorsContextProvider reloadConnectors prop expects a Promise. @@ -251,7 +252,12 @@ const ConfigureCasesComponent: React.FC = ({ userC {!connectorIsValid && ( - + {i18n.WARNING_NO_CONNECTOR_MESSAGE} @@ -283,11 +289,13 @@ const ConfigureCasesComponent: React.FC = ({ userC /> {actionBarVisible && ( - + - {i18n.UNSAVED_CHANGES(totalConfigurationChanges)} + + {i18n.UNSAVED_CHANGES(totalConfigurationChanges)} + @@ -300,6 +308,7 @@ const ConfigureCasesComponent: React.FC = ({ userC isLoading={persistLoading} aria-label={i18n.CANCEL} href={getCaseUrl(search)} + data-test-subj="case-configure-action-bottom-bar-cancel-button" > {i18n.CANCEL} @@ -313,6 +322,7 @@ const ConfigureCasesComponent: React.FC = ({ userC isDisabled={isLoadingAny} isLoading={persistLoading} onClick={handleSubmit} + data-test-subj="case-configure-action-bottom-bar-save-button" > {i18n.SAVE_CHANGES} diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx new file mode 100644 index 0000000000000..fefcb2ca8cf6a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.test.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { TestProviders } from '../../../../mock'; +import { Mapping, MappingProps } from './mapping'; +import { mapping } from './__mock__'; + +describe('Mapping', () => { + let wrapper: ReactWrapper; + const onChangeMapping = jest.fn(); + const setEditFlyoutVisibility = jest.fn(); + const props: MappingProps = { + disabled: false, + mapping, + updateConnectorDisabled: false, + onChangeMapping, + setEditFlyoutVisibility, + }; + + beforeAll(() => { + wrapper = mount(, { wrappingComponent: TestProviders }); + }); + + test('it shows mapping form group', () => { + expect( + wrapper + .find('[data-test-subj="case-mapping-form-group"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows mapping form row', () => { + expect( + wrapper + .find('[data-test-subj="case-mapping-form-row"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the update button', () => { + expect( + wrapper + .find('[data-test-subj="case-mapping-update-connector-button"]') + .first() + .exists() + ).toBe(true); + }); + + test('it shows the field mapping', () => { + expect( + wrapper + .find('[data-test-subj="case-mapping-field"]') + .first() + .exists() + ).toBe(true); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx index 8cba73d1249df..7340a49f6d0bb 100644 --- a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/mapping.tsx @@ -20,7 +20,7 @@ import * as i18n from './translations'; import { FieldMapping } from './field_mapping'; import { CasesConfigurationMapping } from '../../../../containers/case/configure/types'; -interface MappingProps { +export interface MappingProps { disabled: boolean; updateConnectorDisabled: boolean; mapping: CasesConfigurationMapping[] | null; @@ -45,20 +45,27 @@ const MappingComponent: React.FC = ({ fullWidth title={

{i18n.FIELD_MAPPING_TITLE}

} description={i18n.FIELD_MAPPING_DESC} + data-test-subj="case-mapping-form-group" > - + {i18n.UPDATE_CONNECTOR} - + ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.test.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.test.ts new file mode 100644 index 0000000000000..df958b75dc6b8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/reducer.test.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 { configureCasesReducer, Action, State } from './reducer'; +import { initialState, mapping } from './__mock__'; + +describe('Reducer', () => { + let reducer: (state: State, action: Action) => State; + + beforeAll(() => { + reducer = configureCasesReducer(); + }); + + test('it should set the correct configuration', () => { + const action: Action = { + type: 'setCurrentConfiguration', + currentConfiguration: { connectorId: '123', closureType: 'close-by-user' }, + }; + const state = reducer(initialState, action); + + expect(state).toEqual({ + ...state, + currentConfiguration: action.currentConfiguration, + }); + }); + + test('it should set the correct connector id', () => { + const action: Action = { + type: 'setConnectorId', + connectorId: '456', + }; + const state = reducer(initialState, action); + + expect(state).toEqual({ + ...state, + connectorId: action.connectorId, + }); + }); + + test('it should set the closure type', () => { + const action: Action = { + type: 'setClosureType', + closureType: 'close-by-pushing', + }; + const state = reducer(initialState, action); + + expect(state).toEqual({ + ...state, + closureType: action.closureType, + }); + }); + + test('it should set the mapping', () => { + const action: Action = { + type: 'setMapping', + mapping, + }; + const state = reducer(initialState, action); + + expect(state).toEqual({ + ...state, + mapping: action.mapping, + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.test.tsx b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.test.tsx new file mode 100644 index 0000000000000..1c6fc9b2d405f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.test.tsx @@ -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 { mapping } from './__mock__'; +import { setActionTypeToMapping, setThirdPartyToMapping } from './utils'; +import { CasesConfigurationMapping } from '../../../../containers/case/configure/types'; + +describe('FieldMappingRow', () => { + test('it should change the action type', () => { + const newMapping = setActionTypeToMapping('title', 'nothing', mapping); + expect(newMapping[0].actionType).toBe('nothing'); + }); + + test('it should not change other fields', () => { + const [newTitle, description, comments] = setActionTypeToMapping('title', 'nothing', mapping); + expect(newTitle).not.toEqual(mapping[0]); + expect(description).toEqual(mapping[1]); + expect(comments).toEqual(mapping[2]); + }); + + test('it should return a new array when changing action type', () => { + const newMapping = setActionTypeToMapping('title', 'nothing', mapping); + expect(newMapping).not.toBe(mapping); + }); + + test('it should change the third party', () => { + const newMapping = setThirdPartyToMapping('title', 'description', mapping); + expect(newMapping[0].target).toBe('description'); + }); + + test('it should not change other fields when there is not a conflict', () => { + const tempMapping: CasesConfigurationMapping[] = [ + { + source: 'title', + target: 'short_description', + actionType: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + actionType: 'append', + }, + ]; + + const [newTitle, comments] = setThirdPartyToMapping('title', 'description', tempMapping); + + expect(newTitle).not.toEqual(mapping[0]); + expect(comments).toEqual(tempMapping[1]); + }); + + test('it should return a new array when changing third party', () => { + const newMapping = setThirdPartyToMapping('title', 'description', mapping); + expect(newMapping).not.toBe(mapping); + }); + + test('it should change the target of the conflicting third party field to not_mapped', () => { + const newMapping = setThirdPartyToMapping('title', 'description', mapping); + expect(newMapping[1].target).toBe('not_mapped'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.ts b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.ts new file mode 100644 index 0000000000000..2ac6cc1a38587 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/case/components/configure_cases/utils.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 { + CaseField, + ActionType, + CasesConfigurationMapping, + ThirdPartyField, +} from '../../../../containers/case/configure/types'; + +export const setActionTypeToMapping = ( + caseField: CaseField, + newActionType: ActionType, + mapping: CasesConfigurationMapping[] +): CasesConfigurationMapping[] => { + const findItemIndex = mapping.findIndex(item => item.source === caseField); + + if (findItemIndex >= 0) { + return [ + ...mapping.slice(0, findItemIndex), + { ...mapping[findItemIndex], actionType: newActionType }, + ...mapping.slice(findItemIndex + 1), + ]; + } + + return [...mapping]; +}; + +export const setThirdPartyToMapping = ( + caseField: CaseField, + newThirdPartyField: ThirdPartyField, + mapping: CasesConfigurationMapping[] +): CasesConfigurationMapping[] => + mapping.map(item => { + if (item.source !== caseField && item.target === newThirdPartyField) { + return { ...item, target: 'not_mapped' }; + } else if (item.source === caseField) { + return { ...item, target: newThirdPartyField }; + } + return item; + }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx index 60ad68b8c9141..454ef18e0ae14 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx @@ -14,13 +14,15 @@ import { enableRulesAction, exportRulesAction, } from './actions'; -import { ActionToaster } from '../../../../components/toasters'; +import { ActionToaster, displayWarningToast } from '../../../../components/toasters'; import { Rule } from '../../../../containers/detection_engine/rules'; +import * as detectionI18n from '../../translations'; interface GetBatchItems { closePopover: () => void; dispatch: Dispatch; dispatchToaster: Dispatch; + hasMlPermissions: boolean; loadingRuleIds: string[]; reFetchRules: (refreshPrePackagedRule?: boolean) => void; rules: Rule[]; @@ -31,6 +33,7 @@ export const getBatchItems = ({ closePopover, dispatch, dispatchToaster, + hasMlPermissions, loadingRuleIds, reFetchRules, rules, @@ -57,7 +60,22 @@ export const getBatchItems = ({ const deactivatedIds = selectedRuleIds.filter( id => !rules.find(r => r.id === id)?.enabled ?? false ); - await enableRulesAction(deactivatedIds, true, dispatch, dispatchToaster); + + const deactivatedIdsNoML = deactivatedIds.filter( + id => rules.find(r => r.id === id)?.type !== 'machine_learning' ?? false + ); + + const mlRuleCount = deactivatedIds.length - deactivatedIdsNoML.length; + if (!hasMlPermissions && mlRuleCount > 0) { + displayWarningToast(detectionI18n.ML_RULES_UNAVAILABLE(mlRuleCount), dispatchToaster); + } + + await enableRulesAction( + hasMlPermissions ? deactivatedIds : deactivatedIdsNoML, + true, + dispatch, + dispatchToaster + ); }} > {i18n.BATCH_ACTION_ACTIVATE_SELECTED} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index 9a84d33ab5fdf..80e644f800334 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -13,6 +13,7 @@ import { EuiTableActionsColumnType, EuiText, EuiHealth, + EuiToolTip, } from '@elastic/eui'; import { FormattedRelative } from '@kbn/i18n/react'; import * as H from 'history'; @@ -36,6 +37,8 @@ import { } from './actions'; import { Action } from './reducer'; import { LocalizedDateTooltip } from '../../../../components/localized_date_tooltip'; +import * as detectionI18n from '../../translations'; +import { isMlRule } from '../../../../../common/detection_engine/ml_helpers'; export const getActions = ( dispatch: React.Dispatch, @@ -88,6 +91,7 @@ interface GetColumns { dispatch: React.Dispatch; dispatchToaster: Dispatch; history: H.History; + hasMlPermissions: boolean; hasNoPermissions: boolean; loadingRuleIds: string[]; reFetchRules: (refreshPrePackagedRule?: boolean) => void; @@ -98,6 +102,7 @@ export const getColumns = ({ dispatch, dispatchToaster, history, + hasMlPermissions, hasNoPermissions, loadingRuleIds, reFetchRules, @@ -182,14 +187,25 @@ export const getColumns = ({ field: 'enabled', name: i18n.COLUMN_ACTIVATE, render: (value: Rule['enabled'], item: Rule) => ( - + + + ), sortable: true, width: '95px', diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx index ccdfd1ed1be38..e96ed856208bd 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx @@ -40,6 +40,8 @@ import { getColumns, getMonitoringColumns } from './columns'; import { showRulesTable } from './helpers'; import { allRulesReducer, State } from './reducer'; import { RulesTableFilters } from './rules_table_filters/rules_table_filters'; +import { useMlCapabilities } from '../../../../components/ml_popover/hooks/use_ml_capabilities'; +import { hasMlAdminPermissions } from '../../../../components/ml/permissions/has_ml_admin_permissions'; const SORT_FIELD = 'enabled'; const initialState: State = { @@ -111,6 +113,11 @@ export const AllRules = React.memo( const { loading: isLoadingRulesStatuses, rulesStatuses } = useRulesStatuses(rules); const history = useHistory(); const [, dispatchToaster] = useStateToaster(); + const mlCapabilities = useMlCapabilities(); + + // TODO: Refactor license check + hasMlAdminPermissions to common check + const hasMlPermissions = + mlCapabilities.isPlatinumOrTrialLicense && hasMlAdminPermissions(mlCapabilities); const setRules = useCallback((newRules: Rule[], newPagination: Partial) => { dispatch({ @@ -145,6 +152,7 @@ export const AllRules = React.memo( closePopover, dispatch, dispatchToaster, + hasMlPermissions, loadingRuleIds, selectedRuleIds, reFetchRules: reFetchRulesData, @@ -152,7 +160,15 @@ export const AllRules = React.memo( })} /> ), - [dispatch, dispatchToaster, loadingRuleIds, reFetchRulesData, rules, selectedRuleIds] + [ + dispatch, + dispatchToaster, + hasMlPermissions, + loadingRuleIds, + reFetchRulesData, + rules, + selectedRuleIds, + ] ); const paginationMemo = useMemo( @@ -184,6 +200,7 @@ export const AllRules = React.memo( dispatch, dispatchToaster, history, + hasMlPermissions, hasNoPermissions, loadingRuleIds: loadingRulesAction != null && @@ -192,7 +209,15 @@ export const AllRules = React.memo( : [], reFetchRules: reFetchRulesData, }); - }, [dispatch, dispatchToaster, history, loadingRuleIds, loadingRulesAction, reFetchRulesData]); + }, [ + dispatch, + dispatchToaster, + hasMlPermissions, + history, + loadingRuleIds, + loadingRulesAction, + reFetchRulesData, + ]); const monitoringColumns = useMemo(() => getMonitoringColumns(), []); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx index 8276aa3578563..5a9593f1a6de2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/ml_job_description.tsx @@ -66,7 +66,9 @@ const Wrapper = styled.div` `; export const MlJobDescription: React.FC<{ job: SiemJob }> = ({ job }) => { - const jobUrl = useKibana().services.application.getUrlForApp('ml#/jobs'); + const jobUrl = useKibana().services.application.getUrlForApp( + `ml#/jobs?mlManagement=(jobId:${encodeURI(job.id)})` + ); return ( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.tsx index 3d253b71b53d6..794edf0ab5de7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/ml_job_select/index.tsx @@ -4,37 +4,64 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiFormRow, + EuiIcon, EuiLink, EuiSuperSelect, EuiText, } from '@elastic/eui'; +import styled from 'styled-components'; import { FieldHook, getFieldValidityAndErrorMessage } from '../../../../../shared_imports'; import { useSiemJobs } from '../../../../../components/ml_popover/hooks/use_siem_jobs'; import { useKibana } from '../../../../../lib/kibana'; -import { ML_JOB_SELECT_PLACEHOLDER_TEXT } from '../step_define_rule/translations'; +import { + ML_JOB_SELECT_PLACEHOLDER_TEXT, + ENABLE_ML_JOB_WARNING, +} from '../step_define_rule/translations'; +import { isJobStarted } from '../../../../../../common/detection_engine/ml_helpers'; + +const HelpTextWarningContainer = styled.div` + margin-top: 10px; +`; + +const MlJobSelectEuiFlexGroup = styled(EuiFlexGroup)` + margin-bottom: 5px; +`; -const HelpText: React.FC<{ href: string }> = ({ href }) => ( - - - - ), - }} - /> +const HelpText: React.FC<{ href: string; showEnableWarning: boolean }> = ({ + href, + showEnableWarning = false, +}) => ( + <> + + + + ), + }} + /> + {showEnableWarning && ( + + + + {ENABLE_ML_JOB_WARNING} + + + )} + ); const JobDisplay: React.FC<{ title: string; description: string }> = ({ title, description }) => ( @@ -77,26 +104,37 @@ export const MlJobSelect: React.FC = ({ describedByIds = [], f const options = [placeholderOption, ...jobOptions]; + const isJobRunning = useMemo(() => { + // If the selected job is not found in the list, it means the placeholder is selected + // and so we don't want to show the warning, thus isJobRunning will be true when 'job == null' + const job = siemJobs.find(j => j.id === jobId); + return job == null || isJobStarted(job.jobState, job.datafeedState); + }, [siemJobs, jobId]); + return ( - } - isInvalid={isInvalid} - error={errorMessage} - data-test-subj="mlJobSelect" - describedByIds={describedByIds} - > - - - - - - + + + } + isInvalid={isInvalid} + error={errorMessage} + data-test-subj="mlJobSelect" + describedByIds={describedByIds} + > + + + + + + + + ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx index 1d8821aceb249..bbdb2130ce298 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx @@ -62,3 +62,11 @@ export const ML_JOB_SELECT_PLACEHOLDER_TEXT = i18n.translate( defaultMessage: 'Select a job', } ); + +export const ENABLE_ML_JOB_WARNING = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepDefineRule.mlEnableJobWarningTitle', + { + defaultMessage: + 'This ML job is not currently running. Please set this job to run via "ML job settings" before activating this rule.', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.test.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.test.ts index efb601b6bd207..8d793f39afa99 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.test.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.test.ts @@ -515,7 +515,7 @@ describe('helpers', () => { actions: [], enabled: false, meta: { - kibanaSiemAppUrl: 'http://localhost:5601/app/siem', + kibana_siem_app_url: 'http://localhost:5601/app/siem', }, throttle: 'no_actions', }; @@ -533,7 +533,7 @@ describe('helpers', () => { actions: [], enabled: false, meta: { - kibanaSiemAppUrl: mockStepData.kibanaSiemAppUrl, + kibana_siem_app_url: mockStepData.kibanaSiemAppUrl, }, throttle: 'no_actions', }; @@ -566,7 +566,7 @@ describe('helpers', () => { ], enabled: false, meta: { - kibanaSiemAppUrl: mockStepData.kibanaSiemAppUrl, + kibana_siem_app_url: mockStepData.kibanaSiemAppUrl, }, throttle: 'rule', }; @@ -599,7 +599,7 @@ describe('helpers', () => { ], enabled: false, meta: { - kibanaSiemAppUrl: mockStepData.kibanaSiemAppUrl, + kibana_siem_app_url: mockStepData.kibanaSiemAppUrl, }, throttle: mockStepData.throttle, }; @@ -631,7 +631,7 @@ describe('helpers', () => { ], enabled: false, meta: { - kibanaSiemAppUrl: mockStepData.kibanaSiemAppUrl, + kibana_siem_app_url: mockStepData.kibanaSiemAppUrl, }, throttle: 'no_actions', }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts index 151e3a9bdf4d6..7ad116c313361 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts @@ -155,7 +155,7 @@ export const formatActionsStepData = (actionsStepData: ActionsStepRule): Actions enabled, throttle: actions.length ? throttle : NOTIFICATION_THROTTLE_NO_ACTIONS, meta: { - kibanaSiemAppUrl, + kibana_siem_app_url: kibanaSiemAppUrl, }, }; }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index cb4d88a8bb539..2b648a3b3f825 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -14,6 +14,7 @@ import { EuiSpacer, EuiTab, EuiTabs, + EuiToolTip, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { FC, memo, useCallback, useMemo, useState } from 'react'; @@ -66,6 +67,8 @@ import { RuleActionsOverflow } from '../components/rule_actions_overflow'; import { RuleStatusFailedCallOut } from './status_failed_callout'; import { FailureHistory } from './failure_history'; import { RuleStatus } from '../components/rule_status'; +import { useMlCapabilities } from '../../../../components/ml_popover/hooks/use_ml_capabilities'; +import { hasMlAdminPermissions } from '../../../../components/ml/permissions/has_ml_admin_permissions'; enum RuleDetailTabs { signals = 'signals', @@ -114,6 +117,11 @@ const RuleDetailsPageComponent: FC = ({ scheduleRuleData: null, }; const [lastSignals] = useSignalInfo({ ruleId }); + const mlCapabilities = useMlCapabilities(); + + // TODO: Refactor license check + hasMlAdminPermissions to common check + const hasMlPermissions = + mlCapabilities.isPlatinumOrTrialLicense && hasMlAdminPermissions(mlCapabilities); const title = isLoading === true || rule === null ? : rule.name; const subTitle = useMemo( @@ -259,13 +267,25 @@ const RuleDetailsPageComponent: FC = ({ > - + + + diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx index db1f2298b5ea7..58a1b0fd3133e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx @@ -64,7 +64,7 @@ export const getActionsStepsData = ( actions: actions?.map(transformRuleToAlertAction), isNew: false, throttle, - kibanaSiemAppUrl: meta?.kibanaSiemAppUrl, + kibanaSiemAppUrl: meta?.kibana_siem_app_url, enabled, }; }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts index 39277b3d3c77e..008d660be9d88 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts @@ -86,3 +86,17 @@ export const USER_UNAUTHENTICATED_MSG_BODY = i18n.translate( 'You do not have the required permissions for viewing the detection engine. For more help, contact your administrator.', } ); + +export const ML_RULES_DISABLED_MESSAGE = i18n.translate( + 'xpack.siem.detectionEngine.mlRulesDisabledMessageTitle', + { + defaultMessage: 'ML rules require Platinum License and ML Admin Permissions', + } +); + +export const ML_RULES_UNAVAILABLE = (totalRules: number) => + i18n.translate('xpack.siem.detectionEngine.mlUnavailableTitle', { + values: { totalRules }, + defaultMessage: + '{totalRules} {totalRules, plural, =1 {rule requires} other {rules require}} Machine Learning to enable.', + }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/add_tags.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/add_tags.ts index 6955e57d099be..14b2e1ae9e366 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/add_tags.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/add_tags.ts @@ -6,5 +6,5 @@ import { INTERNAL_RULE_ALERT_ID_KEY } from '../../../../common/constants'; -export const addTags = (tags: string[] = [], ruleAlertId: string): string[] => +export const addTags = (tags: string[], ruleAlertId: string): string[] => Array.from(new Set([...tags, `${INTERNAL_RULE_ALERT_ID_KEY}:${ruleAlertId}`])); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/build_signals_query.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/build_signals_query.test.ts index 189c596a77125..c6923283bec16 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/build_signals_query.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/build_signals_query.test.ts @@ -41,6 +41,7 @@ describe('buildSignalsSearchQuery', () => { '@timestamp': { gt: from, lte: to, + format: 'epoch_millis', }, }, }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/build_signals_query.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/build_signals_query.ts index b973d4c5f4e98..be0943c014225 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/build_signals_query.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/build_signals_query.ts @@ -32,6 +32,7 @@ export const buildSignalsSearchQuery = ({ ruleId, index, from, to }: BuildSignal '@timestamp': { gt: from, lte: to, + format: 'epoch_millis', }, }, }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts index 073251b68f414..3878f5dae8889 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.test.ts @@ -24,7 +24,6 @@ describe('createNotifications', () => { enabled: true, interval: '', name: '', - tags: [], }); expect(alertsClient.create).toHaveBeenCalledWith( @@ -52,7 +51,6 @@ describe('createNotifications', () => { enabled: true, interval: '', name: '', - tags: [], }); expect(alertsClient.create).toHaveBeenCalledWith( diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts index 3a1697f1c8afc..ccd7576255d83 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/create_notifications.ts @@ -17,12 +17,11 @@ export const createNotifications = async ({ ruleAlertId, interval, name, - tags, }: CreateNotificationParams): Promise => alertsClient.create({ data: { name, - tags: addTags(tags, ruleAlertId), + tags: addTags([], ruleAlertId), alertTypeId: NOTIFICATIONS_ID, consumer: APP_ID, params: { @@ -30,7 +29,7 @@ export const createNotifications = async ({ }, schedule: { interval }, enabled, - actions: actions?.map(transformRuleToAlertAction), + actions: actions.map(transformRuleToAlertAction), throttle: null, }, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts index 33cee6d074b70..7ff6a4e5164bd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/get_signals_count.ts @@ -4,63 +4,40 @@ * you may not use this file except in compliance with the Elastic License. */ -import moment from 'moment'; -import { getNotificationResultsLink } from './utils'; -import { NotificationExecutorOptions } from './types'; -import { parseScheduleDates } from '../signals/utils'; +import { AlertServices } from '../../../../../../../plugins/alerting/server'; import { buildSignalsSearchQuery } from './build_signals_query'; -interface SignalsCountResults { - signalsCount: string; - resultsLink: string; -} - interface GetSignalsCount { - from: Date | string; - to: Date | string; - ruleAlertId: string; + from?: string; + to?: string; ruleId: string; index: string; - kibanaSiemAppUrl: string | undefined; - callCluster: NotificationExecutorOptions['services']['callCluster']; + callCluster: AlertServices['callCluster']; +} + +interface CountResult { + count: number; } export const getSignalsCount = async ({ from, to, - ruleAlertId, ruleId, index, callCluster, - kibanaSiemAppUrl = '', -}: GetSignalsCount): Promise => { - const fromMoment = moment.isDate(from) ? moment(from) : parseScheduleDates(from); - const toMoment = moment.isDate(to) ? moment(to) : parseScheduleDates(to); - - if (!fromMoment || !toMoment) { - throw new Error(`There was an issue with parsing ${from} or ${to} into Moment object`); +}: GetSignalsCount): Promise => { + if (from == null || to == null) { + throw Error('"from" or "to" was not provided to signals count query'); } - const fromInMs = fromMoment.format('x'); - const toInMs = toMoment.format('x'); - const query = buildSignalsSearchQuery({ index, ruleId, - to: toInMs, - from: fromInMs, + to, + from, }); - const result = await callCluster('count', query); - const resultsLink = getNotificationResultsLink({ - kibanaSiemAppUrl: `${kibanaSiemAppUrl}`, - id: ruleAlertId, - from: fromInMs, - to: toInMs, - }); + const result: CountResult = await callCluster('count', query); - return { - signalsCount: result.count, - resultsLink, - }; + return result.count; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts index e74da583e9193..e4ad53de742d6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts @@ -13,6 +13,8 @@ import { getSignalsCount } from './get_signals_count'; import { RuleAlertAttributes } from '../signals/types'; import { siemRuleActionGroups } from '../signals/siem_rule_action_groups'; import { scheduleNotificationActions } from './schedule_notification_actions'; +import { getNotificationResultsLink } from './utils'; +import { parseScheduleDates } from '../signals/utils'; export const rulesNotificationAlertType = ({ logger, @@ -42,21 +44,33 @@ export const rulesNotificationAlertType = ({ const { params: ruleAlertParams, name: ruleName } = ruleAlertSavedObject.attributes; const ruleParams = { ...ruleAlertParams, name: ruleName, id: ruleAlertSavedObject.id }; - const { signalsCount, resultsLink } = await getSignalsCount({ - from: previousStartedAt ?? `now-${ruleParams.interval}`, - to: startedAt, + const fromInMs = parseScheduleDates( + previousStartedAt + ? previousStartedAt.toISOString() + : `now-${ruleAlertSavedObject.attributes.schedule.interval}` + )?.format('x'); + const toInMs = parseScheduleDates(startedAt.toISOString())?.format('x'); + + const signalsCount = await getSignalsCount({ + from: fromInMs, + to: toInMs, index: ruleParams.outputIndex, - ruleId: ruleParams.ruleId!, - kibanaSiemAppUrl: ruleAlertParams.meta?.kibanaSiemAppUrl as string, - ruleAlertId: ruleAlertSavedObject.id, + ruleId: ruleParams.ruleId, callCluster: services.callCluster, }); + const resultsLink = getNotificationResultsLink({ + from: fromInMs, + to: toInMs, + id: ruleAlertSavedObject.id, + kibanaSiemAppUrl: ruleAlertParams.meta?.kibana_siem_app_url, + }); + logger.info( `Found ${signalsCount} signals using signal rule name: "${ruleParams.name}", id: "${params.ruleAlertId}", rule_id: "${ruleParams.ruleId}" in "${ruleParams.outputIndex}" index` ); - if (signalsCount) { + if (signalsCount !== 0) { const alertInstance = services.alertInstanceFactory(alertId); scheduleNotificationActions({ alertInstance, signalsCount, resultsLink, ruleParams }); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts index b858b25377ffe..9f145af79ca90 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/schedule_notification_actions.ts @@ -8,14 +8,14 @@ import { mapKeys, snakeCase } from 'lodash/fp'; import { AlertInstance } from '../../../../../../../plugins/alerting/server'; import { RuleTypeParams } from '../types'; -type NotificationRuleTypeParams = RuleTypeParams & { +export type NotificationRuleTypeParams = RuleTypeParams & { name: string; id: string; }; interface ScheduleNotificationActions { alertInstance: AlertInstance; - signalsCount: string; + signalsCount: number; resultsLink: string; ruleParams: NotificationRuleTypeParams; } @@ -23,7 +23,7 @@ interface ScheduleNotificationActions { export const scheduleNotificationActions = ({ alertInstance, signalsCount, - resultsLink, + resultsLink = '', ruleParams, }: ScheduleNotificationActions): AlertInstance => alertInstance diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/types.ts index 128a7965cd7dc..32a8737adc7c9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/types.ts @@ -45,10 +45,11 @@ export interface Clients { alertsClient: AlertsClient; } -export type UpdateNotificationParams = Omit & { +export type UpdateNotificationParams = Omit< + NotificationAlertParams, + 'interval' | 'actions' | 'tags' +> & { actions: RuleAlertAction[]; - id?: string; - tags?: string[]; interval: string | null | undefined; ruleAlertId: string; } & Clients; @@ -64,8 +65,6 @@ export interface NotificationAlertParams { ruleAlertId: string; interval: string; name: string; - tags?: string[]; - throttle?: null; } export type CreateNotificationParams = NotificationAlertParams & Clients; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts index 4c077dd9fc1fb..e1f7526438c31 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.test.ts @@ -9,6 +9,7 @@ import { updateNotifications } from './update_notifications'; import { readNotifications } from './read_notifications'; import { createNotifications } from './create_notifications'; import { getNotificationResult } from '../routes/__mocks__/request_responses'; +import { UpdateNotificationParams } from './types'; jest.mock('./read_notifications'); jest.mock('./create_notifications'); @@ -30,7 +31,6 @@ describe('updateNotifications', () => { enabled: true, interval: '10m', name: '', - tags: [], }); expect(alertsClient.update).toHaveBeenCalledWith( @@ -48,14 +48,13 @@ describe('updateNotifications', () => { it('should create a new notification if did not exist', async () => { (readNotifications as jest.Mock).mockResolvedValue(null); - const params = { + const params: UpdateNotificationParams = { alertsClient, actions: [], ruleAlertId: 'new-rule-id', enabled: true, interval: '10m', name: '', - tags: [], }; await updateNotifications(params); @@ -73,7 +72,6 @@ describe('updateNotifications', () => { enabled: true, interval: null, name: '', - tags: [], }); expect(alertsClient.delete).toHaveBeenCalledWith( @@ -98,7 +96,6 @@ describe('updateNotifications', () => { enabled: true, interval: '10m', name: '', - tags: [], }); expect(alertsClient.update).toHaveBeenCalledWith( @@ -125,10 +122,8 @@ describe('updateNotifications', () => { alertsClient, actions: [], enabled: true, - id: notification.id, ruleAlertId, name: notification.name, - tags: notification.tags, interval: null, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts index 3197d21c0e95a..ac0de406aceb2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/update_notifications.ts @@ -15,50 +15,41 @@ export const updateNotifications = async ({ alertsClient, actions, enabled, - id, ruleAlertId, name, - tags, interval, }: UpdateNotificationParams): Promise => { - const notification = await readNotifications({ alertsClient, id, ruleAlertId }); + const notification = await readNotifications({ alertsClient, id: undefined, ruleAlertId }); if (interval && notification) { - const result = await alertsClient.update({ + return alertsClient.update({ id: notification.id, data: { - tags: addTags(tags, ruleAlertId), + tags: addTags([], ruleAlertId), name, schedule: { interval, }, - actions: actions?.map(transformRuleToAlertAction), + actions: actions.map(transformRuleToAlertAction), params: { ruleAlertId, }, throttle: null, }, }); - return result; - } - - if (interval && !notification) { - const result = await createNotifications({ + } else if (interval && !notification) { + return createNotifications({ alertsClient, enabled, - tags, name, interval, actions, ruleAlertId, }); - return result; - } - - if (!interval && notification) { + } else if (!interval && notification) { await alertsClient.delete({ id: notification.id }); return null; + } else { + return null; } - - return null; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/utils.ts index b8a3c4199c4f0..c91c4490e8eba 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/utils.ts @@ -5,14 +5,17 @@ */ export const getNotificationResultsLink = ({ - kibanaSiemAppUrl, + kibanaSiemAppUrl = '/app/siem', id, from, to, }: { - kibanaSiemAppUrl: string; + kibanaSiemAppUrl?: string; id: string; - from: string; - to: string; -}) => - `${kibanaSiemAppUrl}#/detections/rules/id/${id}?timerange=(global:(linkTo:!(timeline),timerange:(from:${from},kind:absolute,to:${to})),timeline:(linkTo:!(global),timerange:(from:${from},kind:absolute,to:${to})))`; + from?: string; + to?: string; +}) => { + if (from == null || to == null) return ''; + + return `${kibanaSiemAppUrl}#/detections/rules/id/${id}?timerange=(global:(linkTo:!(timeline),timerange:(from:${from},kind:absolute,to:${to})),timeline:(linkTo:!(global),timerange:(from:${from},kind:absolute,to:${to})))`; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 3b24b722f7356..e400360a5a5b2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -383,6 +383,7 @@ export const createActionResult = (): ActionResult => ({ actionTypeId: 'action-id-1', name: '', config: {}, + isPreconfigured: false, }); export const nonRuleAlert = () => ({ @@ -450,25 +451,31 @@ export const getResult = (): RuleAlertType => ({ lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, @@ -512,6 +519,7 @@ export const updateActionResult = (): ActionResult => ({ actionTypeId: 'action-id-1', name: '', config: {}, + isPreconfigured: false, }); export const getMockPrivilegesResult = () => ({ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/utils.ts index 63fe51f2c81cd..c929b0718207d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/utils.ts @@ -141,25 +141,31 @@ export const getOutputRuleAlertForRest = (): Omit< lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index d0e36515946a8..5377e9039785e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -144,6 +144,7 @@ export const createRulesBulkRoute = (router: IRouter) => { note, version, lists, + actions: throttle === 'rule' ? actions : [], // Only enable actions if throttle is set to rule, otherwise we are a notification and should not enable it, }); const ruleActions = await updateRulesNotifications({ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts index 6038ad2095323..9a329b78b8f12 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -132,6 +132,7 @@ export const createRulesRoute = (router: IRouter): void => { note, version: 1, lists, + actions: throttle === 'rule' ? actions : [], // Only enable actions if throttle is rule, otherwise we are a notification and should not enable it, }); const ruleActions = await updateRulesNotifications({ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts index 43e970702ba72..29ae5056a3ae8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -196,6 +196,7 @@ export const importRulesRoute = (router: IRouter, config: LegacyServices['config note, version, lists, + actions: [], // Actions are not imported nor exported at this time }); resolve({ rule_id: ruleId, status_code: 200 }); } else if (rule != null && request.query.overwrite) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts index 85255594ee480..8c0fceb7a5f29 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_bulk_route.ts @@ -121,15 +121,15 @@ export const patchRulesBulkRoute = (router: IRouter) => { anomalyThreshold, machineLearningJobId, }); - if (rule != null) { + if (rule != null && rule.enabled != null && rule.name != null) { const ruleActions = await updateRulesNotifications({ ruleAlertId: rule.id, alertsClient, savedObjectsClient, - enabled: rule.enabled!, + enabled: rule.enabled, actions, throttle, - name: rule.name!, + name: rule.name, }); const ruleStatuses = await savedObjectsClient.find< IRuleSavedAttributesSavedObjectAttributes diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts index f553ccd2c6f81..9c5000d70e5fe 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/patch_rules_route.ts @@ -117,15 +117,15 @@ export const patchRulesRoute = (router: IRouter) => { anomalyThreshold, machineLearningJobId, }); - if (rule != null) { + if (rule != null && rule.enabled != null && rule.name != null) { const ruleActions = await updateRulesNotifications({ ruleAlertId: rule.id, alertsClient, savedObjectsClient, - enabled: rule.enabled!, + enabled: rule.enabled, actions, throttle, - name: rule.name!, + name: rule.name, }); const ruleStatuses = await savedObjectsClient.find< IRuleSavedAttributesSavedObjectAttributes diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index 9916972f41843..36e15780f5cb3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -122,6 +122,7 @@ export const updateRulesBulkRoute = (router: IRouter) => { note, version, lists, + actions, }); if (rule != null) { const ruleActions = await updateRulesNotifications({ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts index 21dd2a4429cca..0444c757a9b31 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -118,6 +118,7 @@ export const updateRulesRoute = (router: IRouter) => { note, version, lists, + actions: throttle === 'rule' ? actions : [], // Only enable actions if throttle is rule, otherwise we are a notification and should not enable it }); if (rule != null) { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts index 77e05796fbcbe..7537401e5a366 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/validate.test.ts @@ -74,25 +74,31 @@ export const ruleOutput: RulesSchema = { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts index b10627d151fa2..8c741c937bf15 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts @@ -1561,25 +1561,31 @@ describe('add prepackaged rules schema', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts index 08bd01ee9a1a0..e56e8e5fe34d3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts @@ -1526,25 +1526,31 @@ describe('create rules schema', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts index c8e5bb981f921..40f7b19ea12b3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts @@ -1747,25 +1747,31 @@ describe('import rules schema', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/patch_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/patch_rules_schema.test.ts index 45b5028f392b9..e01a8f40fcea4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/patch_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/patch_rules_schema.test.ts @@ -1229,25 +1229,31 @@ describe('patch rules schema', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, @@ -1263,25 +1269,28 @@ describe('patch rules schema', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + ], }, ], }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/response/__mocks__/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/response/__mocks__/utils.ts index 46cd1b653b5b4..d5ea950d163f5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/response/__mocks__/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/response/__mocks__/utils.ts @@ -66,25 +66,31 @@ export const getBaseResponsePayload = (anchorDate: string = ANCHOR_DATE): RulesS lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/response/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/response/schemas.ts index 538c8f754fd6e..faae1dde83545 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/response/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/response/schemas.ts @@ -154,13 +154,34 @@ export const note = t.string; // NOTE: Experimental list support not being shipped currently and behind a feature flag // TODO: Remove this comment once we lists have passed testing and is ready for the release -export const boolean_operator = t.keyof({ and: null, 'and not': null }); -export const list_type = t.keyof({ value: null }); // TODO: (LIST-FEATURE) Eventually this can include "list" when we support lists CRUD -export const list_value = t.exact(t.type({ name: t.string, type: list_type })); +export const list_field = t.string; +export const list_values_operator = t.keyof({ included: null, excluded: null }); +export const list_values_type = t.keyof({ match: null, match_all: null, list: null, exists: null }); +export const list_values = t.exact( + t.intersection([ + t.type({ + name: t.string, + }), + t.partial({ + id: t.string, + description: t.string, + created_at, + }), + ]) +); export const list = t.exact( - t.type({ - field: t.string, - boolean_operator, - values: t.array(list_value), - }) + t.intersection([ + t.type({ + field: t.string, + values_operator: list_values_operator, + values_type: list_values_type, + }), + t.partial({ values: t.array(list_values) }), + ]) ); +export const list_and = t.intersection([ + list, + t.partial({ + and: t.array(list), + }), +]); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts index 16e419f389f09..c17ae8466a86c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts @@ -126,12 +126,28 @@ export const note = Joi.string(); // NOTE: Experimental list support not being shipped currently and behind a feature flag // TODO: (LIST-FEATURE) Remove this comment once we lists have passed testing and is ready for the release -export const boolean_operator = Joi.string().valid('and', 'and not'); -export const list_type = Joi.string().valid('value'); // TODO: (LIST-FEATURE) Eventually this can be "list" when we support list types -export const list_value = Joi.object({ name: Joi.string().required(), type: list_type.required() }); +export const list_field = Joi.string(); +export const list_values_operator = Joi.string().valid(['included', 'excluded']); +export const list_values_types = Joi.string().valid(['match', 'match_all', 'list', 'exists']); +export const list_values = Joi.object({ + name: Joi.string().required(), + id: Joi.string(), + description: Joi.string(), + created_at, +}); export const list = Joi.object({ - field: Joi.string().required(), - boolean_operator: boolean_operator.required(), - values: Joi.array().items(list_value), + field: list_field.required(), + values_operator: list_values_operator.required(), + values_type: list_values_types.required(), + values: Joi.when('values_type', { + is: 'exists', + then: Joi.forbidden(), + otherwise: Joi.array() + .items(list_values) + .required(), + }), +}); +export const list_and = Joi.object({ + and: Joi.array().items(list), }); -export const lists = Joi.array().items(list); +export const lists = Joi.array().items(list.concat(list_and)); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts index 14df1c3d8cd55..e8a9c7b0886a1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.test.ts @@ -23,25 +23,79 @@ describe('lists_default_array', () => { const payload = [ { field: 'source.ip', - boolean_operator: 'and', + values_operator: 'included', + values_type: 'exists', + }, + { + field: 'host.name', + values_operator: 'excluded', + values_type: 'match', values: [ { - name: '127.0.0.1', - type: 'value', + name: 'rock01', + }, + ], + and: [ + { + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, + ]; + const decoded = ListsDefaultArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should not validate an array of lists that includes a values_operator other than included or excluded', () => { + const payload = [ + { + field: 'source.ip', + values_operator: 'included', + values_type: 'exists', + }, + { + field: 'host.name', + values_operator: 'excluded', + values_type: 'exists', + }, + { + field: 'host.hostname', + values_operator: 'jibber jabber', + values_type: 'exists', + }, + ]; + const decoded = ListsDefaultArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "jibber jabber" supplied to "values_operator"', + ]); + expect(message.schema).toEqual({}); + }); + + // TODO - this scenario should never come up, as the values key is forbidden when values_type is "exists" in the incoming schema - need to find a good way to do this in io-ts + test('it will validate an array of lists that includes "values" when "values_type" is "exists"', () => { + const payload = [ { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'exists', values: [ { - name: 'rock01', - type: 'value', - }, - { - name: 'mothra', - type: 'value', + name: '127.0.0.1', }, ], }, @@ -53,15 +107,63 @@ describe('lists_default_array', () => { expect(message.schema).toEqual(payload); }); + // TODO - this scenario should never come up, as the values key is required when values_type is "match" in the incoming schema - need to find a good way to do this in io-ts + test('it will validate an array of lists that does not include "values" when "values_type" is "match"', () => { + const payload = [ + { + field: 'host.name', + values_operator: 'excluded', + values_type: 'match', + }, + ]; + const decoded = ListsDefaultArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + // TODO - this scenario should never come up, as the values key is required when values_type is "match_all" in the incoming schema - need to find a good way to do this in io-ts + test('it will validate an array of lists that does not include "values" when "values_type" is "match_all"', () => { + const payload = [ + { + field: 'host.name', + values_operator: 'excluded', + values_type: 'match_all', + }, + ]; + const decoded = ListsDefaultArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + // TODO - this scenario should never come up, as the values key is required when values_type is "list" in the incoming schema - need to find a good way to do this in io-ts + test('it should not validate an array of lists that does not include "values" when "values_type" is "list"', () => { + const payload = [ + { + field: 'host.name', + values_operator: 'excluded', + values_type: 'list', + }, + ]; + const decoded = ListsDefaultArray.decode(payload); + const message = pipe(decoded, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + test('it should not validate an array with a number', () => { const payload = [ { field: 'source.ip', - boolean_operator: 'and', + values_operator: 'included', + values_type: 'exists', values: [ { name: '127.0.0.1', - type: 'value', }, ], }, @@ -70,7 +172,10 @@ describe('lists_default_array', () => { const decoded = ListsDefaultArray.decode(payload); const message = pipe(decoded, foldLeftRight); - expect(getPaths(left(message.errors))).toEqual(['Invalid value "5" supplied to ""']); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "5" supplied to ""', + 'Invalid value "5" supplied to ""', + ]); expect(message.schema).toEqual({}); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.ts index 0e0944a11b416..85a38e296494a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/types/lists_default_array.ts @@ -7,10 +7,10 @@ import * as t from 'io-ts'; import { Either } from 'fp-ts/lib/Either'; -import { list } from '../response/schemas'; +import { list_and as listAnd } from '../response/schemas'; export type ListsDefaultArrayC = t.Type; -type List = t.TypeOf; +type List = t.TypeOf; /** * Types the ListsDefaultArray as: @@ -18,9 +18,9 @@ type List = t.TypeOf; */ export const ListsDefaultArray: ListsDefaultArrayC = new t.Type( 'listsWithDefaultArray', - t.array(list).is, + t.array(listAnd).is, (input): Either => - input == null ? t.success([]) : t.array(list).decode(input), + input == null ? t.success([]) : t.array(listAnd).decode(input), t.identity ); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts index 6f6beea7fa5fb..e8f9aad620ca0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts @@ -1552,25 +1552,28 @@ describe('create rules schema', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + ], }, ], }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts index 8d7360bae8eb9..e4015ad8bafa4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts @@ -309,7 +309,7 @@ export const validateLicenseForRuleType = ({ }: { license: ILicense; ruleType: RuleType; -}) => { +}): void => { if (isMlRule(ruleType) && !license.hasAtLeast(MINIMUM_ML_LICENSE)) { const message = i18n.translate('xpack.siem.licensing.unsupportedMachineLearningMessage', { defaultMessage: diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts index 23c99b36cb4a7..991690d901d8a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/create_rule_actions_saved_object.ts @@ -9,12 +9,13 @@ import { AlertServices } from '../../../../../../../plugins/alerting/server'; import { ruleActionsSavedObjectType } from './saved_object_mappings'; import { IRuleActionsAttributesSavedObjectAttributes } from './types'; import { getThrottleOptions, getRuleActionsFromSavedObject } from './utils'; +import { RulesActionsSavedObject } from './get_rule_actions_saved_object'; interface CreateRuleActionsSavedObject { ruleAlertId: string; savedObjectsClient: AlertServices['savedObjectsClient']; actions: RuleAlertAction[] | undefined; - throttle: string | undefined; + throttle: string | null | undefined; } export const createRuleActionsSavedObject = async ({ @@ -22,7 +23,7 @@ export const createRuleActionsSavedObject = async ({ savedObjectsClient, actions = [], throttle, -}: CreateRuleActionsSavedObject) => { +}: CreateRuleActionsSavedObject): Promise => { const ruleActionsSavedObject = await savedObjectsClient.create< IRuleActionsAttributesSavedObjectAttributes >(ruleActionsSavedObjectType, { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts index 4e8781dd45692..91489334940bd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/delete_rule_actions_saved_object.ts @@ -16,10 +16,11 @@ interface DeleteRuleActionsSavedObject { export const deleteRuleActionsSavedObject = async ({ ruleAlertId, savedObjectsClient, -}: DeleteRuleActionsSavedObject) => { +}: DeleteRuleActionsSavedObject): Promise<{} | null> => { const ruleActions = await getRuleActionsSavedObject({ ruleAlertId, savedObjectsClient }); - - if (!ruleActions) return null; - - return savedObjectsClient.delete(ruleActionsSavedObjectType, ruleActions.id); + if (ruleActions != null) { + return savedObjectsClient.delete(ruleActionsSavedObjectType, ruleActions.id); + } else { + return null; + } }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts index 3ae9090526d69..dad35f6cb1f96 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/get_rule_actions_saved_object.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { AlertServices } from '../../../../../../../plugins/alerting/server'; import { ruleActionsSavedObjectType } from './saved_object_mappings'; import { IRuleActionsAttributesSavedObjectAttributes } from './types'; @@ -14,10 +15,17 @@ interface GetRuleActionsSavedObject { savedObjectsClient: AlertServices['savedObjectsClient']; } +export interface RulesActionsSavedObject { + id: string; + actions: RuleAlertAction[]; + alertThrottle: string | null; + ruleThrottle: string; +} + export const getRuleActionsSavedObject = async ({ ruleAlertId, savedObjectsClient, -}: GetRuleActionsSavedObject) => { +}: GetRuleActionsSavedObject): Promise => { const { saved_objects } = await savedObjectsClient.find< IRuleActionsAttributesSavedObjectAttributes >({ @@ -29,7 +37,7 @@ export const getRuleActionsSavedObject = async ({ if (!saved_objects[0]) { return null; + } else { + return getRuleActionsFromSavedObject(saved_objects[0]); } - - return getRuleActionsFromSavedObject(saved_objects[0]); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts index 3856f75255262..d79c61f6200e3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_or_create_rule_actions_saved_object.ts @@ -15,7 +15,7 @@ interface UpdateOrCreateRuleActionsSavedObject { ruleAlertId: string; savedObjectsClient: AlertServices['savedObjectsClient']; actions: RuleAlertAction[] | undefined; - throttle: string | undefined; + throttle: string | null | undefined; } export const updateOrCreateRuleActionsSavedObject = async ({ @@ -24,16 +24,17 @@ export const updateOrCreateRuleActionsSavedObject = async ({ actions, throttle, }: UpdateOrCreateRuleActionsSavedObject): Promise => { - const currentRuleActions = await getRuleActionsSavedObject({ ruleAlertId, savedObjectsClient }); + const ruleActions = await getRuleActionsSavedObject({ ruleAlertId, savedObjectsClient }); - if (currentRuleActions) { + if (ruleActions != null) { return updateRuleActionsSavedObject({ ruleAlertId, savedObjectsClient, actions, throttle, - }) as Promise; + ruleActions, + }); + } else { + return createRuleActionsSavedObject({ ruleAlertId, savedObjectsClient, actions, throttle }); } - - return createRuleActionsSavedObject({ ruleAlertId, savedObjectsClient, actions, throttle }); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts index 56bce3c8b67a3..2a2c84838ed93 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/update_rule_actions_saved_object.ts @@ -6,7 +6,7 @@ import { AlertServices } from '../../../../../../../plugins/alerting/server'; import { ruleActionsSavedObjectType } from './saved_object_mappings'; -import { getRuleActionsSavedObject } from './get_rule_actions_saved_object'; +import { RulesActionsSavedObject } from './get_rule_actions_saved_object'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { getThrottleOptions } from './utils'; import { IRuleActionsAttributesSavedObjectAttributes } from './types'; @@ -15,7 +15,8 @@ interface DeleteRuleActionsSavedObject { ruleAlertId: string; savedObjectsClient: AlertServices['savedObjectsClient']; actions: RuleAlertAction[] | undefined; - throttle: string | undefined; + throttle: string | null | undefined; + ruleActions: RulesActionsSavedObject; } export const updateRuleActionsSavedObject = async ({ @@ -23,11 +24,8 @@ export const updateRuleActionsSavedObject = async ({ savedObjectsClient, actions, throttle, -}: DeleteRuleActionsSavedObject) => { - const ruleActions = await getRuleActionsSavedObject({ ruleAlertId, savedObjectsClient }); - - if (!ruleActions) return null; - + ruleActions, +}: DeleteRuleActionsSavedObject): Promise => { const throttleOptions = throttle ? getThrottleOptions(throttle) : { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/utils.ts index 3c297ed848555..554c2b4a3dd90 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rule_actions/utils.ts @@ -5,16 +5,27 @@ */ import { SavedObjectsUpdateResponse } from 'kibana/server'; +import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { IRuleActionsAttributesSavedObjectAttributes } from './types'; -export const getThrottleOptions = (throttle = 'no_actions') => ({ - ruleThrottle: throttle, - alertThrottle: ['no_actions', 'rule'].includes(throttle) ? null : throttle, +export const getThrottleOptions = ( + throttle: string | undefined | null = 'no_actions' +): { + ruleThrottle: string; + alertThrottle: string | null; +} => ({ + ruleThrottle: throttle ?? 'no_actions', + alertThrottle: ['no_actions', 'rule'].includes(throttle ?? 'no_actions') ? null : throttle, }); export const getRuleActionsFromSavedObject = ( savedObject: SavedObjectsUpdateResponse -) => ({ +): { + id: string; + actions: RuleAlertAction[]; + alertThrottle: string | null; + ruleThrottle: string; +} => ({ id: savedObject.id, actions: savedObject.attributes.actions || [], alertThrottle: savedObject.attributes.alertThrottle || null, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts index 4c8d0f51f251b..a60f1d4177978 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.test.ts @@ -34,6 +34,7 @@ describe('createRules', () => { interval: '', name: '', tags: [], + actions: [], }); expect(alertsClient.create).toHaveBeenCalledWith( diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts index bebf4f350483b..91effb4741b8b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; import { Alert } from '../../../../../../../plugins/alerting/common'; import { APP_ID, SIGNALS_ID } from '../../../../common/constants'; import { CreateRuleParams } from './types'; @@ -42,6 +43,7 @@ export const createRules = async ({ note, version, lists, + actions, }: CreateRuleParams): Promise => { // TODO: Remove this and use regular lists once the feature is stable for a release const listsParam = hasListsFeature() ? { lists } : {}; @@ -81,7 +83,7 @@ export const createRules = async ({ }, schedule: { interval }, enabled, - actions: [], + actions: actions.map(transformRuleToAlertAction), throttle: null, }, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_all.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_all.test.ts index ca6fb15e1fad9..dd004e3685b1d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_all.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_all.test.ts @@ -82,25 +82,31 @@ describe('getExportAll', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts index 175c906f7996c..715cb23e8444a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts @@ -90,25 +90,31 @@ describe('get_export_by_object_ids', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, @@ -212,25 +218,31 @@ describe('get_export_by_object_ids', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts index bcbe460fb6a66..6d4bacb9cc243 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -83,6 +83,7 @@ export const installPrepackagedRules = ( note, version, lists, + actions: [], // At this time there is no pre-packaged actions }), ]; }, []); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts index d7655a15499eb..5c4889ec5fd68 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/patch_rules.ts @@ -120,7 +120,7 @@ export const patchRules = async ({ id: rule.id, data: { tags: addTags(tags ?? rule.tags, rule.params.ruleId, immutable ?? rule.params.immutable), - throttle: rule.throttle, + throttle: null, name: calculateName({ updatedName: name, originalName: rule.name }), schedule: { interval: calculateInterval(interval, rule.schedule.interval), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/403_response_to_a_post.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/403_response_to_a_post.json index 3b043439759c1..d4118d0686b11 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/403_response_to_a_post.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/403_response_to_a_post.json @@ -20,5 +20,5 @@ "Elastic" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/405_response_method_not_allowed.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/405_response_method_not_allowed.json index 12c6a5feabebb..da27f0a71d281 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/405_response_method_not_allowed.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/405_response_method_not_allowed.json @@ -20,5 +20,5 @@ "Elastic" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_adversary_behavior_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_adversary_behavior_detected.json index a3302896b7e98..cfc322788d4be 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_adversary_behavior_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_adversary_behavior_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Adversary Behavior - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:rules_engine_event", + "query": "event.kind:alert and event.module:endgame and (event.action:rules_engine_event or endgame.event_subtype_full:rules_engine_event)", "risk_score": 47, "rule_id": "77a3c3df-8ec4-4da4-b758-878f551dee69", "severity": "medium", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_detected.json index 8c2c5f32feab7..0647fe9c9ce10 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Credential Dumping - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:detection", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:cred_theft_event or endgame.event_subtype_full:cred_theft_event)", "risk_score": 73, "rule_id": "571afc56-5ed9-465d-a2a9-045f099f6e7e", "severity": "high", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_prevented.json index 6a96da3218bf2..036c88688d9bd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_prevented.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Credential Dumping - Prevented - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:prevention", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:cred_theft_event or endgame.event_subtype_full:cred_theft_event)", "risk_score": 47, "rule_id": "db8c33a8-03cd-4988-9e2c-d0a4863adb13", "severity": "medium", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_detected.json index 954e35ccd644a..0fe610d551152 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Credential Manipulation - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:token_manipulation_event and endgame.metadata.type:detection", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:token_manipulation_event or endgame.event_subtype_full:token_manipulation_event)", "risk_score": 73, "rule_id": "c0be5f31-e180-48ed-aa08-96b36899d48f", "severity": "high", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_prevented.json index 0de35891a3e81..a317c77bcd90a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_prevented.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Credential Manipulation - Prevented - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:token_manipulation_event and endgame.metadata.type:prevention", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:token_manipulation_event or endgame.event_subtype_full:token_manipulation_event)", "risk_score": 47, "rule_id": "c9e38e64-3f4c-4bf3-ad48-0e61a60ea1fa", "severity": "medium", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_detected.json index 3652b7068ecd2..97640c0cea9b2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Exploit - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:exploit_event and endgame.metadata.type:detection", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:exploit_event or endgame.event_subtype_full:exploit_event)", "risk_score": 73, "rule_id": "2003cdc8-8d83-4aa5-b132-1f9a8eb48514", "severity": "high", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_prevented.json index dbc910c3002a7..069687a5af00f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_prevented.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Exploit - Prevented - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:exploit_event and endgame.metadata.type:prevention", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:exploit_event or endgame.event_subtype_full:exploit_event)", "risk_score": 47, "rule_id": "2863ffeb-bf77-44dd-b7a5-93ef94b72036", "severity": "medium", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_detected.json index efe2806532be0..a7d3371190ced 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Malware - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:file_classification_event and endgame.metadata.type:detection", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:file_classification_event or endgame.event_subtype_full:file_classification_event)", "risk_score": 99, "rule_id": "0a97b20f-4144-49ea-be32-b540ecc445de", "severity": "critical", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_prevented.json index 51028b9dbeeb3..dd7bf72c34f90 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_prevented.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Malware - Prevented - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:file_classification_event and endgame.metadata.type:prevention", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:file_classification_event or endgame.event_subtype_full:file_classification_event)", "risk_score": 73, "rule_id": "3b382770-efbb-44f4-beed-f5e0a051b895", "severity": "high", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_detected.json index c30ca0632f410..a8e102cc4619d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Permission Theft - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:token_protection_event and endgame.metadata.type:detection", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:token_protection_event or endgame.event_subtype_full:token_protection_event)", "risk_score": 73, "rule_id": "c3167e1b-f73c-41be-b60b-87f4df707fe3", "severity": "high", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_prevented.json index ed0c714254743..c97330f2349eb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_prevented.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Permission Theft - Prevented - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:token_protection_event and endgame.metadata.type:prevention", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:token_protection_event or endgame.event_subtype_full:token_protection_event)", "risk_score": 47, "rule_id": "453f659e-0429-40b1-bfdb-b6957286e04b", "severity": "medium", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_detected.json index 63b008849487a..e644c0e8d66eb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Process Injection - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:kernel_shellcode_event and endgame.metadata.type:detection", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:kernel_shellcode_event or endgame.event_subtype_full:kernel_shellcode_event)", "risk_score": 73, "rule_id": "80c52164-c82a-402c-9964-852533d58be1", "severity": "high", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_prevented.json index 135b4a95e8005..61cbe267f9a46 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_prevented.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Process Injection - Prevented - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:kernel_shellcode_event and endgame.metadata.type:prevention", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:kernel_shellcode_event or endgame.event_subtype_full:kernel_shellcode_event)", "risk_score": 47, "rule_id": "990838aa-a953-4f3e-b3cb-6ddf7584de9e", "severity": "medium", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_detected.json index d4042a5e6b9e1..0e88b26cb2c75 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_detected.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Ransomware - Detected - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:ransomware_event and endgame.metadata.type:detection", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:detection and (event.action:ransomware_event or endgame.event_subtype_full:ransomware_event)", "risk_score": 99, "rule_id": "8cb4f625-7743-4dfb-ae1b-ad92be9df7bd", "severity": "critical", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_prevented.json index befdf611da223..ba341f059f26d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_prevented.json @@ -7,7 +7,7 @@ "interval": "10m", "language": "kuery", "name": "Ransomware - Prevented - Elastic Endpoint", - "query": "event.kind:alert and event.module:endgame and event.action:ransomware_event and endgame.metadata.type:prevention", + "query": "event.kind:alert and event.module:endgame and endgame.metadata.type:prevention and (event.action:ransomware_event or endgame.event_subtype_full:ransomware_event)", "risk_score": 73, "rule_id": "e3c5d5cb-41d5-4206-805c-f30561eae3ac", "severity": "high", @@ -16,5 +16,5 @@ "Endpoint" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json index 6c9b54b8ddb02..25d2232d3f6dc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json @@ -46,5 +46,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json index 244d329cc4bb7..1c73d6c276ce6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json index 4087542816588..0bfa18398eada 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json index eca06723e68b8..e7293eda6390f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json index e37c877c62889..2896d27e19112 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json index f6b4bc67ed9b1..42fe51f4e0373 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json index 38162889737ff..eef112503da5b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json index 42007f153bd55..dbacb2537e60f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json @@ -34,5 +34,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json index 9559baabe0e40..648e83b4a5267 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json index 3e34aacf605c7..5e8b260d44b55 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json index 769614e8faf53..88bd248e258d8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json @@ -34,5 +34,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json index ac170665042f6..f763d2aa03363 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json @@ -49,5 +49,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json index 1c001caa1539c..f1b1879fc2652 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json index 0165f4d7512e4..2a7960c939d01 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json index 0b4bf9ff32945..9a28c87c77089 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json index 2c88a2061844c..43a3d6f6af0b2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json index 240df34419132..7054e7f67c358 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json index e12c2e70138c9..24f1cb72504f3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json index 94b8846741e3e..bad3c65024e42 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json index b0a754a662c0e..52323b169cb22 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json index bb8e8983661e6..04a56241ea6f6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json @@ -20,5 +20,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json index 4e49702855a76..80358cc775e3b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json @@ -20,5 +20,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json index cf8cd72b7aa6f..b50fcc4c9980b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json @@ -37,5 +37,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json index 3bd3848c07581..d65440e95ff17 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json @@ -20,5 +20,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json index cd523b6594ccd..df8e46be7a1c3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json @@ -22,5 +22,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json index 604cfa172fd84..2e5c899ebc625 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json @@ -20,5 +20,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json index 8e71b5b906711..168b30121c4bb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json @@ -20,5 +20,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json index c50026d7736ae..0865ac6c70cb2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json @@ -17,5 +17,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json index 01f117e0a225b..e9c4c95bb9284 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json @@ -37,5 +37,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json index a16b164e9ee4a..404fea63aff94 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json @@ -20,5 +20,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json index 9b18039b63fd0..fbdfa9e66682d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json @@ -20,5 +20,5 @@ "Linux" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_tcpdump_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_tcpdump_activity.json index 5ae48c8db9984..82771074e7c29 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_tcpdump_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_tcpdump_activity.json @@ -49,5 +49,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json index 7fef4e813da98..7e7f041581eb0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json @@ -34,5 +34,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/null_user_agent.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/null_user_agent.json index afbbb2a34d545..01246de5595e9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/null_user_agent.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/null_user_agent.json @@ -38,5 +38,5 @@ "Elastic" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/sqlmap_user_agent.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/sqlmap_user_agent.json index fd240262d021f..10412c19da1b1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/sqlmap_user_agent.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/sqlmap_user_agent.json @@ -20,5 +20,5 @@ "Elastic" ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_certutil_network_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_certutil_network_connection.json index 2cda21cf7d5ef..52a373e3aeb77 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_certutil_network_connection.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_certutil_network_connection.json @@ -4,7 +4,6 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, "name": "Network Connection via Certutil", "query": "process.name:certutil.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:(10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_prompt_connecting_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_prompt_connecting_to_the_internet.json index 2427ab4d7cc55..2bee265a74e11 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_prompt_connecting_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_prompt_connecting_to_the_internet.json @@ -49,5 +49,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_powershell.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_powershell.json index f8e5bd22576a4..d8f91dba7dd89 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_powershell.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_powershell.json @@ -46,5 +46,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_svchost.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_svchost.json index 71aafa9984ecb..6fd194ee2fa22 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_svchost.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_svchost.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_credential_dumping_msbuild.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_credential_dumping_msbuild.json index 4ff7891438554..43050e2769a24 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_credential_dumping_msbuild.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_credential_dumping_msbuild.json @@ -35,4 +35,4 @@ ], "type": "query", "version": 1 -} +} \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_cve_2020_0601.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_cve_2020_0601.json index c08bb7b3315f5..f5eb37c70d268 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_cve_2020_0601.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_cve_2020_0601.json @@ -4,7 +4,6 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, "name": "Windows CryptoAPI Spoofing Vulnerability (CVE-2020-0601 - CurveBall)", "query": "event.provider:\"Microsoft-Windows-Audit-CVE\" and message:\"[CVE-2020-0601]\"", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_defense_evasion_via_filter_manager.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_defense_evasion_via_filter_manager.json index 3f97f7aca74f6..0e8c5a5f2f631 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_defense_evasion_via_filter_manager.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_defense_evasion_via_filter_manager.json @@ -31,5 +31,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_compiled_html_file.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_compiled_html_file.json index 2b6e1fb3daaec..7755ff0233f7c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_compiled_html_file.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_compiled_html_file.json @@ -49,5 +49,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_net_com_assemblies.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_net_com_assemblies.json index c397c955fe64f..d6acb81c10e3f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_net_com_assemblies.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_net_com_assemblies.json @@ -4,7 +4,6 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, "name": "Execution via Regsvcs/Regasm", "query": "process.name:(RegAsm.exe or RegSvcs.exe) and event.action:\"Process Create (rule: ProcessCreate)\"", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_trusted_developer_utilities.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_trusted_developer_utilities.json index f60a986996d6f..87e38febb0743 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_trusted_developer_utilities.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_trusted_developer_utilities.json @@ -49,5 +49,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_html_help_executable_program_connecting_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_html_help_executable_program_connecting_to_the_internet.json index 4b3efead776d2..6c8cd0673256a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_html_help_executable_program_connecting_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_html_help_executable_program_connecting_to_the_internet.json @@ -46,5 +46,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_misc_lolbin_connecting_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_misc_lolbin_connecting_to_the_internet.json index 0cd68ba5c1ed8..a0e311d8eb154 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_misc_lolbin_connecting_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_misc_lolbin_connecting_to_the_internet.json @@ -46,5 +46,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_modification_of_boot_config.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_modification_of_boot_config.json index d761226276496..045a9789b1260 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_modification_of_boot_config.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_modification_of_boot_config.json @@ -4,7 +4,6 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, "name": "Modification of Boot Configuration", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:bcdedit.exe and process.args:(/set and (bootstatuspolicy and ignoreallfailures or no and recoveryenabled))", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_msxsl_network.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_msxsl_network.json index 9b45d03aae375..e80dcde1e398d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_msxsl_network.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_msxsl_network.json @@ -4,7 +4,6 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, "name": "Network Connection via MsXsl", "query": "process.name:msxsl.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:(10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_net_command_system_account.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_net_command_system_account.json index 390c9c278905c..c2379142df002 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_net_command_system_account.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_net_command_system_account.json @@ -4,7 +4,6 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, "name": "Net command via SYSTEM account", "query": "(process.name:net.exe or process.name:net1.exe and not process.parent.name:net.exe) and user.name:SYSTEM and event.action:\"Process Create (rule: ProcessCreate)\"", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_persistence_via_application_shimming.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_persistence_via_application_shimming.json index 0488667d06c82..2f44727f9e6f0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_persistence_via_application_shimming.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_persistence_via_application_shimming.json @@ -46,5 +46,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_priv_escalation_via_accessibility_features.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_priv_escalation_via_accessibility_features.json index 26f0a0bcc245c..aeff071ed4514 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_priv_escalation_via_accessibility_features.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_priv_escalation_via_accessibility_features.json @@ -46,5 +46,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_discovery_via_tasklist_command.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_discovery_via_tasklist_command.json index 28ebdb44fddd2..3a883fa51b763 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_discovery_via_tasklist_command.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_discovery_via_tasklist_command.json @@ -34,5 +34,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_register_server_program_connecting_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_register_server_program_connecting_to_the_internet.json index 920ff28a9a9cd..1e061f2ef9463 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_register_server_program_connecting_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_register_server_program_connecting_to_the_internet.json @@ -49,5 +49,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_uac_bypass_event_viewer.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_uac_bypass_event_viewer.json index 0d4168640bc60..df7a6fe1285d1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_uac_bypass_event_viewer.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_uac_bypass_event_viewer.json @@ -4,7 +4,6 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, "name": "Bypass UAC via Event Viewer", "query": "process.parent.name:eventvwr.exe and event.action:\"Process Create (rule: ProcessCreate)\" and not process.executable:(\"C:\\Windows\\SysWOW64\\mmc.exe\" or \"C:\\Windows\\System32\\mmc.exe\")", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_whoami_command_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_whoami_command_activity.json index 46af0c5b586a5..93ce1f83dd64e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_whoami_command_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_whoami_command_activity.json @@ -34,5 +34,5 @@ } ], "type": "query", - "version": 1 + "version": 2 } \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts index 38b1097a845f8..b1bed5d716155 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts @@ -142,12 +142,12 @@ export interface Clients { actionsClient: ActionsClient; } -export type PatchRuleParams = Partial> & { +export type PatchRuleParams = Partial> & { id: string | undefined | null; savedObjectsClient: SavedObjectsClientContract; } & Clients; -export type UpdateRuleParams = Omit & { +export type UpdateRuleParams = Omit & { id: string | undefined | null; savedObjectsClient: SavedObjectsClientContract; } & Clients; @@ -157,7 +157,7 @@ export type DeleteRuleParams = Clients & { ruleId: string | undefined | null; }; -export type CreateRuleParams = Omit & { +export type CreateRuleParams = Omit & { ruleId: string; } & Clients; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rule_actions.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rule_actions.ts deleted file mode 100644 index ac10143c1d8d0..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rule_actions.ts +++ /dev/null @@ -1,50 +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 { AlertsClient, AlertServices } from '../../../../../../../plugins/alerting/server'; -import { getRuleActionsSavedObject } from '../rule_actions/get_rule_actions_saved_object'; -import { readRules } from './read_rules'; -import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; - -interface UpdateRuleActions { - alertsClient: AlertsClient; - savedObjectsClient: AlertServices['savedObjectsClient']; - ruleAlertId: string; -} - -export const updateRuleActions = async ({ - alertsClient, - savedObjectsClient, - ruleAlertId, -}: UpdateRuleActions) => { - const rule = await readRules({ alertsClient, id: ruleAlertId }); - if (rule == null) { - return null; - } - - const ruleActions = await getRuleActionsSavedObject({ - savedObjectsClient, - ruleAlertId, - }); - - if (!ruleActions) { - return null; - } - - return alertsClient.update({ - id: ruleAlertId, - data: { - actions: !ruleActions.alertThrottle - ? ruleActions.actions.map(transformRuleToAlertAction) - : [], - throttle: null, - name: rule.name, - tags: rule.tags, - schedule: rule.schedule, - params: rule.params, - }, - }); -}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts index ca299db6ace50..72f4cbcbe68e8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.test.ts @@ -35,6 +35,7 @@ describe('updateRules', () => { interval: '', name: '', tags: [], + actions: [], }); expect(alertsClient.disable).toHaveBeenCalledWith( @@ -61,6 +62,7 @@ describe('updateRules', () => { interval: '', name: '', tags: [], + actions: [], }); expect(alertsClient.enable).toHaveBeenCalledWith( @@ -89,6 +91,7 @@ describe('updateRules', () => { interval: '', name: '', tags: [], + actions: [], }); expect(alertsClient.update).toHaveBeenCalledWith( diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts index 0e70e05f4de78..99326768ed33b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { transformRuleToAlertAction } from '../../../../common/detection_engine/transform_actions'; import { PartialAlert } from '../../../../../../../plugins/alerting/server'; import { readRules } from './read_rules'; import { IRuleSavedAttributesSavedObjectAttributes, UpdateRuleParams } from './types'; @@ -46,6 +47,7 @@ export const updateRules = async ({ lists, anomalyThreshold, machineLearningJobId, + actions, }: UpdateRuleParams): Promise => { const rule = await readRules({ alertsClient, ruleId, id }); if (rule == null) { @@ -90,8 +92,8 @@ export const updateRules = async ({ tags: addTags(tags, rule.params.ruleId, rule.params.immutable), name, schedule: { interval }, - actions: rule.actions, - throttle: rule.throttle, + actions: actions.map(transformRuleToAlertAction), + throttle: null, params: { description, ruleId: rule.params.ruleId, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts index f70c591243a76..994a54048b71a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules_notifications.ts @@ -8,14 +8,14 @@ import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { AlertsClient, AlertServices } from '../../../../../../../plugins/alerting/server'; import { updateOrCreateRuleActionsSavedObject } from '../rule_actions/update_or_create_rule_actions_saved_object'; import { updateNotifications } from '../notifications/update_notifications'; -import { updateRuleActions } from './update_rule_actions'; +import { RuleActions } from '../rule_actions/types'; interface UpdateRulesNotifications { alertsClient: AlertsClient; savedObjectsClient: AlertServices['savedObjectsClient']; ruleAlertId: string; actions: RuleAlertAction[] | undefined; - throttle: string | undefined; + throttle: string | null | undefined; enabled: boolean; name: string; } @@ -28,7 +28,7 @@ export const updateRulesNotifications = async ({ enabled, name, throttle, -}: UpdateRulesNotifications) => { +}: UpdateRulesNotifications): Promise => { const ruleActions = await updateOrCreateRuleActionsSavedObject({ savedObjectsClient, ruleAlertId, @@ -36,19 +36,13 @@ export const updateRulesNotifications = async ({ throttle, }); - await updateRuleActions({ - alertsClient, - savedObjectsClient, - ruleAlertId, - }); - await updateNotifications({ alertsClient, ruleAlertId, enabled, name, actions: ruleActions.actions, - interval: ruleActions?.alertThrottle, + interval: ruleActions.alertThrottle, }); return ruleActions; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/get_action_instances.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/get_action_instances.sh index 7804439ce0734..750c5574f4a72 100755 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/get_action_instances.sh +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/get_action_instances.sh @@ -13,5 +13,5 @@ set -e # https://github.com/elastic/kibana/blob/master/x-pack/legacy/plugins/actions/README.md#get-apiaction_find-find-actions curl -s -k \ -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ - -X GET ${KIBANA_URL}${SPACE_URL}/api/action/_find \ + -X GET ${KIBANA_URL}${SPACE_URL}/api/action/_getAll \ | jq . diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/patches/update_list.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/patches/update_list.json index 8c86f4c85af1d..4db8724db4e13 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/patches/update_list.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/patches/update_list.json @@ -3,21 +3,28 @@ "lists": [ { "field": "source.ip", - "boolean_operator": "and", - "values": [ - { - "name": "127.0.0.1", - "type": "value" - } - ] + "values_operator": "excluded", + "values_type": "exists" }, { "field": "host.name", - "boolean_operator": "and not", + "values_operator": "included", + "values_type": "match", "values": [ { - "name": "rock01", - "type": "value" + "name": "rock01" + } + ], + "and": [ + { + "field": "host.id", + "values_operator": "included", + "values_type": "match_all", + "values": [ + { + "name": "123456" + } + ] } ] } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_list.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_list.json index f6856eec59966..997d03369a699 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_list.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_list.json @@ -9,25 +9,31 @@ "lists": [ { "field": "source.ip", - "boolean_operator": "and", - "values": [ - { - "name": "127.0.0.1", - "type": "value" - } - ] + "values_operator": "included", + "values_type": "exists" }, { "field": "host.name", - "boolean_operator": "and not", + "values_operator": "excluded", + "values_type": "match", "values": [ { - "name": "rock01", - "type": "value" - }, + "name": "rock01" + } + ], + "and": [ { - "name": "mothra", - "type": "value" + "field": "host.id", + "values_operator": "included", + "values_type": "match_all", + "values": [ + { + "name": "123" + }, + { + "name": "678" + } + ] } ] } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_list.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_list.json index 6704c9676fa56..66b198974f574 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_list.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_list.json @@ -9,21 +9,28 @@ "lists": [ { "field": "source.ip", - "boolean_operator": "and", - "values": [ - { - "name": "127.0.0.1", - "type": "value" - } - ] + "values_operator": "excluded", + "values_type": "exists" }, { "field": "host.name", - "boolean_operator": "and not", + "values_operator": "included", + "values_type": "match", "values": [ { - "name": "rock01", - "type": "value" + "name": "rock01" + } + ], + "and": [ + { + "field": "host.id", + "values_operator": "included", + "values_type": "match_all", + "values": [ + { + "name": "123456" + } + ] } ] } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts index 6d7d7e93d7e6e..7a211c5631da6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -47,25 +47,31 @@ export const sampleRuleAlertParams = ( lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts index f2c2b99bdac8c..f1729e35ce1f0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_bulk_body.test.ts @@ -93,25 +93,31 @@ describe('buildBulkBody', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, @@ -213,25 +219,31 @@ describe('buildBulkBody', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, @@ -331,25 +343,31 @@ describe('buildBulkBody', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, @@ -442,25 +460,31 @@ describe('buildBulkBody', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.test.ts index e360ceaf02f4d..e5183ed4df7bd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.test.ts @@ -82,25 +82,31 @@ describe('buildRule', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, @@ -159,25 +165,31 @@ describe('buildRule', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, @@ -235,25 +247,31 @@ describe('buildRule', () => { lists: [ { field: 'source.ip', - boolean_operator: 'and', - values: [ - { - name: '127.0.0.1', - type: 'value', - }, - ], + values_operator: 'included', + values_type: 'exists', }, { field: 'host.name', - boolean_operator: 'and not', + values_operator: 'excluded', + values_type: 'match', values: [ { name: 'rock01', - type: 'value', }, + ], + and: [ { - name: 'mothra', - type: 'value', + field: 'host.id', + values_operator: 'included', + values_type: 'match_all', + values: [ + { + name: '123', + }, + { + name: '678', + }, + ], }, ], }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts index 355041d9efbdb..ba8938f116fc6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/bulk_create_ml_signals.ts @@ -10,7 +10,7 @@ import { SearchResponse } from 'elasticsearch'; import { Logger } from '../../../../../../../../src/core/server'; import { AlertServices } from '../../../../../../../plugins/alerting/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { RuleTypeParams } from '../types'; +import { RuleTypeParams, RefreshTypes } from '../types'; import { singleBulkCreate, SingleBulkCreateResponse } from './single_bulk_create'; import { AnomalyResults, Anomaly } from '../../machine_learning'; @@ -29,6 +29,7 @@ interface BulkCreateMlSignalsParams { updatedBy: string; interval: string; enabled: boolean; + refresh: RefreshTypes; tags: string[]; throttle: string; } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts index 06652028b3741..81600b0b8dd9b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.test.ts @@ -34,7 +34,7 @@ describe('searchAfterAndBulkCreate', () => { test('if successful with empty search results', async () => { const sampleParams = sampleRuleAlertParams(); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: sampleEmptyDocSearchResults(), ruleParams: sampleParams, services: mockService, @@ -52,11 +52,13 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); expect(mockService.callCluster).toHaveBeenCalledTimes(0); expect(success).toEqual(true); + expect(createdSignalsCount).toEqual(0); }); test('if successful iteration of while loop with maxDocs', async () => { @@ -70,6 +72,11 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }) .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(0, 3))) @@ -80,6 +87,11 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }) .mockReturnValueOnce(repeatedSearchResultsWithSortId(3, 1, someGuids.slice(3, 6))) @@ -90,9 +102,14 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(3, 1, someGuids.slice(6, 9)), ruleParams: sampleParams, services: mockService, @@ -110,18 +127,20 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); expect(mockService.callCluster).toHaveBeenCalledTimes(5); expect(success).toEqual(true); + expect(createdSignalsCount).toEqual(3); }); test('if unsuccessful first bulk create', async () => { const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); const sampleParams = sampleRuleAlertParams(10); mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -139,11 +158,13 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); expect(mockLogger.error).toHaveBeenCalled(); expect(success).toEqual(false); + expect(createdSignalsCount).toEqual(1); }); test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids', async () => { @@ -155,9 +176,14 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: sampleDocSearchResultsNoSortId(), ruleParams: sampleParams, services: mockService, @@ -175,11 +201,13 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); expect(mockLogger.error).toHaveBeenCalled(); expect(success).toEqual(false); + expect(createdSignalsCount).toEqual(1); }); test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids and 0 total hits', async () => { @@ -191,9 +219,14 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: sampleDocSearchResultsNoSortIdNoHits(), ruleParams: sampleParams, services: mockService, @@ -211,10 +244,12 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdSignalsCount).toEqual(1); }); test('if successful iteration of while loop with maxDocs and search after returns results with no sort ids', async () => { @@ -228,10 +263,15 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }) .mockReturnValueOnce(sampleDocSearchResultsNoSortId()); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -249,10 +289,12 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdSignalsCount).toEqual(1); }); test('if successful iteration of while loop with maxDocs and search after returns empty results with no sort ids', async () => { @@ -266,10 +308,15 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }) .mockReturnValueOnce(sampleEmptyDocSearchResults()); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -287,10 +334,12 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdSignalsCount).toEqual(1); }); test('if returns false when singleSearchAfter throws an exception', async () => { @@ -304,12 +353,17 @@ describe('searchAfterAndBulkCreate', () => { { fakeItemValue: 'fakeItemKey', }, + { + create: { + status: 201, + }, + }, ], }) .mockImplementation(() => { throw Error('Fake Error'); }); - const { success } = await searchAfterAndBulkCreate({ + const { success, createdSignalsCount } = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, services: mockService, @@ -327,9 +381,11 @@ describe('searchAfterAndBulkCreate', () => { enabled: true, pageSize: 1, filter: undefined, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); expect(success).toEqual(false); + expect(createdSignalsCount).toEqual(1); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts index a5d5dd0a7b710..3a964cb91fbdb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -6,7 +6,7 @@ import { AlertServices } from '../../../../../../../plugins/alerting/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { RuleTypeParams } from '../types'; +import { RuleTypeParams, RefreshTypes } from '../types'; import { Logger } from '../../../../../../../../src/core/server'; import { singleSearchAfter } from './single_search_after'; import { singleBulkCreate } from './single_bulk_create'; @@ -30,6 +30,7 @@ interface SearchAfterAndBulkCreateParams { enabled: boolean; pageSize: number; filter: unknown; + refresh: RefreshTypes; tags: string[]; throttle: string; } @@ -39,6 +40,7 @@ export interface SearchAfterAndBulkCreateReturnType { searchAfterTimes: string[]; bulkCreateTimes: string[]; lastLookBackDate: Date | null | undefined; + createdSignalsCount: number; } // search_after through documents and re-index using bulk endpoint. @@ -60,6 +62,7 @@ export const searchAfterAndBulkCreate = async ({ interval, enabled, pageSize, + refresh, tags, throttle, }: SearchAfterAndBulkCreateParams): Promise => { @@ -68,6 +71,7 @@ export const searchAfterAndBulkCreate = async ({ searchAfterTimes: [], bulkCreateTimes: [], lastLookBackDate: null, + createdSignalsCount: 0, }; if (someResult.hits.hits.length === 0) { toReturn.success = true; @@ -75,7 +79,7 @@ export const searchAfterAndBulkCreate = async ({ } logger.debug('[+] starting bulk insertion'); - const { bulkCreateDuration } = await singleBulkCreate({ + const { bulkCreateDuration, createdItemsCount } = await singleBulkCreate({ someResult, ruleParams, services, @@ -90,6 +94,7 @@ export const searchAfterAndBulkCreate = async ({ updatedBy, interval, enabled, + refresh, tags, throttle, }); @@ -97,6 +102,9 @@ export const searchAfterAndBulkCreate = async ({ someResult.hits.hits.length > 0 ? new Date(someResult.hits.hits[someResult.hits.hits.length - 1]?._source['@timestamp']) : null; + if (createdItemsCount) { + toReturn.createdSignalsCount = createdItemsCount; + } if (bulkCreateDuration) { toReturn.bulkCreateTimes.push(bulkCreateDuration); } @@ -156,7 +164,10 @@ export const searchAfterAndBulkCreate = async ({ } sortId = sortIds[0]; logger.debug('next bulk index'); - const { bulkCreateDuration: bulkDuration } = await singleBulkCreate({ + const { + bulkCreateDuration: bulkDuration, + createdItemsCount: createdCount, + } = await singleBulkCreate({ someResult: searchResult, ruleParams, services, @@ -171,10 +182,12 @@ export const searchAfterAndBulkCreate = async ({ updatedBy, interval, enabled, + refresh, tags, throttle, }); logger.debug('finished next bulk index'); + toReturn.createdSignalsCount += createdCount; if (bulkDuration) { toReturn.bulkCreateTimes.push(bulkDuration); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts new file mode 100644 index 0000000000000..03fb5832fdf42 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -0,0 +1,431 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { savedObjectsClientMock } from 'src/core/server/mocks'; +import { loggerMock } from 'src/core/server/logging/logger.mock'; +import { getResult, getMlResult } from '../routes/__mocks__/request_responses'; +import { signalRulesAlertType } from './signal_rule_alert_type'; +import { AlertInstance } from '../../../../../../../plugins/alerting/server'; +import { ruleStatusServiceFactory } from './rule_status_service'; +import { getGapBetweenRuns } from './utils'; +import { RuleExecutorOptions } 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'; +import { bulkCreateMlSignals } from './bulk_create_ml_signals'; + +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('../notifications/schedule_notification_actions'); +jest.mock('./find_ml_signals'); +jest.mock('./bulk_create_ml_signals'); + +const getPayload = ( + ruleAlert: RuleAlertType, + alertInstanceFactoryMock: () => AlertInstance, + savedObjectsClient: ReturnType, + callClusterMock: jest.Mock +) => ({ + alertId: ruleAlert.id, + services: { + savedObjectsClient, + alertInstanceFactory: alertInstanceFactoryMock, + callCluster: callClusterMock, + }, + params: { + ...ruleAlert.params, + actions: [], + enabled: ruleAlert.enabled, + interval: ruleAlert.schedule.interval, + name: ruleAlert.name, + tags: ruleAlert.tags, + throttle: ruleAlert.throttle, + scrollSize: 10, + scrollLock: '0', + }, + state: {}, + spaceId: '', + name: 'name', + tags: [], + startedAt: new Date('2019-12-13T16:50:33.400Z'), + previousStartedAt: new Date('2019-12-13T16:40:33.400Z'), + createdBy: 'elastic', + updatedBy: 'elastic', +}); + +describe('rules_notification_alert_type', () => { + const version = '8.0.0'; + const jobsSummaryMock = jest.fn(); + const mlMock = { + mlClient: { + callAsInternalUser: jest.fn(), + close: jest.fn(), + asScoped: jest.fn(), + }, + jobServiceProvider: jest.fn().mockReturnValue({ + jobsSummary: jobsSummaryMock, + }), + anomalyDetectorsProvider: jest.fn(), + mlSystemProvider: jest.fn(), + modulesProvider: jest.fn(), + resultsServiceProvider: jest.fn(), + }; + let payload: RuleExecutorOptions; + let alert: ReturnType; + let alertInstanceMock: Record; + let alertInstanceFactoryMock: () => AlertInstance; + let savedObjectsClient: ReturnType; + let logger: ReturnType; + let callClusterMock: jest.Mock; + let ruleStatusService: Record; + + beforeEach(() => { + alertInstanceMock = { + scheduleActions: jest.fn(), + replaceState: jest.fn(), + }; + alertInstanceMock.replaceState.mockReturnValue(alertInstanceMock); + alertInstanceFactoryMock = jest.fn().mockReturnValue(alertInstanceMock); + callClusterMock = jest.fn(); + savedObjectsClient = savedObjectsClientMock.create(); + logger = loggerMock.create(); + ruleStatusService = { + success: jest.fn(), + find: jest.fn(), + goingToRun: jest.fn(), + error: jest.fn(), + }; + (ruleStatusServiceFactory as jest.Mock).mockReturnValue(ruleStatusService); + (getGapBetweenRuns as jest.Mock).mockReturnValue(moment.duration(0)); + (searchAfterAndBulkCreate as jest.Mock).mockClear(); + (searchAfterAndBulkCreate as jest.Mock).mockResolvedValue({ + success: true, + searchAfterTimes: [], + createdSignalsCount: 10, + }); + callClusterMock.mockResolvedValue({ + hits: { + total: { value: 10 }, + }, + }); + const ruleAlert = getResult(); + savedObjectsClient.get.mockResolvedValue({ + id: 'id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + + payload = getPayload(ruleAlert, alertInstanceFactoryMock, savedObjectsClient, callClusterMock); + + alert = signalRulesAlertType({ + logger, + version, + ml: mlMock, + }); + }); + + describe('executor', () => { + it('should warn about the gap between runs', async () => { + (getGapBetweenRuns as jest.Mock).mockReturnValue(moment.duration(1000)); + await alert.executor(payload); + expect(logger.warn).toHaveBeenCalled(); + expect(logger.warn.mock.calls[0][0]).toContain( + 'a few seconds (1000ms) has passed since last rule execution, and signals may have been missed.' + ); + expect(ruleStatusService.error).toHaveBeenCalled(); + expect(ruleStatusService.error.mock.calls[0][0]).toContain( + 'a few seconds (1000ms) has passed since last rule execution, and signals may have been missed.' + ); + expect(ruleStatusService.error.mock.calls[0][1]).toEqual({ + gap: 'a few seconds', + }); + }); + + it("should set refresh to 'wait_for' when actions are present", async () => { + const ruleAlert = getResult(); + ruleAlert.actions = [ + { + actionTypeId: '.slack', + params: { + message: + 'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}', + }, + group: 'default', + id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', + }, + ]; + + savedObjectsClient.get.mockResolvedValue({ + id: 'id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + await alert.executor(payload); + expect((searchAfterAndBulkCreate as jest.Mock).mock.calls[0][0].refresh).toEqual('wait_for'); + (searchAfterAndBulkCreate as jest.Mock).mockClear(); + }); + + it('should set refresh to false when actions are not present', async () => { + await alert.executor(payload); + expect((searchAfterAndBulkCreate as jest.Mock).mock.calls[0][0].refresh).toEqual(false); + (searchAfterAndBulkCreate as jest.Mock).mockClear(); + }); + + it('should call scheduleActions if signalsCount was greater than 0 and rule has actions defined', async () => { + const ruleAlert = getResult(); + ruleAlert.actions = [ + { + actionTypeId: '.slack', + params: { + message: + 'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}', + }, + group: 'default', + id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', + }, + ]; + + savedObjectsClient.get.mockResolvedValue({ + id: 'id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + + await alert.executor(payload); + + expect(scheduleNotificationActions).toHaveBeenCalledWith( + expect.objectContaining({ + signalsCount: 10, + }) + ); + }); + + describe('ML rule', () => { + it('should throw an error if ML plugin was not available', async () => { + const ruleAlert = getMlResult(); + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + alert = signalRulesAlertType({ + logger, + version, + ml: undefined, + }); + await alert.executor(payload); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error.mock.calls[0][0]).toContain( + 'ML plugin unavailable during rule execution' + ); + }); + + it('should throw an error if machineLearningJobId or anomalyThreshold was not null', async () => { + const ruleAlert = getMlResult(); + ruleAlert.params.anomalyThreshold = undefined; + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + await alert.executor(payload); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error.mock.calls[0][0]).toContain( + 'Machine learning rule is missing job id and/or anomaly threshold' + ); + }); + + it('should throw an error if Machine learning job summary was null', async () => { + const ruleAlert = getMlResult(); + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + jobsSummaryMock.mockResolvedValue([]); + await alert.executor(payload); + expect(logger.warn).toHaveBeenCalled(); + expect(logger.warn.mock.calls[0][0]).toContain('Machine learning job is not started'); + expect(ruleStatusService.error).toHaveBeenCalled(); + expect(ruleStatusService.error.mock.calls[0][0]).toContain( + 'Machine learning job is not started' + ); + }); + + it('should log an error if Machine learning job was not started', async () => { + const ruleAlert = getMlResult(); + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + jobsSummaryMock.mockResolvedValue([ + { + id: 'some_job_id', + jobState: 'starting', + datafeedState: 'started', + }, + ]); + (findMlSignals as jest.Mock).mockResolvedValue({ + hits: { + hits: [], + }, + }); + await alert.executor(payload); + expect(logger.warn).toHaveBeenCalled(); + expect(logger.warn.mock.calls[0][0]).toContain('Machine learning job is not started'); + expect(ruleStatusService.error).toHaveBeenCalled(); + expect(ruleStatusService.error.mock.calls[0][0]).toContain( + 'Machine learning job is not started' + ); + }); + + it('should not call ruleStatusService.success if no anomalies were found', async () => { + const ruleAlert = getMlResult(); + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + jobsSummaryMock.mockResolvedValue([]); + (findMlSignals as jest.Mock).mockResolvedValue({ + hits: { + hits: [], + }, + }); + (bulkCreateMlSignals as jest.Mock).mockResolvedValue({ + success: true, + bulkCreateDuration: 0, + createdItemsCount: 0, + }); + await alert.executor(payload); + expect(ruleStatusService.success).not.toHaveBeenCalled(); + }); + + it('should call ruleStatusService.success if signals were created', async () => { + const ruleAlert = getMlResult(); + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + jobsSummaryMock.mockResolvedValue([ + { + id: 'some_job_id', + jobState: 'started', + datafeedState: 'started', + }, + ]); + (findMlSignals as jest.Mock).mockResolvedValue({ + hits: { + hits: [{}], + }, + }); + (bulkCreateMlSignals as jest.Mock).mockResolvedValue({ + success: true, + bulkCreateDuration: 1, + createdItemsCount: 1, + }); + await alert.executor(payload); + expect(ruleStatusService.success).toHaveBeenCalled(); + }); + + it('should call scheduleActions if signalsCount was greater than 0 and rule has actions defined', async () => { + const ruleAlert = getMlResult(); + ruleAlert.actions = [ + { + actionTypeId: '.slack', + params: { + message: + 'Rule generated {{state.signals_count}} signals\n\n{{context.rule.name}}\n{{{context.results_link}}}', + }, + group: 'default', + id: '99403909-ca9b-49ba-9d7a-7e5320e68d05', + }, + ]; + payload = getPayload( + ruleAlert, + alertInstanceFactoryMock, + savedObjectsClient, + callClusterMock + ); + savedObjectsClient.get.mockResolvedValue({ + id: 'id', + type: 'type', + references: [], + attributes: ruleAlert, + }); + jobsSummaryMock.mockResolvedValue([]); + (findMlSignals as jest.Mock).mockResolvedValue({ + hits: { + hits: [{}], + }, + }); + (bulkCreateMlSignals as jest.Mock).mockResolvedValue({ + success: true, + bulkCreateDuration: 1, + createdItemsCount: 1, + }); + + await alert.executor(payload); + + expect(scheduleNotificationActions).toHaveBeenCalledWith( + expect.objectContaining({ + signalsCount: 1, + }) + ); + }); + }); + }); + + describe('should catch error', () => { + it('when bulk indexing failed', async () => { + (searchAfterAndBulkCreate as jest.Mock).mockResolvedValue({ + success: false, + searchAfterTimes: [], + bulkCreateTimes: [], + lastLookBackDate: null, + createdSignalsCount: 0, + }); + await alert.executor(payload); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error.mock.calls[0][0]).toContain( + 'Bulk Indexing of signals failed. Check logs for further details.' + ); + expect(ruleStatusService.error).toHaveBeenCalled(); + }); + + it('when error was thrown', async () => { + (searchAfterAndBulkCreate as jest.Mock).mockResolvedValue({}); + await alert.executor(payload); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error.mock.calls[0][0]).toContain('An error occurred during rule execution'); + expect(ruleStatusService.error).toHaveBeenCalled(); + }); + + it('and call ruleStatusService with the default message', async () => { + (searchAfterAndBulkCreate as jest.Mock).mockRejectedValue({}); + await alert.executor(payload); + expect(logger.error).toHaveBeenCalled(); + expect(logger.error.mock.calls[0][0]).toContain('An error occurred during rule execution'); + expect(ruleStatusService.error).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 246701e94c99a..0357f906f8035 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -19,16 +19,19 @@ import { } from './search_after_bulk_create'; import { getFilter } from './get_filter'; import { SignalRuleAlertTypeDefinition, RuleAlertAttributes } from './types'; -import { getGapBetweenRuns, makeFloatString } from './utils'; +import { getGapBetweenRuns, makeFloatString, parseScheduleDates } from './utils'; import { signalParamsSchema } from './signal_params_schema'; import { siemRuleActionGroups } from './siem_rule_action_groups'; import { findMlSignals } from './find_ml_signals'; import { bulkCreateMlSignals } from './bulk_create_ml_signals'; -import { getSignalsCount } from '../notifications/get_signals_count'; -import { scheduleNotificationActions } from '../notifications/schedule_notification_actions'; +import { + scheduleNotificationActions, + NotificationRuleTypeParams, +} from '../notifications/schedule_notification_actions'; import { ruleStatusServiceFactory } from './rule_status_service'; import { buildRuleMessageFactory } from './rule_messages'; import { ruleStatusSavedObjectsClientFactory } from './rule_status_saved_objects_client'; +import { getNotificationResultsLink } from '../notifications/utils'; export const signalRulesAlertType = ({ logger, @@ -71,6 +74,7 @@ export const signalRulesAlertType = ({ bulkCreateTimes: [], searchAfterTimes: [], lastLookBackDate: null, + createdSignalsCount: 0, }; const ruleStatusClient = ruleStatusSavedObjectsClientFactory(services.savedObjectsClient); const ruleStatusService = await ruleStatusServiceFactory({ @@ -94,6 +98,7 @@ export const signalRulesAlertType = ({ params: ruleParams, } = savedObject.attributes; const updatedAt = savedObject.updated_at ?? ''; + const refresh = actions.length ? 'wait_for' : false; const buildRuleMessage = buildRuleMessageFactory({ id: alertId, ruleId, @@ -161,7 +166,7 @@ export const signalRulesAlertType = ({ logger.info(buildRuleMessage(`Found ${anomalyCount} signals from ML anomalies.`)); } - const { success, bulkCreateDuration } = await bulkCreateMlSignals({ + const { success, bulkCreateDuration, createdItemsCount } = await bulkCreateMlSignals({ actions, throttle, someResult: anomalyResults, @@ -177,9 +182,11 @@ export const signalRulesAlertType = ({ updatedAt, interval, enabled, + refresh, tags, }); result.success = success; + result.createdSignalsCount = createdItemsCount; if (bulkCreateDuration) { result.bulkCreateTimes.push(bulkCreateDuration); } @@ -236,6 +243,7 @@ export const signalRulesAlertType = ({ interval, enabled, pageSize: searchAfterSize, + refresh, tags, throttle, }); @@ -244,28 +252,31 @@ export const signalRulesAlertType = ({ if (result.success) { if (actions.length) { - const notificationRuleParams = { + const notificationRuleParams: NotificationRuleTypeParams = { ...ruleParams, name, id: savedObject.id, }; - const { signalsCount, resultsLink } = await getSignalsCount({ - from: `now-${interval}`, - to: 'now', - index: ruleParams.outputIndex, - ruleId: ruleParams.ruleId!, - kibanaSiemAppUrl: meta?.kibanaSiemAppUrl as string, - ruleAlertId: savedObject.id, - callCluster: services.callCluster, + + const fromInMs = parseScheduleDates(`now-${interval}`)?.format('x'); + const toInMs = parseScheduleDates('now')?.format('x'); + + const resultsLink = getNotificationResultsLink({ + from: fromInMs, + to: toInMs, + id: savedObject.id, + kibanaSiemAppUrl: meta?.kibana_siem_app_url, }); - logger.info(buildRuleMessage(`Found ${signalsCount} signals for notification.`)); + logger.info( + buildRuleMessage(`Found ${result.createdSignalsCount} signals for notification.`) + ); - if (signalsCount) { + if (result.createdSignalsCount) { const alertInstance = services.alertInstanceFactory(alertId); scheduleNotificationActions({ alertInstance, - signalsCount, + signalsCount: result.createdSignalsCount, resultsLink, ruleParams: notificationRuleParams, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts index 45b5610e2d3c3..45365b446cbf0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.test.ts @@ -144,7 +144,7 @@ describe('singleBulkCreate', () => { }, ], }); - const { success } = await singleBulkCreate({ + const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleDocSearchResultsNoSortId(), ruleParams: sampleParams, services: mockService, @@ -159,10 +159,12 @@ describe('singleBulkCreate', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdItemsCount).toEqual(0); }); test('create successful bulk create with docs with no versioning', async () => { @@ -176,7 +178,7 @@ describe('singleBulkCreate', () => { }, ], }); - const { success } = await singleBulkCreate({ + const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleDocSearchResultsNoSortIdNoVersion(), ruleParams: sampleParams, services: mockService, @@ -191,16 +193,18 @@ describe('singleBulkCreate', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdItemsCount).toEqual(0); }); test('create unsuccessful bulk create due to empty search results', async () => { const sampleParams = sampleRuleAlertParams(); mockService.callCluster.mockReturnValue(false); - const { success } = await singleBulkCreate({ + const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleEmptyDocSearchResults(), ruleParams: sampleParams, services: mockService, @@ -215,17 +219,19 @@ describe('singleBulkCreate', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); expect(success).toEqual(true); + expect(createdItemsCount).toEqual(0); }); test('create successful bulk create when bulk create has duplicate errors', async () => { const sampleParams = sampleRuleAlertParams(); const sampleSearchResult = sampleDocSearchResultsNoSortId; mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); - const { success } = await singleBulkCreate({ + const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleSearchResult(), ruleParams: sampleParams, services: mockService, @@ -240,19 +246,21 @@ describe('singleBulkCreate', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); expect(mockLogger.error).not.toHaveBeenCalled(); expect(success).toEqual(true); + expect(createdItemsCount).toEqual(1); }); test('create successful bulk create when bulk create has multiple error statuses', async () => { const sampleParams = sampleRuleAlertParams(); const sampleSearchResult = sampleDocSearchResultsNoSortId; mockService.callCluster.mockReturnValue(sampleBulkCreateErrorResult); - const { success } = await singleBulkCreate({ + const { success, createdItemsCount } = await singleBulkCreate({ someResult: sampleSearchResult(), ruleParams: sampleParams, services: mockService, @@ -267,12 +275,14 @@ describe('singleBulkCreate', () => { updatedBy: 'elastic', interval: '5m', enabled: true, + refresh: false, tags: ['some fake tag 1', 'some fake tag 2'], throttle: 'no_actions', }); expect(mockLogger.error).toHaveBeenCalled(); expect(success).toEqual(true); + expect(createdItemsCount).toEqual(1); }); test('filter duplicate rules will return an empty array given an empty array', () => { @@ -341,4 +351,30 @@ describe('singleBulkCreate', () => { }, ]); }); + + test('create successful and returns proper createdItemsCount', async () => { + const sampleParams = sampleRuleAlertParams(); + mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); + const { success, createdItemsCount } = await singleBulkCreate({ + someResult: sampleDocSearchResultsNoSortId(), + ruleParams: sampleParams, + services: mockService, + logger: mockLogger, + id: sampleRuleGuid, + signalsIndex: DEFAULT_SIGNALS_INDEX, + actions: [], + name: 'rule-name', + createdAt: '2020-01-28T15:58:34.810Z', + updatedAt: '2020-01-28T15:59:14.004Z', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + refresh: false, + tags: ['some fake tag 1', 'some fake tag 2'], + throttle: 'no_actions', + }); + expect(success).toEqual(true); + expect(createdItemsCount).toEqual(1); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts index ffec40b839bf6..fc33d0e15e43f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/single_bulk_create.ts @@ -9,7 +9,7 @@ import { performance } from 'perf_hooks'; import { AlertServices } from '../../../../../../../plugins/alerting/server'; import { SignalSearchResponse, BulkResponse } from './types'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; -import { RuleTypeParams } from '../types'; +import { RuleTypeParams, RefreshTypes } from '../types'; import { generateId, makeFloatString } from './utils'; import { buildBulkBody } from './build_bulk_body'; import { Logger } from '../../../../../../../../src/core/server'; @@ -31,6 +31,7 @@ interface SingleBulkCreateParams { enabled: boolean; tags: string[]; throttle: string; + refresh: RefreshTypes; } /** @@ -58,6 +59,7 @@ export const filterDuplicateRules = ( export interface SingleBulkCreateResponse { success: boolean; bulkCreateDuration?: string; + createdItemsCount: number; } // Bulk Index documents. @@ -76,12 +78,13 @@ export const singleBulkCreate = async ({ updatedBy, interval, enabled, + refresh, tags, throttle, }: SingleBulkCreateParams): Promise => { someResult.hits.hits = filterDuplicateRules(id, someResult); if (someResult.hits.hits.length === 0) { - return { success: true }; + return { success: true, createdItemsCount: 0 }; } // index documents after creating an ID based on the // source documents' originating index, and the original @@ -123,7 +126,7 @@ export const singleBulkCreate = async ({ const start = performance.now(); const response: BulkResponse = await services.callCluster('bulk', { index: signalsIndex, - refresh: false, + refresh, body: bulkBody, }); const end = performance.now(); @@ -145,5 +148,8 @@ export const singleBulkCreate = async ({ ); } } - return { success: true, bulkCreateDuration: makeFloatString(end - start) }; + + const createdItemsCount = countBy(response.items, 'create.status')['201'] ?? 0; + + return { success: true, bulkCreateDuration: makeFloatString(end - start), createdItemsCount }; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts index 543e8bf0619b0..040e32aa0d360 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/types.ts @@ -162,5 +162,10 @@ export interface AlertAttributes { } export interface RuleAlertAttributes extends AlertAttributes { - params: RuleAlertParams; + params: Omit< + RuleAlertParams, + 'ruleId' | 'name' | 'enabled' | 'interval' | 'tags' | 'actions' | 'throttle' + > & { + ruleId: string; + }; } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts index efa0a92cc573b..035f1b10ff8b2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts @@ -29,6 +29,11 @@ export interface ThreatParams { // We don't have the input types defined through io-ts just yet but as we being introducing types from there we will more and more remove // types and share them between input and output schema but have an input Rule Schema and an output Rule Schema. +export interface Meta { + [key: string]: {} | string | undefined | null; + kibana_siem_app_url?: string | undefined; +} + export interface RuleAlertParams { actions: RuleAlertAction[]; anomalyThreshold: number | undefined; @@ -51,7 +56,7 @@ export interface RuleAlertParams { query: string | undefined | null; references: string[]; savedId?: string | undefined | null; - meta: Record | undefined | null; + meta: Meta | undefined | null; severity: string; tags: string[]; to: string; @@ -60,7 +65,7 @@ export interface RuleAlertParams { threat: ThreatParams[] | undefined | null; type: RuleType; version: number; - throttle: string; + throttle: string | undefined | null; lists: ListsDefaultArraySchema | null | undefined; } @@ -144,3 +149,5 @@ export type CallWithRequest, V> = ( params: T, options?: CallAPIOptions ) => Promise; + +export type RefreshTypes = false | 'wait_for'; diff --git a/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.test.ts b/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.test.ts index 93472b8539efd..20bc1387a3c4e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.test.ts @@ -23,77 +23,108 @@ describe('Index Fields', () => { ).toEqual( sortBy('name', [ { - aggregatable: true, - 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.', + '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', - indexes: ['auditbeat', 'filebeat', 'packetbeat'], name: '@timestamp', - searchable: true, type: 'date', + searchable: true, + aggregatable: true, + category: 'base', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], }, { + 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', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + }, + { + 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, - category: 'agent', + readFromDocValues: true, + category: '_index', + indexes: ['auditbeat', 'filebeat', 'packetbeat'], + }, + { description: - 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', + 'Ephemeral identifier of this agent (if one exists).\n\nThis id normally changes across restarts, but `agent.id` does not.', example: '8a4f500f', - indexes: ['auditbeat'], name: 'agent.ephemeral_id', - searchable: true, type: 'string', - }, - { + searchable: true, aggregatable: true, category: 'agent', - indexes: ['filebeat'], + indexes: ['auditbeat'], + }, + { name: 'agent.hostname', searchable: true, type: 'string', - }, - { aggregatable: true, category: 'agent', + indexes: ['filebeat'], + }, + { description: - 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', + 'Unique identifier of this agent (if one exists).\n\nExample: For Beats this would be beat.id.', example: '8a4f500d', - indexes: ['packetbeat'], name: 'agent.id', - searchable: true, type: 'string', - }, - { + searchable: true, aggregatable: true, category: 'agent', + indexes: ['packetbeat'], + }, + { 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.', + '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', - indexes: ['auditbeat', 'filebeat'], name: 'agent.name', - searchable: true, type: 'string', - }, - { + searchable: true, aggregatable: true, category: 'agent', + indexes: ['auditbeat', 'filebeat'], + }, + { 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.', + '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', - indexes: ['auditbeat', 'packetbeat'], name: 'agent.type', - searchable: true, type: 'string', - }, - { + searchable: true, aggregatable: true, category: 'agent', + indexes: ['auditbeat', 'packetbeat'], + }, + { description: 'Version of the agent.', example: '6.0.0-rc2', - indexes: ['auditbeat', 'filebeat'], name: 'agent.version', - searchable: true, type: 'string', + searchable: true, + aggregatable: true, + category: 'agent', + indexes: ['auditbeat', 'filebeat'], }, ]) ); diff --git a/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts index 48b9750ddd949..7cfb5ad391149 100644 --- a/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/index_fields/elasticsearch_adapter.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash/fp'; +import { isEmpty, get } from 'lodash/fp'; import { IndexField } from '../../graphql/types'; import { @@ -51,6 +51,23 @@ export class ElasticsearchIndexFieldAdapter implements FieldsAdapter { } } +const missingFields = [ + { + name: '_id', + type: 'string', + searchable: true, + aggregatable: false, + readFromDocValues: true, + }, + { + name: '_index', + type: 'string', + searchable: true, + aggregatable: true, + readFromDocValues: true, + }, +]; + export const formatIndexFields = ( responsesIndexFields: IndexFieldDescriptor[][], indexesAlias: IndexAlias[] @@ -59,20 +76,23 @@ export const formatIndexFields = ( .reduce( (accumulator: IndexField[], indexFields: IndexFieldDescriptor[], indexesAliasIdx: number) => [ ...accumulator, - ...indexFields.reduce((itemAccumulator: IndexField[], index: IndexFieldDescriptor) => { - const alias: IndexAlias = indexesAlias[indexesAliasIdx]; - const splitName = index.name.split('.'); - const category = baseCategoryFields.includes(splitName[0]) ? 'base' : splitName[0]; - return [ - ...itemAccumulator, - { - ...(hasDocumentation(alias, index.name) ? getDocumentation(alias, index.name) : {}), - ...index, - category, - indexes: [alias], - } as IndexField, - ]; - }, []), + ...[...missingFields, ...indexFields].reduce( + (itemAccumulator: IndexField[], index: IndexFieldDescriptor) => { + const alias: IndexAlias = indexesAlias[indexesAliasIdx]; + const splitName = index.name.split('.'); + const category = baseCategoryFields.includes(splitName[0]) ? 'base' : splitName[0]; + return [ + ...itemAccumulator, + { + ...(hasDocumentation(alias, index.name) ? getDocumentation(alias, index.name) : {}), + ...index, + category, + indexes: [alias], + } as IndexField, + ]; + }, + [] + ), ], [] ) @@ -84,7 +104,10 @@ export const formatIndexFields = ( ...accumulator.slice(0, alreadyExistingIndexField), { ...existingIndexField, - indexes: [...existingIndexField.indexes, ...indexfield.indexes], + description: isEmpty(existingIndexField.description) + ? indexfield.description + : existingIndexField.description, + indexes: Array.from(new Set([...existingIndexField.indexes, ...indexfield.indexes])), }, ...accumulator.slice(alreadyExistingIndexField + 1), ]; diff --git a/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/auditbeat.ts b/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/auditbeat.ts index 773d4ed6a7e95..76c865679dd05 100644 --- a/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/auditbeat.ts +++ b/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/auditbeat.ts @@ -15,91 +15,129 @@ export const auditbeatSchema: Schema = [ { key: 'ecs', title: 'ECS', - description: 'ECS fields.', + description: 'ECS Fields.', fields: [ { name: '@timestamp', - type: 'date', level: 'core', required: true, - example: '2016-05-23T08:05:34.853Z', + type: 'date', 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.', - }, - { - name: 'tags', - level: 'core', - type: 'keyword', - example: '["production", "env2"]', - description: 'List of keywords used to tag each event.', + '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', - example: { - env: 'production', - application: 'foo-bar', - }, + object_type: 'keyword', description: - '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.', + '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', - example: 'Hello World', description: - 'For log events the message field contains the log message. In other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.', + '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 any, that collects, detects, or observes events on a host, or takes measurements on a host. Examples include Beats. Agents may also run on observers. ECS agent.* fields shall be populated with details of the agent running on the host or observer where the event happened or the measurement was taken.', + '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. For APM, it is the agent running in the app/service. The agent information does not change if data is sent through queuing systems like Kafka, Redis, or processing systems such as Logstash or APM Server.', + '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: 'version', + 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', - description: 'Version of the agent.', - example: '6.0.0-rc2', + 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: - '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.', + '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. 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.', + '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: 'id', + 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 identifier of this agent (if one exists). Example: For Beats this would be beat.id.', - example: '8a4f500d', + 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', + example: 15169, }, { - name: 'ephemeral_id', + name: 'organization.name', level: 'extended', type: 'keyword', - description: - 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', + ignore_above: 1024, + multi_fields: [ + { + name: 'text', + type: 'text', + norms: false, + default_field: false, + }, + ], + description: 'Organization name.', + example: 'Google LLC', }, ], }, @@ -108,4693 +146,7750 @@ export const auditbeatSchema: Schema = [ title: 'Client', group: 2, description: - 'A client is defined as the initiator of a network connection for events regarding sessions, connections, or bidirectional flow records. For TCP events, the client is the initiator of the TCP connection that sends the SYN packet(s). For other protocols, the client is generally the initiator or requestor in the network transaction. Some systems use the term "originator" to refer the client in TCP connections. The client fields describe details about the system acting as the client in the network event. Client fields are usually populated in conjunction with server fields. Client fields are generally not populated for packet-level events. Client / server representations can add semantic context to an exchange, which is helpful to visualize the data in certain situations. If your context falls in that category, you should still ensure that source and destination are filled appropriately.', + '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 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.', + '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: 'ip', - level: 'core', - type: 'ip', - description: 'IP address of the client. Can be one or multiple IPv4 or IPv6 addresses.', + 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: 'port', + 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', - description: 'Port of the client.', + format: 'bytes', + description: 'Bytes sent from the client to the server.', + example: 184, }, { - name: 'mac', + name: 'domain', level: 'core', type: 'keyword', - description: 'MAC address of the client.', + ignore_above: 1024, + description: 'Client domain.', }, { - name: 'domain', + name: 'geo.city_name', level: 'core', type: 'keyword', - description: 'Client domain.', + ignore_above: 1024, + description: 'City name.', + example: 'Montreal', }, { - name: 'bytes', + name: 'geo.continent_name', level: 'core', - type: 'long', - format: 'bytes', - example: 184, - description: 'Bytes sent from the client to the server.', + type: 'keyword', + ignore_above: 1024, + description: 'Name of the continent.', + example: 'North America', }, { - name: 'packets', + name: 'geo.country_iso_code', level: 'core', - type: 'long', - example: 12, - description: 'Packets sent from the client to the server.', + type: 'keyword', + ignore_above: 1024, + description: 'Country ISO code.', + example: 'CA', }, { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - 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.country_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Country name.', + example: 'Canada', }, - ], - }, - { - name: 'cloud', - title: 'Cloud', - group: 2, - description: 'Fields related to the cloud or infrastructure the events are coming from.', - footnote: - 'Examples: If Metricbeat is running on an EC2 host and fetches data from its host, the cloud info contains the data about this machine. If Metricbeat runs on a remote machine outside the cloud and fetches data from a service running in the cloud, the field contains cloud data from the machine the service is running on.', - type: 'group', - fields: [ { - name: 'provider', + name: 'geo.location', + level: 'core', + type: 'geo_point', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', + }, + { + name: 'geo.name', level: 'extended', - example: 'ec2', type: 'keyword', + ignore_above: 1024, description: - 'Name of the cloud provider. Example values are ec2, gce, or digitalocean.', + '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: 'availability_zone', - level: 'extended', - example: 'us-east-1c', + name: 'geo.region_iso_code', + level: 'core', type: 'keyword', - description: 'Availability zone in which this host is running.', + ignore_above: 1024, + description: 'Region ISO code.', + example: 'CA-QC', }, { - name: 'region', - level: 'extended', + name: 'geo.region_name', + level: 'core', type: 'keyword', - example: 'us-east-1', - description: 'Region in which this host is running.', + ignore_above: 1024, + description: 'Region name.', + example: 'Quebec', }, { - name: 'instance.id', - level: 'extended', - type: 'keyword', - example: 'i-1234567890abcdef0', - description: 'Instance ID of the host machine.', + name: 'ip', + level: 'core', + type: 'ip', + description: + 'IP address of the client.\n\nCan be one or multiple IPv4 or IPv6 addresses.', }, { - name: 'instance.name', - level: 'extended', + name: 'mac', + level: 'core', type: 'keyword', - description: 'Instance name of the host machine.', + ignore_above: 1024, + description: 'MAC address of the client.', }, { - name: 'machine.type', + name: 'nat.ip', level: 'extended', - type: 'keyword', - example: 't2.medium', - description: 'Machine type of the host machine.', + 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: 'account.id', + name: 'nat.port', level: 'extended', - type: 'keyword', - example: 666777888999, + type: 'long', + format: 'string', 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.', + 'Translated port of source based NAT sessions (e.g. internal client\nto internet).\n\nTypically connections traversing load balancers, firewalls, or routers.', }, - ], - }, - { - name: 'container', - title: 'Container', - group: 2, - description: - 'Container fields are used for meta information about the specific container that is the source of information. These fields help correlate data based containers from any runtime.', - type: 'group', - fields: [ { - name: 'runtime', - level: 'extended', - type: 'keyword', - description: 'Runtime managing this container.', - example: 'docker', + name: 'packets', + level: 'core', + type: 'long', + description: 'Packets sent from the client to the server.', + example: 12, }, { - name: 'id', + name: 'port', level: 'core', + type: 'long', + format: 'string', + description: 'Port of the client.', + }, + { + name: 'registered_domain', + level: 'extended', type: 'keyword', - description: 'Unique container id.', + 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: 'image.name', + name: 'top_level_domain', level: 'extended', type: 'keyword', - description: 'Name of the image the container was built on.', + 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: 'image.tag', + name: 'user.domain', level: 'extended', type: 'keyword', - description: 'Container image tag.', + 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: 'name', + name: 'user.email', level: 'extended', type: 'keyword', - description: 'Container name.', + ignore_above: 1024, + description: 'User email address.', }, { - name: 'labels', + name: 'user.full_name', level: 'extended', - type: 'object', - object_type: 'keyword', - description: 'Image labels.', + 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: 'destination', - title: 'Destination', - group: 2, - description: - 'Destination fields describe details about the destination of a packet/event. Destination fields are usually populated in conjunction with source fields.', - type: 'group', - fields: [ { - name: 'address', + name: 'user.group.domain', level: 'extended', type: 'keyword', + ignore_above: 1024, 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 of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', }, { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.', + name: 'user.group.id', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Unique identifier for the group on the system/platform.', }, { - name: 'port', - level: 'core', - type: 'long', - description: 'Port of the destination.', + name: 'user.group.name', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Name of the group.', }, { - name: 'mac', - level: 'core', + name: 'user.hash', + level: 'extended', type: 'keyword', - description: 'MAC address of the destination.', + 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: 'domain', + name: 'user.id', level: 'core', type: 'keyword', - description: 'Destination domain.', + ignore_above: 1024, + description: 'Unique identifiers of the user.', }, { - name: 'bytes', + name: 'user.name', level: 'core', - type: 'long', - format: 'bytes', - example: 184, - description: 'Bytes sent from the destination to the source.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - example: 12, - description: 'Packets sent from the destination to the source.', - }, - { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, + type: 'keyword', + ignore_above: 1024, + multi_fields: [ { - name: 'name', - level: 'extended', - type: 'keyword', - 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: 'text', + type: 'text', + norms: false, + default_field: false, }, ], + description: 'Short name or login of the user.', + example: 'albert', }, ], }, { - name: 'ecs', - title: 'ECS', + name: 'cloud', + title: 'Cloud', group: 2, - description: 'Meta-information specific to ECS.', + 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: 'version', - level: 'core', + name: 'account.id', + level: 'extended', type: 'keyword', - required: true, + ignore_above: 1024, 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. The current version is 1.0.0-beta2 .', - example: '1.0.0-beta2', + '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: 'error', - title: 'Error', - group: 2, - description: - 'These fields can represent errors of any kind. Use them for errors that happen while fetching events or in cases where the event itself contains an error.', - type: 'group', - fields: [ { - name: 'id', - level: 'core', + name: 'availability_zone', + level: 'extended', type: 'keyword', - description: 'Unique identifier for the error.', + ignore_above: 1024, + description: 'Availability zone in which this host is running.', + example: 'us-east-1c', }, { - name: 'message', - level: 'core', - type: 'text', - description: 'Error message.', + name: 'instance.id', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Instance ID of the host machine.', + example: 'i-1234567890abcdef0', }, { - name: 'code', - level: 'core', + name: 'instance.name', + level: 'extended', type: 'keyword', - description: 'Error code describing the error.', + ignore_above: 1024, + description: 'Instance name of the host machine.', }, - ], - }, - { - name: 'event', - title: 'Event', - group: 2, - description: - 'The event fields are used for context information about the log or metric event itself. A log is defined as an event containing details of something that happened. Log events must include the time at which the thing happened. Examples of log events include a process starting on a host, a network packet being sent from a source to a destination, or a network connection between a client and a server being initiated or closed. A metric is defined as an event containing one or more numerical or categorical measurements and the time at which the measurement was taken. Examples of metric events include memory pressure measured on a host, or vulnerabilities measured on a scanned host.', - type: 'group', - fields: [ { - name: 'id', - level: 'core', + name: 'machine.type', + level: 'extended', type: 'keyword', - description: 'Unique ID to describe the event.', - example: '8a4f500d', + ignore_above: 1024, + description: 'Machine type of the host machine.', + example: 't2.medium', }, { - name: 'kind', + name: 'provider', level: 'extended', type: 'keyword', + ignore_above: 1024, description: - 'The kind of the event. This gives information about what type of information the event contains, without being specific to the contents of the event. Examples are `event`, `state`, `alarm`. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.', - example: 'state', + 'Name of the cloud provider. Example values are aws, azure, gcp,\nor digitalocean.', + example: 'aws', }, { - name: 'category', - level: 'core', + name: 'region', + level: 'extended', type: 'keyword', - description: - 'Event category. This contains high-level information about the contents of the event. It is more generic than `event.action`, in the sense that typically a category contains multiple actions. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.', - example: 'user-management', + 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: 'action', + name: 'exists', level: 'core', - type: 'keyword', - 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', + type: 'boolean', + description: 'Boolean to capture if a signature is present.', + example: 'true', + default_field: false, }, { - name: 'outcome', + name: 'status', level: 'extended', type: 'keyword', + ignore_above: 1024, description: - 'The outcome of the event. If the event describes an action, this fields contains the outcome of that action. Examples outcomes are `success` and `failure`. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.', - example: 'success', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - description: 'Reserved for future usage. Please avoid using this field for user data.', + '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: 'module', + name: 'subject_name', level: 'core', type: 'keyword', - description: - 'Name of the module this data is coming from. This information is coming from the modules used in Beats or Logstash.', - example: 'mysql', + ignore_above: 1024, + description: 'Subject name of the code signer', + example: 'Microsoft Corporation', + default_field: false, }, { - name: 'dataset', - level: 'core', - type: 'keyword', + name: 'trusted', + level: 'extended', + type: 'boolean', description: - 'Name of the dataset. The concept of a `dataset` (fileset / metricset) is used in Beats as a subset of modules. It contains the information which is currently stored in metricset.name and metricset.module or fileset.name.', - example: 'stats', + '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: 'severity', - level: 'core', - type: 'long', - example: '7', + name: 'valid', + level: 'extended', + type: 'boolean', description: - "Severity describes the severity of the event. What the different severity values mean can very different between use cases. It's up to the implementer to make sure severities are consistent across events. ", + '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: 'original', + name: 'id', level: 'core', type: 'keyword', - 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', - 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`.', - index: false, - doc_values: false, + ignore_above: 1024, + description: 'Unique container id.', }, { - name: 'hash', + name: 'image.name', level: 'extended', type: 'keyword', - example: '123456789012345678901234567890ABCD', - description: - 'Hash (perhaps logstash fingerprint) of raw field to be able to demonstrate log integrity.', - }, - { - name: 'duration', - level: 'core', - type: 'long', - format: 'duration', - input_format: 'nanoseconds', - 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.', + ignore_above: 1024, + description: 'Name of the image the container was built on.', }, { - name: 'timezone', + name: 'image.tag', level: 'extended', type: 'keyword', - 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: 'created', - level: 'core', - type: 'date', - description: - 'event.created contains the date when the event was created. This timestamp is distinct from @timestamp in that @timestamp contains the processed timestamp. For logs these two timestamps can be different as the timestamp in the log line and when the event is read for example by Filebeat are not identical. `@timestamp` must contain the timestamp extracted from the log line, event.created when the log line is read. The same could apply to package capturing where @timestamp contains the timestamp extracted from the network package and event.created when the event was created. In case the two timestamps are identical, @timestamp should be used.', + ignore_above: 1024, + description: 'Container image tags.', }, { - name: 'start', + name: 'labels', level: 'extended', - type: 'date', - description: - 'event.start contains the date when the event started or when the activity was first observed.', + type: 'object', + object_type: 'keyword', + description: 'Image labels.', }, { - name: 'end', + name: 'name', level: 'extended', - type: 'date', - description: - 'event.end contains the date when the event ended or when the activity was last observed.', - }, - { - name: 'risk_score', - level: 'core', - type: 'float', - description: - "Risk score or priority of the event (e.g. security solutions). Use your system's original value here. ", + type: 'keyword', + ignore_above: 1024, + description: 'Container name.', }, { - name: 'risk_score_norm', + name: 'runtime', level: 'extended', - type: 'float', - 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.', + type: 'keyword', + ignore_above: 1024, + description: 'Runtime managing this container.', + example: 'docker', }, ], }, { - name: 'file', + name: 'destination', + title: 'Destination', group: 2, - title: 'File', description: - 'A file is defined as a set of information that has been created on, or has existed on a filesystem. File objects can be associated with host events, network events, and/or file events (e.g., those produced by File Integrity Monitoring [FIM] products or services). File fields provide details about the affected file associated with the event or metric.', + '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: 'path', + name: 'address', level: 'extended', type: 'keyword', - description: 'Path to the file.', + 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: 'target_path', + name: 'as.number', level: 'extended', - type: 'keyword', - description: 'Target path for symlinks.', + 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: 'extension', + name: 'as.organization.name', level: 'extended', type: 'keyword', - description: 'File extension. This should allow easy filtering by file extensions.', - example: 'png', + ignore_above: 1024, + multi_fields: [ + { + name: 'text', + type: 'text', + norms: false, + default_field: false, + }, + ], + description: 'Organization name.', + example: 'Google LLC', }, { - name: 'type', - level: 'extended', - type: 'keyword', - description: 'File type (file, dir, or symlink).', + name: 'bytes', + level: 'core', + type: 'long', + format: 'bytes', + description: 'Bytes sent from the destination to the source.', + example: 184, }, { - name: 'device', - level: 'extended', + name: 'domain', + level: 'core', type: 'keyword', - description: 'Device that is the source of the file.', + ignore_above: 1024, + description: 'Destination domain.', }, { - name: 'inode', - level: 'extended', + name: 'geo.city_name', + level: 'core', type: 'keyword', - description: 'Inode representing the file in the filesystem.', + ignore_above: 1024, + description: 'City name.', + example: 'Montreal', }, { - name: 'uid', - level: 'extended', + name: 'geo.continent_name', + level: 'core', type: 'keyword', - description: 'The user ID (UID) or security identifier (SID) of the file owner.', + ignore_above: 1024, + description: 'Name of the continent.', + example: 'North America', }, { - name: 'owner', - level: 'extended', + name: 'geo.country_iso_code', + level: 'core', type: 'keyword', - description: "File owner's username.", + ignore_above: 1024, + description: 'Country ISO code.', + example: 'CA', }, { - name: 'gid', - level: 'extended', + name: 'geo.country_name', + level: 'core', type: 'keyword', - description: 'Primary group ID (GID) of the file.', + ignore_above: 1024, + description: 'Country name.', + example: 'Canada', }, { - name: 'group', - level: 'extended', - type: 'keyword', - description: 'Primary group name of the file.', - }, - { - name: 'mode', - level: 'extended', - type: 'keyword', - example: 416, - description: 'Mode of the file in octal representation.', - }, - { - name: 'size', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'File size in bytes (field is only added when `type` is `file`).', - }, - { - name: 'mtime', - level: 'extended', - type: 'date', - description: 'Last time file content was modified.', - }, - { - name: 'ctime', - level: 'extended', - type: 'date', - description: 'Last time file metadata changed.', - }, - ], - }, - { - name: 'group', - title: 'Group', - group: 2, - description: - 'The group fields are meant to represent groups that are relevant to the event.', - type: 'group', - fields: [ - { - name: 'id', - level: 'extended', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.', + name: 'geo.location', + level: 'core', + type: 'geo_point', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', }, { - name: 'name', + name: 'geo.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. ECS host.* fields should be populated with details about the host on which the event happened, or on which the measurement was taken. Host types include hardware, virtual machines, Docker containers, and Kubernetes nodes.', - type: 'group', - fields: [ - { - name: 'hostname', - level: 'core', - type: 'keyword', + ignore_above: 1024, description: - 'Hostname of the host. It normally contains what the `hostname` command returns on the host machine.', + '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: 'name', + name: 'geo.region_iso_code', level: 'core', type: 'keyword', - 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.', + ignore_above: 1024, + description: 'Region ISO code.', + example: 'CA-QC', }, { - name: 'id', + name: 'geo.region_name', level: 'core', type: 'keyword', - 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`.', + ignore_above: 1024, + description: 'Region name.', + example: 'Quebec', }, { name: 'ip', level: 'core', type: 'ip', - description: 'Host ip address.', + description: + 'IP address of the destination.\n\nCan be one or multiple IPv4 or IPv6 addresses.', }, { name: 'mac', level: 'core', type: 'keyword', - description: 'Host mac address.', + ignore_above: 1024, + description: 'MAC address of the destination.', }, { - name: 'type', - level: 'core', - type: 'keyword', + name: 'nat.ip', + level: 'extended', + type: 'ip', 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.', + 'Translated ip of destination based NAT sessions (e.g. internet\nto private DMZ)\n\nTypically used with load balancers, firewalls, or routers.', }, { - name: 'architecture', - level: 'core', - type: 'keyword', - example: 'x86_64', - description: 'Operating system architecture.', + 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: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - reusable: { - top_level: false, - expected: ['observer', 'host', 'user_agent'], - }, - type: 'group', - fields: [ - { - name: 'platform', - level: 'extended', - type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - example: 'Mac OS X', - description: 'Operating system name, without the version.', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - example: 'Mac OS Mojave', - description: 'Operating system name, including the version or code name.', - }, - { - name: 'family', - level: 'extended', - type: 'keyword', - example: 'debian', - description: 'OS family (such as redhat, debian, freebsd, windows).', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - example: '10.14.1', - description: 'Operating system version as a raw string.', - }, - { - name: 'kernel', - level: 'extended', - type: 'keyword', - example: '4.4.0-112-generic', - description: 'Operating system kernel version as a raw string.', - }, - ], + name: 'packets', + level: 'core', + type: 'long', + description: 'Packets sent from the destination to the source.', + example: 12, }, { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - 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: 'port', + level: 'core', + type: 'long', + format: 'string', + description: 'Port of the destination.', }, - ], - }, - { - name: 'http', - title: 'HTTP', - group: 2, - description: 'Fields related to HTTP activity.', - type: 'group', - fields: [ { - name: 'request.method', + name: 'registered_domain', level: 'extended', type: 'keyword', + ignore_above: 1024, description: - 'Http request method. The field value must be normalized to lowercase for querying. See "Lowercase Capitalization" in the "Implementing ECS" section.', - example: 'get, post, put', + '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: 'request.body.content', + name: 'top_level_domain', level: 'extended', type: 'keyword', - description: 'The full http request body.', - example: 'Hello world', + 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: 'request.referrer', + name: 'user.domain', level: 'extended', type: 'keyword', - description: 'Referrer for this HTTP request.', - example: 'https://blog.example.com/', - }, - { - name: 'response.status_code', - level: 'extended', - type: 'long', - description: 'Http response status code.', - example: 404, + 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: 'response.body.content', + name: 'user.email', level: 'extended', type: 'keyword', - description: 'The full http response body.', - example: 'Hello world', + ignore_above: 1024, + description: 'User email address.', }, { - name: 'version', + name: 'user.full_name', level: 'extended', type: 'keyword', - description: 'Http version.', - example: 1.1, + 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: 'request.bytes', + name: 'user.group.domain', level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the request (body and headers).', - example: 1437, + 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: 'request.body.bytes', + name: 'user.group.id', level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the request body.', - example: 887, + type: 'keyword', + ignore_above: 1024, + description: 'Unique identifier for the group on the system/platform.', }, { - name: 'response.bytes', + name: 'user.group.name', level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the response (body and headers).', - example: 1437, + type: 'keyword', + ignore_above: 1024, + description: 'Name of the group.', }, { - name: 'response.body.bytes', + name: 'user.hash', level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the response body.', - example: 887, + 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: 'log', - title: 'Log', - description: 'Fields which are specific to log events.', - type: 'group', - fields: [ { - name: 'level', + name: 'user.id', level: 'core', type: 'keyword', - description: 'Log level of the log event. Some examples are `WARN`, `ERR`, `INFO`.', - example: 'ERR', + ignore_above: 1024, + description: 'Unique identifiers of the user.', }, { - name: 'original', + name: 'user.name', level: 'core', type: 'keyword', - example: 'Sep 19 08:26:10 localhost My log', - index: false, - doc_values: false, - 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`. ", + 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: 'network', - title: 'Network', + name: 'dll', + title: 'DLL', group: 2, description: - 'The network is defined as the communication path over which a host or network event happens. The network.* fields should be populated with details about the network activity associated with an event.', + '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: 'name', + 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', - description: 'Name given by operators to sections of their network.', - example: 'Guest Wifi', + 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: 'type', + 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: - '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 "Lowercase Capitalization" in the "Implementing ECS" section.', - example: 'ipv4', + '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: 'iana_number', + name: 'code_signature.valid', level: 'extended', - type: 'keyword', + type: 'boolean', 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, + '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: 'transport', - level: 'core', + name: 'hash.md5', + level: 'extended', type: 'keyword', - 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 "Lowercase Capitalization" in the "Implementing ECS" section.', - example: 'tcp', + ignore_above: 1024, + description: 'MD5 hash.', + default_field: false, }, { - name: 'application', + name: 'hash.sha1', level: 'extended', type: 'keyword', - description: - 'A name given to an application. 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 "Lowercase Capitalization" in the "Implementing ECS" section.', - example: 'aim', + ignore_above: 1024, + description: 'SHA1 hash.', + default_field: false, }, { - name: 'protocol', - level: 'core', + name: 'hash.sha256', + level: 'extended', type: 'keyword', - description: - 'L7 Network protocol name. ex. http, lumberjack, transport protocol. The field value must be normalized to lowercase for querying. See "Lowercase Capitalization" in the "Implementing ECS" section.', - example: 'http', + ignore_above: 1024, + description: 'SHA256 hash.', + default_field: false, }, { - name: 'direction', + 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: - "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 of the library.\n\nThis generally maps to the name of the file on disk.', + example: 'kernel32.dll', + default_field: false, }, { - 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: 'community_id', + name: 'path', level: 'extended', type: 'keyword', - 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: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: - 'Total bytes transferred in both directions. If `source.bytes` and `destination.bytes` are known, `network.bytes` is their sum.', - example: 368, - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: - 'Total packets transferred in both directions. If `source.packets` and `destination.packets` are known, `network.packets` is their sum.', - example: 24, - }, - ], - }, - { - name: 'observer', - title: 'Observer', - group: 2, - description: - 'An observer is defined as a special network, security, or application device used to detect, observe, or create network, security, or application-related events and metrics. This could be a custom hardware appliance or a server that has been configured to run special network, security, or application software. Examples include firewalls, intrusion detection/prevention systems, network monitoring sensors, web application firewalls, data loss prevention systems, and APM servers. The observer.* fields shall be populated with details of the system, if any, that detects, observes and/or creates a network, security, or application event or metric. Message queues and ETL components used in processing events or metrics are not considered observers in ECS.', - type: 'group', - fields: [ - { - name: 'mac', - level: 'core', - type: 'keyword', - description: 'MAC address of the observer', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'IP address of the observer.', + ignore_above: 1024, + description: 'Full file path of the library.', + example: 'C:\\Windows\\System32\\kernel32.dll', + default_field: false, }, { - name: 'hostname', - level: 'core', + name: 'pe.company', + level: 'extended', type: 'keyword', - description: 'Hostname of the observer.', + ignore_above: 1024, + description: 'Internal company name of the file, provided at compile-time.', + example: 'Microsoft Corporation', + default_field: false, }, { - name: 'vendor', - level: 'core', + name: 'pe.description', + level: 'extended', type: 'keyword', - description: 'observer vendor information.', + ignore_above: 1024, + description: 'Internal description of the file, provided at compile-time.', + example: 'Paint', + default_field: false, }, { - name: 'version', - level: 'core', + name: 'pe.file_version', + level: 'extended', type: 'keyword', - description: 'Observer version.', + ignore_above: 1024, + description: 'Internal version of the file, provided at compile-time.', + example: '6.3.9600.17415', + default_field: false, }, { - name: 'serial_number', + name: 'pe.original_file_name', level: 'extended', type: 'keyword', - description: 'Observer serial number.', + ignore_above: 1024, + description: 'Internal name of the file, provided at compile-time.', + example: 'MSPAINT.EXE', + default_field: false, }, { - name: 'type', - level: 'core', + name: 'pe.product', + level: 'extended', type: 'keyword', - 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: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - reusable: { - top_level: false, - expected: ['observer', 'host', 'user_agent'], - }, - type: 'group', - fields: [ - { - name: 'platform', - level: 'extended', - type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - example: 'Mac OS X', - description: 'Operating system name, without the version.', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - example: 'Mac OS Mojave', - description: 'Operating system name, including the version or code name.', - }, - { - name: 'family', - level: 'extended', - type: 'keyword', - example: 'debian', - description: 'OS family (such as redhat, debian, freebsd, windows).', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - example: '10.14.1', - description: 'Operating system version as a raw string.', - }, - { - name: 'kernel', - level: 'extended', - type: 'keyword', - example: '4.4.0-112-generic', - description: 'Operating system kernel version as a raw string.', - }, - ], - }, - { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - 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', - }, - ], + ignore_above: 1024, + description: 'Internal product name of the file, provided at compile-time.', + example: 'Microsoft® Windows® Operating System', + default_field: false, }, ], }, { - name: 'organization', - title: 'Organization', + name: 'dns', + title: 'DNS', group: 2, description: - 'The organization fields enrich data with information about the company or entity the data is associated with. These fields help you arrange or filter data stored in an index by one or multiple organizations.', + '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: 'name', + name: 'answers', level: 'extended', - type: 'keyword', - description: 'Organization name.', + 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: 'id', + name: 'answers.class', level: 'extended', type: 'keyword', - description: 'Unique identifier for the organization.', + ignore_above: 1024, + description: 'The class of DNS data contained in this resource record.', + example: 'IN', }, - ], - }, - { - name: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - reusable: { - top_level: false, - expected: ['observer', 'host', 'user_agent'], - }, - type: 'group', - fields: [ { - name: 'platform', + name: 'answers.data', level: 'extended', type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', + 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: 'name', + name: 'answers.name', level: 'extended', type: 'keyword', - example: 'Mac OS X', - description: 'Operating system name, without the version.', + 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: 'full', + name: 'answers.ttl', level: 'extended', - type: 'keyword', - example: 'Mac OS Mojave', - description: 'Operating system name, including the version or code name.', + 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: 'family', + name: 'answers.type', level: 'extended', type: 'keyword', - example: 'debian', - description: 'OS family (such as redhat, debian, freebsd, windows).', + ignore_above: 1024, + description: 'The type of data contained in this resource record.', + example: 'CNAME', }, { - name: 'version', + name: 'header_flags', level: 'extended', type: 'keyword', - example: '10.14.1', - description: 'Operating system version as a raw string.', + 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: 'kernel', + name: 'id', level: 'extended', type: 'keyword', - example: '4.4.0-112-generic', - description: 'Operating system kernel version as a raw string.', + 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: 'process', - title: 'Process', - group: 2, - description: - 'These fields contain information about a process. These fields can help you correlate metrics information with a process id/name from a log message. The `process.pid` often stays in the metric itself and is copied to the global field for correlation.', - type: 'group', - fields: [ { - name: 'pid', - level: 'core', - type: 'long', - description: 'Process id.', - example: 'ssh', + 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: 'name', + name: 'question.class', level: 'extended', type: 'keyword', - description: 'Process name. Sometimes called program name or similar.', - example: 'ssh', + ignore_above: 1024, + description: 'The class of records being queried.', + example: 'IN', }, { - name: 'ppid', + name: 'question.name', level: 'extended', - type: 'long', - description: 'Process parent id.', + 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: 'args', + name: 'question.registered_domain', level: 'extended', type: 'keyword', - description: 'Process arguments. May be filtered to protect sensitive information.', - example: ['ssh', '-l', 'user', '10.0.0.16'], + 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: 'executable', + name: 'question.subdomain', level: 'extended', type: 'keyword', - description: 'Absolute path to the process executable.', - example: '/usr/bin/ssh', + 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: 'title', + name: 'question.top_level_domain', level: 'extended', type: 'keyword', + ignore_above: 1024, 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.', + '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: 'thread.id', + name: 'question.type', level: 'extended', - type: 'long', - example: 4242, - description: 'Thread ID.', + type: 'keyword', + ignore_above: 1024, + description: 'The type of record being queried.', + example: 'AAAA', }, { - name: 'start', + name: 'resolved_ip', level: 'extended', - type: 'date', - example: '2016-05-23T08:05:34.853Z', - description: 'The time the process started.', + 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: 'working_directory', + name: 'response_code', level: 'extended', type: 'keyword', - example: '/home/alice', - description: 'The working directory of the process.', + 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: 'related', - title: 'Related', + name: 'ecs', + title: 'ECS', group: 2, - description: - 'This field set is meant to facilitate pivoting around a piece of data. Some pieces of information can be seen in many places in ECS. To facilitate searching for them, append values to their corresponding field in `related.`. A concrete example is IP addresses, which can be under host, observer, source, destination, client, server, and network.forwarded_ip. If you append all IPs to `related.ip`, you can then search for a given IP trivially, no matter where it appeared, by querying `related.ip:a.b.c.d`.', + description: 'Meta-information specific to ECS.', type: 'group', fields: [ { - name: 'ip', - level: 'extended', - type: 'ip', - description: 'All of the IPs seen on your event.', + 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: 'server', - title: 'Server', + name: 'error', + title: 'Error', group: 2, description: - 'A Server is defined as the responder in a network connection for events regarding sessions, connections, or bidirectional flow records. For TCP events, the server is the receiver of the initial SYN packet(s) of the TCP connection. For other protocols, the server is generally the responder in the network transaction. Some systems actually use the term "responder" to refer the server in TCP connections. The server fields describe details about the system acting as the server in the network event. Server fields are usually populated in conjunction with client fields. Server fields are generally not populated for packet-level events. Client / server representations can add semantic context to an exchange, which is helpful to visualize the data in certain situations. If your context falls in that category, you should still ensure that source and destination are filled appropriately.', + '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: 'address', - level: 'extended', - type: 'keyword', - 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: 'ip', - level: 'core', - type: 'ip', - description: 'IP address of the server. Can be one or multiple IPv4 or IPv6 addresses.', - }, - { - name: 'port', - level: 'core', - type: 'long', - description: 'Port of the server.', - }, - { - name: 'mac', + name: 'code', level: 'core', type: 'keyword', - description: 'MAC address of the server.', + ignore_above: 1024, + description: 'Error code describing the error.', }, { - name: 'domain', + name: 'id', level: 'core', type: 'keyword', - description: 'Server domain.', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - example: 184, - description: 'Bytes sent from the server to the client.', + ignore_above: 1024, + description: 'Unique identifier for the error.', }, { - name: 'packets', + name: 'message', level: 'core', - type: 'long', - example: 12, - description: 'Packets sent from the server to the client.', + type: 'text', + description: 'Error message.', }, { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, + name: 'stack_trace', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + multi_fields: [ { - name: 'name', - level: 'extended', - type: 'keyword', - 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: '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: 'service', - title: 'Service', + name: 'event', + title: 'Event', group: 2, description: - 'The service fields describe the service for or from which the data was collected. These fields help you find and correlate logs for a specific service and version.', + '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: 'id', + name: 'action', level: 'core', type: 'keyword', + ignore_above: 1024, description: - 'Unique identifier of the running service. This id should uniquely identify this service. This makes it possible to correlate logs and metrics for one specific service. Example: If you are experiencing issues with one redis instance, you can filter on that id to see metrics and logs for that single instance.', - example: 'd37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6', + '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: 'name', + name: 'category', level: 'core', type: 'keyword', - example: 'elasticsearch-metrics', + ignore_above: 1024, description: - 'Name of the service data is collected from. The name of the service is normally user given. This allows if two instances of the same service are running on the same machine they can be differentiated by the `service.name`. Also it 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.', + '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: 'type', - level: 'core', + name: 'code', + level: 'extended', type: 'keyword', - example: 'elasticsearch', + ignore_above: 1024, 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`.', + '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: 'state', + name: 'created', level: 'core', - type: 'keyword', - description: 'Current state of the service.', + 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: 'version', + name: 'dataset', level: 'core', type: 'keyword', - example: '3.2.4', + ignore_above: 1024, 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.', + '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: 'ephemeral_id', + 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: 'keyword', + type: 'date', description: - 'Ephemeral identifier of this service (if one exists). This id normally changes across restarts, but `service.id` does not.', - example: '8a4f500f', + 'event.end contains the date when the event ended or when the activity\nwas last observed.', }, - ], - }, - { - name: 'source', - title: 'Source', - group: 2, - description: - 'Source fields describe details about the source of a packet/event. Source fields are usually populated in conjunction with destination fields.', - type: 'group', - fields: [ { - name: 'address', + name: 'hash', level: 'extended', type: 'keyword', + ignore_above: 1024, 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.', + 'Hash (perhaps logstash fingerprint) of raw field to be able to\ndemonstrate log integrity.', + example: '123456789012345678901234567890ABCD', }, { - name: 'ip', + name: 'id', level: 'core', - type: 'ip', - description: 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.', + type: 'keyword', + ignore_above: 1024, + description: 'Unique ID to describe the event.', + example: '8a4f500d', }, { - name: 'port', + name: 'ingested', level: 'core', - type: 'long', - description: 'Port of the source.', + 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: 'mac', + name: 'kind', level: 'core', type: 'keyword', - description: 'MAC address of the source.', + 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: 'domain', + name: 'module', level: 'core', type: 'keyword', - description: 'Source domain.', + 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: 'bytes', + name: 'original', level: 'core', - type: 'long', - format: 'bytes', - example: 184, - description: 'Bytes sent from the source to the destination.', + 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: 'packets', + name: 'outcome', level: 'core', - type: 'long', - example: 12, - description: 'Packets sent from the source to the destination.', - }, - { - name: 'geo', - title: 'Geo', - group: 2, + type: 'keyword', + ignore_above: 1024, description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - 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', - }, - ], + '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: 'url', - title: 'URL', - description: 'URL fields provide a complete URL, with scheme, host, and path.', - type: 'group', - fields: [ { - name: 'original', + name: 'provider', level: 'extended', type: 'keyword', + ignore_above: 1024, 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', + '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: 'full', + name: 'reference', level: 'extended', type: 'keyword', + ignore_above: 1024, 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', + '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: 'scheme', - level: 'extended', - type: 'keyword', + name: 'risk_score', + level: 'core', + type: 'float', description: - 'Scheme of the request, such as "https". Note: The `:` is not part of the scheme.', - example: 'https', + "Risk score or priority of the event (e.g. security solutions).\nUse your system's original value here.", }, { - name: 'domain', + name: 'risk_score_norm', level: 'extended', - type: 'keyword', + type: 'float', description: - 'Domain of the request, 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', + '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: 'port', + name: 'sequence', level: 'extended', - type: 'integer', - description: 'Port of the request, such as 443.', - example: 443, + 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: 'path', - level: 'extended', - type: 'keyword', - description: 'Path of the request, such as "/search".', + 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: 'query', + name: 'start', level: 'extended', - type: 'keyword', + type: 'date', 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.', + 'event.start contains the date when the event started or when the\nactivity was first observed.', }, { - name: 'fragment', + name: 'timezone', level: 'extended', type: 'keyword', + ignore_above: 1024, description: - 'Portion of the url after the `#`, such as "top". The `#` is not part of the fragment.', + '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: 'username', - level: 'extended', + name: 'type', + level: 'core', type: 'keyword', - description: 'Username of the request.', + 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: 'password', + name: 'url', level: 'extended', type: 'keyword', - description: 'Password of the request.', + 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: 'user', - title: 'User', + name: 'file', + title: 'File', group: 2, description: - 'The user fields describe information about the user that is relevant to the event. Fields can have one entry or multiple entries. If a user has more than one id, provide an array that includes all of them.', - reusable: { - top_level: true, - expected: ['client', 'destination', 'host', 'server', 'source'], - }, + '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: 'id', - level: 'core', + 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', - description: 'One or multiple unique identifiers of the user.', + 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: 'name', + name: 'code_signature.exists', level: 'core', - type: 'keyword', - example: 'albert', - description: 'Short name or login of the user.', + type: 'boolean', + description: 'Boolean to capture if a signature is present.', + example: 'true', + default_field: false, }, { - name: 'full_name', + name: 'code_signature.status', level: 'extended', type: 'keyword', - example: 'Albert Einstein', - description: "User's full name, if available. ", + 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: 'email', - level: 'extended', + name: 'code_signature.subject_name', + level: 'core', type: 'keyword', - description: 'User email address.', + ignore_above: 1024, + description: 'Subject name of the code signer', + example: 'Microsoft Corporation', + default_field: false, }, { - name: 'hash', + name: 'code_signature.trusted', level: 'extended', - type: 'keyword', + type: 'boolean', 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.', + '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: 'group', - title: 'Group', - group: 2, + name: 'code_signature.valid', + level: 'extended', + type: 'boolean', description: - 'The group fields are meant to represent groups that are relevant to 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.', - }, - ], + '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: 'user_agent', - title: 'User agent', - group: 2, - description: - 'The user_agent fields normally come from a browser request. They often show up in web service logs coming from the parsed user agent string.', - type: 'group', - fields: [ { - name: 'original', + name: 'created', 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 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', + type: 'date', + description: + 'File creation time.\n\nNote that not all filesystems store the creation time.', }, { - name: 'name', + name: 'ctime', level: 'extended', - type: 'keyword', - example: 'Safari', - description: 'Name of the user agent.', + 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: 'version', + name: 'device', level: 'extended', type: 'keyword', - description: 'Version of the user agent.', - example: 12, + ignore_above: 1024, + description: 'Device that is the source of the file.', + example: 'sda', }, { - name: 'device.name', + name: 'directory', level: 'extended', type: 'keyword', - example: 'iPhone', - description: 'Name of the device.', + ignore_above: 1024, + description: + 'Directory where the file is located. It should include the drive\nletter, when appropriate.', + example: '/home/alice', }, { - name: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - reusable: { - top_level: false, - expected: ['observer', 'host', 'user_agent'], - }, - type: 'group', - fields: [ - { - name: 'platform', - level: 'extended', - type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - example: 'Mac OS X', - description: 'Operating system name, without the version.', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - example: 'Mac OS Mojave', - description: 'Operating system name, including the version or code name.', - }, - { - name: 'family', - level: 'extended', - type: 'keyword', - example: 'debian', - description: 'OS family (such as redhat, debian, freebsd, windows).', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - example: '10.14.1', - description: 'Operating system version as a raw string.', - }, - { - name: 'kernel', - level: 'extended', - type: 'keyword', - example: '4.4.0-112-generic', - description: 'Operating system kernel version as a raw string.', - }, - ], + 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: 'agent.hostname', - type: 'keyword', - description: 'Hostname of the agent.', - }, - ], - }, - { - key: 'beat', - title: 'Beat', - description: 'Contains common beat fields available in all event types.', - fields: [ - { - name: 'beat.timezone', - type: 'alias', - path: 'event.timezone', - migration: true, - }, - { - name: 'fields', - type: 'object', - object_type: 'keyword', - description: 'Contains user configurable fields.', - }, - { - name: 'error', - type: 'group', - description: 'Error fields containing additional info in case of errors.', - fields: [ { - name: 'type', + name: 'extension', + level: 'extended', type: 'keyword', - description: 'Error type.', + ignore_above: 1024, + description: 'File extension.', + example: 'png', }, - ], - }, - { - name: 'beat.name', - type: 'alias', - path: 'host.name', - migration: true, - }, - { - name: 'beat.hostname', - type: 'alias', - path: 'agent.hostname', - migration: true, - }, - ], - }, - { - key: 'cloud', - title: 'Cloud provider metadata', - description: 'Metadata from cloud providers added by the add_cloud_metadata processor.', - fields: [ - { - name: 'cloud.project.id', - example: 'project-x', - description: 'Name of the project in Google Cloud.', - }, - { - 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.', - short_config: false, - anchor: 'docker-processor', - fields: [ - { - name: 'docker', - type: 'group', - fields: [ { - name: 'container.id', - type: 'alias', - path: 'container.id', - migration: true, + name: 'gid', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Primary group ID (GID) of the file.', + example: '1001', }, { - name: 'container.image', - type: 'alias', - path: 'container.image.name', - migration: true, + name: 'group', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Primary group name of the file.', + example: 'alice', }, { - name: 'container.name', - type: 'alias', - path: 'container.name', - migration: true, + name: 'hash.md5', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'MD5 hash.', }, { - name: 'container.labels', - type: 'object', - object_type: 'keyword', - description: 'Image labels.', + name: 'hash.sha1', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'SHA1 hash.', }, - ], - }, - ], - }, - { - key: 'host', - title: 'Host', - description: 'Info collected for the host machine.', - anchor: 'host-processor', - }, - { - key: 'kubernetes', - title: 'Kubernetes', - description: 'Kubernetes metadata added by the kubernetes processor', - short_config: false, - anchor: 'kubernetes-processor', - fields: [ - { - name: 'kubernetes', - type: 'group', - fields: [ { - name: 'pod.name', + name: 'hash.sha256', + level: 'extended', type: 'keyword', - description: 'Kubernetes pod name', + ignore_above: 1024, + description: 'SHA256 hash.', }, { - name: 'pod.uid', + name: 'hash.sha512', + level: 'extended', type: 'keyword', - description: 'Kubernetes Pod UID', + ignore_above: 1024, + description: 'SHA512 hash.', }, { - name: 'namespace', + name: 'inode', + level: 'extended', type: 'keyword', - description: 'Kubernetes namespace', + ignore_above: 1024, + description: 'Inode representing the file in the filesystem.', + example: '256383', }, { - name: 'node.name', + name: 'mime_type', + level: 'extended', type: 'keyword', - description: 'Kubernetes node name', + 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: 'labels', - type: 'object', - description: 'Kubernetes labels map', + name: 'mode', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Mode of the file in octal representation.', + example: '0640', }, { - name: 'annotations', - type: 'object', - description: 'Kubernetes annotations map', + name: 'mtime', + level: 'extended', + type: 'date', + description: 'Last time the file content was modified.', }, { - name: 'container.name', + name: 'name', + level: 'extended', type: 'keyword', - description: 'Kubernetes container name', + ignore_above: 1024, + description: 'Name of the file including the extension, without the directory.', + example: 'example.png', }, { - name: 'container.image', + name: 'owner', + level: 'extended', type: 'keyword', - description: 'Kubernetes container image', - }, - ], - }, - ], - }, - { - key: 'process', - title: 'Process', - description: 'Process metadata fields', - fields: [ - { - name: 'process', - type: 'group', - fields: [ - { - name: 'exe', - type: 'alias', - path: 'process.executable', - migration: true, - }, - ], - }, - ], - }, - { - key: 'common', - title: 'Common', - description: - 'These fields contain data about the environment in which the transaction or flow was captured.', - fields: [ - { - name: 'type', - description: - 'The type of the transaction (for example, HTTP, MySQL, Redis, or RUM) or "flow" in case of flows.', - required: true, - }, - { - name: 'server.process.name', - description: 'The name of the process that served the transaction.', - }, - { - name: 'server.process.args', - description: 'The command-line of the process that served the transaction.', - }, - { - name: 'server.process.executable', - description: 'Absolute path to the server process executable.', - }, - { - name: 'server.process.working_directory', - description: 'The working directory of the server process.', - }, - { - name: 'server.process.start', - description: 'The time the server process started.', - }, - { - name: 'client.process.name', - description: 'The name of the process that initiated the transaction.', - }, - { - name: 'client.process.args', - description: 'The command-line of the process that initiated the transaction.', - }, - { - name: 'client.process.executable', - description: 'Absolute path to the client process executable.', - }, - { - name: 'client.process.working_directory', - description: 'The working directory of the client process.', - }, - { - name: 'client.process.start', - description: 'The time the client process started.', - }, - { - 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`. Unless this field is disabled, it always has a value, and it matches the `client_ip` for non proxy clients.', - }, - { - name: 'transport', - type: 'alias', - path: 'network.transport', - migration: true, - description: - 'The transport protocol used for the transaction. If not specified, then tcp is assumed.', - }, - ], - }, - { - key: 'flows_event', - title: 'Flow Event', - description: 'These fields contain data about the flow itself.', - 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.', - }, - { - name: 'flow.id', - description: 'Internal flow ID based on connection meta data and address.', - }, - { - 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. ", - }, - { - 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.', - 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.', - 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).', - }, - { - 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.', - }, - { - 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.', - }, - { - 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`.', - }, - { - 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.', - }, - { - 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.', - }, - ], - }, - { - 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.', - }, - { - 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.', - }, - ], - }, - { - key: 'trans_measurements', - title: 'Measurements (Transactions)', - description: 'These fields contain measurements related to the transaction.', - 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.', - }, - { - 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.', - }, - ], - }, - { - 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', - example: 404, + ignore_above: 1024, + description: "File owner's username.", + example: 'alice', }, { - name: 'reply-text', + name: 'path', + level: 'extended', type: 'keyword', - description: 'Text explaining the error.', - }, - { - name: 'class-id', - type: 'long', - description: 'Failing method class.', + 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: 'method-id', - type: 'long', - description: 'Failing method ID.', + 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: 'exchange', + name: 'pe.description', + level: 'extended', type: 'keyword', - description: 'Name of the exchange.', + ignore_above: 1024, + description: 'Internal description of the file, provided at compile-time.', + example: 'Paint', + default_field: false, }, { - name: 'exchange-type', + name: 'pe.file_version', + level: 'extended', type: 'keyword', - description: 'Exchange type.', - example: 'fanout', + ignore_above: 1024, + description: 'Internal version of the file, provided at compile-time.', + example: '6.3.9600.17415', + default_field: false, }, { - name: 'passive', - type: 'boolean', - description: 'If set, do not create exchange/queue.', + 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: 'durable', - type: 'boolean', - description: 'If set, request a durable exchange/queue.', + 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: 'exclusive', - type: 'boolean', - description: 'If set, request an exclusive queue.', + name: 'size', + level: 'extended', + type: 'long', + description: 'File size in bytes.\n\nOnly relevant when `file.type` is "file".', + example: 16384, }, { - name: 'auto-delete', - type: 'boolean', - description: 'If set, auto-delete queue when unused.', + 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: 'no-wait', - type: 'boolean', - description: 'If set, the server will not respond to the method.', + name: 'type', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'File type (file, dir, or symlink).', + example: 'file', }, { - name: 'consumer-tag', - description: 'Identifier for the consumer, valid within the current channel.', + 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: 'delivery-tag', - type: 'long', - description: 'The server-assigned and channel-specific delivery tag.', + name: 'city_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'City name.', + example: 'Montreal', }, { - name: 'message-count', - type: 'long', - description: - 'The number of messages in the queue, which will be zero for newly-declared queues.', + name: 'continent_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Name of the continent.', + example: 'North America', }, { - name: 'consumer-count', - type: 'long', - description: 'The number of consumers of a queue.', + name: 'country_iso_code', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Country ISO code.', + example: 'CA', }, { - name: 'routing-key', + name: 'country_name', + level: 'core', type: 'keyword', - description: 'Message routing key.', + ignore_above: 1024, + description: 'Country name.', + example: 'Canada', }, { - name: 'no-ack', - type: 'boolean', - description: 'If set, the server does not expect acknowledgements for messages.', + name: 'location', + level: 'core', + type: 'geo_point', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', }, { - name: 'no-local', - type: 'boolean', + name: 'name', + level: 'extended', + type: 'keyword', + ignore_above: 1024, description: - 'If set, the server will not send messages to the connection that published them.', + '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: 'if-unused', - type: 'boolean', - description: 'Delete only if unused.', + name: 'region_iso_code', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Region ISO code.', + example: 'CA-QC', }, { - name: 'if-empty', - type: 'boolean', - description: 'Delete only if empty.', + 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: 'queue', + name: 'domain', + level: 'extended', type: 'keyword', - description: 'The queue name identifies the queue within the vhost.', - }, - { - name: 'redelivered', - type: 'boolean', - description: - 'Indicates that the message has been previously delivered to this or another client.', - }, - { - name: 'multiple', - type: 'boolean', - description: 'Acknowledge multiple messages.', - }, - { - name: 'arguments', - type: 'object', + ignore_above: 1024, description: - 'Optional additional arguments passed to some methods. Can be of various types.', - }, - { - name: 'mandatory', - type: 'boolean', - description: 'Indicates mandatory routing.', + 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', }, { - name: 'immediate', - type: 'boolean', - description: 'Request immediate delivery.', - }, - { - name: 'content-type', + name: 'id', + level: 'extended', type: 'keyword', - description: 'MIME content type.', - example: 'text/plain', + ignore_above: 1024, + description: 'Unique identifier for the group on the system/platform.', }, { - name: 'content-encoding', + name: 'name', + level: 'extended', type: 'keyword', - description: 'MIME content encoding.', - }, - { - name: 'headers', - type: 'object', - object_type: 'keyword', - description: 'Message header field table.', + 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: 'delivery-mode', + name: 'md5', + level: 'extended', type: 'keyword', - description: 'Non-persistent (1) or persistent (2).', - }, - { - name: 'priority', - type: 'long', - description: 'Message priority, 0 to 9.', + ignore_above: 1024, + description: 'MD5 hash.', }, { - name: 'correlation-id', + name: 'sha1', + level: 'extended', type: 'keyword', - description: 'Application correlation identifier.', + ignore_above: 1024, + description: 'SHA1 hash.', }, { - name: 'reply-to', + name: 'sha256', + level: 'extended', type: 'keyword', - description: 'Address to reply to.', + ignore_above: 1024, + description: 'SHA256 hash.', }, { - name: 'expiration', + name: 'sha512', + level: 'extended', type: 'keyword', - description: 'Message expiration specification.', + 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: 'message-id', + name: 'architecture', + level: 'core', type: 'keyword', - description: 'Application message identifier.', + ignore_above: 1024, + description: 'Operating system architecture.', + example: 'x86_64', }, { - name: 'timestamp', + name: 'domain', + level: 'extended', type: 'keyword', - description: 'Message timestamp.', + 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: 'type', + name: 'geo.city_name', + level: 'core', type: 'keyword', - description: 'Message type name.', + ignore_above: 1024, + description: 'City name.', + example: 'Montreal', }, { - name: 'user-id', + name: 'geo.continent_name', + level: 'core', type: 'keyword', - description: 'Creating user id.', + ignore_above: 1024, + description: 'Name of the continent.', + example: 'North America', }, { - name: 'app-id', + name: 'geo.country_iso_code', + level: 'core', type: 'keyword', - description: 'Creating application id.', - }, - ], - }, - ], - }, - { - 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.', + ignore_above: 1024, + description: 'Country ISO code.', + example: 'CA', }, { - 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: 'geo.country_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Country name.', + example: 'Canada', }, { - 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.', - }, - ], - }, - ], - }, - ], + name: 'geo.location', + level: 'core', + type: 'geo_point', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', }, - ], - }, - ], - }, - { - key: 'dhcpv4', - title: 'DHCPv4', - description: 'DHCPv4 event fields', - fields: [ - { - name: 'dhcpv4', - type: 'group', - fields: [ { - name: 'transaction_id', + name: 'geo.name', + level: 'extended', type: 'keyword', + ignore_above: 1024, 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: 'seconds', - type: 'long', - description: - 'Number of seconds elapsed since client began address acquisition or renewal process.', + '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: 'flags', + name: 'geo.region_iso_code', + level: 'core', type: 'keyword', - description: - 'Flags are set by the client to indicate how the DHCP server should its reply -- either unicast or broadcast.', + ignore_above: 1024, + description: 'Region ISO code.', + example: 'CA-QC', }, { - name: 'client_ip', - type: 'ip', - description: 'The current IP address of the client.', + name: 'geo.region_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Region name.', + example: 'Quebec', }, { - name: 'assigned_ip', - type: 'ip', + name: 'hostname', + level: 'core', + type: 'keyword', + ignore_above: 1024, description: - 'The IP address that the DHCP server is assigning to the client. This field is also known as "your" IP address.', + 'Hostname of the host.\n\nIt normally contains what the `hostname` command returns on the host machine.', }, { - name: 'server_ip', - type: 'ip', + name: 'id', + level: 'core', + type: 'keyword', + ignore_above: 1024, description: - 'The IP address of the DHCP server that the client should use for the next step in the bootstrap process.', + '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: 'relay_ip', + name: 'ip', + level: 'core', type: 'ip', - description: - 'The relay IP address used by the client to contact the server (i.e. a DHCP relay server).', + description: 'Host ip addresses.', }, { - name: 'client_mac', + name: 'mac', + level: 'core', type: 'keyword', - description: "The client's MAC address (layer two).", + ignore_above: 1024, + description: 'Host mac addresses.', }, { - name: 'server_name', + name: 'name', + level: 'core', type: 'keyword', + ignore_above: 1024, description: - 'The name of the server sending the message. Optional. Used in DHCPOFFER or DHCPACK messages.', + '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: 'op_code', + name: 'os.family', + level: 'extended', type: 'keyword', - example: 'bootreply', - description: 'The message op code (bootrequest or bootreply).', - }, - { - name: 'hops', - type: 'long', - description: 'The number of hops the DHCP message went through.', + ignore_above: 1024, + description: 'OS family (such as redhat, debian, freebsd, windows).', + example: 'debian', }, { - name: 'hardware_type', + name: 'os.full', + level: 'extended', type: 'keyword', - description: - 'The type of hardware used for the local network (Ethernet, LocalTalk, etc).', - }, - { - name: 'option', - type: 'group', - fields: [ - { - name: 'message_type', - type: 'keyword', - example: 'ack', - description: - 'The specific type of DHCP message being sent (e.g. discover, offer, request, decline, ack, nak, release, inform).', - }, - { - name: 'parameter_request_list', - type: 'keyword', - description: - 'This option is used by a DHCP client to request values for specified configuration parameters.', - }, - { - name: 'requested_ip_address', - type: 'ip', - description: - 'This option is used in a client request (DHCPDISCOVER) to allow the client to request that a particular IP address be assigned.', - }, - { - name: 'server_identifier', - type: 'ip', - description: 'IP address of the individual DHCP server which handled this message.', - }, - { - name: 'broadcast_address', - type: 'ip', - description: - "This option specifies the broadcast address in use on the client's subnet. ", - }, - { - name: 'max_dhcp_message_size', - type: 'long', - description: - 'This option specifies the maximum length DHCP message that the client is willing to accept.', - }, - { - name: 'class_identifier', - type: 'keyword', - 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: 'domain_name', - type: 'keyword', - description: - 'This option specifies the domain name that client should use when resolving hostnames via the Domain Name System.', - }, - { - name: 'dns_servers', - type: 'ip', - description: - 'The domain name server option specifies a list of Domain Name System servers available to the client.', - }, - { - name: 'vendor_identifying_options', - type: 'object', - 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: 'subnet_mask', - type: 'ip', - description: 'The subnet mask that the client should use on the currnet network.', - }, - { - name: 'utc_time_offset_sec', - type: 'long', - description: - "The time offset field specifies the offset of the client's subnet in seconds from Coordinated Universal Time (UTC). ", - }, - { - name: 'router', - type: 'ip', - description: - "The router option specifies a list of IP addresses for routers on the client's subnet. ", - }, - { - name: 'time_servers', - type: 'ip', - description: - 'The time server option specifies a list of RFC 868 time servers available to the client.', - }, - { - name: 'ntp_servers', - type: 'ip', - description: - 'This option specifies a list of IP addresses indicating NTP servers available to the client.', - }, + ignore_above: 1024, + multi_fields: [ { - name: 'hostname', - type: 'keyword', - description: 'This option specifies the name of the client.', - }, - { - name: 'ip_address_lease_time_sec', - type: 'long', - 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: 'message', + name: 'text', type: 'text', - 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: 'renewal_time_sec', - type: 'long', - description: - 'This option specifies the time interval from address assignment until the client transitions to the RENEWING state.', - }, - { - name: 'rebinding_time_sec', - type: 'long', - description: - 'This option specifies the time interval from address assignment until the client transitions to the REBINDING state.', - }, - { - name: 'boot_file_name', - type: 'keyword', - description: - "This option is used to identify a bootfile when the 'file' field in the DHCP header has been used for DHCP options. ", + norms: false, + default_field: false, }, ], + description: 'Operating system name, including the version or code name.', + example: 'Mac OS Mojave', }, - ], - }, - ], - }, - { - key: 'dns', - title: 'DNS', - description: 'DNS-specific event fields.', - fields: [ - { - name: 'dns', - type: 'group', - fields: [ { - name: 'id', - type: 'long', - description: - 'The DNS packet identifier assigned by the program that generated the query. The identifier is copied to the response.', + 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: 'op_code', - 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: '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: 'flags.authoritative', - type: 'boolean', - description: - 'A DNS flag specifying that the responding server is an authority for the domain name used in the question.', + name: 'os.platform', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Operating system platform (such centos, ubuntu, windows).', + example: 'darwin', }, { - name: 'flags.recursion_available', - type: 'boolean', - description: - 'A DNS flag specifying whether recursive query support is available in the name server.', + name: 'os.version', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Operating system version as a raw string.', + example: '10.14.1', }, { - name: 'flags.recursion_desired', - type: 'boolean', + name: 'type', + level: 'core', + type: 'keyword', + ignore_above: 1024, description: - 'A DNS flag specifying that the client directs the server to pursue a query recursively. Recursive query support is optional.', + '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: 'flags.authentic_data', - type: 'boolean', - description: - 'A DNS flag specifying that the recursive server considers the response authentic.', + name: 'uptime', + level: 'extended', + type: 'long', + description: 'Seconds the host has been up.', + example: 1325, }, { - name: 'flags.checking_disabled', - type: 'boolean', + name: 'user.domain', + level: 'extended', + type: 'keyword', + ignore_above: 1024, description: - 'A DNS flag specifying that the client disables the server signature validation of the query.', + 'Name of the directory the user is a member of.\n\nFor example, an LDAP or Active Directory domain name.', }, { - name: 'flags.truncated_response', - type: 'boolean', - description: - 'A DNS flag specifying that only the first 512 bytes of the reply were returned.', + name: 'user.email', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'User email address.', }, { - name: 'response_code', - description: 'The DNS status code.', - example: 'NOERROR', + 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: 'question.name', + name: 'user.group.domain', + level: 'extended', + type: 'keyword', + ignore_above: 1024, description: - 'The domain name being queried. If the name field contains non-printable characters (below 32 or above 126), then those characters are represented as escaped base 10 integers (\\DDD). Back slashes and quotes are escaped. Tabs, carriage returns, and line feeds are converted to \\t, \\r, and respectively.', - example: 'www.google.com.', - }, - { - name: 'question.type', - description: 'The type of records being queried.', - example: 'AAAA', + 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', }, { - name: 'question.class', - description: 'The class of of records being queried.', - example: 'IN', + name: 'user.group.id', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Unique identifier for the group on the system/platform.', }, { - name: 'question.etld_plus_one', - 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: 'user.group.name', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Name of the group.', }, { - name: 'answers', - type: 'object', + name: 'user.hash', + level: 'extended', + type: 'keyword', + ignore_above: 1024, description: - 'An array containing a dictionary about each answer section returned by the server.', + '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: 'answers_count', - type: 'long', - description: 'The number of resource records contained in the `dns.answers` field.', + name: 'user.id', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Unique identifiers of the user.', }, { - name: 'answers.name', - description: 'The domain name to which this resource record pertains.', - example: 'example.com.', + 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: 'answers.type', - description: 'The type of data contained in this resource record.', - example: 'MX', + name: 'request.body.bytes', + level: 'extended', + type: 'long', + format: 'bytes', + description: 'Size in bytes of the request body.', + example: 887, }, { - name: 'answers.class', - description: 'The class of DNS data contained in this resource record.', - example: 'IN', + 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: 'answers.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.', + name: 'request.bytes', + level: 'extended', type: 'long', + format: 'bytes', + description: 'Total size in bytes of the request (body and headers).', + example: 1437, }, { - name: 'answers.data', + name: 'request.method', + level: 'extended', + type: 'keyword', + ignore_above: 1024, description: - 'The data describing the resource. The meaning of this data depends on the type and class of the resource record.', + '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: 'authorities', - type: 'object', - description: - 'An array containing a dictionary for each authority section from the answer.', + name: 'request.referrer', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Referrer for this HTTP request.', + example: 'https://blog.example.com/', }, { - name: 'authorities_count', + name: 'response.body.bytes', + level: 'extended', 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.', - }, - { - name: 'authorities.name', - description: 'The domain name to which this resource record pertains.', - example: 'example.com.', + format: 'bytes', + description: 'Size in bytes of the response body.', + example: 887, }, { - name: 'authorities.type', - description: 'The type of data contained in this resource record.', - example: 'NS', + 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: 'authorities.class', - description: 'The class of DNS data contained in this resource record.', - example: 'IN', + name: 'response.bytes', + level: 'extended', + type: 'long', + format: 'bytes', + description: 'Total size in bytes of the response (body and headers).', + example: 1437, }, { - name: 'additionals', - type: 'object', - description: - 'An array containing a dictionary for each additional section from the answer.', - }, - { - name: 'additionals_count', + name: 'response.status_code', + level: 'extended', 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.', - }, - { - 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', + format: 'string', + description: 'HTTP response status code.', + example: 404, }, { - 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.', - type: 'long', + 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: 'additionals.data', + name: 'alias', + level: 'extended', + type: 'keyword', + ignore_above: 1024, description: - 'The data describing the resource. The meaning of this data depends on the type and class of the resource record.', + '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: '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: '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: 'opt.udp_size', - type: 'long', - description: "Requestor's UDP payload size (in bytes).", + name: 'name', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Interface name as reported by the system.', + example: 'eth0', + default_field: false, }, ], }, - ], - }, - { - key: 'http', - title: 'HTTP', - description: 'HTTP-specific event fields.', - fields: [ { - name: 'http', + 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', - 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.', - }, - { - name: 'params', - type: 'alias', - migration: true, - path: 'url.query', - }, - ], + 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: '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.', - }, - { - name: 'code', - type: 'alias', - migration: true, - path: 'http.response.status_code', - }, - { - name: 'phrase', - type: 'alias', - migration: true, - path: 'http.response.status_phrase', - }, - ], + 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', }, - ], - }, - ], - }, - { - key: 'icmp', - title: 'ICMP', - description: 'ICMP specific event fields.', - fields: [ - { - name: 'icmp', - type: 'group', - fields: [ { - name: 'version', - description: 'The version of the ICMP protocol.', - possible_values: [4, 6], + 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: 'request.message', + name: 'origin.function', + level: 'extended', type: 'keyword', - description: 'A human readable form of the request.', + ignore_above: 1024, + description: 'The name of the function or method which originated the log event.', + example: 'init', }, { - name: 'request.type', - type: 'long', - description: 'The request type.', + 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: 'request.code', + name: 'syslog.facility.code', + level: 'extended', type: 'long', - description: 'The request code.', + 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: 'response.message', + name: 'syslog.facility.name', + level: 'extended', type: 'keyword', - description: 'A human readable form of the response.', + ignore_above: 1024, + description: 'The Syslog text-based facility of the log event, if available.', + example: 'local7', }, { - name: 'response.type', + name: 'syslog.priority', + level: 'extended', type: 'long', - description: 'The response type.', + 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: 'response.code', + name: 'syslog.severity.code', + level: 'extended', type: 'long', - description: 'The response code.', + 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', }, ], }, - ], - }, - { - key: 'memcache', - title: 'Memcache', - description: 'Memcached-specific event fields', - fields: [ { - name: 'memcache', + 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: 'protocol_type', + name: 'application', + level: 'extended', type: 'keyword', + ignore_above: 1024, 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.', + '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: 'request.line', - type: 'keyword', - description: 'The raw command line for unknown commands ONLY.', + 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: 'request.command', + name: 'community_id', + level: 'extended', type: 'keyword', + ignore_above: 1024, 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.', + '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: 'response.command', + name: 'direction', + level: 'core', type: 'keyword', + ignore_above: 1024, description: - 'Either the text based protocol response message type or the name of the originating request if binary protocol is used.', + "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: '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".', + 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: 'response.type', + name: 'iana_number', + level: 'extended', type: 'keyword', + ignore_above: 1024, 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).', + '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: 'response.error_msg', - type: 'keyword', + name: 'inner', + level: 'extended', + type: 'object', + object_type: 'keyword', description: - 'The optional error message in the memcache response (text based protocol only).', + '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: 'request.opcode', + name: 'inner.vlan.id', + level: 'extended', type: 'keyword', - description: 'The binary protocol message opcode name.', + ignore_above: 1024, + description: 'VLAN ID as reported by the observer.', + example: 10, + default_field: false, }, { - name: 'response.opcode', + name: 'inner.vlan.name', + level: 'extended', type: 'keyword', - description: 'The binary protocol message opcode name.', + ignore_above: 1024, + description: 'Optional VLAN name as reported by the observer.', + example: 'outside', + default_field: false, }, { - name: 'request.opcode_value', - type: 'long', - description: 'The binary protocol message opcode value.', - }, - { - name: 'response.opcode_value', - type: 'long', - description: 'The binary protocol message opcode value.', + name: 'name', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Name given by operators to sections of their network.', + example: 'Guest Wifi', }, { - name: 'request.opaque', + name: 'packets', + level: 'core', type: 'long', description: - 'The binary protocol opaque header value used for correlating request with response messages.', + 'Total packets transferred in both directions.\n\nIf `source.packets` and `destination.packets` are known, `network.packets`\nis their sum.', + example: 24, }, { - name: 'response.opaque', - type: 'long', + name: 'protocol', + level: 'core', + type: 'keyword', + ignore_above: 1024, description: - 'The binary protocol opaque header value used for correlating request with response messages.', - }, - { - name: 'request.vbucket', - type: 'long', - description: 'The vbucket index sent in the binary message.', + '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: 'response.status', + name: 'transport', + level: 'core', type: 'keyword', + ignore_above: 1024, description: - 'The textual representation of the response error code (binary protocol only).', + '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: 'response.status_code', - type: 'long', - description: 'The status code value returned in the response (binary protocol only).', + 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: 'request.keys', - type: 'array', - description: 'The list of keys sent in the store or load commands.', + name: 'vlan.id', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'VLAN ID as reported by the observer.', + example: 10, + default_field: false, }, { - name: 'response.keys', - type: 'array', - description: 'The list of keys returned for the load command (if present).', + 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: 'request.count_values', - type: 'long', + name: 'egress', + level: 'extended', + type: 'object', + object_type: 'keyword', description: - 'The number of values found in the memcache request message. If the command does not send any data, this field is missing.', + '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: 'response.count_values', - type: 'long', + name: 'egress.interface.alias', + level: 'extended', + type: 'keyword', + ignore_above: 1024, description: - 'The number of values found in the memcache response message. If the command does not send any data, this field is missing.', + '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: 'request.values', - type: 'array', - description: 'The list of base64 encoded values sent with the request (if present).', + 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: 'response.values', - type: 'array', - description: 'The list of base64 encoded values sent with the response (if present).', + 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: 'request.bytes', - type: 'long', - format: 'bytes', - description: 'The byte count of the values being transferred.', + 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: 'response.bytes', - type: 'long', - format: 'bytes', - description: 'The byte count of the values being transferred.', + 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: 'request.delta', - type: 'long', - description: 'The counter increment/decrement delta value.', + 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: 'request.initial', - type: 'long', - description: - 'The counter increment/decrement initial value parameter (binary protocol only).', + name: 'geo.city_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'City name.', + example: 'Montreal', }, { - name: 'request.verbosity', - type: 'long', - description: 'The value of the memcache "verbosity" command.', + name: 'geo.continent_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Name of the continent.', + example: 'North America', }, { - name: 'request.raw_args', + name: 'geo.country_iso_code', + level: 'core', type: 'keyword', - description: - 'The text protocol raw arguments for the "stats ..." and "lru crawl ..." commands.', + ignore_above: 1024, + description: 'Country ISO code.', + example: 'CA', }, { - name: 'request.source_class', - type: 'long', - description: "The source class id in 'slab reassign' command. ", + name: 'geo.country_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Country name.', + example: 'Canada', }, { - name: 'request.dest_class', - type: 'long', - description: "The destination class id in 'slab reassign' command. ", + name: 'geo.location', + level: 'core', + type: 'geo_point', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', }, { - name: 'request.automove', + name: 'geo.name', + level: 'extended', type: 'keyword', + ignore_above: 1024, 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.', + '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: 'request.flags', - type: 'long', - description: 'The memcache command flags sent in the request (if present).', + name: 'geo.region_iso_code', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Region ISO code.', + example: 'CA-QC', }, { - name: 'response.flags', - type: 'long', - description: 'The memcache message flags sent in the response (if present).', + name: 'geo.region_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Region name.', + example: 'Quebec', }, { - name: 'request.exptime', - type: 'long', + name: 'hostname', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Hostname of the observer.', + }, + { + name: 'ingress', + level: 'extended', + type: 'object', + object_type: 'keyword', 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).', + '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: 'request.sleep_us', - type: 'long', - description: "The sleep setting in microseconds for the 'lru_crawler sleep' command. ", + 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: 'response.value', - type: 'long', - description: 'The counter value returned by a counter operation.', + 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: 'request.noreply', - type: 'boolean', - description: - 'Set to true if noreply was set in the request. The `memcache.response` field will be missing.', + 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: 'request.quiet', - type: 'boolean', + 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: - 'Set to true if the binary protocol message is to be treated as a quiet message.', + '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: 'request.cas_unique', - type: 'long', - description: 'The CAS (compare-and-swap) identifier if present.', + name: 'ip', + level: 'core', + type: 'ip', + description: 'IP addresses of the observer.', }, { - name: 'response.cas_unique', - type: 'long', + 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: - 'The CAS (compare-and-swap) identifier to be used with CAS-based updates (if present).', + '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: 'response.stats', - type: 'array', + name: 'type', + level: 'core', + type: 'keyword', + ignore_above: 1024, description: - 'The list of statistic values returned. Each entry is a dictionary with the fields "name" and "value".', + '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: 'response.version', + name: 'version', + level: 'core', type: 'keyword', - description: 'The returned memcache version string.', + ignore_above: 1024, + description: 'Observer version.', }, ], }, - ], - }, - { - 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.', - fields: [ { - name: 'mongodb', + 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: 'error', - description: - 'If the MongoDB request has resulted in an error, this field contains the error message returned by the server.', + name: 'id', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Unique identifier for the organization.', }, { - 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.', + 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: 'numberToSkip', - type: 'long', + 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: - 'Sets the number of documents to omit - starting from the first document in the resulting dataset - when returning the result of the query.', + '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: 'numberToReturn', - type: 'long', - description: 'The requested maximum number of documents to be returned.', + name: 'checksum', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Checksum of the installed package for verification.', + example: '68b329da9893e34099c7d8ad5cb9c940', }, { - name: 'numberReturned', - type: 'long', - description: 'The number of documents in the reply.', + 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: 'startingFrom', - description: 'Where in the cursor this reply is starting.', + 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: 'query', + name: 'installed', + level: 'extended', + type: 'date', + description: 'Time when package was installed.', + }, + { + name: 'license', + level: 'extended', + type: 'keyword', + ignore_above: 1024, 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.', + '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: 'returnFieldsSelector', + name: 'reference', + level: 'extended', + type: 'keyword', + ignore_above: 1024, 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.', + '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: 'selector', + name: 'type', + level: 'extended', + type: 'keyword', + ignore_above: 1024, description: - 'A BSON document that specifies the query for selecting the document to update or delete.', + '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: 'update', + name: 'messages', + type: 'alias', + migration: true, + path: 'event.original', 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.', + '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: 'cursorId', + name: 'warnings', + type: 'alias', + migration: true, + path: 'error.message', description: - 'The cursor identifier returned in the OP_REPLY. This must be the value that was returned from the database.', + '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', }, ], }, - ], - }, - { - key: 'mysql', - title: 'MySQL', - description: 'MySQL-specific event fields.', - fields: [ { - name: 'mysql', + 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: 'affected_rows', - type: 'long', - description: - 'If the MySQL command is successful, this field contains the affected number of rows of the last statement.', - }, - { - name: 'insert_id', - description: - 'If the INSERT query is successful, this field contains the id of the newly inserted row.', - }, - { - name: 'num_fields', - description: - 'If the SELECT query is successful, this field is set to the number of fields returned.', + name: 'continent_name', + type: 'keyword', + description: 'The name of the continent.\n', }, { - name: 'num_rows', - description: - 'If the SELECT query is successful, this field is set to the number of rows returned.', + name: 'city_name', + type: 'keyword', + description: 'The name of the city.\n', }, { - name: 'query', - description: "The row mysql query as read from the transaction's request. ", + name: 'region_name', + type: 'keyword', + description: 'The name of the region.\n', }, { - name: 'error_code', - type: 'long', - description: 'The error code returned by MySQL.', + name: 'country_iso_code', + type: 'keyword', + description: 'Country ISO code.\n', }, { - name: 'error_message', - description: 'The error info message returned by MySQL.', + name: 'location', + type: 'geo_point', + description: 'The longitude and latitude.\n', }, ], }, ], }, { - key: 'nfs', - title: 'NFS', - description: 'NFS v4/3 specific event fields.', + key: 'file_integrity', + title: 'File Integrity', + description: 'These are the fields generated by the file_integrity module.', 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.', - }, - { - name: 'status', - description: 'NFS operation reply status.', - }, - ], - }, - { - name: 'rpc', + name: 'hash', type: 'group', - description: 'ONC RPC specific event fields.', + description: + 'Hashes of the file. The keys are algorithm names and the values are the hex encoded digest values.\n', 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: 'blake2b_256', + type: 'keyword', + description: 'BLAKE2b-256 hash of the file.', }, { - name: 'cred.stamp', - type: 'long', - description: 'Arbitrary ID which the caller machine may generate.', + name: 'blake2b_384', + type: 'keyword', + description: 'BLAKE2b-384 hash of the file.', }, { - name: 'cred.machinename', - description: "The name of the caller's machine.", + name: 'blake2b_512', + type: 'keyword', + description: 'BLAKE2b-512 hash of the file.', }, { - name: 'call_size', - type: 'alias', - path: 'source.bytes', - migration: true, - description: 'RPC call size with argument.', + name: 'md5', + overwrite: true, + type: 'keyword', + description: 'MD5 hash of the file.', }, { - name: 'reply_size', - type: 'alias', - path: 'destination.bytes', - migration: true, - description: 'RPC reply size with argument.', + name: 'sha1', + overwrite: true, + type: 'keyword', + description: 'SHA1 hash of the file.', }, - ], - }, - ], - }, - { - key: 'pgsql', - title: 'PostgreSQL', - description: 'PostgreSQL-specific event fields.', - fields: [ - { - name: 'pgsql', - type: 'group', - fields: [ { - name: 'error_code', - description: 'The PostgreSQL error code.', - type: 'long', + name: 'sha224', + type: 'keyword', + description: 'SHA224 hash of the file.', }, { - name: 'error_message', - description: 'The PostgreSQL error message.', + name: 'sha256', + overwrite: true, + type: 'keyword', + description: 'SHA256 hash of the file.', }, { - name: 'error_severity', - description: 'The PostgreSQL error severity.', - possible_values: ['ERROR', 'FATAL', 'PANIC'], + name: 'sha384', + type: 'keyword', + description: 'SHA384 hash of the file.', }, { - name: 'num_fields', - description: - 'If the SELECT query if successful, this field is set to the number of fields returned.', + name: 'sha3_224', + type: 'keyword', + description: 'SHA3_224 hash of the file.', }, { - name: 'num_rows', - description: - 'If the SELECT query if successful, this field is set to the number of rows returned.', + name: 'sha3_256', + type: 'keyword', + description: 'SHA3_256 hash of the file.', }, - ], - }, - ], - }, - { - key: 'redis', - title: 'Redis', - description: 'Redis-specific event fields.', - fields: [ - { - name: 'redis', - type: 'group', - fields: [ { - name: 'return_value', - description: 'The return value of the Redis command in a human readable format.', + name: 'sha3_384', + type: 'keyword', + description: 'SHA3_384 hash of the file.', }, { - name: 'error', - description: - 'If the Redis command has resulted in an error, this field contains the error message returned by the Redis server.', + name: 'sha3_512', + type: 'keyword', + description: 'SHA3_512 hash of the file.', }, - ], - }, - ], - }, - { - key: 'thrift', - title: 'Thrift-RPC', - description: 'Thrift-RPC specific event fields.', - 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.', + name: 'sha512', + overwrite: true, + type: 'keyword', + description: 'SHA512 hash of the file.', }, { - name: 'service', - description: 'The name of the Thrift-RPC service as defined in the IDL files.', + name: 'sha512_224', + type: 'keyword', + description: 'SHA512/224 hash of the file.', }, { - name: 'return_value', - description: - 'The value returned by the Thrift-RPC call. This is encoded in a human readable format.', + name: 'sha512_256', + type: 'keyword', + description: 'SHA512/256 hash of the file.', }, { - name: 'exceptions', - description: - 'If the call resulted in exceptions, this field contains the exceptions in a human readable format.', + name: 'xxh64', + type: 'keyword', + description: 'XX64 hash of the file.', }, ], }, ], }, { - key: 'tls', - title: 'TLS', - description: 'TLS-specific event fields.', + key: 'system', + title: 'System', + description: 'These are the fields generated by the system module.\n', + release: 'beta', fields: [ { - name: 'tls', + name: 'event', type: 'group', fields: [ { - name: 'version', + name: 'origin', type: 'keyword', - description: 'The version of the TLS protocol used.', - example: 'TLS 1.3', - }, - { - name: 'handshake_completed', - type: 'boolean', description: - 'Whether the TLS negotiation has been successful and the session has transitioned to encrypted mode.', - }, - { - name: 'resumed', - type: 'boolean', - description: 'If the TLS session has been resumed from a previous session.', + '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: 'resumption_method', + name: 'entity_id', 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.', + '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: 'client_certificate_requested', - type: 'boolean', - description: - 'Whether the server has requested the client to authenticate itself using a client certificate.', + name: 'terminal', + type: 'keyword', + description: 'Terminal of the user.\n', }, + ], + }, + { + name: 'process', + type: 'group', + fields: [ { - name: 'client_hello', + 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: 'version', + name: 'blake2b_256', type: 'keyword', - description: - 'The version of the TLS protocol by which the client wishes to communicate during this session.', + description: 'BLAKE2b-256 hash of the executable.', }, { - name: 'supported_ciphers', - type: 'array', - description: - 'List of ciphers the client is willing to use for this session. See https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4', + name: 'blake2b_384', + type: 'keyword', + description: 'BLAKE2b-384 hash of the executable.', }, { - name: 'supported_compression_methods', - type: 'array', - description: - 'The list of compression methods the client supports. See https://www.iana.org/assignments/comp-meth-ids/comp-meth-ids.xhtml', + name: 'blake2b_512', + type: 'keyword', + description: 'BLAKE2b-512 hash of the executable.', }, { - 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.', - }, - { - name: 'session_ticket', - type: 'keyword', - description: - 'Length of the session ticket, if provided, or an empty string to advertise support for tickets.', - }, - { - name: 'supported_versions', - type: 'keyword', - description: 'List of TLS versions that the client is willing to use.', - }, - { - name: 'supported_groups', - type: 'keyword', - description: - 'List of Elliptic Curve Cryptography (ECC) curve groups supported by the client.', - }, - { - name: 'signature_algorithms', - type: 'keyword', - description: - 'List of signature algorithms that may be use in digital signatures.', - }, - { - 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.', - }, - { - name: '_unparsed_', - type: 'keyword', - description: 'List of extensions that were left unparsed by Packetbeat.', - }, - ], + name: 'sha224', + type: 'keyword', + description: 'SHA224 hash of the executable.', }, - ], - }, - { - name: 'server_hello', - type: 'group', - fields: [ { - name: 'version', + name: 'sha384', 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.', + description: 'SHA384 hash of the executable.', }, { - name: 'selected_cipher', + name: 'sha3_224', type: 'keyword', - description: - 'The cipher suite selected by the server from the list provided by in the client hello.', + description: 'SHA3_224 hash of the executable.', }, { - name: 'selected_compression_method', + name: 'sha3_256', type: 'keyword', - description: - 'The compression method selected by the server from the list provided in the client hello.', + description: 'SHA3_256 hash of the executable.', }, { - name: 'session_id', + name: 'sha3_384', type: 'keyword', - description: - 'Unique number to identify the session for the corresponding connection with the client.', + description: 'SHA3_384 hash of the executable.', }, { - name: 'extensions', - type: 'group', - description: 'The hello extensions provided by the server.', - fields: [ - { - name: 'application_layer_protocol_negotiation', - type: 'array', - 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.', - }, - { - name: 'supported_versions', - type: 'keyword', - description: 'Negotiated TLS version to be used.', - }, - { - 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.', - }, - { - name: '_unparsed_', - type: 'keyword', - description: 'List of extensions that were left unparsed by Packetbeat.', - }, - ], + 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: 'client_certificate', + name: 'host', type: 'group', - description: 'Certificate provided by the client for authentication.', + description: '`host` contains general host information.\n', + release: 'beta', fields: [ { - name: 'version', + name: 'uptime', type: 'long', - description: 'X509 format version.', - }, - { - name: 'serial_number', - type: 'keyword', - description: "The certificate's serial number.", + format: 'duration', + input_format: 'nanoseconds', + output_format: 'asDays', + output_precision: 1, + description: 'Uptime in nanoseconds.\n', }, { - name: 'not_before', + name: 'boottime', type: 'date', - description: 'Date before which the certificate is not valid.', + description: 'Boot time.\n', }, { - name: 'not_after', - type: 'date', - description: 'Date after which the certificate expires.', + name: 'containerized', + type: 'boolean', + description: 'Set if host is a container.\n', }, { - name: 'public_key_algorithm', + name: 'timezone.name', type: 'keyword', - description: - "The algorithm used for this certificate's public key. One of RSA, DSA or ECDSA. ", + description: 'Name of the timezone of the host, e.g. BST.\n', }, { - name: 'public_key_size', + name: 'timezone.offset.sec', type: 'long', - description: 'Size of the public key.', + description: 'Timezone offset in seconds.\n', + }, + { + name: 'hostname', + type: 'keyword', + description: 'Hostname.\n', }, { - name: 'signature_algorithm', + name: 'id', type: 'keyword', - description: "The algorithm used for the certificate's signature. ", + description: 'Host ID.\n', }, { - name: 'alternative_names', - type: 'array', - description: 'Subject Alternative Names for this certificate.', + name: 'architecture', + type: 'keyword', + description: 'Host architecture (e.g. x86_64).\n', }, { - name: 'raw', + name: 'mac', type: 'keyword', - description: 'The raw certificate in PEM format.', + description: 'MAC addresses.\n', }, { - 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: 'ip', + type: 'ip', + description: 'IP addresses.\n', }, { - name: 'issuer', + name: 'os', type: 'group', - description: 'Entity that issued and signed this certificate.', + description: '`os` contains information about the operating system.\n', fields: [ { - name: 'country', + name: 'codename', type: 'keyword', - description: 'Country code.', + description: 'OS codename, if any (e.g. stretch).\n', }, { - name: 'organization', + name: 'platform', type: 'keyword', - description: 'Organization name.', + description: 'OS platform (e.g. centos, ubuntu, windows).\n', }, { - name: 'organizational_unit', + name: 'name', type: 'keyword', - description: 'Unit within organization.', + description: 'OS name (e.g. Mac OS X).\n', }, { - 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: 'fingerprint', - type: 'group', - fields: [ - { - name: 'md5', + name: 'family', type: 'keyword', - description: "Certificate's MD5 fingerprint.", + description: 'OS family (e.g. redhat, debian, freebsd, windows).\n', }, { - name: 'sha1', + name: 'version', type: 'keyword', - description: "Certificate's SHA-1 fingerprint.", + description: 'OS version.\n', }, { - name: 'sha256', + name: 'kernel', type: 'keyword', - description: "Certificate's SHA-256 fingerprint.", + description: "The operating system's kernel version.\n", }, ], }, ], }, { - name: 'server_certificate', + name: 'package', type: 'group', - description: 'Certificate provided by the server for authentication.', + 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: 'long', - description: 'X509 format version.', + type: 'keyword', + description: 'Package version.\n', }, { - name: 'serial_number', + name: 'release', type: 'keyword', - description: "The certificate's serial number.", + description: 'Package release.\n', }, { - name: 'not_before', - type: 'date', - description: 'Date before which the certificate is not valid.', + name: 'arch', + type: 'keyword', + description: 'Package architecture.\n', }, { - name: 'not_after', + name: 'license', + type: 'keyword', + description: 'Package license.\n', + }, + { + name: 'installtime', type: 'date', - description: 'Date after which the certificate expires.', + description: 'Package install time.\n', + }, + { + name: 'size', + type: 'long', + description: 'Package size.\n', + }, + { + name: 'summary', + description: 'Package summary.\n', }, { - name: 'public_key_algorithm', + name: 'url', type: 'keyword', - description: - "The algorithm used for this certificate's public key. One of RSA, DSA or ECDSA. ", + description: 'Package URL.\n', }, + ], + }, + { + name: 'user', + type: 'group', + description: '`user` contains information about the users on a system.\n', + release: 'beta', + fields: [ { - name: 'public_key_size', - type: 'long', - description: 'Size of the public key.', + name: 'name', + type: 'keyword', + description: 'User name.\n', }, { - name: 'signature_algorithm', + name: 'uid', type: 'keyword', - description: "The algorithm used for the certificate's signature. ", + description: 'User ID.\n', }, { - name: 'alternative_names', - type: 'array', - description: 'Subject Alternative Names for this certificate.', + name: 'gid', + type: 'keyword', + description: 'Group ID.\n', }, { - name: 'raw', + name: 'dir', type: 'keyword', - description: 'The raw certificate in PEM format.', + description: "User's home directory.\n", }, { - 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: 'shell', + type: 'keyword', + description: 'Program to run at login.\n', }, { - 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: 'user_information', + type: 'keyword', + description: 'General user information. On Linux, this is the gecos field.\n', }, { - name: 'fingerprint', - type: 'group', + 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: 'md5', + name: 'name', type: 'keyword', - description: "Certificate's MD5 fingerprint.", + description: 'Group name.\n', }, { - name: 'sha1', - type: 'keyword', - description: "Certificate's SHA-1 fingerprint.", - }, - { - name: 'sha256', - type: 'keyword', - description: "Certificate's SHA-256 fingerprint.", + name: 'gid', + type: 'integer', + description: 'Group ID.\n', }, ], }, - ], - }, - { - 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.', - }, - { - name: 'fingerprints', - type: 'group', - description: 'Fingerprints for this TLS session.', - fields: [ { - name: 'ja3', + name: 'password', type: 'group', - description: 'JA3 TLS client fingerprint', + description: + "`password` contains information about a user's password (not the password itself).\n", fields: [ { - name: 'hash', + name: 'type', type: 'keyword', - description: 'The JA3 fingerprint hash for the client side.', + 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: 'str', - type: 'keyword', - description: 'The JA3 string used to calculate the hash.', + name: 'last_changed', + type: 'date', + description: "The day the user's password was last changed.\n", }, ], }, diff --git a/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/ecs.ts b/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/ecs.ts index 34deee7fa8895..a439d105d63df 100644 --- a/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/ecs.ts +++ b/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/ecs.ts @@ -13,1997 +13,5663 @@ * instances e.g. `@timestamp` to `timestamp` */ -import { EcsSchema } from '../type'; +import { Schema } from '../type'; -export const ecsSchema: EcsSchema = { - agent: { - description: - 'The agent fields contain the data about the software entity, if any, that collects, detects, or observes events on a host, or takes measurements on a host. Examples include Beats. Agents may also run on observers. ECS agent.* fields shall be populated with details of the agent running on the host or observer where the event happened or the measurement was taken.\n', - fields: { - 'agent.ephemeral_id': { - description: - 'Ephemeral identifier of this agent (if one exists).\nThis id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - footnote: '', - group: 2, - level: 'extended', - name: 'agent.ephemeral_id', - required: false, - type: 'keyword', - }, - 'agent.id': { - description: - 'Unique identifier of this agent (if one exists).\nExample: For Beats this would be beat.id.', - example: '8a4f500d', - footnote: '', - group: 2, - level: 'core', - name: 'agent.id', - required: false, - type: 'keyword', - }, - 'agent.name': { - description: - 'Name of the agent.\nThis 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.\nIf no name is given, the name is often left empty.', - example: 'foo', - footnote: '', - group: 2, - level: 'core', - name: 'agent.name', - required: false, - type: 'keyword', - }, - 'agent.type': { - description: - 'Type of the agent.\nThe 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', - footnote: '', - group: 2, - level: 'core', - name: 'agent.type', - required: false, - type: 'keyword', - }, - 'agent.version': { - description: 'Version of the agent.', - example: '6.0.0-rc2', - footnote: '', - group: 2, - level: 'core', - name: 'agent.version', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'agent', - title: 'Agent', - type: 'group', - }, - base: { - description: - 'The base set contains all fields which are on the top level. These fields are common across all types of events.\n', - fields: { - '@timestamp': { - description: - 'Date/time when the event originated.\nFor log events this is the date/time when the event was generated, and not when it was read.\nRequired field for all events.', - example: '2016-05-23T08:05:34.853Z', - footnote: '', - group: 1, - level: 'core', +export const ecsSchema: Schema = [ + { + key: 'ecs', + title: 'ECS', + description: 'ECS Fields.', + fields: [ + { name: '@timestamp', + level: 'core', required: true, type: 'date', - }, - labels: { description: - 'Key/value pairs.\nCan be used to add meta information to events. Should not contain nested objects. All values are stored as keyword.\nExample: `docker` and `k8s` labels.', - example: "{'application': 'foo-bar', 'env': 'production'}", - footnote: '', - group: 1, - level: 'core', + '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', - required: false, + level: 'core', type: 'object', - }, - message: { + object_type: 'keyword', description: - 'For log events the message field contains the log message.\nIn other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.', - example: 'Hello World', - footnote: '', - group: 1, - level: 'core', + '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', - required: false, + 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', }, - tags: { - description: 'List of keywords used to tag each event.', - example: '["production", "env2"]', - footnote: '', - group: 1, - level: 'core', + { name: 'tags', - required: false, - type: 'keyword', - }, - }, - group: 1, - name: 'base', - title: 'Base', - type: 'group', - }, - client: { - description: - 'A client is defined as the initiator of a network connection for events regarding sessions, connections, or bidirectional flow records. For TCP events, the client is the initiator of the TCP connection that sends the SYN packet(s). For other protocols, the client is generally the initiator or requestor in the network transaction. Some systems use the term "originator" to refer the client in TCP connections. The client fields describe details about the system acting as the client in the network event. Client fields are usually populated in conjuction with server fields. Client fields are generally not populated for packet-level events. \n', - fields: { - 'client.bytes': { - description: 'Bytes sent from the client to the server.', - example: '184', - footnote: '', - group: 2, level: 'core', - name: 'client.bytes', - required: false, - type: 'long', - }, - 'client.domain': { - description: 'Client domain.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'client.domain', - required: false, - type: 'keyword', - }, - 'client.ip': { - description: 'IP address of the client.\nCan be one or multiple IPv4 or IPv6 addresses.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'client.ip', - required: false, - type: 'ip', - }, - 'client.mac': { - description: 'MAC address of the client.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'client.mac', - required: false, type: 'keyword', + ignore_above: 1024, + description: 'List of keywords used to tag each event.', + example: '["production", "env2"]', }, - 'client.packets': { - description: 'Packets sent from the client to the server.', - example: '12', - footnote: '', - group: 2, - level: 'core', - name: 'client.packets', - required: false, - type: 'long', - }, - 'client.port': { - description: 'Port of the client.', - example: '', - footnote: '', + { + name: 'agent', + title: 'Agent', group: 2, - level: 'core', - name: 'client.port', - required: false, - type: 'long', - }, - }, - group: 2, - name: 'client', - title: 'Client', - type: 'group', - }, - cloud: { - description: 'Fields related to the cloud or infrastructure the events are coming from.\n', - fields: { - 'cloud.account.id': { description: - 'The cloud account or organization id used to identify different entities in a multi-tenant environment.\nExamples: AWS account id, Google Cloud ORG Id, or other unique identifier.', - example: '666777888999', - footnote: '', - group: 2, - level: 'extended', - name: 'cloud.account.id', - required: false, - type: 'keyword', - }, - 'cloud.availability_zone': { - description: 'Availability zone in which this host is running.', - example: 'us-east-1c', - footnote: '', - group: 2, - level: 'extended', - name: 'cloud.availability_zone', - required: false, - type: 'keyword', - }, - 'cloud.instance.id': { - description: 'Instance ID of the host machine.', - example: 'i-1234567890abcdef0', - footnote: '', - group: 2, - level: 'extended', - name: 'cloud.instance.id', - required: false, - type: 'keyword', - }, - 'cloud.instance.name': { - description: 'Instance name of the host machine.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'cloud.instance.name', - required: false, - type: 'keyword', - }, - 'cloud.machine.type': { - description: 'Machine type of the host machine.', - example: 't2.medium', - footnote: '', - group: 2, - level: 'extended', - name: 'cloud.machine.type', - required: false, - type: 'keyword', - }, - 'cloud.provider': { - description: 'Name of the cloud provider. Example values are ec2, gce, or digitalocean.', - example: 'ec2', - footnote: '', - group: 2, - level: 'extended', - name: 'cloud.provider', - required: false, - type: 'keyword', - }, - 'cloud.region': { - description: 'Region in which this host is running.', - example: 'us-east-1', - footnote: '', - group: 2, - level: 'extended', - name: 'cloud.region', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'cloud', - title: 'Cloud', - type: 'group', - }, - container: { - description: - 'Container fields are used for meta information about the specific container that is the source of information. These fields help correlate data based containers from any runtime.\n', - fields: { - 'container.id': { - description: 'Unique container id.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'container.id', - required: false, - type: 'keyword', - }, - 'container.image.name': { - description: 'Name of the image the container was built on.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'container.image.name', - required: false, - type: 'keyword', - }, - 'container.image.tag': { - description: 'Container image tag.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'container.image.tag', - required: false, - type: 'keyword', - }, - 'container.labels': { - description: 'Image labels.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'container.labels', - required: false, - type: 'object', - }, - 'container.name': { - description: 'Container name.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'container.name', - required: false, - type: 'keyword', - }, - 'container.runtime': { - description: 'Runtime managing this container.', - example: 'docker', - footnote: '', + '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, - level: 'extended', - name: 'container.runtime', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'container', - title: 'Container', - type: 'group', - }, - destination: { - description: - 'Destination fields describe details about the destination of a packet/event. Destination fields are usually populated in conjunction with source fields.\n', - fields: { - 'destination.bytes': { - description: 'Bytes sent from the destination to the source.', - example: '184', - footnote: '', - group: 2, - level: 'core', - name: 'destination.bytes', - required: false, - type: 'long', - }, - 'destination.domain': { - description: 'Destination domain.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'destination.domain', - required: false, - type: 'keyword', - }, - 'destination.ip': { description: - 'IP address of the destination.\nCan be one or multiple IPv4 or IPv6 addresses.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'destination.ip', - required: false, - type: 'ip', - }, - 'destination.mac': { - description: 'MAC address of the destination.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'destination.mac', - required: false, - type: 'keyword', - }, - 'destination.packets': { - description: 'Packets sent from the destination to the source.', - example: '12', - footnote: '', - group: 2, - level: 'core', - name: 'destination.packets', - required: false, - type: 'long', - }, - 'destination.port': { - description: 'Port of the destination.', - example: '', - footnote: '', + '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, - level: 'core', - name: 'destination.port', - required: false, - type: 'long', - }, - }, - group: 2, - name: 'destination', - title: 'Destination', - type: 'group', - }, - ecs: { - description: 'Meta-information specific to ECS.\n', - fields: { - 'ecs.version': { description: - 'ECS version this event conforms to. `ecs.version` is a required field and must exist in all events.\nWhen querying across multiple indices -- which may conform to slightly different ECS versions -- this field lets integrations adjust to the schema version of the events.\nThe current version is 1.0.0-beta1 .', - example: '1.0.0-beta1', - footnote: '', - group: 2, - level: 'core', - name: 'ecs.version', - required: true, - type: 'keyword', - }, - }, - group: 2, - name: 'ecs', - title: 'ECS', - type: 'group', - }, - error: { - description: - 'These fields can represent errors of any kind. Use them for errors that happen while fetching events or in cases where the event itself contains an error.\n', - fields: { - 'error.code': { - description: 'Error code describing the error.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'error.code', - required: false, - type: 'keyword', - }, - 'error.id': { - description: 'Unique identifier for the error.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'error.id', - required: false, - type: 'keyword', - }, - 'error.message': { - description: 'Error message.', - example: '', - footnote: '', + '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, - level: 'core', - name: 'error.message', - required: false, - type: 'text', - }, - }, - group: 2, - name: 'error', - title: 'Error', - type: 'group', - }, - event: { - description: - 'The event fields are used for context information about the log or metric event itself. A log is defined as an event containing details of something that happened. Log events must include the time at which the thing happened. Examples of log events include a process starting on a host, a network packet being sent from a source to a destination, or a network connection between a client and a server being initiated or closed. A metric is defined as an event containing one or more numerical or categorical measurements and the time at which the measurement was taken. Examples of metric events include memory pressure measured on a host, or vulnerabilities measured on a scanned host.\n', - fields: { - 'event.action': { description: - 'The action captured by the event.\nThis 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', - footnote: '', + '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, - level: 'core', - name: 'event.action', - required: false, - type: 'keyword', - }, - 'event.category': { description: - 'Event category.\nThis contains high-level information about the contents of the event. It is more generic than `event.action`, in the sense that typically a category contains multiple actions. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.', - example: 'user-management', - footnote: '', + '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, - level: 'core', - name: 'event.category', - required: false, - type: 'keyword', - }, - 'event.created': { description: - 'event.created contains the date when the event was created.\nThis timestamp is distinct from @timestamp in that @timestamp contains the processed timestamp. For logs these two timestamps can be different as the timestamp in the log line and when the event is read for example by Filebeat are not identical. `@timestamp` must contain the timestamp extracted from the log line, event.created when the log line is read. The same could apply to package capturing where @timestamp contains the timestamp extracted from the network package and event.created when the event was created.\nIn case the two timestamps are identical, @timestamp should be used.', - example: '', - footnote: '', + '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, - level: 'core', - name: 'event.created', - required: false, - type: 'date', - }, - 'event.dataset': { description: - 'Name of the dataset.\nThe concept of a `dataset` (fileset / metricset) is used in Beats as a subset of modules. It contains the information which is currently stored in metricset.name and metricset.module or fileset.name.', - example: 'stats', - footnote: '', + '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, - level: 'core', - name: 'event.dataset', - required: false, - type: 'keyword', - }, - 'event.duration': { description: - 'Duration of the event in nanoseconds.\nIf event.start and event.end are known this value should be the difference between the end and start time.', - example: '', - footnote: '', + '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, - level: 'core', - name: 'event.duration', - required: false, - type: 'long', - }, - 'event.end': { description: - 'event.end contains the date when the event ended or when the activity was last observed.', - example: '', - footnote: '', + '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, - level: 'extended', - name: 'event.end', - required: false, - type: 'date', - }, - 'event.hash': { description: - 'Hash (perhaps logstash fingerprint) of raw field to be able to demonstrate log integrity.', - example: '123456789012345678901234567890ABCD', - footnote: '', - group: 2, - level: 'extended', - name: 'event.hash', - required: false, - type: 'keyword', - }, - 'event.id': { - description: 'Unique ID to describe the event.', - example: '8a4f500d', - footnote: '', + '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, - level: 'core', - name: 'event.id', - required: false, - type: 'keyword', - }, - 'event.kind': { description: - 'The kind of the event.\nThis gives information about what type of information the event contains, without being specific to the contents of the event. Examples are `event`, `state`, `alarm`. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.', - example: 'state', - footnote: '', + '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, - level: 'extended', - name: 'event.kind', - required: false, - type: 'keyword', - }, - 'event.module': { description: - 'Name of the module this data is coming from.\nThis information is coming from the modules used in Beats or Logstash.', - example: 'mysql', - footnote: '', + '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, - level: 'core', - name: 'event.module', - required: false, - type: 'keyword', - }, - 'event.original': { description: - 'Raw text message of entire event. Used to demonstrate log integrity.\nThis 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', - footnote: '', + '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, - level: 'core', - name: 'event.original', - required: false, - type: '(not indexed)', - }, - 'event.outcome': { description: - 'The outcome of the event.\nIf the event describes an action, this fields contains the outcome of that action. Examples outcomes are `success` and `failure`. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.', - example: 'success', - footnote: '', + '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, - level: 'extended', - name: 'event.outcome', - required: false, - type: 'keyword', - }, - 'event.risk_score': { description: - "Risk score or priority of the event (e.g. security solutions). Use your system's original value here.", - example: '', - footnote: '', + '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, - level: 'core', - name: 'event.risk_score', - required: false, - type: 'float', - }, - 'event.risk_score_norm': { description: - 'Normalized risk score or priority of the event, on a scale of 0 to 100.\nThis 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.', - example: '', - footnote: '', + '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, - level: 'extended', - name: 'event.risk_score_norm', - required: false, - type: 'float', - }, - 'event.severity': { description: - "Severity describes the severity of the event. What the different severity values mean can very different between use cases. It's up to the implementer to make sure severities are consistent across events.", - example: '7', - footnote: '', + '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, - level: 'core', - name: 'event.severity', - required: false, - type: 'long', - }, - 'event.start': { description: - 'event.start contains the date when the event started or when the activity was first observed.', - example: '', - footnote: '', + '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, - level: 'extended', - name: 'event.start', - required: false, - type: 'date', - }, - 'event.timezone': { 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.\nAcceptable timezone formats are: a canonical ID (e.g. "Europe/Amsterdam"), abbreviated (e.g. "EST") or an HH:mm differential (e.g. "-05:00").', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'event.timezone', - required: false, - type: 'keyword', - }, - 'event.type': { - description: 'Reserved for future usage.\nPlease avoid using this field for user data.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'event.type', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'event', - title: 'Event', - type: 'group', - }, - file: { - description: - 'A file is defined as a set of information that has been created on, or has existed on a filesystem. File objects can be associated with host events, network events, and/or file events (e.g., those produced by File Integrity Monitoring [FIM] products or services). File fields provide details about the affected file associated with the event or metric.\n', - fields: { - 'file.ctime': { - description: 'Last time file metadata changed.', - example: '', - footnote: '', + '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, - level: 'extended', - name: 'file.ctime', - required: false, - type: 'date', - }, - 'file.device': { - description: 'Device that is the source of the file.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'file.device', - required: false, - type: 'keyword', - }, - 'file.extension': { - description: 'File extension.\nThis should allow easy filtering by file extensions.', - example: 'png', - footnote: '', - group: 2, - level: 'extended', - name: 'file.extension', - required: false, - type: 'keyword', - }, - 'file.gid': { - description: 'Primary group ID (GID) of the file.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'file.gid', - required: false, - type: 'keyword', - }, - 'file.group': { - description: 'Primary group name of the file.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'file.group', - required: false, - type: 'keyword', - }, - 'file.inode': { - description: 'Inode representing the file in the filesystem.', - example: '', - footnote: '', + 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, - level: 'extended', - name: 'file.inode', - required: false, - type: 'keyword', - }, - 'file.mode': { - description: 'Mode of the file in octal representation.', - example: '416', - footnote: '', + 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, - level: 'extended', - name: 'file.mode', - required: false, - type: 'keyword', - }, - 'file.mtime': { - description: 'Last time file content was modified.', - example: '', - footnote: '', + 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, - level: 'extended', - name: 'file.mtime', - required: false, - type: 'date', - }, - 'file.owner': { - description: "File owner's username.", - example: '', - footnote: '', + 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, - level: 'extended', - name: 'file.owner', - required: false, - type: 'keyword', - }, - 'file.path': { - description: 'Path to the file.', - example: '', - footnote: '', + 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, - level: 'extended', - name: 'file.path', - required: false, - type: 'keyword', - }, - 'file.size': { - description: 'File size in bytes (field is only added when `type` is `file`).', - example: '', - footnote: '', + 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, - level: 'extended', - name: 'file.size', - required: false, - type: 'long', - }, - 'file.target_path': { - description: 'Target path for symlinks.', - example: '', - footnote: '', + 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, - level: 'extended', - name: 'file.target_path', - required: false, - type: 'keyword', - }, - 'file.type': { - description: 'File type (file, dir, or symlink).', - example: '', - footnote: '', + 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, - level: 'extended', - name: 'file.type', - required: false, - type: 'keyword', - }, - 'file.uid': { - description: 'The user ID (UID) or security identifier (SID) of the file owner.', - example: '', - footnote: '', + 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, - level: 'extended', - name: 'file.uid', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'file', - title: 'File', - type: 'group', - }, - geo: { - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.\n', - fields: { - 'geo.city_name': { - description: 'City name.', - example: 'Montreal', - footnote: '', + 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, - level: 'core', - name: 'geo.city_name', - required: false, - type: 'keyword', - }, - 'geo.continent_name': { - description: 'Name of the continent.', - example: 'North America', - footnote: '', + 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, - level: 'core', - name: 'geo.continent_name', - required: false, - type: 'keyword', - }, - 'geo.country_iso_code': { - description: 'Country ISO code.', - example: 'CA', - footnote: '', + 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, - level: 'core', - name: 'geo.country_iso_code', - required: false, - type: 'keyword', - }, - 'geo.country_name': { - description: 'Country name.', - example: 'Canada', - footnote: '', + 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, - level: 'core', - name: 'geo.country_name', - required: false, - type: 'keyword', - }, - 'geo.location': { - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - footnote: '', + 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, - level: 'core', - name: 'geo.location', - required: false, - type: 'geo_point', - }, - 'geo.name': { description: - 'User-defined description of a location, at the level of granularity they care about.\nCould be the name of their data centers, the floor number, if this describes a local physical entity, city names.\nNot typically used in automated geolocation.', - example: 'boston-dc', - footnote: '', + '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, - level: 'extended', - name: 'geo.name', - required: false, - type: 'keyword', - }, - 'geo.region_iso_code': { - description: 'Region ISO code.', - example: 'CA-QC', - footnote: '', + 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, - level: 'core', - name: 'geo.region_iso_code', - required: false, - type: 'keyword', - }, - 'geo.region_name': { - description: 'Region name.', - example: 'Quebec', - footnote: '', - group: 2, - level: 'core', - name: 'geo.region_name', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'geo', - title: 'Geo', - type: 'group', - }, - group: { - description: 'The group fields are meant to represent groups that are relevant to the event.\n', - fields: { - 'group.id': { - description: 'Unique identifier for the group on the system/platform.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'group.id', - required: false, - type: 'keyword', - }, - 'group.name': { - description: 'Name of the group.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'group.name', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'group', - title: 'Group', - type: 'group', - }, - host: { - description: - 'A host is defined as a general computing instance. ECS host.* fields should be populated with details about the host on which the event happened, or on which the measurement was taken. Host types include hardware, virtual machines, Docker containers, and Kubernetes nodes.\n', - fields: { - 'host.architecture': { - description: 'Operating system architecture.', - example: 'x86_64', - footnote: '', - group: 2, - level: 'core', - name: 'host.architecture', - required: false, - type: 'keyword', - }, - 'host.hostname': { - description: - 'Hostname of the host.\nIt normally contains what the `hostname` command returns on the host machine.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'host.hostname', - required: false, - type: 'keyword', - }, - 'host.id': { - description: - 'Unique host id.\nAs hostname is not always unique, use values that are meaningful in your environment.\nExample: The current usage of `beat.name`.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'host.id', - required: false, - type: 'keyword', - }, - 'host.ip': { - description: 'Host ip address.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'host.ip', - required: false, - type: 'ip', - }, - 'host.mac': { - description: 'Host mac address.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'host.mac', - required: false, - type: 'keyword', - }, - 'host.name': { - description: - 'Name of the host.\nIt 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.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'host.name', - required: false, - type: 'keyword', - }, - 'host.type': { - description: - 'Type of host.\nFor 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.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'host.type', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'host', - title: 'Host', - type: 'group', - }, - http: { - description: 'Fields related to HTTP activity.\n', - fields: { - 'http.request.method': { - description: 'Http request method.', - example: 'GET, POST, PUT', - footnote: '', - group: 2, - level: 'extended', - name: 'http.request.method', - required: false, - type: 'keyword', - }, - 'http.request.referrer': { - description: 'Referrer for this HTTP request.', - example: 'https://blog.example.com/', - footnote: '', - group: 2, - level: 'extended', - name: 'http.request.referrer', - required: false, - type: 'keyword', - }, - 'http.response.body': { - description: 'The full http response body.', - example: 'Hello world', - footnote: '', - group: 2, - level: 'extended', - name: 'http.response.body', - required: false, - type: 'keyword', - }, - 'http.response.status_code': { - description: 'Http response status code.', - example: '404', - footnote: '', - group: 2, - level: 'extended', - name: 'http.response.status_code', - required: false, - type: 'long', - }, - 'http.version': { - description: 'Http version.', - example: '1.1', - footnote: '', - group: 2, - level: 'extended', - name: 'http.version', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'http', - title: 'HTTP', - type: 'group', - }, - log: { - description: 'Fields which are specific to log events.\n', - fields: { - 'log.level': { - description: 'Log level of the log event.\nSome examples are `WARN`, `ERR`, `INFO`.', - example: 'ERR', - footnote: '', - group: 2, - level: 'core', - name: 'log.level', - required: false, - type: 'keyword', - }, - 'log.original': { - description: - "This is the original log message and contains the full log message before splitting it up in multiple parts.\nIn 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.\nThis 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', - footnote: '', - group: 2, - level: 'core', - name: 'log.original', - required: false, - type: '(not indexed)', - }, - }, - group: 2, - name: 'log', - title: 'Log', - type: 'group', - }, - network: { - description: - 'The network is defined as the communication path over which a host or network event happens. The network.* fields should be populated with details about the network activity associated with an event.\n', - fields: { - 'network.application': { - description: - 'A name given to an application. 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.', - example: 'AIM', - footnote: '', - group: 2, - level: 'extended', - name: 'network.application', - required: false, - type: 'keyword', - }, - 'network.bytes': { - description: - 'Total bytes transferred in both directions.\nIf `source.bytes` and `destination.bytes` are known, `network.bytes` is their sum.', - example: '368', - footnote: '', - group: 2, - level: 'core', - name: 'network.bytes', - required: false, - type: 'long', - }, - 'network.community_id': { - 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.\nLearn more at https://github.com/corelight/community-id-spec.', - example: '1:hO+sN4H+MG5MY/8hIrXPqc4ZQz0=', - footnote: '', - group: 2, - level: 'extended', - name: 'network.community_id', - required: false, - type: 'keyword', - }, - 'network.direction': { - 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', - footnote: '', - group: 2, - level: 'core', - name: 'network.direction', - required: false, - type: 'keyword', - }, - 'network.forwarded_ip': { - description: 'Host IP address when the source IP address is the proxy.', - example: '192.1.1.2', - footnote: '', - group: 2, - level: 'core', - name: 'network.forwarded_ip', - required: false, - type: 'ip', - }, - 'network.iana_number': { - 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', - footnote: '', - group: 2, - level: 'extended', - name: 'network.iana_number', - required: false, - type: 'keyword', - }, - 'network.name': { - description: 'Name given by operators to sections of their network.', - example: 'Guest Wifi', - footnote: '', - group: 2, - level: 'extended', - name: 'network.name', - required: false, - type: 'keyword', - }, - 'network.packets': { - description: - 'Total packets transferred in both directions.\nIf `source.packets` and `destination.packets` are known, `network.packets` is their sum.', - example: '24', - footnote: '', - group: 2, - level: 'core', - name: 'network.packets', - required: false, - type: 'long', - }, - 'network.protocol': { - description: 'L7 Network protocol name. ex. http, lumberjack, transport protocol', - example: 'http', - footnote: '', - group: 2, - level: 'core', - name: 'network.protocol', - required: false, - type: 'keyword', - }, - 'network.transport': { - description: - 'Same as network.iana_number, but instead using the Keyword name of the transport layer (UDP, TCP, IPv6-ICMP, etc.)', - example: 'TCP', - footnote: '', - group: 2, - level: 'core', - name: 'network.transport', - required: false, - type: 'keyword', - }, - 'network.type': { - description: - 'In the OSI Model this would be the Network Layer. IPv4, IPv6, IPSec, PIM, etc', - example: 'IPv4', - footnote: '', - group: 2, - level: 'core', - name: 'network.type', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'network', - title: 'Network', - type: 'group', - }, - observer: { - description: - 'An observer is defined as a special network, security, or application device used to detect, observe, or create network, security, or application-related events and metrics. This could be a custom hardware appliance or a server that has been configured to run special network, security, or application software. Examples include firewalls, intrusion detection/prevention systems, network monitoring sensors, web application firewalls, data loss prevention systems, and APM servers. The observer.* fields shall be populated with details of the system, if any, that detects, observes and/or creates a network, security, or application event or metric. Message queues and ETL components used in processing events or metrics are not considered observers in ECS. \n', - fields: { - 'observer.hostname': { - description: 'Hostname of the observer.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'observer.hostname', - required: false, - type: 'keyword', - }, - 'observer.ip': { - description: 'IP address of the observer.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'observer.ip', - required: false, - type: 'ip', - }, - 'observer.mac': { - description: 'MAC address of the observer', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'observer.mac', - required: false, - type: 'keyword', - }, - 'observer.serial_number': { - description: 'Observer serial number.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'observer.serial_number', - required: false, - type: 'keyword', - }, - 'observer.type': { - description: - 'The type of the observer the data is coming from.\nThere is no predefined list of observer types. Some examples are `forwarder`, `firewall`, `ids`, `ips`, `proxy`, `poller`, `sensor`, `APM server`.', - example: 'firewall', - footnote: '', - group: 2, - level: 'core', - name: 'observer.type', - required: false, - type: 'keyword', - }, - 'observer.vendor': { - description: 'observer vendor information.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'observer.vendor', - required: false, - type: 'keyword', - }, - 'observer.version': { - description: 'Observer version.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'observer.version', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'observer', - title: 'Observer', - type: 'group', - }, - organization: { - description: - 'The organization fields enrich data with information about the company or entity the data is associated with. These fields help you arrange or filter data stored in an index by one or multiple organizations.\n', - fields: { - 'organization.id': { - description: 'Unique identifier for the organization.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'organization.id', - required: false, - type: 'keyword', - }, - 'organization.name': { - description: 'Organization name.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'organization.name', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'organization', - title: 'Organization', - type: 'group', - }, - os: { - description: 'The OS fields contain information about the operating system.\n', - fields: { - 'os.family': { - description: 'OS family (such as redhat, debian, freebsd, windows).', - example: 'debian', - footnote: '', - group: 2, - level: 'extended', - name: 'os.family', - required: false, - type: 'keyword', - }, - 'os.kernel': { - description: 'Operating system kernel version as a raw string.', - example: '4.4.0-112-generic', - footnote: '', - group: 2, - level: 'extended', - name: 'os.kernel', - required: false, - type: 'keyword', - }, - 'os.name': { - description: 'Operating system name.', - example: 'Mac OS X', - footnote: '', - group: 2, - level: 'extended', - name: 'os.name', - required: false, - type: 'keyword', - }, - 'os.platform': { - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - footnote: '', - group: 2, - level: 'extended', - name: 'os.platform', - required: false, - type: 'keyword', - }, - 'os.version': { - description: 'Operating system version as a raw string.', - example: '10.12.6-rc2', - footnote: '', - group: 2, - level: 'extended', - name: 'os.version', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'os', - title: 'Operating System', - type: 'group', - }, - process: { - description: - 'These fields contain information about a process. These fields can help you correlate metrics information with a process id/name from a log message. The `process.pid` often stays in the metric itself and is copied to the global field for correlation.\n', - fields: { - 'process.args': { - description: 'Process arguments.\nMay be filtered to protect sensitive information.', - example: "['ssh', '-l', 'user', '10.0.0.16']", - footnote: '', - group: 2, - level: 'extended', - name: 'process.args', - required: false, - type: 'keyword', - }, - 'process.executable': { - description: 'Absolute path to the process executable.', - example: '/usr/bin/ssh', - footnote: '', - group: 2, - level: 'extended', - name: 'process.executable', - required: false, - type: 'keyword', - }, - 'process.name': { - description: 'Process name.\nSometimes called program name or similar.', - example: 'ssh', - footnote: '', - group: 2, - level: 'extended', - name: 'process.name', - required: false, - type: 'keyword', - }, - 'process.pid': { - description: 'Process id.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'process.pid', - required: false, - type: 'long', - }, - 'process.ppid': { - description: 'Process parent id.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'process.ppid', - required: false, - type: 'long', - }, - 'process.start': { - description: 'The time the process started.', - example: '2016-05-23T08:05:34.853Z', - footnote: '', - group: 2, - level: 'extended', - name: 'process.start', - required: false, - type: 'date', - }, - 'process.thread.id': { - description: 'Thread ID.', - example: '4242', - footnote: '', - group: 2, - level: 'extended', - name: 'process.thread.id', - required: false, - type: 'long', - }, - 'process.title': { description: - 'Process title.\nThe 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.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'process.title', - required: false, - type: 'keyword', - }, - 'process.working_directory': { - description: 'The working directory of the process.', - example: '/home/alice', - footnote: '', - group: 2, - level: 'extended', - name: 'process.working_directory', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'process', - title: 'Process', - type: 'group', - }, - related: { - description: - 'This field set is meant to facilitate pivoting around a piece of data. Some pieces of information can be seen in many places in ECS. To facilitate searching for them, append values to their corresponding field in `related.`. A concrete example is IP addresses, which can be under host, observer, source, destination, client, server, and network.forwarded_ip. If you append all IPs to `related.ip`, you can then search for a given IP trivially, no matter where it appeared, by querying `related.ip:a.b.c.d`.\n', - fields: { - 'related.ip': { - description: 'All of the IPs seen on your event.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'related.ip', - required: false, - type: 'ip', - }, - }, - group: 2, - name: 'related', - title: 'Related', - type: 'group', - }, - server: { - description: - 'A Server is defined as the responder in a network connection for events regarding sessions, connections, or bidirectional flow records. For TCP events, the server is the receiver of the initial SYN packet(s) of the TCP connection. For other protocols, the server is generally the responder in the network transaction. Some systems actually use the term "responder" to refer the server in TCP connections. The server fields describe details about the system acting as the server in the network event. Server fields are usually populated in conjunction with client fields. Server fields are generally not populated for packet-level events.\n', - fields: { - 'server.bytes': { - description: 'Bytes sent from the server to the client.', - example: '184', - footnote: '', - group: 2, - level: 'core', - name: 'server.bytes', - required: false, - type: 'long', - }, - 'server.domain': { - description: 'Server domain.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'server.domain', - required: false, - type: 'keyword', - }, - 'server.ip': { - description: 'IP address of the server.\nCan be one or multiple IPv4 or IPv6 addresses.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'server.ip', - required: false, - type: 'ip', - }, - 'server.mac': { - description: 'MAC address of the server.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'server.mac', - required: false, - type: 'keyword', - }, - 'server.packets': { - description: 'Packets sent from the server to the client.', - example: '12', - footnote: '', - group: 2, - level: 'core', - name: 'server.packets', - required: false, - type: 'long', - }, - 'server.port': { - description: 'Port of the server.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'server.port', - required: false, - type: 'long', - }, - }, - group: 2, - name: 'server', - title: 'Server', - type: 'group', - }, - service: { - description: - 'The service fields describe the service for or from which the data was collected. These fields help you find and correlate logs for a specific service and version.\n', - fields: { - 'service.ephemeral_id': { - description: - 'Ephemeral identifier of this service (if one exists).\nThis id normally changes across restarts, but `service.id` does not.', - example: '8a4f500f', - footnote: '', - group: 2, - level: 'extended', - name: 'service.ephemeral_id', - required: false, - type: 'keyword', - }, - 'service.id': { - description: - 'Unique identifier of the running service.\nThis id should uniquely identify this service. This makes it possible to correlate logs and metrics for one specific service.\nExample: If you are experiencing issues with one redis instance, you can filter on that id to see metrics and logs for that single instance.', - example: 'd37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6', - footnote: '', - group: 2, - level: 'core', - name: 'service.id', - required: false, - type: 'keyword', - }, - 'service.name': { - description: - 'Name of the service data is collected from.\nThe name of the service is normally user given. This allows if two instances of the same service are running on the same machine they can be differentiated by the `service.name`.\nAlso it allows for distributed services that run on multiple hosts to correlate the related instances based on the name.\nIn 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', - footnote: '', - group: 2, - level: 'core', - name: 'service.name', - required: false, - type: 'keyword', - }, - 'service.state': { - description: 'Current state of the service.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'service.state', - required: false, - type: 'keyword', - }, - 'service.type': { - description: - 'The type of the service data is collected from.\nThe type can be used to group and correlate logs and metrics from one service type.\nExample: If logs or metrics are collected from Elasticsearch, `service.type` would be `elasticsearch`.', - example: 'elasticsearch', - footnote: '', - group: 2, - level: 'core', - name: 'service.type', - required: false, - type: 'keyword', - }, - 'service.version': { - description: - 'Version of the service the data was collected from.\nThis allows to look at a data set only for a specific version of a service.', - example: '3.2.4', - footnote: '', - group: 2, - level: 'core', - name: 'service.version', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'service', - title: 'Service', - type: 'group', - }, - source: { - description: - 'Source fields describe details about the source of a packet/event. Source fields are usually populated in conjunction with destination fields.\n', - fields: { - 'source.bytes': { - description: 'Bytes sent from the source to the destination.', - example: '184', - footnote: '', - group: 2, - level: 'core', - name: 'source.bytes', - required: false, - type: 'long', - }, - 'source.domain': { - description: 'Source domain.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'source.domain', - required: false, - type: 'keyword', - }, - 'source.ip': { - description: 'IP address of the source.\nCan be one or multiple IPv4 or IPv6 addresses.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'source.ip', - required: false, - type: 'ip', - }, - 'source.mac': { - description: 'MAC address of the source.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'source.mac', - required: false, - type: 'keyword', - }, - 'source.packets': { - description: 'Packets sent from the source to the destination.', - example: '12', - footnote: '', - group: 2, - level: 'core', - name: 'source.packets', - required: false, - type: 'long', - }, - 'source.port': { - description: 'Port of the source.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'source.port', - required: false, - type: 'long', - }, - }, - group: 2, - name: 'source', - title: 'Source', - type: 'group', - }, - url: { - description: 'URL fields provide a complete URL, with scheme, host, and path.\n', - fields: { - 'url.domain': { - description: - 'Domain of the request, such as "www.elastic.co".\nIn 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', - footnote: '', - group: 2, - level: 'extended', - name: 'url.domain', - required: false, - type: 'keyword', - }, - 'url.fragment': { - description: - 'Portion of the url after the `#`, such as "top".\nThe `#` is not part of the fragment.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'url.fragment', - required: false, - type: 'keyword', - }, - 'url.full': { - 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', - footnote: '', - group: 2, - level: 'extended', - name: 'url.full', - required: false, - type: 'keyword', - }, - 'url.original': { - description: - 'Unmodified original url as seen in the event source.\nNote 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.\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', - footnote: '', - group: 2, - level: 'extended', - name: 'url.original', - required: false, - type: 'keyword', - }, - 'url.password': { - description: 'Password of the request.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'url.password', - required: false, - type: 'keyword', - }, - 'url.path': { - description: 'Path of the request, such as "/search".', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'url.path', - required: false, - type: 'keyword', - }, - 'url.port': { - description: 'Port of the request, such as 443.', - example: '443', - footnote: '', - group: 2, - level: 'extended', - name: 'url.port', - required: false, - type: 'integer', - }, - 'url.query': { - description: - 'The query field describes the query string of the request, such as "q=elasticsearch".\nThe `?` 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.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'url.query', - required: false, - type: 'keyword', - }, - 'url.scheme': { - description: - 'Scheme of the request, such as "https".\nNote: The `:` is not part of the scheme.', - example: 'https', - footnote: '', - group: 2, - level: 'extended', - name: 'url.scheme', - required: false, - type: 'keyword', - }, - 'url.username': { - description: 'Username of the request.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'url.username', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'url', - title: 'URL', - type: 'group', - }, - user: { - description: - 'The user fields describe information about the user that is relevant to the event. Fields can have one entry or multiple entries. If a user has more than one id, provide an array that includes all of them.\n', - fields: { - 'user.email': { - description: 'User email address.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'user.email', - required: false, - type: 'keyword', - }, - 'user.full_name': { - description: "User's full name, if available.", - example: 'Albert Einstein', - footnote: '', - group: 2, - level: 'extended', - name: 'user.full_name', - required: false, - type: 'keyword', - }, - 'user.group': { - description: - 'Group the user is a part of. This field can contain a list of groups, if necessary.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'user.group', - required: false, - type: 'keyword', - }, - 'user.hash': { - description: - 'Unique user hash to correlate information for a user in anonymized form.\nUseful if `user.id` or `user.name` contain confidential information and cannot be used.', - example: '', - footnote: '', - group: 2, - level: 'extended', - name: 'user.hash', - required: false, - type: 'keyword', - }, - 'user.id': { - description: 'One or multiple unique identifiers of the user.', - example: '', - footnote: '', - group: 2, - level: 'core', - name: 'user.id', - required: false, - type: 'keyword', - }, - 'user.name': { - description: 'Short name or login of the user.', - example: 'albert', - footnote: '', - group: 2, - level: 'core', - name: 'user.name', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'user', - title: 'User', - type: 'group', - }, - user_agent: { - description: - 'The user_agent fields normally come from a browser request. They often show up in web service logs coming from the parsed user agent string.\n', - fields: { - 'user_agent.device.name': { - description: 'Name of the device.', - example: 'iPhone', - footnote: '', - group: 2, - level: 'extended', - name: 'user_agent.device.name', - required: false, - type: 'keyword', - }, - 'user_agent.name': { - description: 'Name of the user agent.', - example: 'Safari', - footnote: '', - group: 2, - level: 'extended', - name: 'user_agent.name', - required: false, - type: 'keyword', - }, - 'user_agent.original': { - 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 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', - footnote: '', - group: 2, - level: 'extended', - name: 'user_agent.original', - required: false, - type: '(not indexed)', - }, - 'user_agent.version': { - description: 'Version of the user agent.', - example: '12.0', - footnote: '', - group: 2, - level: 'extended', - name: 'user_agent.version', - required: false, - type: 'keyword', - }, - }, - group: 2, - name: 'user_agent', - title: 'User agent', - type: 'group', + '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/legacy/plugins/siem/server/utils/beat_schema/8.0.0/filebeat.ts b/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/filebeat.ts index a5877f6c34b8f..3b8c92ebba269 100644 --- a/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/filebeat.ts +++ b/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/filebeat.ts @@ -15,91 +15,129 @@ export const filebeatSchema: Schema = [ { key: 'ecs', title: 'ECS', - description: 'ECS fields.', + description: 'ECS Fields.', fields: [ { name: '@timestamp', - type: 'date', level: 'core', required: true, - example: '2016-05-23T08:05:34.853Z', + type: 'date', 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.', - }, - { - name: 'tags', - level: 'core', - type: 'keyword', - example: '["production", "env2"]', - description: 'List of keywords used to tag each event.', + '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', - example: { - env: 'production', - application: 'foo-bar', - }, + object_type: 'keyword', description: - '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.', + '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', - example: 'Hello World', description: - 'For log events the message field contains the log message. In other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.', + '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 any, that collects, detects, or observes events on a host, or takes measurements on a host. Examples include Beats. Agents may also run on observers. ECS agent.* fields shall be populated with details of the agent running on the host or observer where the event happened or the measurement was taken.', + '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. For APM, it is the agent running in the app/service. The agent information does not change if data is sent through queuing systems like Kafka, Redis, or processing systems such as Logstash or APM Server.', + '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: 'version', + 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', - description: 'Version of the agent.', - example: '6.0.0-rc2', + 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: - '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.', + '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. 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.', + '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: 'id', + 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 identifier of this agent (if one exists). Example: For Beats this would be beat.id.', - example: '8a4f500d', + 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', + example: 15169, }, { - name: 'ephemeral_id', + name: 'organization.name', level: 'extended', type: 'keyword', - description: - 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', + ignore_above: 1024, + multi_fields: [ + { + name: 'text', + type: 'text', + norms: false, + default_field: false, + }, + ], + description: 'Organization name.', + example: 'Google LLC', }, ], }, @@ -108,7183 +146,18212 @@ export const filebeatSchema: Schema = [ title: 'Client', group: 2, description: - 'A client is defined as the initiator of a network connection for events regarding sessions, connections, or bidirectional flow records. For TCP events, the client is the initiator of the TCP connection that sends the SYN packet(s). For other protocols, the client is generally the initiator or requestor in the network transaction. Some systems use the term "originator" to refer the client in TCP connections. The client fields describe details about the system acting as the client in the network event. Client fields are usually populated in conjunction with server fields. Client fields are generally not populated for packet-level events. Client / server representations can add semantic context to an exchange, which is helpful to visualize the data in certain situations. If your context falls in that category, you should still ensure that source and destination are filled appropriately.', + '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 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.', + '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: 'ip', - level: 'core', - type: 'ip', - description: 'IP address of the client. Can be one or multiple IPv4 or IPv6 addresses.', + 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: 'port', + 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', - description: 'Port of the client.', + format: 'bytes', + description: 'Bytes sent from the client to the server.', + example: 184, }, { - name: 'mac', + name: 'domain', level: 'core', type: 'keyword', - description: 'MAC address of the client.', + ignore_above: 1024, + description: 'Client domain.', }, { - name: 'domain', + name: 'geo.city_name', level: 'core', type: 'keyword', - description: 'Client domain.', + ignore_above: 1024, + description: 'City name.', + example: 'Montreal', }, { - name: 'bytes', + name: 'geo.continent_name', level: 'core', - type: 'long', - format: 'bytes', - example: 184, - description: 'Bytes sent from the client to the server.', + type: 'keyword', + ignore_above: 1024, + description: 'Name of the continent.', + example: 'North America', }, { - name: 'packets', + name: 'geo.country_iso_code', level: 'core', - type: 'long', - example: 12, - description: 'Packets sent from the client to the server.', + type: 'keyword', + ignore_above: 1024, + description: 'Country ISO code.', + example: 'CA', }, { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - 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.country_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Country name.', + example: 'Canada', }, - ], - }, - { - name: 'cloud', - title: 'Cloud', - group: 2, - description: 'Fields related to the cloud or infrastructure the events are coming from.', - footnote: - 'Examples: If Metricbeat is running on an EC2 host and fetches data from its host, the cloud info contains the data about this machine. If Metricbeat runs on a remote machine outside the cloud and fetches data from a service running in the cloud, the field contains cloud data from the machine the service is running on.', - type: 'group', - fields: [ { - name: 'provider', + name: 'geo.location', + level: 'core', + type: 'geo_point', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', + }, + { + name: 'geo.name', level: 'extended', - example: 'ec2', type: 'keyword', + ignore_above: 1024, description: - 'Name of the cloud provider. Example values are ec2, gce, or digitalocean.', + '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: 'availability_zone', - level: 'extended', - example: 'us-east-1c', + name: 'geo.region_iso_code', + level: 'core', type: 'keyword', - description: 'Availability zone in which this host is running.', + ignore_above: 1024, + description: 'Region ISO code.', + example: 'CA-QC', }, { - name: 'region', - level: 'extended', + name: 'geo.region_name', + level: 'core', type: 'keyword', - example: 'us-east-1', - description: 'Region in which this host is running.', + ignore_above: 1024, + description: 'Region name.', + example: 'Quebec', }, { - name: 'instance.id', - level: 'extended', + 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', - example: 'i-1234567890abcdef0', - description: 'Instance ID of the host machine.', + ignore_above: 1024, + description: 'MAC address of the client.', }, { - name: 'instance.name', + name: 'nat.ip', level: 'extended', - type: 'keyword', - description: 'Instance name of the host machine.', + 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: 'machine.type', + 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', - example: 't2.medium', - description: 'Machine type of the host machine.', + 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: 'account.id', + name: 'top_level_domain', level: 'extended', type: 'keyword', - example: 666777888999, + ignore_above: 1024, 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.', + '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: 'container', - title: 'Container', - group: 2, - description: - 'Container fields are used for meta information about the specific container that is the source of information. These fields help correlate data based containers from any runtime.', - type: 'group', - fields: [ { - name: 'runtime', + name: 'user.domain', level: 'extended', type: 'keyword', - description: 'Runtime managing this container.', - example: 'docker', + 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: 'id', - level: 'core', + name: 'user.email', + level: 'extended', type: 'keyword', - description: 'Unique container id.', + ignore_above: 1024, + description: 'User email address.', }, { - name: 'image.name', + name: 'user.full_name', level: 'extended', type: 'keyword', - description: 'Name of the image the container was built on.', + 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: 'image.tag', + name: 'user.group.domain', level: 'extended', type: 'keyword', - description: 'Container image tag.', + 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: 'name', + name: 'user.group.id', level: 'extended', type: 'keyword', - description: 'Container name.', + ignore_above: 1024, + description: 'Unique identifier for the group on the system/platform.', }, { - name: 'labels', + name: 'user.group.name', level: 'extended', - type: 'object', - object_type: 'keyword', - description: 'Image labels.', + type: 'keyword', + ignore_above: 1024, + description: 'Name of the group.', }, - ], - }, - { - name: 'destination', - title: 'Destination', - group: 2, - description: - 'Destination fields describe details about the destination of a packet/event. Destination fields are usually populated in conjunction with source fields.', - type: 'group', - fields: [ { - name: 'address', + name: 'user.hash', level: 'extended', type: 'keyword', + ignore_above: 1024, 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.', + '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: 'ip', + name: 'user.id', level: 'core', - type: 'ip', - description: - 'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.', + type: 'keyword', + ignore_above: 1024, + description: 'Unique identifiers of the user.', }, { - name: 'port', + name: 'user.name', level: 'core', - type: 'long', - description: 'Port of the destination.', + 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: 'mac', - level: 'core', + name: 'account.id', + level: 'extended', type: 'keyword', - description: 'MAC address of the destination.', + 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: 'domain', - level: 'core', + name: 'availability_zone', + level: 'extended', type: 'keyword', - description: 'Destination domain.', + ignore_above: 1024, + description: 'Availability zone in which this host is running.', + example: 'us-east-1c', }, { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - example: 184, - description: 'Bytes sent from the destination to the source.', + name: 'instance.id', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Instance ID of the host machine.', + example: 'i-1234567890abcdef0', }, { - name: 'packets', - level: 'core', - type: 'long', - example: 12, - description: 'Packets sent from the destination to the source.', + 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: 'geo', - title: 'Geo', - group: 2, + name: 'provider', + level: 'extended', + type: 'keyword', + ignore_above: 1024, description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - 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 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: 'ecs', - title: 'ECS', + name: 'code_signature', + title: 'Code Signature', group: 2, - description: 'Meta-information specific to ECS.', + description: 'These fields contain information about binary code signatures.', type: 'group', fields: [ { - name: 'version', + 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', - required: true, + 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: - '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. The current version is 1.0.0-beta2 .', - example: '1.0.0-beta2', + '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: 'error', - title: 'Error', + name: 'container', + title: 'Container', group: 2, description: - 'These fields can represent errors of any kind. Use them for errors that happen while fetching events or in cases where the event itself contains an error.', + '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 identifier for the error.', + ignore_above: 1024, + description: 'Unique container id.', }, { - name: 'message', - level: 'core', - type: 'text', - description: 'Error message.', + name: 'image.name', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Name of the image the container was built on.', }, { - name: 'code', - level: 'core', + name: 'image.tag', + level: 'extended', type: 'keyword', - description: 'Error code describing the error.', + 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: 'event', - title: 'Event', + name: 'destination', + title: 'Destination', group: 2, description: - 'The event fields are used for context information about the log or metric event itself. A log is defined as an event containing details of something that happened. Log events must include the time at which the thing happened. Examples of log events include a process starting on a host, a network packet being sent from a source to a destination, or a network connection between a client and a server being initiated or closed. A metric is defined as an event containing one or more numerical or categorical measurements and the time at which the measurement was taken. Examples of metric events include memory pressure measured on a host, or vulnerabilities measured on a scanned host.', + '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: 'id', - level: 'core', + name: 'address', + level: 'extended', type: 'keyword', - description: 'Unique ID to describe the event.', - example: '8a4f500d', + 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: 'kind', + name: 'as.number', level: 'extended', - type: 'keyword', + type: 'long', description: - 'The kind of the event. This gives information about what type of information the event contains, without being specific to the contents of the event. Examples are `event`, `state`, `alarm`. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.', - example: 'state', + 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', + example: 15169, }, { - name: 'category', - level: 'core', + name: 'as.organization.name', + level: 'extended', type: 'keyword', - description: - 'Event category. This contains high-level information about the contents of the event. It is more generic than `event.action`, in the sense that typically a category contains multiple actions. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.', - example: 'user-management', + ignore_above: 1024, + multi_fields: [ + { + name: 'text', + type: 'text', + norms: false, + default_field: false, + }, + ], + description: 'Organization name.', + example: 'Google LLC', }, { - name: 'action', + name: 'bytes', level: 'core', - type: 'keyword', - 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', + type: 'long', + format: 'bytes', + description: 'Bytes sent from the destination to the source.', + example: 184, }, { - name: 'outcome', - level: 'extended', + name: 'domain', + level: 'core', type: 'keyword', - description: - 'The outcome of the event. If the event describes an action, this fields contains the outcome of that action. Examples outcomes are `success` and `failure`. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.', - example: 'success', + ignore_above: 1024, + description: 'Destination domain.', }, { - name: 'type', + name: 'geo.city_name', level: 'core', type: 'keyword', - description: 'Reserved for future usage. Please avoid using this field for user data.', + ignore_above: 1024, + description: 'City name.', + example: 'Montreal', }, { - name: 'module', + name: 'geo.continent_name', level: 'core', type: 'keyword', - description: - 'Name of the module this data is coming from. This information is coming from the modules used in Beats or Logstash.', - example: 'mysql', + ignore_above: 1024, + description: 'Name of the continent.', + example: 'North America', }, { - name: 'dataset', + name: 'geo.country_iso_code', level: 'core', type: 'keyword', - description: - 'Name of the dataset. The concept of a `dataset` (fileset / metricset) is used in Beats as a subset of modules. It contains the information which is currently stored in metricset.name and metricset.module or fileset.name.', - example: 'stats', + ignore_above: 1024, + description: 'Country ISO code.', + example: 'CA', }, { - name: 'severity', + name: 'geo.country_name', level: 'core', - type: 'long', - example: '7', - description: - "Severity describes the severity of the event. What the different severity values mean can very different between use cases. It's up to the implementer to make sure severities are consistent across events. ", + type: 'keyword', + ignore_above: 1024, + description: 'Country name.', + example: 'Canada', }, { - name: 'original', + name: 'geo.location', level: 'core', - type: 'keyword', - 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', - 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`.', - index: false, - doc_values: false, + type: 'geo_point', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', }, { - name: 'hash', + name: 'geo.name', level: 'extended', type: 'keyword', - example: '123456789012345678901234567890ABCD', + ignore_above: 1024, description: - 'Hash (perhaps logstash fingerprint) of raw field to be able to demonstrate log integrity.', + '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: 'duration', + name: 'geo.region_iso_code', level: 'core', - type: 'long', - format: 'duration', - input_format: 'nanoseconds', - 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.', + type: 'keyword', + ignore_above: 1024, + description: 'Region ISO code.', + example: 'CA-QC', }, { - name: 'timezone', - level: 'extended', + name: 'geo.region_name', + level: 'core', type: 'keyword', - 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").', + ignore_above: 1024, + description: 'Region name.', + example: 'Quebec', }, { - name: 'created', + name: 'ip', level: 'core', - type: 'date', + type: 'ip', description: - 'event.created contains the date when the event was created. This timestamp is distinct from @timestamp in that @timestamp contains the processed timestamp. For logs these two timestamps can be different as the timestamp in the log line and when the event is read for example by Filebeat are not identical. `@timestamp` must contain the timestamp extracted from the log line, event.created when the log line is read. The same could apply to package capturing where @timestamp contains the timestamp extracted from the network package and event.created when the event was created. In case the two timestamps are identical, @timestamp should be used.', + 'IP address of the destination.\n\nCan be one or multiple IPv4 or IPv6 addresses.', }, { - name: 'start', + name: 'mac', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'MAC address of the destination.', + }, + { + name: 'nat.ip', level: 'extended', - type: 'date', + type: 'ip', description: - 'event.start contains the date when the event started or when the activity was first observed.', + 'Translated ip of destination based NAT sessions (e.g. internet\nto private DMZ)\n\nTypically used with load balancers, firewalls, or routers.', }, { - name: 'end', + name: 'nat.port', level: 'extended', - type: 'date', + type: 'long', + format: 'string', description: - 'event.end contains the date when the event ended or when the activity was last observed.', + 'Port the source session is translated to by NAT Device.\n\nTypically used with load balancers, firewalls, or routers.', }, { - name: 'risk_score', + name: 'packets', level: 'core', - type: 'float', - description: - "Risk score or priority of the event (e.g. security solutions). Use your system's original value here. ", + type: 'long', + description: 'Packets sent from the destination to the source.', + example: 12, }, { - name: 'risk_score_norm', + name: 'port', + level: 'core', + type: 'long', + format: 'string', + description: 'Port of the destination.', + }, + { + name: 'registered_domain', level: 'extended', - type: 'float', + type: 'keyword', + ignore_above: 1024, 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.', + '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: 'file', - group: 2, - title: 'File', - description: - 'A file is defined as a set of information that has been created on, or has existed on a filesystem. File objects can be associated with host events, network events, and/or file events (e.g., those produced by File Integrity Monitoring [FIM] products or services). File fields provide details about the affected file associated with the event or metric.', - type: 'group', - fields: [ { - name: 'path', + name: 'top_level_domain', level: 'extended', type: 'keyword', - description: 'Path to the file.', + 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: 'target_path', + name: 'user.domain', level: 'extended', type: 'keyword', - description: 'Target path for symlinks.', + 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: 'extension', + name: 'user.email', level: 'extended', type: 'keyword', - description: 'File extension. This should allow easy filtering by file extensions.', - example: 'png', + ignore_above: 1024, + description: 'User email address.', }, { - name: 'type', + name: 'user.full_name', level: 'extended', type: 'keyword', - description: 'File type (file, dir, or symlink).', + 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: 'device', + name: 'user.group.domain', level: 'extended', type: 'keyword', - description: 'Device that is the source of the file.', + 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: 'inode', + name: 'user.group.id', level: 'extended', type: 'keyword', - description: 'Inode representing the file in the filesystem.', + ignore_above: 1024, + description: 'Unique identifier for the group on the system/platform.', }, { - name: 'uid', + name: 'user.group.name', level: 'extended', type: 'keyword', - description: 'The user ID (UID) or security identifier (SID) of the file owner.', + ignore_above: 1024, + description: 'Name of the group.', }, { - name: 'owner', + name: 'user.hash', level: 'extended', type: 'keyword', - description: "File owner's username.", + 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: 'gid', - level: 'extended', + name: 'user.id', + level: 'core', type: 'keyword', - description: 'Primary group ID (GID) of the file.', + ignore_above: 1024, + description: 'Unique identifiers of the user.', }, { - name: 'group', - level: 'extended', + name: 'user.name', + level: 'core', type: 'keyword', - description: 'Primary group name of the file.', + 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: 'mode', + name: 'code_signature.status', level: 'extended', type: 'keyword', - example: 416, - description: 'Mode of the file in octal representation.', + 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: 'size', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'File size in bytes (field is only added when `type` is `file`).', + 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: 'mtime', + name: 'code_signature.trusted', level: 'extended', - type: 'date', - description: 'Last time file content was modified.', + 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: 'ctime', + name: 'code_signature.valid', level: 'extended', - type: 'date', - description: 'Last time file metadata changed.', + 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: 'group', - title: 'Group', - group: 2, - description: - 'The group fields are meant to represent groups that are relevant to the event.', - type: 'group', - fields: [ { - name: 'id', + name: 'hash.md5', level: 'extended', type: 'keyword', - description: 'Unique identifier for the group on the system/platform.', + ignore_above: 1024, + description: 'MD5 hash.', + default_field: false, }, { - name: 'name', + name: 'hash.sha1', level: 'extended', type: 'keyword', - description: 'Name of the group.', + ignore_above: 1024, + description: 'SHA1 hash.', + default_field: false, }, - ], - }, - { - name: 'host', - title: 'Host', - group: 2, - description: - 'A host is defined as a general computing instance. ECS host.* fields should be populated with details about the host on which the event happened, or on which the measurement was taken. Host types include hardware, virtual machines, Docker containers, and Kubernetes nodes.', - type: 'group', - fields: [ { - name: 'hostname', - level: 'core', + name: 'hash.sha256', + level: 'extended', type: 'keyword', - description: - 'Hostname of the host. It normally contains what the `hostname` command returns on the host machine.', + ignore_above: 1024, + description: 'SHA256 hash.', + default_field: false, }, { - name: 'name', - level: 'core', + name: 'hash.sha512', + level: 'extended', type: 'keyword', - 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.', + ignore_above: 1024, + description: 'SHA512 hash.', + default_field: false, }, { - name: 'id', + name: 'name', level: 'core', type: 'keyword', + ignore_above: 1024, 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 of the library.\n\nThis generally maps to the name of the file on disk.', + example: 'kernel32.dll', + default_field: false, }, { - name: 'ip', - level: 'core', - type: 'ip', - description: 'Host ip address.', + 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: 'mac', - level: 'core', + name: 'pe.company', + level: 'extended', type: 'keyword', - description: 'Host mac address.', + ignore_above: 1024, + description: 'Internal company name of the file, provided at compile-time.', + example: 'Microsoft Corporation', + default_field: false, }, { - name: 'type', - level: 'core', + name: 'pe.description', + level: 'extended', type: 'keyword', - 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.', + ignore_above: 1024, + description: 'Internal description of the file, provided at compile-time.', + example: 'Paint', + default_field: false, }, { - name: 'architecture', - level: 'core', + name: 'pe.file_version', + level: 'extended', type: 'keyword', - example: 'x86_64', - description: 'Operating system architecture.', + ignore_above: 1024, + description: 'Internal version of the file, provided at compile-time.', + example: '6.3.9600.17415', + default_field: false, }, { - name: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - reusable: { - top_level: false, - expected: ['observer', 'host', 'user_agent'], - }, - type: 'group', - fields: [ - { - name: 'platform', - level: 'extended', - type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - example: 'Mac OS X', - description: 'Operating system name, without the version.', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - example: 'Mac OS Mojave', - description: 'Operating system name, including the version or code name.', - }, - { - name: 'family', - level: 'extended', - type: 'keyword', - example: 'debian', - description: 'OS family (such as redhat, debian, freebsd, windows).', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - example: '10.14.1', - description: 'Operating system version as a raw string.', - }, - { - name: 'kernel', - level: 'extended', - type: 'keyword', - example: '4.4.0-112-generic', - description: 'Operating system kernel version as a raw string.', - }, - ], + 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: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - 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: '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: 'http', - title: 'HTTP', + name: 'dns', + title: 'DNS', group: 2, - description: 'Fields related to HTTP activity.', + 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: 'request.method', + name: 'answers', level: 'extended', - type: 'keyword', + type: 'object', + object_type: 'keyword', description: - 'Http request method. The field value must be normalized to lowercase for querying. See "Lowercase Capitalization" in the "Implementing ECS" section.', - example: 'get, post, put', + '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: 'request.body.content', + name: 'answers.class', level: 'extended', type: 'keyword', - description: 'The full http request body.', - example: 'Hello world', + ignore_above: 1024, + description: 'The class of DNS data contained in this resource record.', + example: 'IN', }, { - name: 'request.referrer', + name: 'answers.data', level: 'extended', type: 'keyword', - description: 'Referrer for this HTTP request.', - example: 'https://blog.example.com/', + 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: 'response.status_code', + 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: 'Http response status code.', - example: 404, + 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: 'response.body.content', + name: 'answers.type', level: 'extended', type: 'keyword', - description: 'The full http response body.', - example: 'Hello world', + ignore_above: 1024, + description: 'The type of data contained in this resource record.', + example: 'CNAME', }, { - name: 'version', + name: 'header_flags', level: 'extended', type: 'keyword', - description: 'Http version.', - example: 1.1, + 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: 'request.bytes', + name: 'id', level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the request (body and headers).', - example: 1437, + 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: 'request.body.bytes', + name: 'op_code', level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the request body.', - example: 887, + 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: 'response.bytes', + name: 'question.class', level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the response (body and headers).', - example: 1437, + type: 'keyword', + ignore_above: 1024, + description: 'The class of records being queried.', + example: 'IN', }, { - name: 'response.body.bytes', + name: 'question.name', level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the response body.', - example: 887, + 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: 'log', - title: 'Log', - description: 'Fields which are specific to log events.', + name: 'ecs', + title: 'ECS', + group: 2, + description: 'Meta-information specific to ECS.', type: 'group', fields: [ { - name: 'level', + 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', - description: 'Log level of the log event. Some examples are `WARN`, `ERR`, `INFO`.', - example: 'ERR', + ignore_above: 1024, + description: 'Error code describing the error.', }, { - name: 'original', + name: 'id', level: 'core', type: 'keyword', - example: 'Sep 19 08:26:10 localhost My log', - index: false, - doc_values: false, - 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`. ", + 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: 'network', - title: 'Network', + name: 'event', + title: 'Event', group: 2, description: - 'The network is defined as the communication path over which a host or network event happens. The network.* fields should be populated with details about the network activity associated with an event.', + '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: 'name', - level: 'extended', + name: 'action', + level: 'core', type: 'keyword', - description: 'Name given by operators to sections of their network.', - example: 'Guest Wifi', + 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: 'type', + name: 'category', level: 'core', type: 'keyword', + ignore_above: 1024, 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 "Lowercase Capitalization" in the "Implementing ECS" section.', - example: 'ipv4', + '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: 'iana_number', + name: 'code', level: 'extended', type: 'keyword', + ignore_above: 1024, 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, + '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: 'transport', + 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: - '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 "Lowercase Capitalization" in the "Implementing ECS" section.', - example: 'tcp', + '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: 'application', + 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: - 'A name given to an application. 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 "Lowercase Capitalization" in the "Implementing ECS" section.', - example: 'aim', + 'Hash (perhaps logstash fingerprint) of raw field to be able to\ndemonstrate log integrity.', + example: '123456789012345678901234567890ABCD', }, { - name: 'protocol', + 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: - 'L7 Network protocol name. ex. http, lumberjack, transport protocol. The field value must be normalized to lowercase for querying. See "Lowercase Capitalization" in the "Implementing ECS" section.', - example: 'http', + '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: 'direction', + name: 'kind', level: 'core', type: 'keyword', + ignore_above: 1024, 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', + '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: 'forwarded_ip', + name: 'module', level: 'core', - type: 'ip', - description: 'Host IP address when the source IP address is the proxy.', - example: '192.1.1.2', + 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: 'community_id', + 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: - '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=', + '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: 'bytes', + 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: 'bytes', + format: 'string', description: - 'Total bytes transferred in both directions. If `source.bytes` and `destination.bytes` are known, `network.bytes` is their sum.', - example: 368, + '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: 'packets', + name: 'severity', level: 'core', type: 'long', + format: 'string', description: - 'Total packets transferred in both directions. If `source.packets` and `destination.packets` are known, `network.packets` is their sum.', - example: 24, + '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: 'observer', - title: 'Observer', + name: 'file', + title: 'File', group: 2, description: - 'An observer is defined as a special network, security, or application device used to detect, observe, or create network, security, or application-related events and metrics. This could be a custom hardware appliance or a server that has been configured to run special network, security, or application software. Examples include firewalls, intrusion detection/prevention systems, network monitoring sensors, web application firewalls, data loss prevention systems, and APM servers. The observer.* fields shall be populated with details of the system, if any, that detects, observes and/or creates a network, security, or application event or metric. Message queues and ETL components used in processing events or metrics are not considered observers in ECS.', + '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: 'mac', - level: 'core', - type: 'keyword', - description: 'MAC address of the observer', + 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: 'ip', - level: 'core', - type: 'ip', - description: 'IP address of the observer.', + 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: 'hostname', + name: 'code_signature.exists', level: 'core', - type: 'keyword', - description: 'Hostname of the observer.', + type: 'boolean', + description: 'Boolean to capture if a signature is present.', + example: 'true', + default_field: false, }, { - name: 'vendor', - level: 'core', + name: 'code_signature.status', + level: 'extended', type: 'keyword', - description: 'observer vendor information.', + 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: 'version', + name: 'code_signature.subject_name', level: 'core', type: 'keyword', - description: 'Observer version.', + ignore_above: 1024, + description: 'Subject name of the code signer', + example: 'Microsoft Corporation', + default_field: false, }, { - name: 'serial_number', + name: 'code_signature.trusted', level: 'extended', - type: 'keyword', - description: 'Observer serial number.', - }, - { - name: 'type', - level: 'core', - type: 'keyword', + type: 'boolean', 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', + '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: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - reusable: { - top_level: false, - expected: ['observer', 'host', 'user_agent'], - }, - type: 'group', - fields: [ - { - name: 'platform', - level: 'extended', - type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - example: 'Mac OS X', - description: 'Operating system name, without the version.', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - example: 'Mac OS Mojave', - description: 'Operating system name, including the version or code name.', - }, - { - name: 'family', - level: 'extended', - type: 'keyword', - example: 'debian', - description: 'OS family (such as redhat, debian, freebsd, windows).', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - example: '10.14.1', - description: 'Operating system version as a raw string.', - }, - { - name: 'kernel', - level: 'extended', - type: 'keyword', - example: '4.4.0-112-generic', - description: 'Operating system kernel version as a raw string.', - }, - ], + 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: 'geo', - title: 'Geo', - group: 2, + name: 'created', + level: 'extended', + type: 'date', description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - 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', - }, - ], + 'File creation time.\n\nNote that not all filesystems store the creation time.', }, - ], - }, - { - name: 'organization', - title: 'Organization', - group: 2, - description: - 'The organization fields enrich data with information about the company or entity the data is associated with. These fields help you arrange or filter data stored in an index by one or multiple organizations.', - type: 'group', - fields: [ { - name: 'name', + 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', - description: 'Organization name.', + ignore_above: 1024, + description: 'Device that is the source of the file.', + example: 'sda', }, { - name: 'id', + name: 'directory', level: 'extended', type: 'keyword', - description: 'Unique identifier for the organization.', + ignore_above: 1024, + description: + 'Directory where the file is located. It should include the drive\nletter, when appropriate.', + example: '/home/alice', }, - ], - }, - { - name: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - reusable: { - top_level: false, - expected: ['observer', 'host', 'user_agent'], - }, - type: 'group', - fields: [ { - name: 'platform', + name: 'drive_letter', level: 'extended', type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', + 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: 'name', + name: 'extension', level: 'extended', type: 'keyword', - example: 'Mac OS X', - description: 'Operating system name, without the version.', + ignore_above: 1024, + description: 'File extension.', + example: 'png', }, { - name: 'full', + name: 'gid', level: 'extended', type: 'keyword', - example: 'Mac OS Mojave', - description: 'Operating system name, including the version or code name.', + ignore_above: 1024, + description: 'Primary group ID (GID) of the file.', + example: '1001', }, { - name: 'family', + name: 'group', level: 'extended', type: 'keyword', - example: 'debian', - description: 'OS family (such as redhat, debian, freebsd, windows).', + ignore_above: 1024, + description: 'Primary group name of the file.', + example: 'alice', }, { - name: 'version', + name: 'hash.md5', level: 'extended', type: 'keyword', - example: '10.14.1', - description: 'Operating system version as a raw string.', + ignore_above: 1024, + description: 'MD5 hash.', }, { - name: 'kernel', + name: 'hash.sha1', level: 'extended', type: 'keyword', - example: '4.4.0-112-generic', - description: 'Operating system kernel version as a raw string.', + ignore_above: 1024, + description: 'SHA1 hash.', }, - ], - }, - { - name: 'process', - title: 'Process', - group: 2, - description: - 'These fields contain information about a process. These fields can help you correlate metrics information with a process id/name from a log message. The `process.pid` often stays in the metric itself and is copied to the global field for correlation.', - type: 'group', - fields: [ { - name: 'pid', - level: 'core', - type: 'long', - description: 'Process id.', - example: 'ssh', + name: 'hash.sha256', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'SHA256 hash.', }, { - name: 'name', + name: 'hash.sha512', level: 'extended', type: 'keyword', - description: 'Process name. Sometimes called program name or similar.', - example: 'ssh', + ignore_above: 1024, + description: 'SHA512 hash.', }, { - name: 'ppid', + name: 'inode', level: 'extended', - type: 'long', - description: 'Process parent id.', + type: 'keyword', + ignore_above: 1024, + description: 'Inode representing the file in the filesystem.', + example: '256383', }, { - name: 'args', + name: 'mime_type', level: 'extended', type: 'keyword', - description: 'Process arguments. May be filtered to protect sensitive information.', - example: ['ssh', '-l', 'user', '10.0.0.16'], + 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: 'executable', + name: 'mode', level: 'extended', type: 'keyword', - description: 'Absolute path to the process executable.', - example: '/usr/bin/ssh', + ignore_above: 1024, + description: 'Mode of the file in octal representation.', + example: '0640', }, { - name: 'title', + 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: - '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.', + 'Full path to the file, including the file name. It should include\nthe drive letter, when appropriate.', + example: '/home/alice/example.png', }, { - name: 'thread.id', + 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', - example: 4242, - description: 'Thread ID.', + description: 'File size in bytes.\n\nOnly relevant when `file.type` is "file".', + example: 16384, }, { - name: 'start', + name: 'target_path', level: 'extended', - type: 'date', - example: '2016-05-23T08:05:34.853Z', - description: 'The time the process started.', + type: 'keyword', + ignore_above: 1024, + multi_fields: [ + { + name: 'text', + type: 'text', + norms: false, + default_field: false, + }, + ], + description: 'Target path for symlinks.', }, { - name: 'working_directory', + name: 'type', level: 'extended', type: 'keyword', - example: '/home/alice', - description: 'The working directory of the process.', + ignore_above: 1024, + description: 'File type (file, dir, or symlink).', + example: 'file', }, - ], - }, - { - name: 'related', - title: 'Related', - group: 2, - description: - 'This field set is meant to facilitate pivoting around a piece of data. Some pieces of information can be seen in many places in ECS. To facilitate searching for them, append values to their corresponding field in `related.`. A concrete example is IP addresses, which can be under host, observer, source, destination, client, server, and network.forwarded_ip. If you append all IPs to `related.ip`, you can then search for a given IP trivially, no matter where it appeared, by querying `related.ip:a.b.c.d`.', - type: 'group', - fields: [ { - name: 'ip', + name: 'uid', level: 'extended', - type: 'ip', - description: 'All of the IPs seen on your event.', + type: 'keyword', + ignore_above: 1024, + description: 'The user ID (UID) or security identifier (SID) of the file owner.', + example: '1001', }, ], }, { - name: 'server', - title: 'Server', + name: 'geo', + title: 'Geo', group: 2, description: - 'A Server is defined as the responder in a network connection for events regarding sessions, connections, or bidirectional flow records. For TCP events, the server is the receiver of the initial SYN packet(s) of the TCP connection. For other protocols, the server is generally the responder in the network transaction. Some systems actually use the term "responder" to refer the server in TCP connections. The server fields describe details about the system acting as the server in the network event. Server fields are usually populated in conjunction with client fields. Server fields are generally not populated for packet-level events. Client / server representations can add semantic context to an exchange, which is helpful to visualize the data in certain situations. If your context falls in that category, you should still ensure that source and destination are filled appropriately.', + '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: 'address', - level: 'extended', + name: 'city_name', + level: 'core', type: 'keyword', - 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.', + ignore_above: 1024, + description: 'City name.', + example: 'Montreal', }, { - name: 'ip', + name: 'continent_name', level: 'core', - type: 'ip', - description: 'IP address of the server. Can be one or multiple IPv4 or IPv6 addresses.', + type: 'keyword', + ignore_above: 1024, + description: 'Name of the continent.', + example: 'North America', }, { - name: 'port', + name: 'country_iso_code', level: 'core', - type: 'long', - description: 'Port of the server.', + type: 'keyword', + ignore_above: 1024, + description: 'Country ISO code.', + example: 'CA', }, { - name: 'mac', + name: 'country_name', level: 'core', type: 'keyword', - description: 'MAC address of the server.', + ignore_above: 1024, + description: 'Country name.', + example: 'Canada', }, { - name: 'domain', + name: 'location', level: 'core', - type: 'keyword', - description: 'Server domain.', + type: 'geo_point', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', }, { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - example: 184, - description: 'Bytes sent from the server to the client.', + 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: 'packets', + name: 'region_iso_code', level: 'core', - type: 'long', - example: 12, - description: 'Packets sent from the server to the client.', + type: 'keyword', + ignore_above: 1024, + description: 'Region ISO code.', + example: 'CA-QC', }, { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - 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: 'region_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Region name.', + example: 'Quebec', }, ], }, { - name: 'service', - title: 'Service', + name: 'group', + title: 'Group', group: 2, description: - 'The service fields describe the service for or from which the data was collected. These fields help you find and correlate logs for a specific service and version.', + 'The group fields are meant to represent groups that are relevant\nto the event.', type: 'group', fields: [ { - name: 'id', - level: 'core', + name: 'domain', + level: 'extended', type: 'keyword', + ignore_above: 1024, description: - 'Unique identifier of the running service. This id should uniquely identify this service. This makes it possible to correlate logs and metrics for one specific service. Example: If you are experiencing issues with one redis instance, you can filter on that id to see metrics and logs for that single instance.', - example: 'd37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6', + '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: 'core', + level: 'extended', type: 'keyword', - example: 'elasticsearch-metrics', - description: - 'Name of the service data is collected from. The name of the service is normally user given. This allows if two instances of the same service are running on the same machine they can be differentiated by the `service.name`. Also it 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.', + 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: 'type', - level: 'core', + name: 'md5', + level: 'extended', type: 'keyword', - example: 'elasticsearch', - 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`.', + ignore_above: 1024, + description: 'MD5 hash.', }, { - name: 'state', - level: 'core', + name: 'sha1', + level: 'extended', type: 'keyword', - description: 'Current state of the service.', + ignore_above: 1024, + description: 'SHA1 hash.', }, { - name: 'version', - level: 'core', + name: 'sha256', + level: 'extended', type: 'keyword', - example: '3.2.4', - 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.', + ignore_above: 1024, + description: 'SHA256 hash.', }, { - name: 'ephemeral_id', + name: 'sha512', level: 'extended', type: 'keyword', - description: - 'Ephemeral identifier of this service (if one exists). This id normally changes across restarts, but `service.id` does not.', - example: '8a4f500f', + ignore_above: 1024, + description: 'SHA512 hash.', }, ], }, { - name: 'source', - title: 'Source', + name: 'host', + title: 'Host', group: 2, description: - 'Source fields describe details about the source of a packet/event. Source fields are usually populated in conjunction with destination fields.', + '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: 'address', + 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: - '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 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: 'ip', + name: 'geo.city_name', level: 'core', - type: 'ip', - description: 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.', + type: 'keyword', + ignore_above: 1024, + description: 'City name.', + example: 'Montreal', }, { - name: 'port', + name: 'geo.continent_name', level: 'core', - type: 'long', - description: 'Port of the source.', + type: 'keyword', + ignore_above: 1024, + description: 'Name of the continent.', + example: 'North America', }, { - name: 'mac', + name: 'geo.country_iso_code', level: 'core', type: 'keyword', - description: 'MAC address of the source.', + ignore_above: 1024, + description: 'Country ISO code.', + example: 'CA', }, { - name: 'domain', + name: 'geo.country_name', level: 'core', type: 'keyword', - description: 'Source domain.', + ignore_above: 1024, + description: 'Country name.', + example: 'Canada', }, { - name: 'bytes', + name: 'geo.location', level: 'core', - type: 'long', - format: 'bytes', - example: 184, - description: 'Bytes sent from the source to the destination.', + type: 'geo_point', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', }, { - name: 'packets', + 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: 'long', - example: 12, - description: 'Packets sent from the source to the destination.', + type: 'keyword', + ignore_above: 1024, + description: 'Region ISO code.', + example: 'CA-QC', }, { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - 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.region_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Region name.', + example: 'Quebec', }, - ], - }, - { - name: 'url', - title: 'URL', - description: 'URL fields provide a complete URL, with scheme, host, and path.', - type: 'group', - fields: [ { - name: 'original', - level: 'extended', + name: 'hostname', + level: 'core', type: 'keyword', + ignore_above: 1024, 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', + 'Hostname of the host.\n\nIt normally contains what the `hostname` command returns on the host machine.', }, { - name: 'full', - level: 'extended', + name: 'id', + level: 'core', type: 'keyword', + ignore_above: 1024, 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', + '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: 'scheme', - level: 'extended', + name: 'ip', + level: 'core', + type: 'ip', + description: 'Host ip addresses.', + }, + { + name: 'mac', + level: 'core', type: 'keyword', - description: - 'Scheme of the request, such as "https". Note: The `:` is not part of the scheme.', - example: 'https', + ignore_above: 1024, + description: 'Host mac addresses.', }, { - name: 'domain', - level: 'extended', + name: 'name', + level: 'core', type: 'keyword', + ignore_above: 1024, description: - 'Domain of the request, 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 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: 'port', + name: 'os.family', level: 'extended', - type: 'integer', - description: 'Port of the request, such as 443.', - example: 443, + type: 'keyword', + ignore_above: 1024, + description: 'OS family (such as redhat, debian, freebsd, windows).', + example: 'debian', }, { - name: 'path', + name: 'os.full', level: 'extended', type: 'keyword', - description: 'Path of the request, such as "/search".', + 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: 'query', + name: 'os.kernel', level: 'extended', type: 'keyword', - 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.', + ignore_above: 1024, + description: 'Operating system kernel version as a raw string.', + example: '4.4.0-112-generic', }, { - name: 'fragment', + name: 'os.name', level: 'extended', type: 'keyword', - description: - 'Portion of the url after the `#`, such as "top". The `#` is not part of the fragment.', + 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: 'username', + name: 'os.platform', level: 'extended', type: 'keyword', - description: 'Username of the request.', + ignore_above: 1024, + description: 'Operating system platform (such centos, ubuntu, windows).', + example: 'darwin', }, { - name: 'password', + name: 'os.version', level: 'extended', type: 'keyword', - description: 'Password of the request.', + ignore_above: 1024, + description: 'Operating system version as a raw string.', + example: '10.14.1', }, - ], - }, - { - name: 'user', - title: 'User', - group: 2, - description: - 'The user fields describe information about the user that is relevant to the event. Fields can have one entry or multiple entries. If a user has more than one id, provide an array that includes all of them.', - reusable: { - top_level: true, - expected: ['client', 'destination', 'host', 'server', 'source'], - }, - type: 'group', - fields: [ { - name: 'id', + name: 'type', level: 'core', type: 'keyword', - description: 'One or multiple unique identifiers of the user.', + 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: 'name', - level: 'core', - type: 'keyword', - example: 'albert', - description: 'Short name or login of the user.', + name: 'uptime', + level: 'extended', + type: 'long', + description: 'Seconds the host has been up.', + example: 1325, }, { - name: 'full_name', + name: 'user.domain', level: 'extended', type: 'keyword', - example: 'Albert Einstein', - description: "User's full name, if available. ", + 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', + name: 'user.email', level: 'extended', type: 'keyword', + ignore_above: 1024, description: 'User email address.', }, { - name: 'hash', + 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: - '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 of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', }, { - name: 'group', - title: 'Group', - group: 2, + 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: - 'The group fields are meant to represent groups that are relevant to the event.', - type: 'group', - fields: [ - { - name: 'id', - level: 'extended', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.', - }, + '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: 'name', - level: 'extended', - type: 'keyword', - description: 'Name of the group.', + 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', + name: 'http', + title: 'HTTP', group: 2, description: - 'The user_agent fields normally come from a browser request. They often show up in web service logs coming from the parsed user agent string.', + 'Fields related to HTTP activity. Use the `url` field set to store\nthe url of the request.', type: 'group', fields: [ { - name: 'original', + name: 'request.body.bytes', 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 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', + type: 'long', + format: 'bytes', + description: 'Size in bytes of the request body.', + example: 887, }, { - name: 'name', + name: 'request.body.content', level: 'extended', type: 'keyword', - example: 'Safari', - description: 'Name of the user agent.', + ignore_above: 1024, + multi_fields: [ + { + name: 'text', + type: 'text', + norms: false, + default_field: false, + }, + ], + description: 'The full HTTP request body.', + example: 'Hello world', }, { - name: 'version', + 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: 'Version of the user agent.', - example: 12, + 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: 'device.name', + name: 'request.referrer', level: 'extended', type: 'keyword', - example: 'iPhone', - description: 'Name of the device.', + ignore_above: 1024, + description: 'Referrer for this HTTP request.', + example: 'https://blog.example.com/', }, { - name: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - reusable: { - top_level: false, - expected: ['observer', 'host', 'user_agent'], - }, - type: 'group', - fields: [ - { - name: 'platform', - level: 'extended', - type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - example: 'Mac OS X', - description: 'Operating system name, without the version.', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - example: 'Mac OS Mojave', - description: 'Operating system name, including the version or code name.', - }, - { - name: 'family', - level: 'extended', - type: 'keyword', - example: 'debian', - description: 'OS family (such as redhat, debian, freebsd, windows).', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - example: '10.14.1', - description: 'Operating system version as a raw string.', - }, + 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: 'kernel', - level: 'extended', - type: 'keyword', - example: '4.4.0-112-generic', - description: 'Operating system kernel version as a raw string.', + 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: 'agent.hostname', - type: 'keyword', - description: 'Hostname of the agent.', - }, - ], - }, - { - key: 'beat', - title: 'Beat', - description: 'Contains common beat fields available in all event types.', - fields: [ - { - name: 'beat.timezone', - type: 'alias', - path: 'event.timezone', - migration: true, - }, - { - name: 'fields', - type: 'object', - object_type: 'keyword', - description: 'Contains user configurable fields.', - }, - { - name: 'error', + 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', - description: 'Error fields containing additional info in case of errors.', fields: [ { - name: 'type', + 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', - description: 'Error type.', + ignore_above: 1024, + description: 'Interface name as reported by the system.', + example: 'eth0', + default_field: false, }, ], }, { - name: 'beat.name', - type: 'alias', - path: 'host.name', - migration: true, - }, - { - name: 'beat.hostname', - type: 'alias', - path: 'agent.hostname', - migration: true, - }, - ], - }, - { - key: 'cloud', - title: 'Cloud provider metadata', - description: 'Metadata from cloud providers added by the add_cloud_metadata processor.', - fields: [ - { - name: 'cloud.project.id', - example: 'project-x', - description: 'Name of the project in Google Cloud.', - }, - { - 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.', - short_config: false, - anchor: 'docker-processor', - fields: [ - { - name: 'docker', + 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: 'container.id', - type: 'alias', - path: 'container.id', - migration: true, + 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: 'container.image', - type: 'alias', - path: 'container.image.name', - migration: true, + 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: 'container.name', - type: 'alias', - path: 'container.name', - migration: true, + 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: 'container.labels', + 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: 'Image labels.', + 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', }, ], }, - ], - }, - { - key: 'host', - title: 'Host', - description: 'Info collected for the host machine.', - anchor: 'host-processor', - }, - { - key: 'kubernetes', - title: 'Kubernetes', - description: 'Kubernetes metadata added by the kubernetes processor', - short_config: false, - anchor: 'kubernetes-processor', - fields: [ { - name: 'kubernetes', + 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: 'pod.name', + name: 'application', + level: 'extended', type: 'keyword', - description: 'Kubernetes pod name', + 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: 'pod.uid', + 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: 'Kubernetes Pod UID', + 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: 'namespace', + name: 'direction', + level: 'core', type: 'keyword', - description: 'Kubernetes namespace', + 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: 'node.name', + 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: 'Kubernetes node name', + 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: 'labels', + name: 'inner', + level: 'extended', type: 'object', - description: 'Kubernetes labels map', + 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: 'annotations', - type: 'object', - description: 'Kubernetes annotations map', + 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: 'container.name', + name: 'inner.vlan.name', + level: 'extended', type: 'keyword', - description: 'Kubernetes container name', + ignore_above: 1024, + description: 'Optional VLAN name as reported by the observer.', + example: 'outside', + default_field: false, }, { - name: 'container.image', + 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', - description: 'Kubernetes container image', + ignore_above: 1024, + description: 'Optional VLAN name as reported by the observer.', + example: 'outside', + default_field: false, }, ], }, - ], - }, - { - key: 'process', - title: 'Process', - description: 'Process metadata fields', - fields: [ { - name: 'process', + 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: 'exe', - type: 'alias', - path: 'process.executable', - migration: true, + 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, }, - ], - }, - ], - }, - { - key: 'log', - title: 'Log file content', - description: 'Contains log file lines.', - 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`.', - }, - { - name: 'log.source.address', - type: 'keyword', - required: false, - description: 'Source address from which the log event was read / sent from.', - }, - { - name: 'log.offset', - type: 'long', - required: false, - description: 'The file offset the reported line starts at.', - }, - { - name: 'stream', - type: 'keyword', - required: false, - description: "Log stream when reading container logs, can be 'stdout' or 'stderr' ", - }, - { - 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.', - }, - { - name: 'event.sequence', - type: 'long', - required: false, - description: 'The sequence number of this event.', - }, - { - name: 'syslog.facility', - type: 'long', - required: false, - description: 'The facility extracted from the priority.', - }, - { - name: 'syslog.priority', - type: 'long', - required: false, - description: 'The priority of the syslog event.', - }, - { - name: 'syslog.severity_label', - type: 'keyword', - required: false, - description: 'The human readable severity.', - }, - { - name: 'syslog.facility_label', - type: 'keyword', - required: false, - description: 'The human readable facility.', - }, - { - name: 'process.program', - type: 'keyword', - required: false, - description: 'The name of the program.', - }, - { - name: 'log.flags', - description: 'This field contains the flags of the event.', - }, - { - 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: '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: 'full_name', - type: 'keyword', + 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: 'fileset.name', - type: 'keyword', - description: 'The Filebeat fileset that generated this event.', - }, - { - name: 'fileset.module', - type: 'alias', - path: 'event.module', - migration: true, - }, - { - name: 'read_timestamp', - type: 'alias', - path: 'event.created', - migration: true, - }, - ], - }, - { - key: 'apache', - title: 'Apache', - description: 'Apache Module', - short_config: true, - fields: [ - { - name: 'apache2', + 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', - description: 'Aliases for backward compatibility with old apache2 fields', fields: [ { - name: 'access', - 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: '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: 'text', + type: 'text', + norms: false, + default_field: false, }, ], + description: 'Organization name.', }, ], }, { - name: 'apache', + name: 'os', + title: 'Operating System', + group: 2, + description: 'The OS fields contain information about the operating system.', type: 'group', - description: 'Apache fields.', fields: [ { - name: 'access', - type: 'group', - description: 'Contains fields for the Apache HTTP Server access logs.', - fields: [ - { - name: 'ssl.protocol', - type: 'keyword', - description: 'SSL protocol version.', - }, + 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: 'ssl.cipher', - type: 'keyword', - description: 'SSL cipher name.', + name: 'text', + type: 'text', + norms: false, + default_field: false, }, ], + description: 'Operating system name, including the version or code name.', + example: 'Mac OS Mojave', }, { - name: 'error', - type: 'group', - description: 'Fields from the Apache error logs.', - fields: [ + 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: 'module', - type: 'keyword', - description: 'The module producing the logged message.', + 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', }, ], }, - ], - }, - { - key: 'auditd', - title: 'Auditd', - description: 'Module for parsing auditd logs.', - short_config: true, - fields: [ { - name: 'user', + 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: 'terminal', + 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: - 'Terminal or tty device on which the user is performing the observed activity.', + '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: 'audit', - type: 'group', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.', - }, - { - name: 'name', - type: 'keyword', - example: 'albert', - description: 'Short name or login of the user.', - }, - { - name: 'group.id', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'group.name', - type: 'keyword', - description: 'Name of the group.', - }, - ], + name: 'checksum', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Checksum of the installed package for verification.', + example: '68b329da9893e34099c7d8ad5cb9c940', }, { - name: 'effective', - type: 'group', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.', - }, - { - name: 'name', - type: 'keyword', - example: 'albert', - description: 'Short name or login of the user.', - }, - { - name: 'group.id', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'group.name', - type: 'keyword', - description: 'Name of the group.', - }, - ], + 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: 'filesystem', - type: 'group', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.', - }, - { - name: 'name', - type: 'keyword', - example: 'albert', - description: 'Short name or login of the user.', - }, - { - name: 'group.id', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'group.name', - type: 'keyword', - description: 'Name of the group.', - }, - ], + 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: 'owner', - type: 'group', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.', - }, - { - name: 'name', - type: 'keyword', - example: 'albert', - description: 'Short name or login of the user.', - }, - { - name: 'group.id', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'group.name', - type: 'keyword', - description: 'Name of the group.', - }, - ], + name: 'installed', + level: 'extended', + type: 'date', + description: 'Time when package was installed.', }, { - name: 'saved', - type: 'group', - fields: [ - { - name: 'id', - type: 'keyword', - description: 'One or multiple unique identifiers of the user.', - }, - { - name: 'name', - type: 'keyword', - example: 'albert', - description: 'Short name or login of the user.', - }, - { - name: 'group.id', - type: 'keyword', - description: 'Unique identifier for the group on the system/platform.', - }, - { - name: 'group.name', - type: 'keyword', - description: 'Name of the group.', - }, - ], + 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: 'auditd', + name: 'pe', + title: 'PE Header', + group: 2, + description: 'These fields contain Windows Portable Executable (PE) metadata.', type: 'group', - description: 'Fields from the auditd logs.', fields: [ { - name: 'log', - type: 'group', + 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: - 'Fields from the Linux audit log. Not all fields are documented here because they are dynamic and vary by audit event type.', - fields: [ - { - name: 'old_auid', - description: - 'For login events this is the old audit ID used for the user prior to this login.', - }, - { - 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).', - }, - { - name: 'old_ses', - description: - 'For login events this is the old session ID used for the user prior to this login.', - }, - { - 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.', - }, - { - name: 'sequence', - type: 'long', - description: 'The audit event sequence number.', - }, - { - name: 'items', - description: 'The number of items in an event.', - }, + '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: '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.', + 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: 'tty', - type: 'keyword', - definition: 'TTY udevice the user is running programs on.', - }, - { - name: 'a0', - description: 'The first argument to the system call.', - }, - { - name: 'addr', - type: 'ip', - definition: 'Remote address that the user is connecting from.', - }, - { - name: 'rport', - type: 'long', - definition: 'Remote port number.', - }, - { - name: 'laddr', - type: 'ip', - definition: 'Local network address.', - }, - { - name: 'lport', - type: 'long', - definition: 'Local port number.', - }, - { - 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, + name: 'text', + type: 'text', + norms: false, + default_field: false, }, ], + description: 'Absolute path to the process executable.', + example: '/usr/bin/ssh', }, - ], - }, - ], - }, - { - key: 'elasticsearch', - title: 'elasticsearch', - description: 'elasticsearch Module', - fields: [ - { - name: 'elasticsearch', - type: 'group', - description: '', - fields: [ { - name: 'component', - description: 'Elasticsearch component from where the log event originated', - example: 'o.e.c.m.MetaDataCreateIndexService', - type: 'keyword', + 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: 'cluster.uuid', - description: 'UUID of the cluster', - example: 'GmvrbHlNTiSVYiPf8kxg9g', + name: 'hash.md5', + level: 'extended', type: 'keyword', + ignore_above: 1024, + description: 'MD5 hash.', }, { - name: 'cluster.name', - description: 'Name of the cluster', - example: 'docker-cluster', + name: 'hash.sha1', + level: 'extended', type: 'keyword', + ignore_above: 1024, + description: 'SHA1 hash.', }, { - name: 'node.id', - description: 'ID of the node', - example: 'DSiWcTyeThWtUXLB9J0BMw', + name: 'hash.sha256', + level: 'extended', type: 'keyword', + ignore_above: 1024, + description: 'SHA256 hash.', }, { - name: 'node.name', - description: 'Name of the node', - example: 'vWNJsZ3', + name: 'hash.sha512', + level: 'extended', type: 'keyword', + ignore_above: 1024, + description: 'SHA512 hash.', }, { - name: 'index.name', - description: 'Index name', - example: 'filebeat-test-input', + 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: 'index.id', - description: 'Index id', - example: 'aOGgDwbURfCV57AScqbCgw', + 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: 'shard.id', - description: 'Id of the shard', - example: '0', + 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: 'audit', - type: 'group', - description: '', - fields: [ - { - name: 'layer', - description: - 'The layer from which this event originated: rest, transport or ip_filter', - example: 'rest', - 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_admin', '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: 'event_type', - type: 'alias', - path: 'event.type', - migration: true, - }, + 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: 'origin_address', - type: 'alias', - path: 'source.ip', - migration: true, + 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: 'uri', - type: 'alias', - path: 'url.original', - migration: true, + 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: 'principal', - type: 'alias', - path: 'user.name', - migration: true, + name: 'text', + type: 'text', + norms: false, }, ], + description: 'Process name.\n\nSometimes called program name or similar.', + example: 'ssh', + default_field: false, }, { - name: 'deprecation', - type: 'group', - description: '', + name: 'parent.pgid', + level: 'extended', + type: 'long', + format: 'string', + description: 'Identifier of the group of processes the process belongs to.', + default_field: false, }, { - name: 'gc', - type: 'group', - description: 'GC fileset fields.', - fields: [ + 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: 'phase', - type: 'group', - description: 'Fields specific to GC phase.', - fields: [ - { - name: 'name', - type: 'keyword', - description: 'Name of the GC collection phase.', - }, - { - name: 'duration_sec', - type: 'float', - description: 'Collection phase duration according to the Java virtual machine.', - }, - { - name: 'scrub_symbol_table_time_sec', - type: 'float', - description: 'Pause time in seconds cleaning up symbol tables.', - }, - { - name: 'scrub_string_table_time_sec', - type: 'float', - description: 'Pause time in seconds cleaning up string tables.', - }, - { - name: 'weak_refs_processing_time_sec', - type: 'float', - description: 'Time spent processing weak references in seconds.', - }, - { - name: 'parallel_rescan_time_sec', - type: 'float', - description: - 'Time spent in seconds marking live objects while application is stopped.', - }, - { - name: 'class_unload_time_sec', - type: 'float', - description: 'Time spent unloading unused classes in seconds.', - }, - { - name: 'cpu_time', - type: 'group', - description: 'Process CPU time spent performing collections.', - fields: [ - { - name: 'user_sec', - type: 'float', - description: 'CPU time spent outside the kernel.', - }, - { - name: 'sys_sec', - type: 'float', - description: 'CPU time spent inside the kernel.', - }, - { - name: 'real_sec', - type: 'float', - description: - 'Total elapsed CPU time spent to complete the collection from start to finish.', - }, - ], - }, - ], + 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: 'jvm_runtime_sec', - type: 'float', - description: 'The time from JVM start up in seconds, as a floating point number.', - }, - { - name: 'threads_total_stop_time_sec', - type: 'float', - description: 'Garbage collection threads total stop time seconds.', - }, - { - name: 'stopping_threads_time_sec', - type: 'float', - description: 'Time took to stop threads seconds.', - }, - { - name: 'tags', - type: 'keyword', - description: 'GC logging tags.', - }, - { - name: 'heap', - type: 'group', - description: 'Heap allocation and total size.', - fields: [ - { - name: 'size_kb', - type: 'integer', - description: 'Total heap size in kilobytes.', - }, - { - name: 'used_kb', - type: 'integer', - description: 'Used heap in kilobytes.', - }, - ], - }, - { - name: 'old_gen', - type: 'group', - description: 'Old generation occupancy and total size.', - fields: [ - { - name: 'size_kb', - type: 'integer', - description: 'Total size of old generation in kilobytes.', - }, - { - name: 'used_kb', - type: 'integer', - description: 'Old generation occupancy in kilobytes.', - }, - ], - }, - { - name: 'young_gen', - type: 'group', - description: 'Young generation occupancy and total size.', - fields: [ - { - name: 'size_kb', - type: 'integer', - description: 'Total size of young generation in kilobytes.', - }, - { - name: 'used_kb', - type: 'integer', - description: 'Young generation occupancy in kilobytes.', - }, - ], + name: 'text', + type: 'text', + norms: false, }, ], + description: 'The working directory of the process.', + example: '/home/alice', + default_field: false, }, { - 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: '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: '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: 'pe.description', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Internal description of the file, provided at compile-time.', + example: 'Paint', + default_field: false, }, - ], - }, - ], - }, - { - key: 'haproxy', - title: 'haproxy', - description: 'haproxy Module', - fields: [ - { - name: 'haproxy', - type: 'group', - description: '', - fields: [ { - name: 'frontend_name', - description: - 'Name of the frontend (or listener) which received and processed the connection.', + 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: 'backend_name', - description: - 'Name of the backend (or listener) which was selected to manage the connection to the server.', + 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: 'server_name', - description: 'Name of the last server to which the connection was sent.', + 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: 'total_waiting_time_ms', - description: 'Total time in milliseconds spent waiting in the various queues', + name: 'pgid', + level: 'extended', type: 'long', + format: 'string', + description: 'Identifier of the group of processes the process belongs to.', }, { - name: 'connection_wait_time_ms', - description: - 'Total time in milliseconds spent waiting for the connection to establish to the final server', + name: 'pid', + level: 'core', type: 'long', + format: 'string', + description: 'Process id.', + example: 4242, }, { - name: 'bytes_read', - description: 'Total number of bytes transmitted to the client when the log is emitted.', + name: 'ppid', + level: 'extended', type: 'long', + format: 'string', + description: "Parent process' pid.", + example: 4241, }, { - name: 'time_queue', - description: 'Total time in milliseconds spent waiting in the various queues.', - type: 'long', + name: 'start', + level: 'extended', + type: 'date', + description: 'The time the process started.', + example: '2016-05-23T08:05:34.853Z', }, { - name: 'time_backend_connect', - description: - 'Total time in milliseconds spent waiting for the connection to establish to the final server, including retries.', + name: 'thread.id', + level: 'extended', type: 'long', + format: 'string', + description: 'Thread ID.', + example: 4242, }, { - name: 'server_queue', - description: - 'Total number of requests which were processed before this one in the server queue.', - type: 'long', + name: 'thread.name', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Thread name.', + example: 'thread-0', }, { - 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', + name: 'title', + level: 'extended', type: 'keyword', - description: 'The HAProxy source of the log', + 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: 'termination_state', - description: 'Condition the session was in when the session ended.', + name: 'uptime', + level: 'extended', + type: 'long', + description: 'Seconds the process has been up.', + example: 1325, }, { - name: 'mode', + name: 'working_directory', + level: 'extended', 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', - }, + ignore_above: 1024, + multi_fields: [ { - name: 'retries', - description: - 'Number of connection retries experienced by this session when trying to connect to the server.', - type: 'long', + 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: '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: '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: 'process_name', - type: 'alias', - path: 'process.name', - migration: true, + 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: 'pid', - type: 'alias', - path: 'process.pid', - migration: true, + name: 'data.type', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Standard registry type for encoding contents', + example: 'REG_SZ', + default_field: false, }, { - 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: 'hive', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Abbreviated name for the hive.', + example: 'HKLM', + default_field: false, }, { - 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.', - 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: '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: '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.', - }, - { - name: 'captured_headers', - description: - 'List of headers captured in the response due to the presence of the "capture response header" statement in the frontend.', - 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.', - }, - { - name: 'captured_headers', - description: - 'List of headers captured in the request due to the presence of the "capture request header" statement in the frontend.', - 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: '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: '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', - }, - ], + name: 'value', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Name of the value written.', + example: 'Debugger', + default_field: false, }, ], }, - ], - }, - { - key: 'icinga', - title: 'Icinga', - description: 'Icinga Module', - fields: [ { - name: 'icinga', + 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', - description: '', fields: [ { - name: 'debug', - type: 'group', - description: 'Contains fields for the Icinga debug logs.', - fields: [ - { - name: 'facility', - type: 'keyword', - description: 'Specifies what component of Icinga logged the message.', - }, - { - name: 'severity', - type: 'alias', - path: 'log.level', - migration: true, - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - ], + 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: 'main', - type: 'group', - description: 'Contains fields for the Icinga main logs.', - fields: [ - { - name: 'facility', - type: 'keyword', - description: 'Specifies what component of Icinga logged the message.', - }, - { - name: 'severity', - type: 'alias', - path: 'log.level', - migration: true, - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - ], + name: 'ip', + level: 'extended', + type: 'ip', + description: 'All of the IPs seen on your event.', }, { - name: 'startup', - type: 'group', - description: 'Contains fields for the Icinga startup logs.', - fields: [ - { - name: 'facility', - type: 'keyword', - description: 'Specifies what component of Icinga logged the message.', - }, - { - name: 'severity', - type: 'alias', - path: 'log.level', - migration: true, - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - ], + name: 'user', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'All the user names seen on your event.', + default_field: false, }, ], }, - ], - }, - { - key: 'iis', - title: 'IIS', - description: 'Module for parsing IIS log files.', - fields: [ { - name: 'iis', + 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', - description: 'Fields from IIS log files.', fields: [ { - name: 'access', - type: 'group', - description: 'Contains fields for IIS access logs.', - fields: [ - { - name: 'sub_status', - type: 'long', - description: 'The HTTP substatus code.', - }, - { - name: 'win32_status', - type: 'long', - description: 'The Windows status code.', - }, - { - name: 'site_name', - type: 'keyword', - description: 'The site name and instance number.', - }, - { - name: 'server_name', - type: 'keyword', - description: 'The name of the server on which the log file entry was generated.', - }, - { - name: 'cookie', - type: 'keyword', - description: 'The content of the cookie sent or received, if any.', - }, - { - 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: '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: '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: 'text', + type: 'text', + norms: false, + default_field: false, }, ], + description: 'Organization name.', + example: 'Google LLC', }, { - name: 'error', - type: 'group', - description: 'Contains fields for IIS error logs.', - fields: [ - { - name: 'reason_phrase', - type: 'keyword', - description: 'The HTTP reason phrase.', - }, - { - name: 'queue_name', - type: 'keyword', - description: 'The IIS application pool name.', - }, - { - 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, - }, - ], - }, - ], + name: 'bytes', + level: 'core', + type: 'long', + format: 'bytes', + description: 'Bytes sent from the server to the client.', + example: 184, }, - ], - }, - ], - }, - { - key: 'kafka', - title: 'Kafka', - description: 'Kafka module', - fields: [ - { - name: 'kafka', - type: 'group', - description: '', - fields: [ { - name: 'log', - type: 'group', - description: 'Kafka log lines.', - 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.', - }, - { - name: 'class', - type: 'keyword', - description: 'Java class the log is coming from.', - }, - { - name: 'trace', - type: 'group', - description: 'Trace in the log line.', - fields: [ - { - name: 'class', - type: 'keyword', - description: 'Java class the trace is coming from.', - }, - { - name: 'message', - type: 'text', - description: 'Message part of the trace.', - }, - ], - }, - ], + name: 'domain', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Server domain.', }, - ], - }, - ], - }, - { - key: 'kibana', - title: 'kibana', - description: 'kibana Module', - fields: [ - { - name: 'kibana', - type: 'group', - description: '', - fields: [ { - name: 'log', - type: 'group', - description: 'Kafka log lines.', - fields: [ - { - name: 'tags', - type: 'keyword', - description: 'Kibana logging tags.', - }, - { - name: 'state', - type: 'keyword', - description: 'Current state of Kibana.', - }, - { - 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: '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: 'kibana.log.meta.statusCode', - type: 'alias', - path: 'http.response.status_code', - migration: true, + 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: 'kibana.log.meta.method', - type: 'alias', - path: 'http.request.method', - migration: true, + name: 'text', + type: 'text', + norms: false, + default_field: false, }, ], + description: 'Short name or login of the user.', + example: 'albert', }, ], }, - ], - }, - { - key: 'logstash', - title: 'logstash', - description: 'logstash Module', - fields: [ { - name: 'logstash', + 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', - description: '', fields: [ { - name: 'log', - title: 'Logstash', - type: 'group', - description: 'Fields from the Logstash logs.', - fields: [ - { - name: 'module', - type: 'keyword', - description: 'The module or class where the event originate.', - }, - { - name: 'thread', - type: 'keyword', - description: 'Information about the running thread where the log originate.', - multi_fields: [ - { - name: 'text', - type: 'text', - }, - ], - }, - { - name: 'log_event', - type: 'object', - description: 'key and value debugging information.', - }, - { - name: 'message', - type: 'alias', - path: 'message', - migration: true, - }, - { - name: 'level', - type: 'alias', - path: 'log.level', - migration: true, - }, - ], + 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: 'slowlog', - type: 'group', - description: 'slowlog', - fields: [ - { - name: 'module', - type: 'keyword', - description: 'The module or class where the event originate.', - }, - { - name: 'thread', - type: 'keyword', - description: 'Information about the running thread where the log originate.', - multi_fields: [ - { - name: 'text', - type: 'text', - }, - ], - }, - { - name: 'event', - type: 'keyword', - description: 'Raw dump of the original event', - multi_fields: [ - { - name: 'text', - type: 'text', - }, - ], - }, - { - name: 'plugin_name', - type: 'keyword', - description: 'Name of the plugin', - }, - { - name: 'plugin_type', - type: 'keyword', - description: 'Type of the plugin: Inputs, Filters, Outputs or Codecs.', - }, - { - name: 'took_in_millis', - type: 'long', - description: 'Execution time for the plugin in milliseconds.', - }, - { - name: 'plugin_params', - type: 'keyword', - description: 'String value of the plugin configuration', - multi_fields: [ - { - name: 'text', - type: 'text', - }, - ], - }, - { - name: 'plugin_params_object', - type: 'object', - description: 'key -> value of the configuration used by the plugin.', - }, - { - name: 'level', - type: 'alias', - path: 'log.level', - migration: true, - }, - { - name: 'took_in_nanos', - type: 'alias', - path: 'event.duration', - migration: true, - }, - ], + 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', }, ], }, - ], - }, - { - key: 'mongodb', - title: 'mongodb', - description: 'Module for parsing MongoDB log files.', - fields: [ { - name: 'mongodb', + 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', - description: 'Fields from MongoDB logs.', fields: [ { - name: 'log', - type: 'group', - description: 'Contains fields from MongoDB logs.', - fields: [ - { - name: 'component', - description: 'Functional categorization of message', - example: 'COMMAND', - type: 'keyword', - }, - { - name: 'context', - description: 'Context of message', - example: 'initandlisten', - type: 'keyword', - }, - { - name: 'severity', - type: 'alias', - path: 'log.level', - migration: true, - }, + 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: 'message', - type: 'alias', - path: 'message', - migration: true, + name: 'text', + type: 'text', + norms: false, + default_field: false, }, ], + description: 'Organization name.', + example: 'Google LLC', }, - ], - }, - ], - }, - { - key: 'mysql', - title: 'MySQL', - description: 'Module for parsing the MySQL log files.', - short_config: true, - fields: [ - { - name: 'mysql', - type: 'group', - description: 'Fields from the MySQL log files.', - fields: [ { - name: 'thread_id', + name: 'bytes', + level: 'core', type: 'long', - description: 'The connection or thread ID for the query.', + format: 'bytes', + description: 'Bytes sent from the source to the destination.', + example: 184, }, { - name: 'error', - type: 'group', - description: 'Contains fields from the MySQL error logs.', - fields: [ - { - name: 'thread_id', - type: 'alias', - path: 'mysql.thread_id', - migration: true, - }, - { - name: 'level', - type: 'alias', - path: 'log.level', - migration: true, - }, + 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: 'message', - type: 'alias', - path: 'message', - migration: true, + name: 'text', + type: 'text', + norms: false, + default_field: false, }, ], + description: "User's full name, if available.", + example: 'Albert Einstein', }, { - name: 'slowlog', - type: 'group', - description: 'Contains fields from the MySQL slow logs.', - 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.', - }, - { - name: 'rows_sent', - type: 'long', - description: 'The number of rows returned by the query.', - }, - { - name: 'rows_examined', - type: 'long', - description: 'The number of rows scanned by the query.', - }, - { - name: 'rows_affected', - type: 'long', - description: 'The number of rows modified by the query.', - }, - { - name: 'bytes_sent', - type: 'long', - format: 'bytes', - description: 'The size of the query result.', - }, - { - name: 'query', - description: 'The slow query.', - }, - { - name: 'id', - type: 'alias', - path: 'mysql.thread_id', - migration: true, - }, - { - name: 'schema', - type: 'keyword', - description: 'The schema where the slow query was executed.', - }, - { - name: 'current_user', - type: 'keyword', - description: - 'Current authenticated user, used to determine access privileges. Can differ from the value for user.', - }, - { - name: 'last_errno', - type: 'keyword', - description: 'Last SQL error seen.', - }, - { - name: 'killed', - type: 'keyword', - description: 'Code of the reason if the query was killed.', - }, - { - name: 'query_cache_hit', - type: 'boolean', - description: 'Whether the query cache was hit.', - }, - { - name: 'tmp_table', - type: 'boolean', - description: 'Whether a temporary table was used to resolve the query.', - }, - { - name: 'tmp_table_on_disk', - type: 'boolean', - description: 'Whether the query needed temporary tables on disk.', - }, - { - name: 'tmp_tables', - type: 'long', - description: 'Number of temporary tables created for this query', - }, - { - name: 'tmp_disk_tables', - type: 'long', - description: 'Number of temporary tables created on disk for this query.', - }, - { - 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.', - }, - { - name: 'filesort_on_disk', - type: 'boolean', - description: - 'Whether filesort optimization was used and it needed temporary tables on disk.', - }, - { - name: 'priority_queue', - type: 'boolean', - description: 'Whether a priority queue was used for filesort.', - }, - { - name: 'full_scan', - type: 'boolean', - description: 'Whether a full table scan was needed for the slow query.', - }, - { - name: 'full_join', - type: 'boolean', - description: - 'Whether a full join was needed for the slow query (no indexes were used for joins).', - }, - { - name: 'merge_passes', - type: 'long', - description: 'Number of merge passes executed for the query.', - }, - { - 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.', - }, - { - 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.', - }, - { - name: 'innodb', - type: 'group', - description: 'Contains fields relative to InnoDB engine', - fields: [ - { - name: 'trx_id', - type: 'keyword', - description: 'Transaction ID', - }, - { - name: 'io_r_ops', - type: 'long', - description: 'Number of page read operations.', - }, - { - name: 'io_r_bytes', - type: 'long', - format: 'bytes', - description: 'Bytes read during page read operations.', - }, - { - name: 'io_r_wait.sec', - type: 'long', - description: 'How long it took to read all needed data from storage.', - }, - { - name: 'rec_lock_wait.sec', - type: 'long', - description: 'How long the query waited for locks.', - }, - { - 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.', - }, - { - name: 'pages_distinct', - type: 'long', - description: 'Approximated count of pages accessed to execute the query.', - }, - ], - }, - { - name: 'user', - type: 'alias', - path: 'user.name', - migration: true, - }, - { - name: 'host', - type: 'alias', - path: 'source.domain', - migration: true, - }, + 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: 'ip', - type: 'alias', - path: 'source.ip', - migration: true, + name: 'text', + type: 'text', + norms: false, + default_field: false, }, ], + description: 'Short name or login of the user.', + example: 'albert', }, ], }, - ], - }, - { - key: 'nats', - title: 'nats', - description: 'Module for parsing NATS log files.', - release: 'beta', - fields: [ { - name: 'nats', + 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', - description: 'Fields from NATS logs.', fields: [ { - name: 'log', - type: 'group', - description: 'Nats log files', - release: 'beta', + 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/', }, ], }, - ], - }, - { - key: 'nginx', - title: 'Nginx', - description: 'Module for parsing the Nginx log files.', - short_config: true, - fields: [ { - name: 'nginx', + 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', - description: 'Fields from the Nginx log files.', fields: [ { - name: 'access', - type: 'group', - description: 'Contains fields for the Nginx access logs.', - 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`.', - }, - { - name: 'body_sent.bytes', - type: 'alias', - path: 'http.response.body.bytes', - migration: true, - }, - { - name: 'remote_ip', - type: 'alias', - path: 'source.ip', - 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: '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: 'error', - type: 'group', - description: 'Contains fields for the Nginx error logs.', - fields: [ - { - name: 'connection_id', - type: 'long', - description: 'Connection identifier.', - }, - { - 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: '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, }, - ], - }, - ], - }, - { - key: 'osquery', - title: 'Osquery', - description: 'Fields exported by the `osquery` module', - fields: [ - { - name: 'osquery', - type: 'group', - description: '', - fields: [ { - name: 'result', - type: 'group', - description: 'Common fields exported by the result metricset.', - fields: [ - { - name: 'name', - type: 'keyword', - description: 'The name of the query that generated this event.', - }, - { - 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".', - }, - { - name: 'host_identifier', - type: 'keyword', - description: - 'The identifier for the host on which the osquery agent is running. Normally the hostname.', - }, - { - name: 'unix_time', - type: 'long', - description: - 'Unix timestamp of the event, in seconds since the epoch. Used for computing the `@timestamp` column.', - }, - { - name: 'calendar_time', - type: 'keyword', - description: - 'String representation of the collection time, as formatted by osquery.', - }, - ], + 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, }, - ], - }, - ], - }, - { - key: 'postgresql', - title: 'PostgreSQL', - description: 'Module for parsing the PostgreSQL log files.', - short_config: true, - fields: [ - { - name: 'postgresql', - type: 'group', - description: 'Fields from PostgreSQL logs.', - fields: [ { - name: 'log', - type: 'group', - description: 'Fields from the PostgreSQL log files.', - fields: [ - { - name: 'timestamp', - description: 'The timestamp from the log line.', - }, - { - name: 'core_id', - type: 'long', - description: 'Core id', - }, - { - name: 'database', - example: 'mydb', - description: 'Name of database', - }, - { - name: 'query', - example: 'SELECT * FROM users;', - description: 'Query statement.', - }, - { - 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, - }, + 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, }, ], }, - ], - }, - { - key: 'redis', - title: 'Redis', - description: 'Redis Module', - fields: [ { - name: 'redis', + 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', - description: '', fields: [ { - name: 'log', - type: 'group', - description: 'Redis log files', - 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`.', - }, - { - 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: '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: 'slowlog', - type: 'group', - description: 'Slow logs are retrieved from Redis via a network connection.', - fields: [ - { - name: 'cmd', - type: 'keyword', - description: 'The command executed.', - }, - { - name: 'duration.us', - type: 'long', - description: 'How long it took to execute the command in microseconds.', - }, - { - name: 'id', - type: 'long', - description: 'The ID of the query.', - }, - { - name: 'key', - type: 'keyword', - description: 'The key on which the command was executed.', - }, - { - name: 'args', - type: 'keyword', - description: 'The arguments with which the command was called.', - }, - ], + 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', }, ], }, - ], - }, - { - key: 'santa', - title: 'Google Santa', - description: 'Santa Module', - fields: [ { - name: 'santa', + 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', - description: '', fields: [ { - name: 'action', + name: 'domain', + level: 'extended', type: 'keyword', - example: 'EXEC', - description: 'Action', + 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: 'decision', + name: 'extension', + level: 'extended', type: 'keyword', - example: 'ALLOW', - description: 'Decision that santad took.', + 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: 'reason', + name: 'fragment', + level: 'extended', type: 'keyword', - example: 'CERT', - description: 'Reason for the decsision.', + ignore_above: 1024, + description: + 'Portion of the url after the `#`, such as "top".\n\nThe `#` is not part of the fragment.', }, { - name: 'mode', + name: 'full', + level: 'extended', type: 'keyword', - example: 'M', - description: 'Operating mode of Santa.', - }, - { - name: 'disk', - type: 'group', - description: 'Fields for DISKAPPEAR actions.', - fields: [ + ignore_above: 1024, + multi_fields: [ { - name: 'volume', - description: 'The volume name.', + 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: 'bus', - description: 'The disk bus protocol.', + name: 'text', + type: 'text', + norms: false, + default_field: false, }, - { - name: 'serial', - description: 'The disk serial number.', + ], + 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: 'bsdname', - example: 'disk1s3', - description: 'The disk BSD name.', + 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: 'model', - example: 'APPLE SSD SM0512L', - description: 'The disk model.', + 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: 'fs', - example: 'apfs', - description: 'The disk volume kind (filesystem type).', + 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: 'mount', - description: 'The disk volume path.', + 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: 'certificate.common_name', - type: 'keyword', - description: 'Common name from code signing certificate.', - }, - { - name: 'certificate.sha256', - type: 'keyword', - description: 'SHA256 hash of code signing certificate.', - }, - { - name: 'hash.sha256', - type: 'keyword', - description: 'Hash of process executable.', + 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, + }, + ], }, - ], - }, - { - key: 'system', - title: 'System', - description: 'Module for parsing system log files.', - short_config: true, - fields: [ { - name: 'system', + name: 'vulnerability', + title: 'Vulnerability', + group: 2, + description: + 'The vulnerability fields describe information about a vulnerability\nthat is relevant to an event.', type: 'group', - description: 'Fields from the system log files.', fields: [ { - name: 'auth', - type: 'group', - description: 'Fields from the Linux authorization logs.', - 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: '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".', - }, - { - name: 'signature', - description: 'The signature of the client public key.', - }, - { - name: 'dropped_ip', - type: 'ip', - description: - 'The client IP from SSH connections that are open and immediately dropped.', - }, - { - name: 'event', - type: 'alias', - path: 'event.action', - migration: true, - }, - { - 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.', - fields: [ - { - name: 'error', - example: 'user NOT in sudoers', - description: 'The error message in case the sudo command failed.', - }, - { - name: 'tty', - description: 'The TTY where the sudo command is executed.', - }, - { - name: 'pwd', - description: 'The current directory where the sudo command is executed.', - }, - { - name: 'user', - example: 'root', - description: 'The target user to which the sudo command is switching.', - }, - { - name: 'command', - description: 'The command executed via sudo.', - }, - ], - }, - { - name: 'useradd', - type: 'group', - description: 'Fields specific to events created by the `useradd` command.', - 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.', - fields: [ - { - name: 'name', - type: 'alias', - path: 'group.name', - migration: true, - }, - { - name: 'gid', - type: 'alias', - path: 'group.id', - migration: true, - }, - ], + 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: 'syslog', - type: 'group', - description: 'Contains fields from the syslog system logs.', - 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: '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, }, - ], - }, - ], - }, - { - key: 'traefik', - title: 'Traefik', - description: 'Module for parsing the Traefik log files.', - fields: [ - { - name: 'traefik', - type: 'group', - description: 'Fields from the Traefik log files.', - fields: [ { - name: 'access', - type: 'group', - description: 'Contains fields for the Traefik access logs.', - fields: [ - { - name: 'user_identifier', - type: 'keyword', - description: 'Is the RFC 1413 identity of the client', - }, - { - name: 'request_count', - type: 'long', - description: 'The number of requests', - }, - { - name: 'frontend_name', - type: 'keyword', - description: 'The name of the frontend used', - }, - { - 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: 'iptables', - title: 'iptables', - description: 'Module for handling the iptables logs.', - fields: [ - { - name: 'iptables', - type: 'group', - description: 'Fields from the iptables logs.', - fields: [ - { - name: 'ether_type', - type: 'long', - description: 'Value of the ethernet type field identifying the network layer protocol.', - }, - { - name: 'flow_label', - type: 'integer', - description: 'IPv6 flow label.', - }, - { - name: 'fragment_flags', + name: 'id', + level: 'extended', type: 'keyword', - description: 'IP fragment flags. A combination of CE, DF and MF.', - }, - { - name: 'fragment_offset', - type: 'long', - description: 'Offset of the current IP fragment.', - }, - { - name: 'icmp', - type: 'group', - description: 'ICMP fields.', - fields: [ - { - name: 'code', - type: 'long', - description: 'ICMP code.', - }, - { - name: 'id', - type: 'long', - description: 'ICMP ID.', - }, - { - name: 'parameter', - type: 'long', - description: 'ICMP parameter.', - }, - { - name: 'redirect', - type: 'ip', - description: 'ICMP redirect address.', - }, - { - name: 'seq', - type: 'long', - description: 'ICMP sequence number.', - }, - { - name: 'type', - type: 'long', - description: 'ICMP type.', - }, - ], + 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: 'id', - type: 'long', - description: 'Packet identifier.', + 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: 'incomplete_bytes', - type: 'long', - description: 'Number of incomplete bytes.', + name: 'report_id', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'The report or scan identification number.', + example: 20191018.0001, + default_field: false, }, { - name: 'input_device', + name: 'scanner.vendor', + level: 'extended', type: 'keyword', - description: 'Device that received the packet.', + ignore_above: 1024, + description: 'The name of the vulnerability scanner vendor.', + example: 'Tenable', + default_field: false, }, { - name: 'precedence_bits', - type: 'short', - description: 'IP precedence bits.', + 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: 'tos', - type: 'long', - description: 'IP Type of Service field.', + 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: 'length', - type: 'long', - description: 'Packet length.', + 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: 'output_device', + name: 'score.version', + level: 'extended', type: 'keyword', - description: 'Device that output the packet.', - }, - { - name: 'tcp', - type: 'group', - description: 'TCP fields.', - fields: [ - { - name: 'flags', - type: 'keyword', - description: 'TCP flags.', - }, - { - name: 'reserved_bits', - type: 'short', - description: 'TCP reserved bits.', - }, - { - name: 'seq', - type: 'long', - description: 'TCP sequence number.', - }, - { - name: 'ack', - type: 'long', - description: 'TCP Acknowledgment number.', - }, - { - name: 'window', - type: 'long', - description: 'Advertised TCP window size.', - }, - ], - }, - { - name: 'ttl', - type: 'integer', - description: 'Time To Live field.', + 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: 'udp', - type: 'group', - description: 'UDP fields.', - fields: [ - { - name: 'length', - type: 'long', - description: 'Length of the UDP header and payload.', - }, - ], + 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, }, - { - name: 'ubiquiti', - type: 'group', - description: 'Fields for Ubiquiti network devices.', - fields: [ - { - name: 'input_zone', - type: 'keyword', - description: 'Input zone.', - }, - { - name: 'output_zone', - type: 'keyword', - description: 'Output zone.', - }, - { - name: 'rule_number', - type: 'keyword', - description: 'The rule number within the rule set.', - }, - { - name: 'rule_set', - type: 'keyword', - description: 'The rule set name.', - }, - ], + ], + }, + ], + }, + { + 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: '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.', + 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: 'suricata', - title: 'Suricata', - description: 'Module for handling the EVE JSON logs produced by Suricata.', + key: 'kubernetes', + title: 'Kubernetes', + description: 'Kubernetes metadata added by the kubernetes processor\n', + short_config: false, + anchor: 'kubernetes-processor', fields: [ { - name: 'suricata', + name: 'kubernetes', type: 'group', - description: 'Fields from the Suricata EVE log file.', fields: [ { - name: 'eve', - type: 'group', - description: 'Fields exported by the EVE JSON logs', - 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: '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: 'flow_id', + name: 'full_name', type: 'keyword', }, - { - name: 'email', - type: 'group', - fields: [ - { - name: 'status', - type: 'keyword', - }, - ], - }, - { - name: 'dest_ip', - type: 'alias', - path: 'destination.ip', + ], + }, + ], + }, + { + 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: 'icmp_code', - type: 'long', + name: 'ssl.protocol', + type: 'alias', + path: 'apache.access.ssl.protocol', + migration: true, }, { - name: 'http', + 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: 'status', + name: 'device', type: 'alias', - path: 'http.response.status_code', + path: 'user_agent.device.name', + migration: true, }, { - name: 'redirect', - type: 'keyword', + name: 'name', + type: 'alias', + path: 'user_agent.name', + migration: true, }, { - name: 'http_user_agent', + name: 'os', type: 'alias', - path: 'user_agent.original', + path: 'user_agent.os.full_name', + migration: true, }, { - name: 'protocol', - type: 'keyword', + name: 'os_name', + type: 'alias', + path: 'user_agent.os.name', + migration: true, }, { - name: 'http_refer', + name: 'original', type: 'alias', - path: 'http.request.referrer', + path: 'user_agent.original', + migration: true, + }, + ], + }, + { + name: 'geoip', + type: 'group', + fields: [ + { + name: 'continent_name', + type: 'alias', + path: 'source.geo.continent_name', + migration: true, }, { - name: 'url', + name: 'country_iso_code', type: 'alias', - path: 'url.original', + path: 'source.geo.country_iso_code', + migration: true, }, { - name: 'hostname', + name: 'location', type: 'alias', - path: 'url.domain', + path: 'source.geo.location', + migration: true, }, { - name: 'length', + name: 'region_name', type: 'alias', - path: 'http.response.body.bytes', + path: 'source.geo.region_name', + migration: true, }, { - name: 'http_method', + name: 'city_name', type: 'alias', - path: 'http.request.method', + path: 'source.geo.city_name', + migration: true, }, { - name: 'http_content_type', - type: 'keyword', + name: 'region_iso_code', + type: 'alias', + path: 'source.geo.region_iso_code', + migration: true, }, ], }, + ], + }, + { + name: 'error', + type: 'group', + fields: [ { - name: 'timestamp', + name: 'level', type: 'alias', - path: '@timestamp', + path: 'log.level', + migration: true, }, { - name: 'in_iface', - type: 'keyword', + name: 'message', + type: 'alias', + path: 'message', + migration: true, }, { - 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: 'pid', + type: 'alias', + path: 'process.pid', + migration: true, }, { - 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: 'tid', + type: 'alias', + path: 'process.thread.id', + migration: true, }, { - 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', + 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: 'segment_memcap_drop', + name: 'avg_pkt_size', type: 'long', }, { - name: 'overlap', + name: 'bytes', type: 'long', }, { - name: 'insert_list_fail', + name: 'tcp', type: 'long', }, { - name: 'rst', + name: 'raw', type: 'long', }, { - name: 'stream_depth_reached', + name: 'ppp', type: 'long', }, { - name: 'reassembly_memuse', + name: 'vlan_qinq', type: 'long', }, { - name: 'reassembly_gap', + name: 'null', type: 'long', }, { - name: 'overlap_diff_data', + 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: 'no_flow', - 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: '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: '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: 'dns', - type: 'group', - fields: [ - { - name: 'memcap_global', - type: 'long', - }, - { - name: 'memcap_state', - type: 'long', - }, - { - name: 'memuse', - type: 'long', - }, - ], + 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: '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: 'passive', + type: 'boolean', + description: 'Whether PASV mode is toggled for control channel.\n', }, { - 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: '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: 'tls', + 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: 'notbefore', - type: 'date', + name: 'indicator', + type: 'keyword', + description: 'The intelligence indicator.\n', }, { - name: 'issuerdn', + name: 'indicator_type', type: 'keyword', + description: 'The type of data the indicator represents.\n', }, { - name: 'sni', + name: 'host', type: 'keyword', + description: + 'If the indicator type was Intel::ADDR, then this field will be present.\n', }, { - name: 'version', + 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: 'session_resumed', - type: 'boolean', + name: 'where', + type: 'keyword', + description: 'Where the data was discovered.\n', }, { - name: 'fingerprint', + name: 'node', type: 'keyword', + description: 'The name of the node where the match was discovered.\n', }, { - name: 'serial', + 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: 'notafter', - type: 'date', + 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: 'subject', + 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: 'app_proto_ts', + name: 'matched', type: 'keyword', + description: + 'Event to represent a match in the intelligence data from data that was seen.\n', }, { - name: 'flow', + 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: 'bytes_toclient', - type: 'alias', - path: 'destination.bytes', + 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: 'start', - type: 'alias', - path: 'event.start', + 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: 'pkts_toclient', - type: 'alias', - path: 'destination.packets', + name: 'code', + type: 'integer', + description: 'Error code.\n', }, { - name: 'age', - type: 'long', + name: 'msg', + type: 'keyword', + description: 'Error message.\n', }, + ], + }, + { + name: 'valid', + type: 'group', + fields: [ { - name: 'state', - type: 'keyword', + name: 'from', + type: 'date', + description: 'Ticket valid from.\n', }, { - name: 'bytes_toserver', - type: 'alias', - path: 'source.bytes', + name: 'until', + type: 'date', + description: 'Ticket valid until.\n', }, { - name: 'reason', + 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: 'pkts_toserver', - type: 'alias', - path: 'source.packets', + name: 'new', + type: 'keyword', + description: 'Hash of ticket returned by the KDC.\n', }, + ], + }, + { + name: 'cert', + type: 'group', + fields: [ { - name: 'end', - type: 'date', + 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: 'alerted', - type: 'boolean', + 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: 'app_proto', - type: 'alias', - path: 'network.protocol', + name: 'function', + type: 'keyword', + description: 'The name of the function message that was sent.\n', }, { - name: 'tx_id', - type: 'long', + 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: 'app_proto_tc', + name: 'arg', type: 'keyword', + description: 'The argument issued to the command.\n', }, { - name: 'smtp', - type: 'group', - fields: [ - { - name: 'rcpt_to', - type: 'keyword', - }, - { - name: 'mail_from', - type: 'keyword', - }, - { - name: 'helo', - type: 'keyword', - }, - ], + name: 'success', + type: 'boolean', + description: 'Whether the command succeeded.\n', }, { - name: 'app_proto_expected', - type: 'keyword', + name: 'rows', + type: 'integer', + description: 'The number of affected rows, if any.\n', }, { - name: 'flags', - type: 'group', + name: 'response', + type: 'keyword', + description: 'Server message, if any.\n', }, ], }, - ], - }, - ], - }, - { - key: 'zeek', - title: 'Zeek', - description: 'Module for handling logs produced by Zeek/Bro', - fields: [ - { - name: 'zeek', - type: 'group', - description: 'Fields from Zeek/Bro logs after normalization', - fields: [ - { - name: 'session_id', - type: 'keyword', - }, - { - name: 'connection.local_orig', - type: 'boolean', - }, - { - name: 'connection.local_resp', - type: 'boolean', - }, - { - name: 'connection.missed_bytes', - type: 'long', - }, - { - name: 'connection.state', - type: 'keyword', - }, - { - name: 'connection.history', - type: 'keyword', - }, - { - name: 'connection.orig_l2_addr', - type: 'keyword', - }, - { - name: 'resp_l2_addr', - type: 'keyword', - }, - { - name: 'vlan', - type: 'keyword', - }, - { - name: 'inner_vlan', - type: 'keyword', - }, - { - name: 'dns.trans_id', - type: 'integer', - }, - { - name: 'dns.rtt', - type: 'double', - }, - { - name: 'dns.query', - type: 'keyword', - }, - { - name: 'dns.qclass', - type: 'long', - }, - { - name: 'dns.qclass_name', - type: 'keyword', - }, - { - name: 'dns.qtype', - type: 'long', - }, - { - name: 'dns.qtype_name', - type: 'keyword', - }, - { - name: 'dns.rcode', - type: 'long', - }, - { - name: 'dns.rcode_name', - type: 'keyword', - }, - { - name: 'dns.AA', - type: 'boolean', - }, - { - name: 'dns.TC', - type: 'boolean', - }, - { - name: 'dns.RD', - type: 'boolean', - }, - { - name: 'dns.RA', - type: 'boolean', - }, - { - name: 'dns.answers', - type: 'keyword', - }, - { - name: 'dns.TTLs', - type: 'double', - }, - { - name: 'dns.rejected', - type: 'boolean', - }, - { - name: 'dns.total_answers', - type: 'integer', - }, - { - name: 'dns.total_replies', - type: 'integer', - }, - { - name: 'dns.saw_query', - type: 'boolean', - }, - { - name: 'dns.saw_reply', - type: 'boolean', - }, { - name: 'http.trans_depth', - type: 'integer', - }, - { - name: 'http.status_msg', - type: 'keyword', - }, - { - name: 'http.info_code', - type: 'integer', - }, - { - name: 'http.info_msg', - type: 'keyword', - }, - { - name: 'http.filename', - type: 'keyword', - }, - { - name: 'http.tags', - type: 'keyword', - }, - { - name: 'http.captured_password', - type: 'boolean', - }, - { - name: 'http.proxied', - type: 'keyword', - }, - { - name: 'http.range_request', - type: 'boolean', - }, - { - name: 'http.client_header_names', - type: 'keyword', - }, - { - name: 'http.server_header_names', - type: 'keyword', - }, - { - name: 'http.orig_fuids', - type: 'keyword', - }, - { - name: 'http.orig_mime_types', - type: 'keyword', - }, - { - name: 'http.orig_filenames', - type: 'keyword', - }, - { - name: 'http.resp_fuids', - type: 'keyword', - }, - { - name: 'http.resp_mime_types', - type: 'keyword', - }, - { - name: 'http.resp_filenames', - type: 'keyword', - }, - { - name: 'http.orig_mime_depth', - type: 'integer', - }, - { - name: 'http.resp_mime_depth', - type: 'integer', - }, - { - name: 'files.fuid', - type: 'keyword', - }, - { - name: 'files.tx_host', - type: 'ip', - }, - { - name: 'files.rx_host', - type: 'ip', - }, - { - name: 'files.session_ids', - type: 'keyword', - }, - { - name: 'files.source', - type: 'keyword', - }, - { - name: 'files.depth', - type: 'long', - }, - { - name: 'files.direction', - type: 'keyword', - }, - { - name: 'files.analyzers', - type: 'keyword', - }, - { - name: 'files.mime_type', - type: 'keyword', - }, - { - name: 'files.filename', - type: 'keyword', - }, - { - name: 'files.local_orig', - type: 'boolean', - }, - { - name: 'files.is_orig', - type: 'boolean', - }, - { - name: 'files.duration', - type: 'double', - }, - { - name: 'files.seen_bytes', - type: 'long', - }, - { - name: 'files.total_bytes', - type: 'long', - }, - { - name: 'files.missing_bytes', - type: 'long', - }, - { - name: 'files.overflow_bytes', - type: 'long', - }, - { - name: 'files.timedout', - type: 'boolean', - }, - { - name: 'files.parent_fuid', - type: 'keyword', - }, - { - name: 'files.md5', - type: 'keyword', - }, - { - name: 'files.sha1', - type: 'keyword', - }, - { - name: 'files.sha256', - type: 'keyword', - }, - { - name: 'files.extracted', - type: 'keyword', + 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: 'files.extracted_cutoff', - type: 'boolean', + 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: 'files.extracted_size', - type: 'long', + 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: 'files.entropy', - type: 'double', + 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: 'ssl.version', - type: 'keyword', + 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: 'ssl.cipher', - type: 'keyword', + 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: 'ssl.curve', - type: 'keyword', + 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: 'ssl.server_name', - type: 'keyword', + 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: 'ssl.resumed', - type: 'boolean', + 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: 'ssl.next_protocol', - type: 'keyword', + 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: 'ssl.established', - type: 'boolean', + 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: 'ssl.cert_chain', - type: 'keyword', + 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: 'ssl.cert_chain_fuids', - type: 'keyword', + 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: 'ssl.client_cert_chain', - type: 'keyword', + 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: 'ssl.client_cert_chain_fuids', - type: 'keyword', + 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.issuer', - type: 'keyword', + 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: 'ssl.client_issuer', - type: 'keyword', + 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: 'ssl.validation_status', - type: 'keyword', + 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: 'ssl.subject', - type: 'keyword', + 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: 'ssl.client_subject', - type: 'keyword', + 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: 'ssl.last_alert', - type: 'keyword', + 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', + }, + ], }, ], }, @@ -7293,47 +18360,47 @@ export const filebeatSchema: Schema = [ { key: 'netflow', title: 'NetFlow', - description: 'Fields from NetFlow and IPFIX flows.', + description: 'Fields from NetFlow and IPFIX flows.\n', fields: [ { name: 'netflow', type: 'group', - description: 'Fields from NetFlow and IPFIX.', + description: 'Fields from NetFlow and IPFIX.\n', fields: [ { name: 'type', type: 'keyword', - description: 'The type of NetFlow record described by this event.', + 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.', + 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. ", + description: "Exporter's network address in IP:port format.\n", }, { name: 'source_id', type: 'long', - description: 'Observation domain ID to which this record belongs.', + description: 'Observation domain ID to which this record belongs.\n', }, { name: 'timestamp', type: 'date', - description: 'Time and date of export.', + description: 'Time and date of export.\n', }, { name: 'uptime_millis', type: 'long', - description: 'How long the exporter process has been running, in milliseconds.', + description: 'How long the exporter process has been running, in milliseconds.\n', }, { name: 'version', - type: 'long', - description: 'NetFlow version used.', + type: 'integer', + description: 'NetFlow version used.\n', }, ], }, @@ -7539,7 +18606,7 @@ export const filebeatSchema: Schema = [ }, { name: 'class_id', - type: 'short', + type: 'long', }, { name: 'minimum_ttl', @@ -8118,19 +19185,19 @@ export const filebeatSchema: Schema = [ type: 'long', }, { - name: 'post_nast_ource_ipv4_address', + name: 'post_nat_source_ipv4_address', type: 'ip', }, { - name: 'post_nadt_estination_ipv4_address', + name: 'post_nat_destination_ipv4_address', type: 'ip', }, { - name: 'post_napst_ource_transport_port', + name: 'post_napt_source_transport_port', type: 'integer', }, { - name: 'post_napdt_estination_transport_port', + name: 'post_napt_destination_transport_port', type: 'integer', }, { @@ -8342,11 +19409,11 @@ export const filebeatSchema: Schema = [ type: 'long', }, { - name: 'post_nast_ource_ipv6_address', + name: 'post_nat_source_ipv6_address', type: 'ip', }, { - name: 'post_nadt_estination_ipv6_address', + name: 'post_nat_destination_ipv6_address', type: 'ip', }, { @@ -8514,11 +19581,11 @@ export const filebeatSchema: Schema = [ type: 'long', }, { - name: 'hash_ipp_ayload_offset', + name: 'hash_ip_payload_offset', type: 'long', }, { - name: 'hash_ipp_ayload_size', + name: 'hash_ip_payload_size', type: 'long', }, { @@ -8550,11 +19617,11 @@ export const filebeatSchema: Schema = [ type: 'keyword', }, { - name: 'upper_cli_imit', + name: 'upper_ci_limit', type: 'double', }, { - name: 'lower_cli_imit', + name: 'lower_ci_limit', type: 'double', }, { @@ -8718,11 +19785,11 @@ export const filebeatSchema: Schema = [ type: 'long', }, { - name: 'distinct_count_of_sourc_eipa_ddress', + name: 'distinct_count_of_source_ip_address', type: 'long', }, { - name: 'distinct_count_of_destinatio_nipa_ddress', + name: 'distinct_count_of_destination_ip_address', type: 'long', }, { @@ -8782,11 +19849,11 @@ export const filebeatSchema: Schema = [ type: 'long', }, { - name: 'selector_itd_otal_flows_observed', + name: 'selector_id_total_flows_observed', type: 'long', }, { - name: 'selector_itd_otal_flows_selected', + name: 'selector_id_total_flows_selected', type: 'long', }, { @@ -8950,7 +20017,7 @@ export const filebeatSchema: Schema = [ type: 'short', }, { - name: 'mib_object_valuei_pa_ddress', + name: 'mib_object_value_ip_address', type: 'ip', }, { @@ -9078,7 +20145,7 @@ export const filebeatSchema: Schema = [ type: 'long', }, { - name: 'max_bieb_ntries', + name: 'max_bib_entries', type: 'long', }, { @@ -9125,4 +20192,1052 @@ export const filebeatSchema: Schema = [ }, ], }, + { + 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/legacy/plugins/siem/server/utils/beat_schema/8.0.0/packetbeat.ts b/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/packetbeat.ts index 6002c9370210e..0be2e48fe4668 100644 --- a/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/packetbeat.ts +++ b/x-pack/legacy/plugins/siem/server/utils/beat_schema/8.0.0/packetbeat.ts @@ -15,91 +15,129 @@ export const packetbeatSchema: Schema = [ { key: 'ecs', title: 'ECS', - description: 'ECS fields.', + description: 'ECS Fields.', fields: [ { name: '@timestamp', - type: 'date', level: 'core', required: true, - example: '2016-05-23T08:05:34.853Z', + type: 'date', 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.', - }, - { - name: 'tags', - level: 'core', - type: 'keyword', - example: '["production", "env2"]', - description: 'List of keywords used to tag each event.', + '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', - example: { - env: 'production', - application: 'foo-bar', - }, + object_type: 'keyword', description: - '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.', + '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', - example: 'Hello World', description: - 'For log events the message field contains the log message. In other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.', + '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 any, that collects, detects, or observes events on a host, or takes measurements on a host. Examples include Beats. Agents may also run on observers. ECS agent.* fields shall be populated with details of the agent running on the host or observer where the event happened or the measurement was taken.', + '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. For APM, it is the agent running in the app/service. The agent information does not change if data is sent through queuing systems like Kafka, Redis, or processing systems such as Logstash or APM Server.', + '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: 'version', + 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', - description: 'Version of the agent.', - example: '6.0.0-rc2', + 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: - '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.', + '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. 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.', + '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: 'id', + 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 identifier of this agent (if one exists). Example: For Beats this would be beat.id.', - example: '8a4f500d', + 'Unique number allocated to the autonomous system. The autonomous\nsystem number (ASN) uniquely identifies each network on the Internet.', + example: 15169, }, { - name: 'ephemeral_id', + name: 'organization.name', level: 'extended', type: 'keyword', - description: - 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', + ignore_above: 1024, + multi_fields: [ + { + name: 'text', + type: 'text', + norms: false, + default_field: false, + }, + ], + description: 'Organization name.', + example: 'Google LLC', }, ], }, @@ -108,2673 +146,6183 @@ export const packetbeatSchema: Schema = [ title: 'Client', group: 2, description: - 'A client is defined as the initiator of a network connection for events regarding sessions, connections, or bidirectional flow records. For TCP events, the client is the initiator of the TCP connection that sends the SYN packet(s). For other protocols, the client is generally the initiator or requestor in the network transaction. Some systems use the term "originator" to refer the client in TCP connections. The client fields describe details about the system acting as the client in the network event. Client fields are usually populated in conjunction with server fields. Client fields are generally not populated for packet-level events. Client / server representations can add semantic context to an exchange, which is helpful to visualize the data in certain situations. If your context falls in that category, you should still ensure that source and destination are filled appropriately.', + '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 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.', + '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: 'ip', - level: 'core', - type: 'ip', - description: 'IP address of the client. Can be one or multiple IPv4 or IPv6 addresses.', + 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: 'port', + 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', - description: 'Port of the client.', + format: 'bytes', + description: 'Bytes sent from the client to the server.', + example: 184, }, { - name: 'mac', + name: 'domain', level: 'core', type: 'keyword', - description: 'MAC address of the client.', + ignore_above: 1024, + description: 'Client domain.', }, { - name: 'domain', + name: 'geo.city_name', level: 'core', type: 'keyword', - description: 'Client domain.', + ignore_above: 1024, + description: 'City name.', + example: 'Montreal', }, { - name: 'bytes', + name: 'geo.continent_name', level: 'core', - type: 'long', - format: 'bytes', - example: 184, - description: 'Bytes sent from the client to the server.', + type: 'keyword', + ignore_above: 1024, + description: 'Name of the continent.', + example: 'North America', }, { - name: 'packets', + name: 'geo.country_iso_code', level: 'core', - type: 'long', - example: 12, - description: 'Packets sent from the client to the server.', + type: 'keyword', + ignore_above: 1024, + description: 'Country ISO code.', + example: 'CA', }, { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - 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.country_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Country name.', + example: 'Canada', }, - ], - }, - { - name: 'cloud', - title: 'Cloud', - group: 2, - description: 'Fields related to the cloud or infrastructure the events are coming from.', - footnote: - 'Examples: If Metricbeat is running on an EC2 host and fetches data from its host, the cloud info contains the data about this machine. If Metricbeat runs on a remote machine outside the cloud and fetches data from a service running in the cloud, the field contains cloud data from the machine the service is running on.', - type: 'group', - fields: [ { - name: 'provider', + name: 'geo.location', + level: 'core', + type: 'geo_point', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', + }, + { + name: 'geo.name', level: 'extended', - example: 'ec2', type: 'keyword', + ignore_above: 1024, description: - 'Name of the cloud provider. Example values are ec2, gce, or digitalocean.', + '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: 'availability_zone', - level: 'extended', - example: 'us-east-1c', + name: 'geo.region_iso_code', + level: 'core', type: 'keyword', - description: 'Availability zone in which this host is running.', + ignore_above: 1024, + description: 'Region ISO code.', + example: 'CA-QC', }, { - name: 'region', - level: 'extended', + name: 'geo.region_name', + level: 'core', type: 'keyword', - example: 'us-east-1', - description: 'Region in which this host is running.', + ignore_above: 1024, + description: 'Region name.', + example: 'Quebec', }, { - name: 'instance.id', - level: 'extended', - type: 'keyword', - example: 'i-1234567890abcdef0', - description: 'Instance ID of the host machine.', + name: 'ip', + level: 'core', + type: 'ip', + description: + 'IP address of the client.\n\nCan be one or multiple IPv4 or IPv6 addresses.', }, { - name: 'instance.name', - level: 'extended', + name: 'mac', + level: 'core', type: 'keyword', - description: 'Instance name of the host machine.', + ignore_above: 1024, + description: 'MAC address of the client.', }, { - name: 'machine.type', + name: 'nat.ip', level: 'extended', - type: 'keyword', - example: 't2.medium', - description: 'Machine type of the host machine.', + 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: 'account.id', + name: 'nat.port', level: 'extended', - type: 'keyword', - example: 666777888999, + type: 'long', + format: 'string', 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.', + 'Translated port of source based NAT sessions (e.g. internal client\nto internet).\n\nTypically connections traversing load balancers, firewalls, or routers.', }, - ], - }, - { - name: 'container', - title: 'Container', - group: 2, - description: - 'Container fields are used for meta information about the specific container that is the source of information. These fields help correlate data based containers from any runtime.', - type: 'group', - fields: [ { - name: 'runtime', - level: 'extended', - type: 'keyword', - description: 'Runtime managing this container.', - example: 'docker', + name: 'packets', + level: 'core', + type: 'long', + description: 'Packets sent from the client to the server.', + example: 12, }, { - name: 'id', + name: 'port', level: 'core', + type: 'long', + format: 'string', + description: 'Port of the client.', + }, + { + name: 'registered_domain', + level: 'extended', type: 'keyword', - description: 'Unique container id.', + 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: 'image.name', + name: 'top_level_domain', level: 'extended', type: 'keyword', - description: 'Name of the image the container was built on.', + 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: 'image.tag', + name: 'user.domain', level: 'extended', type: 'keyword', - description: 'Container image tag.', + 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: 'name', + name: 'user.email', level: 'extended', type: 'keyword', - description: 'Container name.', + ignore_above: 1024, + description: 'User email address.', }, { - name: 'labels', + name: 'user.full_name', level: 'extended', - type: 'object', - object_type: 'keyword', - description: 'Image labels.', + 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: 'destination', - title: 'Destination', - group: 2, - description: - 'Destination fields describe details about the destination of a packet/event. Destination fields are usually populated in conjunction with source fields.', - type: 'group', - fields: [ { - name: 'address', + name: 'user.group.domain', level: 'extended', type: 'keyword', + ignore_above: 1024, 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 of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', }, { - name: 'ip', - level: 'core', - type: 'ip', - description: - 'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.', + name: 'user.group.id', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Unique identifier for the group on the system/platform.', }, { - name: 'port', - level: 'core', - type: 'long', - description: 'Port of the destination.', + name: 'user.group.name', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Name of the group.', }, { - name: 'mac', - level: 'core', + name: 'user.hash', + level: 'extended', type: 'keyword', - description: 'MAC address of the destination.', + 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: 'domain', + name: 'user.id', level: 'core', type: 'keyword', - description: 'Destination domain.', + ignore_above: 1024, + description: 'Unique identifiers of the user.', }, { - name: 'bytes', + name: 'user.name', level: 'core', - type: 'long', - format: 'bytes', - example: 184, - description: 'Bytes sent from the destination to the source.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - example: 12, - description: 'Packets sent from the destination to the source.', - }, - { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, + type: 'keyword', + ignore_above: 1024, + multi_fields: [ { - name: 'name', - level: 'extended', - type: 'keyword', - 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: 'text', + type: 'text', + norms: false, + default_field: false, }, ], + description: 'Short name or login of the user.', + example: 'albert', }, ], }, { - name: 'ecs', - title: 'ECS', + name: 'cloud', + title: 'Cloud', group: 2, - description: 'Meta-information specific to ECS.', + 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: 'version', - level: 'core', + name: 'account.id', + level: 'extended', type: 'keyword', - required: true, + ignore_above: 1024, 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. The current version is 1.0.0-beta2 .', - example: '1.0.0-beta2', + '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: 'error', - title: 'Error', - group: 2, - description: - 'These fields can represent errors of any kind. Use them for errors that happen while fetching events or in cases where the event itself contains an error.', - type: 'group', - fields: [ { - name: 'id', - level: 'core', + name: 'availability_zone', + level: 'extended', type: 'keyword', - description: 'Unique identifier for the error.', + ignore_above: 1024, + description: 'Availability zone in which this host is running.', + example: 'us-east-1c', }, { - name: 'message', - level: 'core', - type: 'text', - description: 'Error message.', + name: 'instance.id', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Instance ID of the host machine.', + example: 'i-1234567890abcdef0', }, { - name: 'code', - level: 'core', + name: 'instance.name', + level: 'extended', type: 'keyword', - description: 'Error code describing the error.', + ignore_above: 1024, + description: 'Instance name of the host machine.', }, - ], - }, - { - name: 'event', - title: 'Event', - group: 2, - description: - 'The event fields are used for context information about the log or metric event itself. A log is defined as an event containing details of something that happened. Log events must include the time at which the thing happened. Examples of log events include a process starting on a host, a network packet being sent from a source to a destination, or a network connection between a client and a server being initiated or closed. A metric is defined as an event containing one or more numerical or categorical measurements and the time at which the measurement was taken. Examples of metric events include memory pressure measured on a host, or vulnerabilities measured on a scanned host.', - type: 'group', - fields: [ { - name: 'id', - level: 'core', + name: 'machine.type', + level: 'extended', type: 'keyword', - description: 'Unique ID to describe the event.', - example: '8a4f500d', + ignore_above: 1024, + description: 'Machine type of the host machine.', + example: 't2.medium', }, { - name: 'kind', + name: 'provider', level: 'extended', type: 'keyword', + ignore_above: 1024, description: - 'The kind of the event. This gives information about what type of information the event contains, without being specific to the contents of the event. Examples are `event`, `state`, `alarm`. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.', - example: 'state', + 'Name of the cloud provider. Example values are aws, azure, gcp,\nor digitalocean.', + example: 'aws', }, { - name: 'category', - level: 'core', + name: 'region', + level: 'extended', type: 'keyword', - description: - 'Event category. This contains high-level information about the contents of the event. It is more generic than `event.action`, in the sense that typically a category contains multiple actions. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.', - example: 'user-management', + 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: 'action', + name: 'exists', level: 'core', - type: 'keyword', - 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', + type: 'boolean', + description: 'Boolean to capture if a signature is present.', + example: 'true', + default_field: false, }, { - name: 'outcome', + name: 'status', level: 'extended', type: 'keyword', + ignore_above: 1024, description: - 'The outcome of the event. If the event describes an action, this fields contains the outcome of that action. Examples outcomes are `success` and `failure`. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.', - example: 'success', - }, - { - name: 'type', - level: 'core', - type: 'keyword', - description: 'Reserved for future usage. Please avoid using this field for user data.', + '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: 'module', + name: 'subject_name', level: 'core', type: 'keyword', - description: - 'Name of the module this data is coming from. This information is coming from the modules used in Beats or Logstash.', - example: 'mysql', + ignore_above: 1024, + description: 'Subject name of the code signer', + example: 'Microsoft Corporation', + default_field: false, }, { - name: 'dataset', - level: 'core', - type: 'keyword', + name: 'trusted', + level: 'extended', + type: 'boolean', description: - 'Name of the dataset. The concept of a `dataset` (fileset / metricset) is used in Beats as a subset of modules. It contains the information which is currently stored in metricset.name and metricset.module or fileset.name.', - example: 'stats', + '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: 'severity', - level: 'core', - type: 'long', - example: '7', + name: 'valid', + level: 'extended', + type: 'boolean', description: - "Severity describes the severity of the event. What the different severity values mean can very different between use cases. It's up to the implementer to make sure severities are consistent across events. ", + '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: 'original', + name: 'id', level: 'core', type: 'keyword', - 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', - 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`.', - index: false, - doc_values: false, + ignore_above: 1024, + description: 'Unique container id.', }, { - name: 'hash', + name: 'image.name', level: 'extended', type: 'keyword', - example: '123456789012345678901234567890ABCD', - description: - 'Hash (perhaps logstash fingerprint) of raw field to be able to demonstrate log integrity.', - }, - { - name: 'duration', - level: 'core', - type: 'long', - format: 'duration', - input_format: 'nanoseconds', - 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.', + ignore_above: 1024, + description: 'Name of the image the container was built on.', }, { - name: 'timezone', + name: 'image.tag', level: 'extended', type: 'keyword', - 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").', + ignore_above: 1024, + description: 'Container image tags.', }, { - name: 'created', - level: 'core', - type: 'date', - description: - 'event.created contains the date when the event was created. This timestamp is distinct from @timestamp in that @timestamp contains the processed timestamp. For logs these two timestamps can be different as the timestamp in the log line and when the event is read for example by Filebeat are not identical. `@timestamp` must contain the timestamp extracted from the log line, event.created when the log line is read. The same could apply to package capturing where @timestamp contains the timestamp extracted from the network package and event.created when the event was created. In case the two timestamps are identical, @timestamp should be used.', - }, - { - name: 'start', + name: 'labels', level: 'extended', - type: 'date', - description: - 'event.start contains the date when the event started or when the activity was first observed.', + type: 'object', + object_type: 'keyword', + description: 'Image labels.', }, { - name: 'end', + name: 'name', level: 'extended', - type: 'date', - description: - 'event.end contains the date when the event ended or when the activity was last observed.', - }, - { - name: 'risk_score', - level: 'core', - type: 'float', - description: - "Risk score or priority of the event (e.g. security solutions). Use your system's original value here. ", + type: 'keyword', + ignore_above: 1024, + description: 'Container name.', }, { - name: 'risk_score_norm', + name: 'runtime', level: 'extended', - type: 'float', - 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.', + type: 'keyword', + ignore_above: 1024, + description: 'Runtime managing this container.', + example: 'docker', }, ], }, { - name: 'file', + name: 'destination', + title: 'Destination', group: 2, - title: 'File', description: - 'A file is defined as a set of information that has been created on, or has existed on a filesystem. File objects can be associated with host events, network events, and/or file events (e.g., those produced by File Integrity Monitoring [FIM] products or services). File fields provide details about the affected file associated with the event or metric.', + '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: 'path', + name: 'address', level: 'extended', type: 'keyword', - description: 'Path to the file.', + 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: 'target_path', + name: 'as.number', level: 'extended', - type: 'keyword', - description: 'Target path for symlinks.', + 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: 'extension', + name: 'as.organization.name', level: 'extended', type: 'keyword', - description: 'File extension. This should allow easy filtering by file extensions.', - example: 'png', + ignore_above: 1024, + multi_fields: [ + { + name: 'text', + type: 'text', + norms: false, + default_field: false, + }, + ], + description: 'Organization name.', + example: 'Google LLC', }, { - name: 'type', - level: 'extended', - type: 'keyword', - description: 'File type (file, dir, or symlink).', + name: 'bytes', + level: 'core', + type: 'long', + format: 'bytes', + description: 'Bytes sent from the destination to the source.', + example: 184, }, { - name: 'device', - level: 'extended', + name: 'domain', + level: 'core', type: 'keyword', - description: 'Device that is the source of the file.', + ignore_above: 1024, + description: 'Destination domain.', }, { - name: 'inode', - level: 'extended', + name: 'geo.city_name', + level: 'core', type: 'keyword', - description: 'Inode representing the file in the filesystem.', + ignore_above: 1024, + description: 'City name.', + example: 'Montreal', }, { - name: 'uid', - level: 'extended', + name: 'geo.continent_name', + level: 'core', type: 'keyword', - description: 'The user ID (UID) or security identifier (SID) of the file owner.', + ignore_above: 1024, + description: 'Name of the continent.', + example: 'North America', }, { - name: 'owner', - level: 'extended', + name: 'geo.country_iso_code', + level: 'core', type: 'keyword', - description: "File owner's username.", + ignore_above: 1024, + description: 'Country ISO code.', + example: 'CA', }, { - name: 'gid', - level: 'extended', + name: 'geo.country_name', + level: 'core', type: 'keyword', - description: 'Primary group ID (GID) of the file.', + ignore_above: 1024, + description: 'Country name.', + example: 'Canada', }, { - name: 'group', - level: 'extended', - type: 'keyword', - description: 'Primary group name of the file.', + name: 'geo.location', + level: 'core', + type: 'geo_point', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', }, { - name: 'mode', - level: 'extended', - type: 'keyword', - example: 416, - description: 'Mode of the file in octal representation.', - }, - { - name: 'size', - level: 'extended', - type: 'long', - format: 'bytes', - description: 'File size in bytes (field is only added when `type` is `file`).', - }, - { - name: 'mtime', - level: 'extended', - type: 'date', - description: 'Last time file content was modified.', - }, - { - name: 'ctime', - level: 'extended', - type: 'date', - description: 'Last time file metadata changed.', - }, - ], - }, - { - name: 'group', - title: 'Group', - group: 2, - description: - 'The group fields are meant to represent groups that are relevant to the event.', - type: 'group', - fields: [ - { - name: 'id', + name: 'geo.name', 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. ECS host.* fields should be populated with details about the host on which the event happened, or on which the measurement was taken. Host types include hardware, virtual machines, Docker containers, and Kubernetes nodes.', - type: 'group', - fields: [ - { - name: 'hostname', - level: 'core', - type: 'keyword', + ignore_above: 1024, description: - 'Hostname of the host. It normally contains what the `hostname` command returns on the host machine.', + '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: 'name', + name: 'geo.region_iso_code', level: 'core', type: 'keyword', - 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.', + ignore_above: 1024, + description: 'Region ISO code.', + example: 'CA-QC', }, { - name: 'id', + name: 'geo.region_name', level: 'core', type: 'keyword', - 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`.', + ignore_above: 1024, + description: 'Region name.', + example: 'Quebec', }, { name: 'ip', level: 'core', type: 'ip', - description: 'Host ip address.', + description: + 'IP address of the destination.\n\nCan be one or multiple IPv4 or IPv6 addresses.', }, { name: 'mac', level: 'core', type: 'keyword', - description: 'Host mac address.', + ignore_above: 1024, + description: 'MAC address of the destination.', }, { - name: 'type', - level: 'core', - type: 'keyword', + name: 'nat.ip', + level: 'extended', + type: 'ip', 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.', + 'Translated ip of destination based NAT sessions (e.g. internet\nto private DMZ)\n\nTypically used with load balancers, firewalls, or routers.', }, { - name: 'architecture', - level: 'core', - type: 'keyword', - example: 'x86_64', - description: 'Operating system architecture.', + 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: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - reusable: { - top_level: false, - expected: ['observer', 'host', 'user_agent'], - }, - type: 'group', - fields: [ - { - name: 'platform', - level: 'extended', - type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - example: 'Mac OS X', - description: 'Operating system name, without the version.', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - example: 'Mac OS Mojave', - description: 'Operating system name, including the version or code name.', - }, - { - name: 'family', - level: 'extended', - type: 'keyword', - example: 'debian', - description: 'OS family (such as redhat, debian, freebsd, windows).', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - example: '10.14.1', - description: 'Operating system version as a raw string.', - }, - { - name: 'kernel', - level: 'extended', - type: 'keyword', - example: '4.4.0-112-generic', - description: 'Operating system kernel version as a raw string.', - }, - ], + name: 'packets', + level: 'core', + type: 'long', + description: 'Packets sent from the destination to the source.', + example: 12, }, { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - 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: 'port', + level: 'core', + type: 'long', + format: 'string', + description: 'Port of the destination.', }, - ], - }, - { - name: 'http', - title: 'HTTP', - group: 2, - description: 'Fields related to HTTP activity.', - type: 'group', - fields: [ { - name: 'request.method', + name: 'registered_domain', level: 'extended', type: 'keyword', + ignore_above: 1024, description: - 'Http request method. The field value must be normalized to lowercase for querying. See "Lowercase Capitalization" in the "Implementing ECS" section.', - example: 'get, post, put', + '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: 'request.body.content', + name: 'top_level_domain', level: 'extended', type: 'keyword', - description: 'The full http request body.', - example: 'Hello world', + 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: 'request.referrer', + name: 'user.domain', level: 'extended', type: 'keyword', - description: 'Referrer for this HTTP request.', - example: 'https://blog.example.com/', - }, - { - name: 'response.status_code', - level: 'extended', - type: 'long', - description: 'Http response status code.', - example: 404, + 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: 'response.body.content', + name: 'user.email', level: 'extended', type: 'keyword', - description: 'The full http response body.', - example: 'Hello world', + ignore_above: 1024, + description: 'User email address.', }, { - name: 'version', + name: 'user.full_name', level: 'extended', type: 'keyword', - description: 'Http version.', - example: 1.1, + 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: 'request.bytes', + name: 'user.group.domain', level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the request (body and headers).', - example: 1437, + 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: 'request.body.bytes', + name: 'user.group.id', level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the request body.', - example: 887, + type: 'keyword', + ignore_above: 1024, + description: 'Unique identifier for the group on the system/platform.', }, { - name: 'response.bytes', + name: 'user.group.name', level: 'extended', - type: 'long', - format: 'bytes', - description: 'Total size in bytes of the response (body and headers).', - example: 1437, + type: 'keyword', + ignore_above: 1024, + description: 'Name of the group.', }, { - name: 'response.body.bytes', + name: 'user.hash', level: 'extended', - type: 'long', - format: 'bytes', - description: 'Size in bytes of the response body.', - example: 887, + 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: 'log', - title: 'Log', - description: 'Fields which are specific to log events.', - type: 'group', - fields: [ { - name: 'level', + name: 'user.id', level: 'core', type: 'keyword', - description: 'Log level of the log event. Some examples are `WARN`, `ERR`, `INFO`.', - example: 'ERR', + ignore_above: 1024, + description: 'Unique identifiers of the user.', }, { - name: 'original', + name: 'user.name', level: 'core', type: 'keyword', - example: 'Sep 19 08:26:10 localhost My log', - index: false, - doc_values: false, - 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`. ", + 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: 'network', - title: 'Network', + name: 'dll', + title: 'DLL', group: 2, description: - 'The network is defined as the communication path over which a host or network event happens. The network.* fields should be populated with details about the network activity associated with an event.', + '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: 'name', + 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', - description: 'Name given by operators to sections of their network.', - example: 'Guest Wifi', + 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: 'type', + 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: - '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 "Lowercase Capitalization" in the "Implementing ECS" section.', - example: 'ipv4', + '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: 'iana_number', + name: 'code_signature.valid', level: 'extended', - type: 'keyword', + type: 'boolean', 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, + '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: 'transport', - level: 'core', + name: 'hash.md5', + level: 'extended', type: 'keyword', - 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 "Lowercase Capitalization" in the "Implementing ECS" section.', - example: 'tcp', + ignore_above: 1024, + description: 'MD5 hash.', + default_field: false, }, { - name: 'application', + name: 'hash.sha1', level: 'extended', type: 'keyword', - description: - 'A name given to an application. 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 "Lowercase Capitalization" in the "Implementing ECS" section.', - example: 'aim', + ignore_above: 1024, + description: 'SHA1 hash.', + default_field: false, }, { - name: 'protocol', - level: 'core', + name: 'hash.sha256', + level: 'extended', type: 'keyword', - description: - 'L7 Network protocol name. ex. http, lumberjack, transport protocol. The field value must be normalized to lowercase for querying. See "Lowercase Capitalization" in the "Implementing ECS" section.', - example: 'http', + ignore_above: 1024, + description: 'SHA256 hash.', + default_field: false, }, { - name: 'direction', - level: 'core', + name: 'hash.sha512', + level: 'extended', 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', + ignore_above: 1024, + description: 'SHA512 hash.', + default_field: false, }, { - name: 'forwarded_ip', + name: 'name', level: 'core', - type: 'ip', - description: 'Host IP address when the source IP address is the proxy.', - example: '192.1.1.2', + 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: 'community_id', + name: 'path', level: 'extended', type: 'keyword', - 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: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - description: - 'Total bytes transferred in both directions. If `source.bytes` and `destination.bytes` are known, `network.bytes` is their sum.', - example: 368, - }, - { - name: 'packets', - level: 'core', - type: 'long', - description: - 'Total packets transferred in both directions. If `source.packets` and `destination.packets` are known, `network.packets` is their sum.', - example: 24, - }, - ], - }, - { - name: 'observer', - title: 'Observer', - group: 2, - description: - 'An observer is defined as a special network, security, or application device used to detect, observe, or create network, security, or application-related events and metrics. This could be a custom hardware appliance or a server that has been configured to run special network, security, or application software. Examples include firewalls, intrusion detection/prevention systems, network monitoring sensors, web application firewalls, data loss prevention systems, and APM servers. The observer.* fields shall be populated with details of the system, if any, that detects, observes and/or creates a network, security, or application event or metric. Message queues and ETL components used in processing events or metrics are not considered observers in ECS.', - type: 'group', - fields: [ - { - name: 'mac', - level: 'core', - type: 'keyword', - description: 'MAC address of the observer', - }, - { - name: 'ip', - level: 'core', - type: 'ip', - description: 'IP address of the observer.', + ignore_above: 1024, + description: 'Full file path of the library.', + example: 'C:\\Windows\\System32\\kernel32.dll', + default_field: false, }, { - name: 'hostname', - level: 'core', + name: 'pe.company', + level: 'extended', type: 'keyword', - description: 'Hostname of the observer.', + ignore_above: 1024, + description: 'Internal company name of the file, provided at compile-time.', + example: 'Microsoft Corporation', + default_field: false, }, { - name: 'vendor', - level: 'core', + name: 'pe.description', + level: 'extended', type: 'keyword', - description: 'observer vendor information.', + ignore_above: 1024, + description: 'Internal description of the file, provided at compile-time.', + example: 'Paint', + default_field: false, }, { - name: 'version', - level: 'core', + name: 'pe.file_version', + level: 'extended', type: 'keyword', - description: 'Observer version.', + ignore_above: 1024, + description: 'Internal version of the file, provided at compile-time.', + example: '6.3.9600.17415', + default_field: false, }, { - name: 'serial_number', + name: 'pe.original_file_name', level: 'extended', type: 'keyword', - description: 'Observer serial number.', + ignore_above: 1024, + description: 'Internal name of the file, provided at compile-time.', + example: 'MSPAINT.EXE', + default_field: false, }, { - name: 'type', - level: 'core', + name: 'pe.product', + level: 'extended', type: 'keyword', - 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: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - reusable: { - top_level: false, - expected: ['observer', 'host', 'user_agent'], - }, - type: 'group', - fields: [ - { - name: 'platform', - level: 'extended', - type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - example: 'Mac OS X', - description: 'Operating system name, without the version.', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - example: 'Mac OS Mojave', - description: 'Operating system name, including the version or code name.', - }, - { - name: 'family', - level: 'extended', - type: 'keyword', - example: 'debian', - description: 'OS family (such as redhat, debian, freebsd, windows).', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - example: '10.14.1', - description: 'Operating system version as a raw string.', - }, - { - name: 'kernel', - level: 'extended', - type: 'keyword', - example: '4.4.0-112-generic', - description: 'Operating system kernel version as a raw string.', - }, - ], - }, - { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - 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', - }, - ], + ignore_above: 1024, + description: 'Internal product name of the file, provided at compile-time.', + example: 'Microsoft® Windows® Operating System', + default_field: false, }, ], }, { - name: 'organization', - title: 'Organization', + name: 'dns', + title: 'DNS', group: 2, description: - 'The organization fields enrich data with information about the company or entity the data is associated with. These fields help you arrange or filter data stored in an index by one or multiple organizations.', + '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: 'name', + name: 'answers', level: 'extended', - type: 'keyword', - description: 'Organization name.', + 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: 'id', + name: 'answers.class', level: 'extended', type: 'keyword', - description: 'Unique identifier for the organization.', + ignore_above: 1024, + description: 'The class of DNS data contained in this resource record.', + example: 'IN', }, - ], - }, - { - name: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - reusable: { - top_level: false, - expected: ['observer', 'host', 'user_agent'], - }, - type: 'group', - fields: [ { - name: 'platform', + name: 'answers.data', level: 'extended', type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', + 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: 'name', + name: 'answers.name', level: 'extended', type: 'keyword', - example: 'Mac OS X', - description: 'Operating system name, without the version.', + 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: 'full', + name: 'answers.ttl', level: 'extended', - type: 'keyword', - example: 'Mac OS Mojave', - description: 'Operating system name, including the version or code name.', + 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: 'family', + name: 'answers.type', level: 'extended', type: 'keyword', - example: 'debian', - description: 'OS family (such as redhat, debian, freebsd, windows).', + ignore_above: 1024, + description: 'The type of data contained in this resource record.', + example: 'CNAME', }, { - name: 'version', + name: 'header_flags', level: 'extended', type: 'keyword', - example: '10.14.1', - description: 'Operating system version as a raw string.', + 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: 'kernel', + name: 'id', level: 'extended', type: 'keyword', - example: '4.4.0-112-generic', - description: 'Operating system kernel version as a raw string.', + 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: 'process', - title: 'Process', - group: 2, - description: - 'These fields contain information about a process. These fields can help you correlate metrics information with a process id/name from a log message. The `process.pid` often stays in the metric itself and is copied to the global field for correlation.', - type: 'group', - fields: [ { - name: 'pid', - level: 'core', - type: 'long', - description: 'Process id.', - example: 'ssh', + 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: 'name', + name: 'question.class', level: 'extended', type: 'keyword', - description: 'Process name. Sometimes called program name or similar.', - example: 'ssh', + ignore_above: 1024, + description: 'The class of records being queried.', + example: 'IN', }, { - name: 'ppid', + name: 'question.name', level: 'extended', - type: 'long', - description: 'Process parent id.', + 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: 'args', + name: 'question.registered_domain', level: 'extended', type: 'keyword', - description: 'Process arguments. May be filtered to protect sensitive information.', - example: ['ssh', '-l', 'user', '10.0.0.16'], + 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: 'executable', + name: 'question.subdomain', level: 'extended', type: 'keyword', - description: 'Absolute path to the process executable.', - example: '/usr/bin/ssh', + 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: 'title', + name: 'question.top_level_domain', level: 'extended', type: 'keyword', + ignore_above: 1024, 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.', + '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: 'thread.id', + name: 'question.type', level: 'extended', - type: 'long', - example: 4242, - description: 'Thread ID.', + type: 'keyword', + ignore_above: 1024, + description: 'The type of record being queried.', + example: 'AAAA', }, { - name: 'start', + name: 'resolved_ip', level: 'extended', - type: 'date', - example: '2016-05-23T08:05:34.853Z', - description: 'The time the process started.', + 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: 'working_directory', + name: 'response_code', level: 'extended', type: 'keyword', - example: '/home/alice', - description: 'The working directory of the process.', + 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: 'related', - title: 'Related', + name: 'ecs', + title: 'ECS', group: 2, - description: - 'This field set is meant to facilitate pivoting around a piece of data. Some pieces of information can be seen in many places in ECS. To facilitate searching for them, append values to their corresponding field in `related.`. A concrete example is IP addresses, which can be under host, observer, source, destination, client, server, and network.forwarded_ip. If you append all IPs to `related.ip`, you can then search for a given IP trivially, no matter where it appeared, by querying `related.ip:a.b.c.d`.', + description: 'Meta-information specific to ECS.', type: 'group', fields: [ { - name: 'ip', - level: 'extended', - type: 'ip', - description: 'All of the IPs seen on your event.', + 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: 'server', - title: 'Server', + name: 'error', + title: 'Error', group: 2, description: - 'A Server is defined as the responder in a network connection for events regarding sessions, connections, or bidirectional flow records. For TCP events, the server is the receiver of the initial SYN packet(s) of the TCP connection. For other protocols, the server is generally the responder in the network transaction. Some systems actually use the term "responder" to refer the server in TCP connections. The server fields describe details about the system acting as the server in the network event. Server fields are usually populated in conjunction with client fields. Server fields are generally not populated for packet-level events. Client / server representations can add semantic context to an exchange, which is helpful to visualize the data in certain situations. If your context falls in that category, you should still ensure that source and destination are filled appropriately.', + '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: 'address', - level: 'extended', + name: 'code', + level: 'core', type: 'keyword', - 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.', + ignore_above: 1024, + description: 'Error code describing the error.', }, { - name: 'ip', + name: 'id', level: 'core', - type: 'ip', - description: 'IP address of the server. Can be one or multiple IPv4 or IPv6 addresses.', + type: 'keyword', + ignore_above: 1024, + description: 'Unique identifier for the error.', }, { - name: 'port', + name: 'message', level: 'core', - type: 'long', - description: 'Port of the server.', - }, - { - name: 'mac', - level: 'core', - type: 'keyword', - description: 'MAC address of the server.', + type: 'text', + description: 'Error message.', }, { - name: 'domain', - level: 'core', + name: 'stack_trace', + level: 'extended', type: 'keyword', - description: 'Server domain.', - }, - { - name: 'bytes', - level: 'core', - type: 'long', - format: 'bytes', - example: 184, - description: 'Bytes sent from the server to the client.', - }, - { - name: 'packets', - level: 'core', - type: 'long', - example: 12, - description: 'Packets sent from the server to the client.', - }, - { - name: 'geo', - title: 'Geo', - group: 2, - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, + ignore_above: 1024, + multi_fields: [ { - name: 'name', - level: 'extended', - type: 'keyword', - 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: '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: 'service', - title: 'Service', + name: 'event', + title: 'Event', group: 2, description: - 'The service fields describe the service for or from which the data was collected. These fields help you find and correlate logs for a specific service and version.', + '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: 'id', + name: 'action', level: 'core', type: 'keyword', + ignore_above: 1024, description: - 'Unique identifier of the running service. This id should uniquely identify this service. This makes it possible to correlate logs and metrics for one specific service. Example: If you are experiencing issues with one redis instance, you can filter on that id to see metrics and logs for that single instance.', - example: 'd37e5ebfe0ae6c4972dbe9f0174a1637bb8247f6', + '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: 'name', + name: 'category', level: 'core', type: 'keyword', - example: 'elasticsearch-metrics', + ignore_above: 1024, description: - 'Name of the service data is collected from. The name of the service is normally user given. This allows if two instances of the same service are running on the same machine they can be differentiated by the `service.name`. Also it 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.', + '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: 'type', - level: 'core', + name: 'code', + level: 'extended', type: 'keyword', - example: 'elasticsearch', + ignore_above: 1024, 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`.', + '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: 'state', + name: 'created', level: 'core', - type: 'keyword', - description: 'Current state of the service.', + 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: 'version', + name: 'dataset', level: 'core', type: 'keyword', - example: '3.2.4', + ignore_above: 1024, 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.', + '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: 'ephemeral_id', + 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: 'keyword', + type: 'date', description: - 'Ephemeral identifier of this service (if one exists). This id normally changes across restarts, but `service.id` does not.', - example: '8a4f500f', + 'event.end contains the date when the event ended or when the activity\nwas last observed.', }, - ], - }, - { - name: 'source', - title: 'Source', - group: 2, - description: - 'Source fields describe details about the source of a packet/event. Source fields are usually populated in conjunction with destination fields.', - type: 'group', - fields: [ { - name: 'address', + name: 'hash', level: 'extended', type: 'keyword', + ignore_above: 1024, 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.', + 'Hash (perhaps logstash fingerprint) of raw field to be able to\ndemonstrate log integrity.', + example: '123456789012345678901234567890ABCD', }, { - name: 'ip', + name: 'id', level: 'core', - type: 'ip', - description: 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.', + type: 'keyword', + ignore_above: 1024, + description: 'Unique ID to describe the event.', + example: '8a4f500d', }, { - name: 'port', + name: 'ingested', level: 'core', - type: 'long', - description: 'Port of the source.', + 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: 'mac', + name: 'kind', level: 'core', type: 'keyword', - description: 'MAC address of the source.', + 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: 'domain', + name: 'module', level: 'core', type: 'keyword', - description: 'Source domain.', + 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: 'bytes', + name: 'original', level: 'core', - type: 'long', - format: 'bytes', - example: 184, - description: 'Bytes sent from the source to the destination.', + 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: 'packets', + name: 'outcome', level: 'core', - type: 'long', - example: 12, - description: 'Packets sent from the source to the destination.', - }, - { - name: 'geo', - title: 'Geo', - group: 2, + type: 'keyword', + ignore_above: 1024, description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - type: 'group', - fields: [ - { - name: 'location', - level: 'core', - type: 'geo_point', - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - }, - { - name: 'continent_name', - level: 'core', - type: 'keyword', - description: 'Name of the continent.', - example: 'North America', - }, - { - name: 'country_name', - level: 'core', - type: 'keyword', - description: 'Country name.', - example: 'Canada', - }, - { - name: 'region_name', - level: 'core', - type: 'keyword', - description: 'Region name.', - example: 'Quebec', - }, - { - name: 'city_name', - level: 'core', - type: 'keyword', - description: 'City name.', - example: 'Montreal', - }, - { - name: 'country_iso_code', - level: 'core', - type: 'keyword', - description: 'Country ISO code.', - example: 'CA', - }, - { - name: 'region_iso_code', - level: 'core', - type: 'keyword', - description: 'Region ISO code.', - example: 'CA-QC', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - 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', - }, - ], + '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: 'url', - title: 'URL', - description: 'URL fields provide a complete URL, with scheme, host, and path.', - type: 'group', - fields: [ { - name: 'original', + name: 'provider', level: 'extended', type: 'keyword', + ignore_above: 1024, 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', + '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: 'full', + name: 'reference', level: 'extended', type: 'keyword', + ignore_above: 1024, 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', + '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: 'scheme', - level: 'extended', - type: 'keyword', + name: 'risk_score', + level: 'core', + type: 'float', description: - 'Scheme of the request, such as "https". Note: The `:` is not part of the scheme.', - example: 'https', + "Risk score or priority of the event (e.g. security solutions).\nUse your system's original value here.", }, { - name: 'domain', + name: 'risk_score_norm', level: 'extended', - type: 'keyword', + type: 'float', description: - 'Domain of the request, 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', + '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: 'port', + name: 'sequence', level: 'extended', - type: 'integer', - description: 'Port of the request, such as 443.', - example: 443, + 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: 'path', - level: 'extended', - type: 'keyword', - description: 'Path of the request, such as "/search".', + 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: 'query', + name: 'start', level: 'extended', - type: 'keyword', + type: 'date', 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.', + 'event.start contains the date when the event started or when the\nactivity was first observed.', }, { - name: 'fragment', + name: 'timezone', level: 'extended', type: 'keyword', + ignore_above: 1024, description: - 'Portion of the url after the `#`, such as "top". The `#` is not part of the fragment.', + '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: 'username', - level: 'extended', + name: 'type', + level: 'core', type: 'keyword', - description: 'Username of the request.', + 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: 'password', + name: 'url', level: 'extended', type: 'keyword', - description: 'Password of the request.', + 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: 'user', - title: 'User', + name: 'file', + title: 'File', group: 2, description: - 'The user fields describe information about the user that is relevant to the event. Fields can have one entry or multiple entries. If a user has more than one id, provide an array that includes all of them.', - reusable: { - top_level: true, - expected: ['client', 'destination', 'host', 'server', 'source'], - }, + '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: 'id', - level: 'core', + 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', - description: 'One or multiple unique identifiers of the user.', + 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: 'name', + name: 'code_signature.exists', level: 'core', - type: 'keyword', - example: 'albert', - description: 'Short name or login of the user.', + type: 'boolean', + description: 'Boolean to capture if a signature is present.', + example: 'true', + default_field: false, }, { - name: 'full_name', + name: 'code_signature.status', level: 'extended', type: 'keyword', - example: 'Albert Einstein', - description: "User's full name, if available. ", + 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: 'email', - level: 'extended', + name: 'code_signature.subject_name', + level: 'core', type: 'keyword', - description: 'User email address.', + ignore_above: 1024, + description: 'Subject name of the code signer', + example: 'Microsoft Corporation', + default_field: false, }, { - name: 'hash', + name: 'code_signature.trusted', level: 'extended', - type: 'keyword', + type: 'boolean', 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.', + '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: 'group', - title: 'Group', - group: 2, + name: 'code_signature.valid', + level: 'extended', + type: 'boolean', description: - 'The group fields are meant to represent groups that are relevant to 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.', - }, - ], + '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: 'user_agent', - title: 'User agent', - group: 2, - description: - 'The user_agent fields normally come from a browser request. They often show up in web service logs coming from the parsed user agent string.', - type: 'group', - fields: [ { - name: 'original', + name: 'created', 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 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1', + type: 'date', + description: + 'File creation time.\n\nNote that not all filesystems store the creation time.', }, { - name: 'name', + name: 'ctime', level: 'extended', - type: 'keyword', - example: 'Safari', - description: 'Name of the user agent.', + 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: 'version', + name: 'device', level: 'extended', type: 'keyword', - description: 'Version of the user agent.', - example: 12, + ignore_above: 1024, + description: 'Device that is the source of the file.', + example: 'sda', }, { - name: 'device.name', + name: 'directory', level: 'extended', type: 'keyword', - example: 'iPhone', - description: 'Name of the device.', + ignore_above: 1024, + description: + 'Directory where the file is located. It should include the drive\nletter, when appropriate.', + example: '/home/alice', }, { - name: 'os', - title: 'Operating System', - group: 2, - description: 'The OS fields contain information about the operating system.', - reusable: { - top_level: false, - expected: ['observer', 'host', 'user_agent'], - }, - type: 'group', - fields: [ - { - name: 'platform', - level: 'extended', - type: 'keyword', - description: 'Operating system platform (such centos, ubuntu, windows).', - example: 'darwin', - }, - { - name: 'name', - level: 'extended', - type: 'keyword', - example: 'Mac OS X', - description: 'Operating system name, without the version.', - }, - { - name: 'full', - level: 'extended', - type: 'keyword', - example: 'Mac OS Mojave', - description: 'Operating system name, including the version or code name.', - }, - { - name: 'family', - level: 'extended', - type: 'keyword', - example: 'debian', - description: 'OS family (such as redhat, debian, freebsd, windows).', - }, - { - name: 'version', - level: 'extended', - type: 'keyword', - example: '10.14.1', - description: 'Operating system version as a raw string.', - }, - { - name: 'kernel', - level: 'extended', - type: 'keyword', - example: '4.4.0-112-generic', - description: 'Operating system kernel version as a raw string.', - }, - ], + 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: 'agent.hostname', - type: 'keyword', - description: 'Hostname of the agent.', - }, - ], - }, - { - key: 'beat', - title: 'Beat', - description: 'Contains common beat fields available in all event types.', - fields: [ - { - name: 'beat.timezone', - type: 'alias', - path: 'event.timezone', - migration: true, - }, - { - name: 'fields', - type: 'object', - object_type: 'keyword', - description: 'Contains user configurable fields.', - }, - { - name: 'error', - type: 'group', - description: 'Error fields containing additional info in case of errors.', - fields: [ { - name: 'type', + name: 'extension', + level: 'extended', type: 'keyword', - description: 'Error type.', + ignore_above: 1024, + description: 'File extension.', + example: 'png', }, - ], - }, - { - name: 'beat.name', - type: 'alias', - path: 'host.name', - migration: true, - }, - { - name: 'beat.hostname', - type: 'alias', - path: 'agent.hostname', - migration: true, - }, - ], - }, - { - key: 'cloud', - title: 'Cloud provider metadata', - description: 'Metadata from cloud providers added by the add_cloud_metadata processor.', - fields: [ - { - name: 'cloud.project.id', - example: 'project-x', - description: 'Name of the project in Google Cloud.', - }, - { - 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.', - short_config: false, - anchor: 'docker-processor', - fields: [ - { - name: 'docker', - type: 'group', - fields: [ { - name: 'container.id', - type: 'alias', - path: 'container.id', - migration: true, + name: 'gid', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Primary group ID (GID) of the file.', + example: '1001', }, { - name: 'container.image', - type: 'alias', - path: 'container.image.name', - migration: true, + name: 'group', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Primary group name of the file.', + example: 'alice', }, { - name: 'container.name', - type: 'alias', - path: 'container.name', - migration: true, + name: 'hash.md5', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'MD5 hash.', }, { - name: 'container.labels', - type: 'object', - object_type: 'keyword', - description: 'Image labels.', + name: 'hash.sha1', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'SHA1 hash.', }, - ], - }, - ], - }, - { - key: 'host', - title: 'Host', - description: 'Info collected for the host machine.', - anchor: 'host-processor', - }, - { - key: 'kubernetes', - title: 'Kubernetes', - description: 'Kubernetes metadata added by the kubernetes processor', - short_config: false, - anchor: 'kubernetes-processor', - fields: [ - { - name: 'kubernetes', - type: 'group', - fields: [ { - name: 'pod.name', + name: 'hash.sha256', + level: 'extended', type: 'keyword', - description: 'Kubernetes pod name', + ignore_above: 1024, + description: 'SHA256 hash.', }, { - name: 'pod.uid', + name: 'hash.sha512', + level: 'extended', type: 'keyword', - description: 'Kubernetes Pod UID', + ignore_above: 1024, + description: 'SHA512 hash.', }, { - name: 'namespace', + name: 'inode', + level: 'extended', type: 'keyword', - description: 'Kubernetes namespace', + ignore_above: 1024, + description: 'Inode representing the file in the filesystem.', + example: '256383', }, { - name: 'node.name', + name: 'mime_type', + level: 'extended', type: 'keyword', - description: 'Kubernetes node name', + 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: 'labels', - type: 'object', - description: 'Kubernetes labels map', + name: 'mode', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Mode of the file in octal representation.', + example: '0640', }, { - name: 'annotations', - type: 'object', - description: 'Kubernetes annotations map', + name: 'mtime', + level: 'extended', + type: 'date', + description: 'Last time the file content was modified.', }, { - name: 'container.name', + name: 'name', + level: 'extended', type: 'keyword', - description: 'Kubernetes container name', + ignore_above: 1024, + description: 'Name of the file including the extension, without the directory.', + example: 'example.png', }, { - name: 'container.image', + name: 'owner', + level: 'extended', type: 'keyword', - description: 'Kubernetes container image', + ignore_above: 1024, + description: "File owner's username.", + example: 'alice', }, - ], - }, - ], - }, - { - key: 'process', - title: 'Process', - description: 'Process metadata fields', - fields: [ - { - name: 'process', - type: 'group', - fields: [ - { - name: 'exe', - type: 'alias', - path: 'process.executable', - migration: true, - }, - ], - }, - ], - }, - { - key: 'common', - title: 'Common', - description: - 'These fields contain data about the environment in which the transaction or flow was captured.', - fields: [ - { - name: 'type', - description: - 'The type of the transaction (for example, HTTP, MySQL, Redis, or RUM) or "flow" in case of flows.', - required: true, - }, - { - name: 'server.process.name', - description: 'The name of the process that served the transaction.', - }, - { - name: 'server.process.args', - description: 'The command-line of the process that served the transaction.', - }, - { - name: 'server.process.executable', - description: 'Absolute path to the server process executable.', - }, - { - name: 'server.process.working_directory', - description: 'The working directory of the server process.', - }, - { - name: 'server.process.start', - description: 'The time the server process started.', - }, - { - name: 'client.process.name', - description: 'The name of the process that initiated the transaction.', - }, - { - name: 'client.process.args', - description: 'The command-line of the process that initiated the transaction.', - }, - { - name: 'client.process.executable', - description: 'Absolute path to the client process executable.', - }, - { - name: 'client.process.working_directory', - description: 'The working directory of the client process.', - }, - { - name: 'client.process.start', - description: 'The time the client process started.', - }, - { - 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`. Unless this field is disabled, it always has a value, and it matches the `client_ip` for non proxy clients.', - }, - { - name: 'transport', - type: 'alias', - path: 'network.transport', - migration: true, - description: - 'The transport protocol used for the transaction. If not specified, then tcp is assumed.', - }, - ], - }, - { - key: 'flows_event', - title: 'Flow Event', - description: 'These fields contain data about the flow itself.', - 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.', - }, - { - name: 'flow.id', - description: 'Internal flow ID based on connection meta data and address.', - }, - { - 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. ", - }, - { - 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.', - 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.', - 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).', - }, - { - 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.', - }, - { - 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.', - }, - { - 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`.', - }, - { - 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.', - }, - { - 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.', - }, - ], - }, - { - 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.', - }, - { - 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.', - }, - ], - }, - { - key: 'trans_measurements', - title: 'Measurements (Transactions)', - description: 'These fields contain measurements related to the transaction.', - 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.', - }, - { - 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.', - }, - ], - }, - { - 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', - example: 404, + 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: 'reply-text', + name: 'pe.company', + level: 'extended', type: 'keyword', - description: 'Text explaining the error.', + ignore_above: 1024, + description: 'Internal company name of the file, provided at compile-time.', + example: 'Microsoft Corporation', + default_field: false, }, { - name: 'class-id', - type: 'long', - description: 'Failing method class.', + 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: 'method-id', - type: 'long', - description: 'Failing method ID.', + 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: 'exchange', + name: 'pe.original_file_name', + level: 'extended', type: 'keyword', - description: 'Name of the exchange.', + ignore_above: 1024, + description: 'Internal name of the file, provided at compile-time.', + example: 'MSPAINT.EXE', + default_field: false, }, { - name: 'exchange-type', + name: 'pe.product', + level: 'extended', type: 'keyword', - description: 'Exchange type.', - example: 'fanout', + ignore_above: 1024, + description: 'Internal product name of the file, provided at compile-time.', + example: 'Microsoft® Windows® Operating System', + default_field: false, }, { - name: 'passive', - type: 'boolean', - description: 'If set, do not create exchange/queue.', + name: 'size', + level: 'extended', + type: 'long', + description: 'File size in bytes.\n\nOnly relevant when `file.type` is "file".', + example: 16384, }, { - name: 'durable', - type: 'boolean', - description: 'If set, request a durable exchange/queue.', + 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: 'exclusive', - type: 'boolean', - description: 'If set, request an exclusive queue.', + name: 'type', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'File type (file, dir, or symlink).', + example: 'file', }, { - name: 'auto-delete', - type: 'boolean', - description: 'If set, auto-delete queue when unused.', + 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: 'no-wait', - type: 'boolean', - description: 'If set, the server will not respond to the method.', + name: 'city_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'City name.', + example: 'Montreal', }, { - name: 'consumer-tag', - description: 'Identifier for the consumer, valid within the current channel.', + name: 'continent_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Name of the continent.', + example: 'North America', }, { - name: 'delivery-tag', - type: 'long', - description: 'The server-assigned and channel-specific delivery tag.', + name: 'country_iso_code', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Country ISO code.', + example: 'CA', }, { - name: 'message-count', - type: 'long', - description: - 'The number of messages in the queue, which will be zero for newly-declared queues.', + name: 'country_name', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Country name.', + example: 'Canada', }, { - name: 'consumer-count', - type: 'long', - description: 'The number of consumers of a queue.', + name: 'location', + level: 'core', + type: 'geo_point', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', }, { - name: 'routing-key', + name: 'name', + level: 'extended', type: 'keyword', - description: 'Message routing key.', + 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: 'no-ack', - type: 'boolean', - description: 'If set, the server does not expect acknowledgements for messages.', + name: 'region_iso_code', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Region ISO code.', + example: 'CA-QC', }, { - name: 'no-local', - type: 'boolean', + 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: - 'If set, the server will not send messages to the connection that published them.', + 'Name of the directory the group is a member of.\n\nFor example, an LDAP or Active Directory domain name.', }, { - name: 'if-unused', - type: 'boolean', - description: 'Delete only if unused.', + name: 'id', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'Unique identifier for the group on the system/platform.', }, { - name: 'if-empty', - type: 'boolean', - description: 'Delete only if empty.', + 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: 'queue', + name: 'md5', + level: 'extended', type: 'keyword', - description: 'The queue name identifies the queue within the vhost.', + ignore_above: 1024, + description: 'MD5 hash.', }, { - name: 'redelivered', - type: 'boolean', - description: - 'Indicates that the message has been previously delivered to this or another client.', + name: 'sha1', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'SHA1 hash.', }, { - name: 'multiple', - type: 'boolean', - description: 'Acknowledge multiple messages.', + name: 'sha256', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'SHA256 hash.', }, { - name: 'arguments', - type: 'object', - description: - 'Optional additional arguments passed to some methods. Can be of various types.', + 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: 'mandatory', - type: 'boolean', - description: 'Indicates mandatory routing.', + name: 'architecture', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Operating system architecture.', + example: 'x86_64', }, { - name: 'immediate', - type: 'boolean', - description: 'Request immediate delivery.', + 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: 'content-type', + name: 'geo.city_name', + level: 'core', type: 'keyword', - description: 'MIME content type.', - example: 'text/plain', + ignore_above: 1024, + description: 'City name.', + example: 'Montreal', }, { - name: 'content-encoding', + name: 'geo.continent_name', + level: 'core', type: 'keyword', - description: 'MIME content encoding.', + ignore_above: 1024, + description: 'Name of the continent.', + example: 'North America', }, { - name: 'headers', - type: 'object', - object_type: 'keyword', - description: 'Message header field table.', + name: 'geo.country_iso_code', + level: 'core', + type: 'keyword', + ignore_above: 1024, + description: 'Country ISO code.', + example: 'CA', }, { - name: 'delivery-mode', + name: 'geo.country_name', + level: 'core', type: 'keyword', - description: 'Non-persistent (1) or persistent (2).', + ignore_above: 1024, + description: 'Country name.', + example: 'Canada', }, { - name: 'priority', - type: 'long', - description: 'Message priority, 0 to 9.', + name: 'geo.location', + level: 'core', + type: 'geo_point', + description: 'Longitude and latitude.', + example: '{ "lon": -73.614830, "lat": 45.505918 }', }, { - name: 'correlation-id', + name: 'geo.name', + level: 'extended', type: 'keyword', - description: 'Application correlation identifier.', + 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: 'reply-to', + name: 'geo.region_iso_code', + level: 'core', type: 'keyword', - description: 'Address to reply to.', + ignore_above: 1024, + description: 'Region ISO code.', + example: 'CA-QC', }, { - name: 'expiration', + name: 'geo.region_name', + level: 'core', type: 'keyword', - description: 'Message expiration specification.', + ignore_above: 1024, + description: 'Region name.', + example: 'Quebec', }, { - name: 'message-id', + name: 'hostname', + level: 'core', type: 'keyword', - description: 'Application message identifier.', + ignore_above: 1024, + description: + 'Hostname of the host.\n\nIt normally contains what the `hostname` command returns on the host machine.', }, { - name: 'timestamp', + name: 'id', + level: 'core', type: 'keyword', - description: 'Message timestamp.', + 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: 'type', - type: 'keyword', - description: 'Message type name.', + name: 'ip', + level: 'core', + type: 'ip', + description: 'Host ip addresses.', }, { - name: 'user-id', + name: 'mac', + level: 'core', type: 'keyword', - description: 'Creating user id.', + ignore_above: 1024, + description: 'Host mac addresses.', }, { - name: 'app-id', + name: 'name', + level: 'core', type: 'keyword', - description: 'Creating application id.', + 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.', }, - ], - }, - ], - }, - { - 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.', + name: 'os.family', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + description: 'OS family (such as redhat, debian, freebsd, windows).', + example: 'debian', }, { - name: 'request', - type: 'group', - description: 'Cassandra request.', - fields: [ + name: 'os.full', + level: 'extended', + type: 'keyword', + ignore_above: 1024, + multi_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: '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: 'query', - type: 'keyword', - description: 'The CQL query which client send to cassandra.', + name: 'text', + type: 'text', + norms: false, + default_field: false, }, ], + description: 'Operating system name, without the version.', + example: 'Mac OS X', }, { - name: 'response', + 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: [ @@ -3243,19 +6791,19 @@ export const packetbeatSchema: Schema = [ name: 'transaction_id', type: 'keyword', 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.', + '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 renewal process.', + '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 its reply -- either unicast or broadcast.', + 'Flags are set by the client to indicate how the DHCP server should\nits reply -- either unicast or broadcast.\n', }, { name: 'client_ip', @@ -3266,19 +6814,19 @@ export const packetbeatSchema: Schema = [ name: 'assigned_ip', type: 'ip', description: - 'The IP address that the DHCP server is assigning to the client. This field is also known as "your" IP address.', + '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 next step in the bootstrap process.', + '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 (i.e. a DHCP relay server).', + 'The relay IP address used by the client to contact the server\n(i.e. a DHCP relay server).\n', }, { name: 'client_mac', @@ -3289,13 +6837,13 @@ export const packetbeatSchema: Schema = [ name: 'server_name', type: 'keyword', description: - 'The name of the server sending the message. Optional. Used in DHCPOFFER or DHCPACK messages.', + '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).', + description: 'The message op code (bootrequest or bootreply).\n', }, { name: 'hops', @@ -3306,7 +6854,7 @@ export const packetbeatSchema: Schema = [ name: 'hardware_type', type: 'keyword', description: - 'The type of hardware used for the local network (Ethernet, LocalTalk, etc).', + 'The type of hardware used for the local network (Ethernet,\nLocalTalk, etc).\n', }, { name: 'option', @@ -3317,124 +6865,126 @@ export const packetbeatSchema: Schema = [ type: 'keyword', example: 'ack', description: - 'The specific type of DHCP message being sent (e.g. discover, offer, request, decline, ack, nak, release, inform).', + '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 specified configuration parameters.', + '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 the client to request that a particular IP address be assigned.', + '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 message.', + 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 client's subnet. ", + "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 client is willing to accept.', + '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 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. ", + "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 when resolving hostnames via the Domain Name System.', + '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 System servers available to the client.', + '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 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.', + '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 network.', + 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 subnet in seconds from Coordinated Universal Time (UTC). ", + "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 on the client's subnet. ", + "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 available to the client.', + '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 servers available to the client.', + '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.', + 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 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.', + '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 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.', + '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 until the client transitions to the RENEWING state.', + '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 until the client transitions to the REBINDING state.', + '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 in the DHCP header has been used for DHCP options. ", + "This option is used to identify a bootfile when the 'file' field\nin the DHCP header has been used for DHCP options.\n", }, ], }, @@ -3451,129 +7001,64 @@ export const packetbeatSchema: Schema = [ name: 'dns', type: 'group', fields: [ - { - name: 'id', - type: 'long', - description: - 'The DNS packet identifier assigned by the program that generated the query. The identifier is copied to the response.', - }, - { - name: 'op_code', - 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: 'flags.authoritative', type: 'boolean', description: - 'A DNS flag specifying that the responding server is an authority for the domain name used in the question.', + '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.', + '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.', + '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.', + '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.', + '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.', - }, - { - name: 'response_code', - description: 'The DNS status code.', - example: 'NOERROR', - }, - { - name: 'question.name', - description: - 'The domain name being queried. If the name field contains non-printable characters (below 32 or above 126), then those characters are represented as escaped base 10 integers (\\DDD). Back slashes and quotes are escaped. Tabs, carriage returns, and line feeds are converted to \\t, \\r, and respectively.', - example: 'www.google.com.', - }, - { - name: 'question.type', - description: 'The type of records being queried.', - example: 'AAAA', - }, - { - name: 'question.class', - description: 'The class of of records being queried.', - example: 'IN', + '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. 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.', + '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', - type: 'object', - description: - 'An array containing a dictionary about each answer section returned by the server.', - }, { name: 'answers_count', type: 'long', - description: 'The number of resource records contained in the `dns.answers` field.', - }, - { - name: 'answers.name', - description: 'The domain name to which this resource record pertains.', - example: 'example.com.', - }, - { - name: 'answers.type', - description: 'The type of data contained in this resource record.', - example: 'MX', - }, - { - name: 'answers.class', - description: 'The class of DNS data contained in this resource record.', - example: 'IN', - }, - { - name: 'answers.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.', - type: 'long', - }, - { - name: 'answers.data', - description: - 'The data describing the resource. The meaning of this data depends on the type and class of the resource record.', + 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.', + '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.', + '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', @@ -3594,13 +7079,13 @@ export const packetbeatSchema: Schema = [ name: 'additionals', type: 'object', description: - 'An array containing a dictionary for each additional section from the answer.', + '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.', + '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', @@ -3620,13 +7105,13 @@ export const packetbeatSchema: Schema = [ { 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.', + '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.', + 'The data describing the resource. The meaning of this data depends on the type and class of the resource record.\n', }, { name: 'opt.version', @@ -3672,7 +7157,7 @@ export const packetbeatSchema: Schema = [ 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.', + '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', @@ -3697,7 +7182,7 @@ export const packetbeatSchema: Schema = [ 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.', + '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', @@ -3720,7 +7205,7 @@ export const packetbeatSchema: Schema = [ { key: 'icmp', title: 'ICMP', - description: 'ICMP specific event fields.', + description: 'ICMP specific event fields.\n', fields: [ { name: 'icmp', @@ -3778,232 +7263,232 @@ export const packetbeatSchema: Schema = [ 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.', + '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.', + 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.', + '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.', + '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".', + '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).', + '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).', + '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.', + description: 'The binary protocol message opcode name.\n', }, { name: 'response.opcode', type: 'keyword', - description: 'The binary protocol message opcode name.', + description: 'The binary protocol message opcode name.\n', }, { name: 'request.opcode_value', type: 'long', - description: 'The binary protocol message opcode value.', + description: 'The binary protocol message opcode value.\n', }, { name: 'response.opcode_value', type: 'long', - description: 'The binary protocol message opcode value.', + 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.', + '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.', + '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.', + 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).', + '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).', + 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.', + 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).', + 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.', + '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.', + '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).', + 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).', + 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.', + 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.', + description: 'The byte count of the values being transferred.\n', }, { name: 'request.delta', type: 'long', - description: 'The counter increment/decrement delta value.', + description: 'The counter increment/decrement delta value.\n', }, { name: 'request.initial', type: 'long', description: - 'The counter increment/decrement initial value parameter (binary protocol only).', + 'The counter increment/decrement initial value parameter (binary protocol only).\n', }, { name: 'request.verbosity', type: 'long', - description: 'The value of the memcache "verbosity" command.', + 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.', + '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. ", + 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. ", + 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.', + '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).', + 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).', + 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).', + '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. ", + 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.', + 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.', + '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.', + '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.', + 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).', + '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".', + '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.', + description: 'The returned memcache version string.\n', }, ], }, @@ -4013,7 +7498,7 @@ export const packetbeatSchema: Schema = [ 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.', + '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', @@ -4022,57 +7507,57 @@ export const packetbeatSchema: Schema = [ { name: 'error', description: - 'If the MongoDB request has resulted in an error, this field contains the error message returned by the server.', + '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.', + '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.', + '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.', + description: 'The requested maximum number of documents to be returned.\n', }, { name: 'numberReturned', type: 'long', - description: 'The number of documents in the reply.', + description: 'The number of documents in the reply.\n', }, { name: 'startingFrom', - description: 'Where in the cursor this reply is starting.', + 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.', + '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.', + '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.', + '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.', + '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.', + 'The cursor identifier returned in the OP_REPLY. This must be the value that was returned from the database.\n', }, ], }, @@ -4081,7 +7566,7 @@ export const packetbeatSchema: Schema = [ { key: 'mysql', title: 'MySQL', - description: 'MySQL-specific event fields.', + description: 'MySQL-specific event fields.\n', fields: [ { name: 'mysql', @@ -4091,35 +7576,35 @@ export const packetbeatSchema: Schema = [ name: 'affected_rows', type: 'long', description: - 'If the MySQL command is successful, this field contains the affected number of rows of the last statement.', + '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.', + '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.', + '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.', + '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. ", + 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.', + description: 'The error code returned by MySQL.\n', }, { name: 'error_message', - description: 'The error info message returned by MySQL.', + description: 'The error info message returned by MySQL.\n', }, ], }, @@ -4150,7 +7635,7 @@ export const packetbeatSchema: Schema = [ }, { name: 'opcode', - description: 'NFS operation name, or main operation name, in case of COMPOUND calls.', + description: 'NFS operation name, or main operation name, in case of COMPOUND calls.\n', }, { name: 'status', @@ -4219,7 +7704,7 @@ export const packetbeatSchema: Schema = [ { key: 'pgsql', title: 'PostgreSQL', - description: 'PostgreSQL-specific event fields.', + description: 'PostgreSQL-specific event fields.\n', fields: [ { name: 'pgsql', @@ -4242,12 +7727,12 @@ export const packetbeatSchema: Schema = [ { name: 'num_fields', description: - 'If the SELECT query if successful, this field is set to the number of fields returned.', + '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.', + 'If the SELECT query if successful, this field is set to the number of rows returned.\n', }, ], }, @@ -4256,7 +7741,7 @@ export const packetbeatSchema: Schema = [ { key: 'redis', title: 'Redis', - description: 'Redis-specific event fields.', + description: 'Redis-specific event fields.\n', fields: [ { name: 'redis', @@ -4264,12 +7749,12 @@ export const packetbeatSchema: Schema = [ fields: [ { name: 'return_value', - description: 'The return value of the Redis command in a human readable format.', + 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.', + 'If the Redis command has resulted in an error, this field contains the error message returned by the Redis server.\n', }, ], }, @@ -4278,7 +7763,7 @@ export const packetbeatSchema: Schema = [ { key: 'thrift', title: 'Thrift-RPC', - description: 'Thrift-RPC specific event fields.', + description: 'Thrift-RPC specific event fields.\n', fields: [ { name: 'thrift', @@ -4287,521 +7772,785 @@ export const packetbeatSchema: Schema = [ { 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.', + '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.', + 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.', + '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.', + 'If the call resulted in exceptions, this field contains the exceptions in a human readable format.\n', }, ], }, ], - }, - { - key: 'tls', - title: 'TLS', - description: 'TLS-specific event fields.', - fields: [ - { - name: 'tls', - type: 'group', - fields: [ - { - name: 'version', - type: 'keyword', - description: 'The version of the TLS protocol used.', - example: 'TLS 1.3', - }, - { - name: 'handshake_completed', - type: 'boolean', - description: - 'Whether the TLS negotiation has been successful and the session has transitioned to encrypted mode.', - }, - { - name: 'resumed', - type: 'boolean', - description: 'If the TLS session has been resumed from a previous session.', - }, - { - 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.', - }, - { - name: 'client_certificate_requested', - type: 'boolean', - description: - 'Whether the server has requested the client to authenticate itself using a client certificate.', - }, - { - 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.', - }, - { - name: 'supported_ciphers', - type: 'array', - description: - 'List of ciphers the client is willing to use for this session. See https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-4', - }, - { - name: 'supported_compression_methods', - type: 'array', - description: - 'The list of compression methods the client supports. See https://www.iana.org/assignments/comp-meth-ids/comp-meth-ids.xhtml', - }, - { - 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.', - }, - { - name: 'session_ticket', - type: 'keyword', - description: - 'Length of the session ticket, if provided, or an empty string to advertise support for tickets.', - }, - { - name: 'supported_versions', - type: 'keyword', - description: 'List of TLS versions that the client is willing to use.', - }, - { - name: 'supported_groups', - type: 'keyword', - description: - 'List of Elliptic Curve Cryptography (ECC) curve groups supported by the client.', - }, - { - name: 'signature_algorithms', - type: 'keyword', - description: - 'List of signature algorithms that may be use in digital signatures.', - }, - { - 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.', - }, - { - name: '_unparsed_', - type: 'keyword', - description: 'List of extensions that were left unparsed by Packetbeat.', - }, - ], - }, - ], - }, + }, + { + key: 'tls_detailed', + title: 'Detailed TLS', + description: 'Detailed TLS-specific event fields.\n', + fields: [ + { + name: 'tls', + type: 'group', + fields: [ { - name: 'server_hello', + name: 'detailed', type: 'group', + default_fields: false, 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.', - }, - { - name: 'selected_cipher', - type: 'keyword', - description: - 'The cipher suite selected by the server from the list provided by in the client hello.', + description: 'The version of the TLS protocol used.\n', + example: 'TLS 1.3', }, { - name: 'selected_compression_method', + name: 'resumption_method', type: 'keyword', description: - 'The compression method selected by the server from the list provided in the client hello.', + '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: 'session_id', - type: 'keyword', + name: 'client_certificate_requested', + type: 'boolean', description: - 'Unique number to identify the session for the corresponding connection with the client.', + 'Whether the server has requested the client to authenticate itself using a client certificate.\n', }, { - name: 'extensions', + name: 'client_hello', type: 'group', - description: 'The hello extensions provided by the server.', fields: [ { - name: 'application_layer_protocol_negotiation', - type: 'array', - description: 'Negotiated application layer protocol', - }, - { - name: 'session_ticket', + name: 'version', type: 'keyword', description: - 'Used to announce that a session ticket will be provided by the server. Always an empty string.', + 'The version of the TLS protocol by which the client wishes to communicate during this session.\n', }, { - name: 'supported_versions', + name: 'session_id', type: 'keyword', - description: 'Negotiated TLS version to be used.', + description: + 'Unique number to identify the session for the corresponding connection with the client.\n', }, { - name: 'ec_points_formats', + name: 'supported_compression_methods', type: 'keyword', description: - 'List of Elliptic Curve (EC) point formats. Indicates the set of point formats that the server can parse.', + 'The list of compression methods the client supports. See https://www.iana.org/assignments/comp-meth-ids/comp-meth-ids.xhtml\n', }, { - name: '_unparsed_', - type: 'keyword', - description: 'List of extensions that were left unparsed by Packetbeat.', + 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: '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. ", - }, - { - 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. ", - }, - { - name: 'alternative_names', - type: 'array', - description: 'Subject Alternative Names for this certificate.', - }, - { - name: 'raw', - type: 'keyword', - description: 'The raw certificate in PEM format.', - }, - { - name: 'subject', + name: 'server_hello', type: 'group', - description: 'Subject represented by this certificate.', fields: [ { - name: 'country', - type: 'keyword', - description: 'Country code.', - }, - { - name: 'organization', + name: 'version', type: 'keyword', - description: 'Organization name.', + 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: 'organizational_unit', + name: 'selected_compression_method', type: 'keyword', - description: 'Unit within organization.', + description: + 'The compression method selected by the server from the list provided in the client hello.\n', }, { - name: 'province', + name: 'session_id', type: 'keyword', - description: 'Province or region within country.', + description: + 'Unique number to identify the session for the corresponding connection with the client.\n', }, { - name: 'common_name', - type: 'keyword', - description: 'Name or host name identified by the certificate.', + 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: 'issuer', + name: 'client_certificate', type: 'group', - description: 'Entity that issued and signed this certificate.', + description: 'Certificate provided by the client for authentication.', fields: [ { - name: 'country', - type: 'keyword', - description: 'Country code.', + name: 'version', + type: 'long', + description: 'X509 format version.', }, { - name: 'organization', + name: 'serial_number', type: 'keyword', - description: 'Organization name.', + description: "The certificate's serial number.", }, { - name: 'organizational_unit', - type: 'keyword', - description: 'Unit within organization.', + name: 'not_before', + type: 'date', + description: 'Date before which the certificate is not valid.', }, { - name: 'province', - type: 'keyword', - description: 'Province or region within country.', + name: 'not_after', + type: 'date', + description: 'Date after which the certificate expires.', }, { - name: 'common_name', + name: 'public_key_algorithm', type: 'keyword', - description: 'Name or host name identified by the certificate.', + description: + "The algorithm used for this certificate's public key. One of RSA, DSA or ECDSA.\n", }, - ], - }, - { - name: 'fingerprint', - type: 'group', - fields: [ { - name: 'md5', - type: 'keyword', - description: "Certificate's MD5 fingerprint.", + name: 'public_key_size', + type: 'long', + description: 'Size of the public key.', }, { - name: 'sha1', + name: 'signature_algorithm', type: 'keyword', - description: "Certificate's SHA-1 fingerprint.", + description: "The algorithm used for the certificate's signature.\n", }, { - name: 'sha256', + name: 'alternative_names', type: 'keyword', - description: "Certificate's SHA-256 fingerprint.", + 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. ", - }, - { - 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. ", - }, - { - name: 'alternative_names', - type: 'array', - description: 'Subject Alternative Names for this certificate.', - }, - { - name: 'raw', - type: 'keyword', - description: 'The raw certificate in PEM format.', - }, - { - name: 'subject', + name: 'server_certificate', type: 'group', - description: 'Subject represented by this certificate.', + description: 'Certificate provided by the server for authentication.', fields: [ { - name: 'country', - type: 'keyword', - description: 'Country code.', + name: 'version', + type: 'long', + description: 'X509 format version.', }, { - name: 'organization', + name: 'serial_number', type: 'keyword', - description: 'Organization name.', + 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: 'organizational_unit', + name: 'public_key_algorithm', type: 'keyword', - description: 'Unit within organization.', + 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: 'province', + name: 'signature_algorithm', type: 'keyword', - description: 'Province or region within country.', + description: "The algorithm used for the certificate's signature.\n", }, { - name: 'common_name', + name: 'alternative_names', type: 'keyword', - description: 'Name or host name identified by the certificate.', + 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: '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: 'server_certificate_chain', + type: 'array', + description: 'Chain of trust for the server certificate.', }, { - name: 'fingerprint', - type: 'group', - fields: [ - { - name: 'md5', - type: 'keyword', - description: "Certificate's MD5 fingerprint.", - }, - { - name: 'sha1', - type: 'keyword', - description: "Certificate's SHA-1 fingerprint.", - }, - { - name: 'sha256', - type: 'keyword', - description: "Certificate's SHA-256 fingerprint.", - }, - ], + name: 'client_certificate_chain', + type: 'array', + description: 'Chain of trust for the client certificate.', }, - ], - }, - { - 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.', - }, - { - name: 'fingerprints', - type: 'group', - description: 'Fingerprints for this TLS session.', - fields: [ { - name: 'ja3', - type: 'group', - description: 'JA3 TLS client fingerprint', - fields: [ - { - name: 'hash', - type: 'keyword', - description: 'The JA3 fingerprint hash for the client side.', - }, - { - name: 'str', - type: 'keyword', - description: 'The JA3 string used to calculate the hash.', - }, - ], + 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/legacy/plugins/siem/server/utils/beat_schema/index.test.ts b/x-pack/legacy/plugins/siem/server/utils/beat_schema/index.test.ts index 53ed7c26b877f..56ceca2b70e9c 100644 --- a/x-pack/legacy/plugins/siem/server/utils/beat_schema/index.test.ts +++ b/x-pack/legacy/plugins/siem/server/utils/beat_schema/index.test.ts @@ -17,175 +17,94 @@ describe('Schema Beat', () => { 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. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.', + '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', }, - tags: { - description: 'List of keywords used to tag each event.', - example: '["production", "env2"]', - name: 'tags', - type: 'keyword', - }, labels: { description: - '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: '{"env":"production","application":"foo-bar"}', + '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. In other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.', + '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 any, that collects, detects, or observes events on a host, or takes measurements on a host. Examples include Beats. Agents may also run on observers. ECS agent.* fields shall be populated with details of the agent running on the host or observer where the event happened or the measurement was taken.', + '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.version': { - description: 'Version of the agent.', - example: '6.0.0-rc2', - name: 'version', + '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: - '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.', + '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. 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.', + '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.id': { - description: - 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', - example: '8a4f500d', - name: 'id', - type: 'keyword', - }, - 'agent.ephemeral_id': { - description: - 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - name: 'ephemeral_id', + 'agent.version': { + description: 'Version of the agent.', + example: '6.0.0-rc2', + name: 'version', type: 'keyword', }, }, }, - client: { + as: { description: - 'A client is defined as the initiator of a network connection for events regarding sessions, connections, or bidirectional flow records. For TCP events, the client is the initiator of the TCP connection that sends the SYN packet(s). For other protocols, the client is generally the initiator or requestor in the network transaction. Some systems use the term "originator" to refer the client in TCP connections. The client fields describe details about the system acting as the client in the network event. Client fields are usually populated in conjunction with server fields. Client fields are generally not populated for packet-level events. Client / server representations can add semantic context to an exchange, which is helpful to visualize the data in certain situations. If your context falls in that category, you should still ensure that source and destination are filled appropriately.', - name: 'client', + '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: { - 'client.address': { + 'as.number': { 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: 'address', - type: 'keyword', - }, - 'client.ip': { - description: - 'IP address of the client. Can be one or multiple IPv4 or IPv6 addresses.', - name: 'ip', - type: 'ip', - }, - 'client.port': { - description: 'Port of the client.', - name: 'port', + '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', }, - 'client.mac': { - description: 'MAC address of the client.', - name: 'mac', - type: 'keyword', - }, - 'client.domain': { - description: 'Client domain.', - name: 'domain', - type: 'keyword', - }, - 'client.bytes': { - description: 'Bytes sent from the client to the server.', - example: 184, - format: 'bytes', - name: 'bytes', - type: 'long', - }, - 'client.packets': { - description: 'Packets sent from the client to the server.', - example: 12, - name: 'packets', - type: 'long', - }, - 'client.geo': { - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - name: 'geo', - type: 'group', - }, - 'client.geo.location': { - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - name: 'location', - type: 'geo_point', - }, - 'client.geo.continent_name': { - description: 'Name of the continent.', - example: 'North America', - name: 'continent_name', - type: 'keyword', - }, - 'client.geo.country_name': { - description: 'Country name.', - example: 'Canada', - name: 'country_name', - type: 'keyword', - }, - 'client.geo.region_name': { - description: 'Region name.', - example: 'Quebec', - name: 'region_name', - type: 'keyword', - }, - 'client.geo.city_name': { - description: 'City name.', - example: 'Montreal', - name: 'city_name', - type: 'keyword', - }, - 'client.geo.country_iso_code': { - description: 'Country ISO code.', - example: 'CA', - name: 'country_iso_code', - type: 'keyword', - }, - 'client.geo.region_iso_code': { - description: 'Region ISO code.', - example: 'CA-QC', - name: 'region_iso_code', - type: 'keyword', - }, - 'client.geo.name': { - 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: 'name', + 'as.organization.name': { + description: 'Organization name.', + example: 'Google LLC', + name: 'organization.name', type: 'keyword', }, }, @@ -198,175 +117,94 @@ describe('Schema Beat', () => { 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. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.', + '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', }, - tags: { - description: 'List of keywords used to tag each event.', - example: '["production", "env2"]', - name: 'tags', - type: 'keyword', - }, labels: { description: - '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: '{"env":"production","application":"foo-bar"}', + '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. In other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.', + '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 any, that collects, detects, or observes events on a host, or takes measurements on a host. Examples include Beats. Agents may also run on observers. ECS agent.* fields shall be populated with details of the agent running on the host or observer where the event happened or the measurement was taken.', + '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.version': { - description: 'Version of the agent.', - example: '6.0.0-rc2', - name: 'version', + '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: - '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.', + '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. 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.', + '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.id': { - description: - 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', - example: '8a4f500d', - name: 'id', - type: 'keyword', - }, - 'agent.ephemeral_id': { - description: - 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - name: 'ephemeral_id', + 'agent.version': { + description: 'Version of the agent.', + example: '6.0.0-rc2', + name: 'version', type: 'keyword', }, }, }, - client: { + as: { description: - 'A client is defined as the initiator of a network connection for events regarding sessions, connections, or bidirectional flow records. For TCP events, the client is the initiator of the TCP connection that sends the SYN packet(s). For other protocols, the client is generally the initiator or requestor in the network transaction. Some systems use the term "originator" to refer the client in TCP connections. The client fields describe details about the system acting as the client in the network event. Client fields are usually populated in conjunction with server fields. Client fields are generally not populated for packet-level events. Client / server representations can add semantic context to an exchange, which is helpful to visualize the data in certain situations. If your context falls in that category, you should still ensure that source and destination are filled appropriately.', - name: 'client', + '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: { - 'client.address': { + 'as.number': { 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: 'address', - type: 'keyword', - }, - 'client.ip': { - description: - 'IP address of the client. Can be one or multiple IPv4 or IPv6 addresses.', - name: 'ip', - type: 'ip', - }, - 'client.port': { - description: 'Port of the client.', - name: 'port', - type: 'long', - }, - 'client.mac': { - description: 'MAC address of the client.', - name: 'mac', - type: 'keyword', - }, - 'client.domain': { - description: 'Client domain.', - name: 'domain', - type: 'keyword', - }, - 'client.bytes': { - description: 'Bytes sent from the client to the server.', - example: 184, - format: 'bytes', - name: 'bytes', - type: 'long', - }, - 'client.packets': { - description: 'Packets sent from the client to the server.', - example: 12, - name: 'packets', + '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', }, - 'client.geo': { - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - name: 'geo', - type: 'group', - }, - 'client.geo.location': { - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - name: 'location', - type: 'geo_point', - }, - 'client.geo.continent_name': { - description: 'Name of the continent.', - example: 'North America', - name: 'continent_name', - type: 'keyword', - }, - 'client.geo.country_name': { - description: 'Country name.', - example: 'Canada', - name: 'country_name', - type: 'keyword', - }, - 'client.geo.region_name': { - description: 'Region name.', - example: 'Quebec', - name: 'region_name', - type: 'keyword', - }, - 'client.geo.city_name': { - description: 'City name.', - example: 'Montreal', - name: 'city_name', - type: 'keyword', - }, - 'client.geo.country_iso_code': { - description: 'Country ISO code.', - example: 'CA', - name: 'country_iso_code', - type: 'keyword', - }, - 'client.geo.region_iso_code': { - description: 'Region ISO code.', - example: 'CA-QC', - name: 'region_iso_code', - type: 'keyword', - }, - 'client.geo.name': { - 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: 'name', + 'as.organization.name': { + description: 'Organization name.', + example: 'Google LLC', + name: 'organization.name', type: 'keyword', }, }, @@ -379,175 +217,94 @@ describe('Schema Beat', () => { 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. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.', + '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', }, - tags: { - description: 'List of keywords used to tag each event.', - example: '["production", "env2"]', - name: 'tags', - type: 'keyword', - }, labels: { description: - '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: '{"env":"production","application":"foo-bar"}', + '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. In other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.', + '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 any, that collects, detects, or observes events on a host, or takes measurements on a host. Examples include Beats. Agents may also run on observers. ECS agent.* fields shall be populated with details of the agent running on the host or observer where the event happened or the measurement was taken.', + '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.version': { - description: 'Version of the agent.', - example: '6.0.0-rc2', - name: 'version', + '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: - '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.', + '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. 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.', + '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.id': { - description: - 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.', - example: '8a4f500d', - name: 'id', - type: 'keyword', - }, - 'agent.ephemeral_id': { - description: - 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.', - example: '8a4f500f', - name: 'ephemeral_id', + 'agent.version': { + description: 'Version of the agent.', + example: '6.0.0-rc2', + name: 'version', type: 'keyword', }, }, }, - client: { + as: { description: - 'A client is defined as the initiator of a network connection for events regarding sessions, connections, or bidirectional flow records. For TCP events, the client is the initiator of the TCP connection that sends the SYN packet(s). For other protocols, the client is generally the initiator or requestor in the network transaction. Some systems use the term "originator" to refer the client in TCP connections. The client fields describe details about the system acting as the client in the network event. Client fields are usually populated in conjunction with server fields. Client fields are generally not populated for packet-level events. Client / server representations can add semantic context to an exchange, which is helpful to visualize the data in certain situations. If your context falls in that category, you should still ensure that source and destination are filled appropriately.', - name: 'client', + '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: { - 'client.address': { + 'as.number': { 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: 'address', - type: 'keyword', - }, - 'client.ip': { - description: - 'IP address of the client. Can be one or multiple IPv4 or IPv6 addresses.', - name: 'ip', - type: 'ip', - }, - 'client.port': { - description: 'Port of the client.', - name: 'port', - type: 'long', - }, - 'client.mac': { - description: 'MAC address of the client.', - name: 'mac', - type: 'keyword', - }, - 'client.domain': { - description: 'Client domain.', - name: 'domain', - type: 'keyword', - }, - 'client.bytes': { - description: 'Bytes sent from the client to the server.', - example: 184, - format: 'bytes', - name: 'bytes', + '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', }, - 'client.packets': { - description: 'Packets sent from the client to the server.', - example: 12, - name: 'packets', - type: 'long', - }, - 'client.geo': { - description: - 'Geo fields can carry data about a specific location related to an event or geo information derived from an IP field.', - name: 'geo', - type: 'group', - }, - 'client.geo.location': { - description: 'Longitude and latitude.', - example: '{ "lon": -73.614830, "lat": 45.505918 }', - name: 'location', - type: 'geo_point', - }, - 'client.geo.continent_name': { - description: 'Name of the continent.', - example: 'North America', - name: 'continent_name', - type: 'keyword', - }, - 'client.geo.country_name': { - description: 'Country name.', - example: 'Canada', - name: 'country_name', - type: 'keyword', - }, - 'client.geo.region_name': { - description: 'Region name.', - example: 'Quebec', - name: 'region_name', - type: 'keyword', - }, - 'client.geo.city_name': { - description: 'City name.', - example: 'Montreal', - name: 'city_name', - type: 'keyword', - }, - 'client.geo.country_iso_code': { - description: 'Country ISO code.', - example: 'CA', - name: 'country_iso_code', - type: 'keyword', - }, - 'client.geo.region_iso_code': { - description: 'Region ISO code.', - example: 'CA-QC', - name: 'region_iso_code', - type: 'keyword', - }, - 'client.geo.name': { - 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: 'name', + 'as.organization.name': { + description: 'Organization name.', + example: 'Google LLC', + name: 'organization.name', type: 'keyword', }, }, @@ -562,40 +319,58 @@ describe('Schema Beat', () => { '_id', '_index', '@timestamp', - 'tags', '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', @@ -605,55 +380,17 @@ describe('Schema Beat', () => { 'meta.cloud.region', 'docker', 'kubernetes', - 'type', - 'server.process.name', - 'server.process.args', - 'server.process.executable', - 'server.process.working_directory', - 'server.process.start', - 'client.process.name', - 'client.process.args', - 'client.process.executable', - 'client.process.working_directory', - 'client.process.start', - 'real_ip', - 'transport', - 'flow.final', - 'flow.id', - 'flow.vlan', - 'flow_id', - 'final', - 'vlan', - 'source.stats.net_bytes_total', - 'source.stats.net_packets_total', - 'dest.stats.net_bytes_total', - 'dest.stats.net_packets_total', - 'status', - 'method', - 'resource', - 'path', - 'query', - 'params', - 'notes', - 'request', - 'response', - 'bytes_in', - 'bytes_out', - 'amqp', - 'no_request', - 'cassandra', - 'dhcpv4', - 'dns', - 'icmp', - 'memcache', - 'mongodb', - 'mysql', - 'nfs', - 'rpc', - 'pgsql', - 'redis', - 'thrift', - 'tls', + '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/legacy/plugins/siem/server/utils/beat_schema/index.ts b/x-pack/legacy/plugins/siem/server/utils/beat_schema/index.ts index a191bd835a7c7..3e9be4e265e63 100644 --- a/x-pack/legacy/plugins/siem/server/utils/beat_schema/index.ts +++ b/x-pack/legacy/plugins/siem/server/utils/beat_schema/index.ts @@ -106,19 +106,14 @@ export const getIndexSchemaDoc = memoize((index: string) => { ...extraSchemaField, ...convertSchemaToAssociativeArray(winlogbeatSchema), }; - } else if (index.match('ecs') != null) { - return { - ...extraSchemaField, - ...ecsSchema, - }; } - return {}; + return { + ...extraSchemaField, + ...convertSchemaToAssociativeArray(ecsSchema), + }; }); export const hasDocumentation = (index: string, path: string): boolean => { - if (index === 'unknown') { - return false; - } const splitPath = path.split('.'); const category = splitPath.length > 0 ? splitPath[0] : null; if (category === null) { @@ -131,16 +126,13 @@ export const hasDocumentation = (index: string, path: string): boolean => { }; export const getDocumentation = (index: string, path: string) => { - if (index === 'unknown') { - return ''; - } const splitPath = path.split('.'); const category = splitPath.length > 0 ? splitPath[0] : null; if (category === null) { - return ''; + return {}; } if (splitPath.length > 1) { - return get([category, 'fields', path], getIndexSchemaDoc(index)) || ''; + return get([category, 'fields', path], getIndexSchemaDoc(index)) || {}; } - return get(category, getIndexSchemaDoc(index)) || ''; + return get(category, getIndexSchemaDoc(index)) || {}; }; diff --git a/x-pack/legacy/plugins/siem/server/utils/beat_schema/type.ts b/x-pack/legacy/plugins/siem/server/utils/beat_schema/type.ts index f34519da34ee8..2b7be8f4b7539 100644 --- a/x-pack/legacy/plugins/siem/server/utils/beat_schema/type.ts +++ b/x-pack/legacy/plugins/siem/server/utils/beat_schema/type.ts @@ -12,25 +12,35 @@ export type IndexAlias = 'auditbeat' | 'filebeat' | 'packetbeat' | 'ecs' | 'winl */ export interface SchemaFields { + default_field: boolean; + default_fields: boolean; definition: string; + deprecated: string; description: string; doc_values: boolean; - example: string | number | object; + 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>; @@ -48,33 +58,6 @@ export interface SchemaItem { export type Schema = Array>; -/* - * ECS Interface - * - */ - -interface EcsField { - description: string; - example: string; - footnote: string; - group: number; - level: string; - name: string; - required: boolean; - type: string; -} - -interface EcsNamespace { - description: string; - fields: Readonly>; - group: number; - name: string; - title: string; - type: string; -} - -export type EcsSchema = Readonly>; - /* * Associative Array Output Interface * diff --git a/x-pack/legacy/plugins/tilemap/index.js b/x-pack/legacy/plugins/tilemap/index.js index 767a0fe72985e..d4105519ee0a7 100644 --- a/x-pack/legacy/plugins/tilemap/index.js +++ b/x-pack/legacy/plugins/tilemap/index.js @@ -15,7 +15,7 @@ export const tilemap = kibana => { require: ['xpack_main', 'vis_type_vislib'], publicDir: resolve(__dirname, 'public'), uiExports: { - visTypeEnhancers: ['plugins/tilemap/vis_type_enhancers/update_tilemap_settings'], + hacks: ['plugins/tilemap/vis_type_enhancers/update_tilemap_settings'], }, init: function(server) { const thisPlugin = this; diff --git a/x-pack/legacy/plugins/uptime/common/constants/rest_api.ts b/x-pack/legacy/plugins/uptime/common/constants/rest_api.ts index 86e2b03e13f22..dffa131870db1 100644 --- a/x-pack/legacy/plugins/uptime/common/constants/rest_api.ts +++ b/x-pack/legacy/plugins/uptime/common/constants/rest_api.ts @@ -15,7 +15,7 @@ export enum API_URLS { PING_HISTOGRAM = `/api/uptime/ping/histogram`, SNAPSHOT_COUNT = `/api/uptime/snapshot/count`, FILTERS = `/api/uptime/filters`, - logPageView = `/api/uptime/logPageView`, + LOG_PAGE_VIEW = `/api/uptime/log_page_view`, ML_MODULE_JOBS = `/api/ml/modules/jobs_exist/`, ML_SETUP_MODULE = '/api/ml/modules/setup/', diff --git a/x-pack/legacy/plugins/uptime/common/types/ping/histogram.ts b/x-pack/legacy/plugins/uptime/common/types/ping/histogram.ts index a4e03a2b762c8..3ae32e15ca55c 100644 --- a/x-pack/legacy/plugins/uptime/common/types/ping/histogram.ts +++ b/x-pack/legacy/plugins/uptime/common/types/ping/histogram.ts @@ -5,15 +5,15 @@ */ export interface HistogramDataPoint { - upCount?: number | null; + upCount?: number; - downCount?: number | null; + downCount?: number; - x?: number | null; + x?: number; - x0?: number | null; + x0?: number; - y?: number | null; + y?: number; } export interface GetPingHistogramParams { diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/alerts/uptime_alerts_flyout_wrapper.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/alerts/uptime_alerts_flyout_wrapper.tsx index b547f8b076f93..a49468ad3dd06 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/alerts/uptime_alerts_flyout_wrapper.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/connected/alerts/uptime_alerts_flyout_wrapper.tsx @@ -17,7 +17,7 @@ interface Props { export const UptimeAlertsFlyoutWrapper = ({ alertTypeId, canChangeTrigger }: Props) => { const dispatch = useDispatch(); - const setAddFlyoutVisiblity = (value: React.SetStateAction) => + const setAddFlyoutVisibility = (value: React.SetStateAction) => // @ts-ignore the value here is a boolean, and it works with the action creator function dispatch(setAlertFlyoutVisible(value)); @@ -28,7 +28,7 @@ export const UptimeAlertsFlyoutWrapper = ({ alertTypeId, canChangeTrigger }: Pro alertFlyoutVisible={alertFlyoutVisible} alertTypeId={alertTypeId} canChangeTrigger={canChangeTrigger} - setAlertFlyoutVisibility={setAddFlyoutVisiblity} + setAlertFlyoutVisibility={setAddFlyoutVisibility} /> ); }; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/alerts/alert_monitor_status.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/alerts/alert_monitor_status.tsx index 5143e1c963904..b86e85f35b17d 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/alerts/alert_monitor_status.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/alerts/alert_monitor_status.tsx @@ -268,7 +268,21 @@ export const AlertMonitorStatusComponent: React.FC = pr /> } data-test-subj="xpack.uptime.alerts.monitorStatus.numTimesExpression" - description="any monitor is down >" + description={ + filters + ? i18n.translate( + 'xpack.uptime.alerts.monitorStatus.numTimesExpression.matchingMonitors.description', + { + defaultMessage: 'matching monitors are down >', + } + ) + : i18n.translate( + 'xpack.uptime.alerts.monitorStatus.numTimesExpression.anyMonitors.description', + { + defaultMessage: 'any monitor is down >', + } + ) + } id="ping-count" value={`${numTimes} times`} /> diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/ping_histogram.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/ping_histogram.tsx index 17fa8781b828b..f988afc7fc84d 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/ping_histogram.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/ping_histogram.tsx @@ -37,6 +37,12 @@ export interface PingHistogramComponentProps { loading?: boolean; } +interface BarPoint { + x?: number; + y?: number; + type: string; +} + export const PingHistogramComponent: React.FC = ({ absoluteStartDate, absoluteEndDate, @@ -65,8 +71,8 @@ export const PingHistogramComponent: React.FC = ({ } else { const { histogram } = data; - const downSpecId = i18n.translate('xpack.uptime.snapshotHistogram.downMonitorsId', { - defaultMessage: 'Down Monitors', + const downSpecId = i18n.translate('xpack.uptime.snapshotHistogram.series.downLabel', { + defaultMessage: 'Down', }); const upMonitorsId = i18n.translate('xpack.uptime.snapshotHistogram.series.upLabel', { @@ -79,6 +85,16 @@ export const PingHistogramComponent: React.FC = ({ dateRangeEnd: moment(max).toISOString(), }); }; + + const barData: BarPoint[] = []; + + histogram.forEach(({ x, upCount, downCount }) => { + barData.push( + { x, y: downCount ?? 0, type: downSpecId }, + { x, y: upCount ?? 0, type: upMonitorsId } + ); + }); + content = ( = ({ /> [x, downCount || 0])} + color={[danger, gray]} + data={barData} id={downSpecId} - name={i18n.translate('xpack.uptime.snapshotHistogram.series.downLabel', { - defaultMessage: 'Down', + name={i18n.translate('xpack.uptime.snapshotHistogram.series.pings', { + defaultMessage: 'Monitor Pings', })} - stackAccessors={[0]} - timeZone="local" - xAccessor={0} - xScaleType="time" - yAccessors={[1]} - yScaleType="linear" - /> - [x, upCount || 0])} - id={upMonitorsId} - name={upMonitorsId} - stackAccessors={[0]} + stackAccessors={['x']} + splitSeriesAccessors={['type']} timeZone="local" - xAccessor={0} + xAccessor="x" xScaleType="time" - yAccessors={[1]} + yAccessors={['y']} yScaleType="linear" /> diff --git a/x-pack/legacy/plugins/uptime/public/hooks/use_telemetry.ts b/x-pack/legacy/plugins/uptime/public/hooks/use_telemetry.ts index fc0e0ce1c3e88..13fe523332ae5 100644 --- a/x-pack/legacy/plugins/uptime/public/hooks/use_telemetry.ts +++ b/x-pack/legacy/plugins/uptime/public/hooks/use_telemetry.ts @@ -30,6 +30,6 @@ export const useUptimeTelemetry = (page?: UptimePage) => { dateEnd: dateRangeEnd, autoRefreshEnabled: !autorefreshIsPaused, }; - apiService.post(API_URLS.logPageView, params); + apiService.post(API_URLS.LOG_PAGE_VIEW, params); }, [autorefreshInterval, autorefreshIsPaused, dateRangeEnd, dateRangeStart, page]); }; diff --git a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx index fa2998532d145..dafb20dc9c323 100644 --- a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx +++ b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx @@ -104,7 +104,10 @@ const Application = (props: UptimeAppProps) => {
- +
diff --git a/x-pack/package.json b/x-pack/package.json index 24b23256bf18e..b2ec4c3dc3f6f 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -185,7 +185,7 @@ "@elastic/eui": "21.0.1", "@elastic/filesaver": "1.1.2", "@elastic/maki": "6.2.0", - "@elastic/node-crypto": "^1.0.0", + "@elastic/node-crypto": "1.1.1", "@elastic/numeral": "2.4.0", "@kbn/babel-preset": "1.0.0", "@kbn/config-schema": "1.0.0", diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md index d217d26e84836..82cc09f5e9eca 100644 --- a/x-pack/plugins/actions/README.md +++ b/x-pack/plugins/actions/README.md @@ -28,7 +28,7 @@ Table of Contents - [RESTful API](#restful-api) - [`POST /api/action`: Create action](#post-apiaction-create-action) - [`DELETE /api/action/{id}`: Delete action](#delete-apiactionid-delete-action) - - [`GET /api/action/_find`: Find actions](#get-apiactionfind-find-actions) + - [`GET /api/action/_getAll`: Get all actions](#get-apiaction-get-all-actions) - [`GET /api/action/{id}`: Get action](#get-apiactionid-get-action) - [`GET /api/action/types`: List action types](#get-apiactiontypes-list-action-types) - [`PUT /api/action/{id}`: Update action](#put-apiactionid-update-action) @@ -92,6 +92,7 @@ Built-In-Actions are configured using the _xpack.actions_ namespoace under _kiba | _xpack.actions._**enabled** | Feature toggle which enabled Actions in Kibana. | boolean | | _xpack.actions._**whitelistedHosts** | Which _hostnames_ are whitelisted for the Built-In-Action? This list should contain hostnames of every external service you wish to interact with using Webhooks, Email or any other built in Action. Note that you may use the string "\*" in place of a specific hostname to enable Kibana to target any URL, but keep in mind the potential use of such a feature to execute [SSRF](https://www.owasp.org/index.php/Server_Side_Request_Forgery) attacks from your server. | Array | | _xpack.actions._**enabledActionTypes** | A list of _actionTypes_ id's that are enabled. A "\*" may be used as an element to indicate all registered actionTypes should be enabled. The actionTypes registered for Kibana are `.server-log`, `.slack`, `.email`, `.index`, `.pagerduty`, `.webhook`. Default: `["*"]` | Array | +| _xpack.actions._**preconfigured** | A list of preconfigured actions. Default: `[]` | Array | #### Whitelisting Built-in Action Types @@ -174,11 +175,13 @@ Params: | -------- | --------------------------------------------- | ------ | | id | The id of the action you're trying to delete. | string | -### `GET /api/action/_find`: Find actions +### `GET /api/action/_getAll`: Get all actions -Params: +No parameters. -See the [saved objects API documentation for find](https://www.elastic.co/guide/en/kibana/master/saved-objects-api-find.html). All the properties are the same except that you cannot pass in `type`. +Return all actions from saved objects merged with predefined list. +Use the [saved objects API for find](https://www.elastic.co/guide/en/kibana/master/saved-objects-api-find.html) with the proprties: `type: 'action'` and `perPage: 10000`. +List of predefined actions should be set up in Kibana.yaml. ### `GET /api/action/{id}`: Get action diff --git a/x-pack/plugins/actions/common/types.ts b/x-pack/plugins/actions/common/types.ts index f3042a701211f..61b338d47b9f5 100644 --- a/x-pack/plugins/actions/common/types.ts +++ b/x-pack/plugins/actions/common/types.ts @@ -20,4 +20,5 @@ export interface ActionResult { actionTypeId: string; name: string; config: Record; + isPreconfigured: boolean; } diff --git a/x-pack/plugins/actions/server/actions_client.mock.ts b/x-pack/plugins/actions/server/actions_client.mock.ts index 8a39d68f40bb6..431bfb1e99c3b 100644 --- a/x-pack/plugins/actions/server/actions_client.mock.ts +++ b/x-pack/plugins/actions/server/actions_client.mock.ts @@ -12,9 +12,9 @@ const createActionsClientMock = () => { const mocked: jest.Mocked = { create: jest.fn(), get: jest.fn(), - find: jest.fn(), delete: jest.fn(), update: jest.fn(), + getAll: jest.fn(), }; return mocked; }; diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts index 0df07ad58fb9e..955e1569380a5 100644 --- a/x-pack/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -51,6 +51,7 @@ beforeEach(() => { savedObjectsClient, scopedClusterClient, defaultKibanaIndex, + preconfiguredActions: [], }); }); @@ -83,6 +84,7 @@ describe('create()', () => { }); expect(result).toEqual({ id: '1', + isPreconfigured: false, name: 'my name', actionTypeId: 'my-action-type', config: {}, @@ -178,6 +180,7 @@ describe('create()', () => { }); expect(result).toEqual({ id: '1', + isPreconfigured: false, name: 'my name', actionTypeId: 'my-action-type', config: { @@ -226,6 +229,7 @@ describe('create()', () => { savedObjectsClient, scopedClusterClient, defaultKibanaIndex, + preconfiguredActions: [], }); const savedObjectCreateResult = { @@ -305,6 +309,7 @@ describe('get()', () => { const result = await actionsClient.get({ id: '1' }); expect(result).toEqual({ id: '1', + isPreconfigured: false, }); expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); expect(savedObjectsClient.get.mock.calls[0]).toMatchInlineSnapshot(` @@ -314,9 +319,44 @@ describe('get()', () => { ] `); }); + + test('return predefined action with id', async () => { + actionsClient = new ActionsClient({ + actionTypeRegistry, + savedObjectsClient, + scopedClusterClient, + defaultKibanaIndex, + preconfiguredActions: [ + { + id: 'testPreconfigured', + actionTypeId: '.slack', + secrets: { + test: 'test1', + }, + isPreconfigured: true, + name: 'test', + config: { + foo: 'bar', + }, + }, + ], + }); + + const result = await actionsClient.get({ id: 'testPreconfigured' }); + expect(result).toEqual({ + id: 'testPreconfigured', + actionTypeId: '.slack', + isPreconfigured: true, + name: 'test', + config: { + foo: 'bar', + }, + }); + expect(savedObjectsClient.get).not.toHaveBeenCalled(); + }); }); -describe('find()', () => { +describe('getAll()', () => { test('calls savedObjectsClient with parameters', async () => { const expectedResult = { total: 1, @@ -327,6 +367,7 @@ describe('find()', () => { id: '1', type: 'type', attributes: { + name: 'test', config: { foo: 'bar', }, @@ -339,31 +380,50 @@ describe('find()', () => { scopedClusterClient.callAsInternalUser.mockResolvedValueOnce({ aggregations: { '1': { doc_count: 6 }, + testPreconfigured: { doc_count: 2 }, }, }); - const result = await actionsClient.find({}); - expect(result).toEqual({ - total: 1, - perPage: 10, - page: 1, - data: [ + + actionsClient = new ActionsClient({ + actionTypeRegistry, + savedObjectsClient, + scopedClusterClient, + defaultKibanaIndex, + preconfiguredActions: [ { - id: '1', + id: 'testPreconfigured', + actionTypeId: '.slack', + secrets: {}, + isPreconfigured: true, + name: 'test', config: { foo: 'bar', }, - referencedByCount: 6, }, ], }); - expect(savedObjectsClient.find).toHaveBeenCalledTimes(1); - expect(savedObjectsClient.find.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "type": "action", + const result = await actionsClient.getAll(); + expect(result).toEqual([ + { + id: '1', + isPreconfigured: false, + name: 'test', + config: { + foo: 'bar', }, - ] - `); + referencedByCount: 6, + }, + { + id: 'testPreconfigured', + actionTypeId: '.slack', + isPreconfigured: true, + name: 'test', + config: { + foo: 'bar', + }, + referencedByCount: 2, + }, + ]); }); }); @@ -420,6 +480,7 @@ describe('update()', () => { }); expect(result).toEqual({ id: 'my-action', + isPreconfigured: false, actionTypeId: 'my-action-type', name: 'my name', config: {}, @@ -524,6 +585,7 @@ describe('update()', () => { }); expect(result).toEqual({ id: 'my-action', + isPreconfigured: false, actionTypeId: 'my-action-type', name: 'my name', config: { diff --git a/x-pack/plugins/actions/server/actions_client.ts b/x-pack/plugins/actions/server/actions_client.ts index 129829850f9c1..8f73bfb31ea4d 100644 --- a/x-pack/plugins/actions/server/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client.ts @@ -11,9 +11,16 @@ import { SavedObject, } from 'src/core/server'; +import { i18n } from '@kbn/i18n'; import { ActionTypeRegistry } from './action_type_registry'; import { validateConfig, validateSecrets } from './lib'; -import { ActionResult, FindActionResult, RawAction } from './types'; +import { ActionResult, FindActionResult, RawAction, PreConfiguredAction } from './types'; +import { PreconfiguredActionDisabledModificationError } from './lib/errors/preconfigured_action_disabled_modification'; + +// We are assuming there won't be many actions. This is why we will load +// all the actions in advance and assume the total count to not go over 10000. +// We'll set this max setting assuming it's never reached. +export const MAX_ACTIONS_RETURNED = 10000; interface ActionUpdate extends SavedObjectAttributes { name: string; @@ -29,35 +36,12 @@ interface CreateOptions { action: Action; } -interface FindOptions { - options?: { - perPage?: number; - page?: number; - search?: string; - defaultSearchOperator?: 'AND' | 'OR'; - searchFields?: string[]; - sortField?: string; - hasReference?: { - type: string; - id: string; - }; - fields?: string[]; - filter?: string; - }; -} - -interface FindResult { - page: number; - perPage: number; - total: number; - data: FindActionResult[]; -} - interface ConstructorOptions { defaultKibanaIndex: string; scopedClusterClient: IScopedClusterClient; actionTypeRegistry: ActionTypeRegistry; savedObjectsClient: SavedObjectsClientContract; + preconfiguredActions: PreConfiguredAction[]; } interface UpdateOptions { @@ -70,17 +54,20 @@ export class ActionsClient { private readonly scopedClusterClient: IScopedClusterClient; private readonly savedObjectsClient: SavedObjectsClientContract; private readonly actionTypeRegistry: ActionTypeRegistry; + private readonly preconfiguredActions: PreConfiguredAction[]; constructor({ actionTypeRegistry, defaultKibanaIndex, scopedClusterClient, savedObjectsClient, + preconfiguredActions, }: ConstructorOptions) { this.actionTypeRegistry = actionTypeRegistry; this.savedObjectsClient = savedObjectsClient; this.scopedClusterClient = scopedClusterClient; this.defaultKibanaIndex = defaultKibanaIndex; + this.preconfiguredActions = preconfiguredActions; } /** @@ -106,6 +93,7 @@ export class ActionsClient { actionTypeId: result.attributes.actionTypeId, name: result.attributes.name, config: result.attributes.config, + isPreconfigured: false, }; } @@ -113,6 +101,20 @@ export class ActionsClient { * Update action */ public async update({ id, action }: UpdateOptions): Promise { + if ( + this.preconfiguredActions.find(preconfiguredAction => preconfiguredAction.id === id) !== + undefined + ) { + throw new PreconfiguredActionDisabledModificationError( + i18n.translate('xpack.actions.serverSideErrors.predefinedActionUpdateDisabled', { + defaultMessage: 'Preconfigured action {id} is not allowed to update.', + values: { + id, + }, + }), + 'update' + ); + } const existingObject = await this.savedObjectsClient.get('action', id); const { actionTypeId } = existingObject.attributes; const { name, config, secrets } = action; @@ -134,6 +136,7 @@ export class ActionsClient { actionTypeId: result.attributes.actionTypeId as string, name: result.attributes.name as string, config: result.attributes.config as Record, + isPreconfigured: false, }; } @@ -141,6 +144,18 @@ export class ActionsClient { * Get an action */ public async get({ id }: { id: string }): Promise { + const preconfiguredActionsList = this.preconfiguredActions.find( + preconfiguredAction => preconfiguredAction.id === id + ); + if (preconfiguredActionsList !== undefined) { + return { + id, + actionTypeId: preconfiguredActionsList.actionTypeId, + name: preconfiguredActionsList.name, + config: preconfiguredActionsList.config, + isPreconfigured: true, + }; + } const result = await this.savedObjectsClient.get('action', id); return { @@ -148,36 +163,56 @@ export class ActionsClient { actionTypeId: result.attributes.actionTypeId, name: result.attributes.name, config: result.attributes.config, + isPreconfigured: false, }; } /** - * Find actions + * Get all actions with preconfigured list */ - public async find({ options = {} }: FindOptions): Promise { - const findResult = await this.savedObjectsClient.find({ - ...options, - type: 'action', - }); + public async getAll(): Promise { + const savedObjectsActions = ( + await this.savedObjectsClient.find({ + perPage: MAX_ACTIONS_RETURNED, + type: 'action', + }) + ).saved_objects.map(actionFromSavedObject); - const data = await injectExtraFindData( + const mergedResult = [ + ...savedObjectsActions, + ...this.preconfiguredActions.map(preconfiguredAction => ({ + id: preconfiguredAction.id, + actionTypeId: preconfiguredAction.actionTypeId, + name: preconfiguredAction.name, + config: preconfiguredAction.config, + isPreconfigured: true, + })), + ].sort((a, b) => a.name.localeCompare(b.name)); + return await injectExtraFindData( this.defaultKibanaIndex, this.scopedClusterClient, - findResult.saved_objects.map(actionFromSavedObject) + mergedResult ); - - return { - page: findResult.page, - perPage: findResult.per_page, - total: findResult.total, - data, - }; } /** * Delete action */ public async delete({ id }: { id: string }) { + if ( + this.preconfiguredActions.find(preconfiguredAction => preconfiguredAction.id === id) !== + undefined + ) { + throw new PreconfiguredActionDisabledModificationError( + i18n.translate('xpack.actions.serverSideErrors.predefinedActionDeleteDisabled', { + defaultMessage: 'Preconfigured action {id} is not allowed to delete.', + values: { + id, + }, + }), + 'delete' + ); + } return await this.savedObjectsClient.delete('action', id); } } @@ -186,6 +221,7 @@ function actionFromSavedObject(savedObject: SavedObject): ActionResul return { id: savedObject.id, ...savedObject.attributes, + isPreconfigured: false, }; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts index 74eead0708545..47d7aff8022ce 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts @@ -65,6 +65,11 @@ export async function sendEmail(logger: Logger, options: SendEmailOptions): Prom transportConfig.host = host; transportConfig.port = port; transportConfig.secure = !!secure; + if (!transportConfig.secure) { + transportConfig.tls = { + rejectUnauthorized: false, + }; + } } const nodemailerTransport = nodemailer.createTransport(transportConfig); diff --git a/x-pack/plugins/actions/server/config.test.ts b/x-pack/plugins/actions/server/config.test.ts index 67b7553c4a736..51e87dbd75b48 100644 --- a/x-pack/plugins/actions/server/config.test.ts +++ b/x-pack/plugins/actions/server/config.test.ts @@ -14,6 +14,44 @@ describe('config validation', () => { "enabledActionTypes": Array [ "*", ], + "preconfigured": Array [], + "whitelistedHosts": Array [ + "*", + ], + } + `); + }); + + test('action with preconfigured actions', () => { + const config: Record = { + preconfigured: [ + { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, + }, + ], + }; + expect(configSchema.validate(config)).toMatchInlineSnapshot(` + Object { + "enabled": true, + "enabledActionTypes": Array [ + "*", + ], + "preconfigured": Array [ + Object { + "actionTypeId": ".slack", + "config": Object { + "webhookUrl": "https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz", + }, + "id": "my-slack1", + "name": "Slack #xyz", + "secrets": Object {}, + }, + ], "whitelistedHosts": Array [ "*", ], diff --git a/x-pack/plugins/actions/server/config.ts b/x-pack/plugins/actions/server/config.ts index 9e4795be6c208..1f04efd1941b4 100644 --- a/x-pack/plugins/actions/server/config.ts +++ b/x-pack/plugins/actions/server/config.ts @@ -21,6 +21,18 @@ export const configSchema = schema.object({ defaultValue: [WhitelistedHosts.Any], } ), + preconfigured: schema.arrayOf( + schema.object({ + id: schema.string({ minLength: 1 }), + name: schema.string(), + actionTypeId: schema.string({ minLength: 1 }), + config: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + secrets: schema.recordOf(schema.string(), schema.any(), { defaultValue: {} }), + }), + { + defaultValue: [], + } + ), }); export type ActionsConfig = TypeOf; diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index eee2ae352fe3d..88553c314112f 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.ts @@ -11,7 +11,13 @@ import { ActionsClient as ActionsClientClass } from './actions_client'; export type ActionsClient = PublicMethodsOf; -export { ActionsPlugin, ActionResult, ActionTypeExecutorOptions, ActionType } from './types'; +export { + ActionsPlugin, + ActionResult, + ActionTypeExecutorOptions, + ActionType, + PreConfiguredAction, +} from './types'; export { PluginSetupContract, PluginStartContract } from './plugin'; export const plugin = (initContext: PluginInitializerContext) => new ActionsPlugin(initContext); diff --git a/x-pack/plugins/actions/server/lib/errors/preconfigured_action_disabled_modification.ts b/x-pack/plugins/actions/server/lib/errors/preconfigured_action_disabled_modification.ts new file mode 100644 index 0000000000000..884353e132b9c --- /dev/null +++ b/x-pack/plugins/actions/server/lib/errors/preconfigured_action_disabled_modification.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 { KibanaResponseFactory } from '../../../../../../src/core/server'; +import { ErrorThatHandlesItsOwnResponse } from './types'; + +export type PreconfiguredActionDisabledFrom = 'update' | 'delete'; + +export class PreconfiguredActionDisabledModificationError extends Error + implements ErrorThatHandlesItsOwnResponse { + public readonly disabledFrom: PreconfiguredActionDisabledFrom; + + constructor(message: string, disabledFrom: PreconfiguredActionDisabledFrom) { + super(message); + this.disabledFrom = disabledFrom; + } + + public sendResponse(res: KibanaResponseFactory) { + return res.badRequest({ body: { message: this.message } }); + } +} diff --git a/x-pack/plugins/actions/server/lib/license_state.mock.ts b/x-pack/plugins/actions/server/lib/license_state.mock.ts index 72a21f878a150..d59e9dbdc540f 100644 --- a/x-pack/plugins/actions/server/lib/license_state.mock.ts +++ b/x-pack/plugins/actions/server/lib/license_state.mock.ts @@ -5,7 +5,6 @@ */ import { ILicenseState } from './license_state'; -import { LICENSE_CHECK_STATE } from '../../../licensing/server'; export const createLicenseStateMock = () => { const licenseState: jest.Mocked = { @@ -14,7 +13,7 @@ export const createLicenseStateMock = () => { ensureLicenseForActionType: jest.fn(), isLicenseValidForActionType: jest.fn(), checkLicense: jest.fn().mockResolvedValue({ - state: LICENSE_CHECK_STATE.Valid, + state: 'valid', }), }; return licenseState; diff --git a/x-pack/plugins/actions/server/lib/license_state.test.ts b/x-pack/plugins/actions/server/lib/license_state.test.ts index ba1fbcb83464a..eb10e69a444e8 100644 --- a/x-pack/plugins/actions/server/lib/license_state.test.ts +++ b/x-pack/plugins/actions/server/lib/license_state.test.ts @@ -8,7 +8,7 @@ import { ActionType } from '../types'; import { BehaviorSubject } from 'rxjs'; import { LicenseState, ILicenseState } from './license_state'; import { licensingMock } from '../../../licensing/server/mocks'; -import { LICENSE_CHECK_STATE, ILicense } from '../../../licensing/server'; +import { ILicense } from '../../../licensing/server'; describe('checkLicense()', () => { let getRawLicense: any; @@ -21,7 +21,7 @@ describe('checkLicense()', () => { beforeEach(() => { const license = licensingMock.createLicense({ license: { status: 'invalid' } }); license.check = jest.fn(() => ({ - state: LICENSE_CHECK_STATE.Invalid, + state: 'invalid', })); getRawLicense.mockReturnValue(license); }); @@ -38,7 +38,7 @@ describe('checkLicense()', () => { beforeEach(() => { const license = licensingMock.createLicense({ license: { status: 'active' } }); license.check = jest.fn(() => ({ - state: LICENSE_CHECK_STATE.Valid, + state: 'valid', })); getRawLicense.mockReturnValue(license); }); diff --git a/x-pack/plugins/actions/server/lib/license_state.ts b/x-pack/plugins/actions/server/lib/license_state.ts index 9d87818805dcf..ae7180c4658bc 100644 --- a/x-pack/plugins/actions/server/lib/license_state.ts +++ b/x-pack/plugins/actions/server/lib/license_state.ts @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import { Observable, Subscription } from 'rxjs'; import { assertNever } from '../../../../../src/core/utils'; -import { ILicense, LICENSE_CHECK_STATE } from '../../../licensing/common/types'; +import { ILicense } from '../../../licensing/common/types'; import { PLUGIN } from '../constants/plugin'; import { ActionType } from '../types'; import { ActionTypeDisabledError } from './errors'; @@ -52,13 +52,13 @@ export class LicenseState { const check = this.license.check(actionType.id, actionType.minimumLicenseRequired); switch (check.state) { - case LICENSE_CHECK_STATE.Expired: + case 'expired': return { isValid: false, reason: 'expired' }; - case LICENSE_CHECK_STATE.Invalid: + case 'invalid': return { isValid: false, reason: 'invalid' }; - case LICENSE_CHECK_STATE.Unavailable: + case 'unavailable': return { isValid: false, reason: 'unavailable' }; - case LICENSE_CHECK_STATE.Valid: + case 'valid': return { isValid: true }; default: return assertNever(check.state); @@ -125,20 +125,20 @@ export class LicenseState { const check = license.check(PLUGIN.ID, PLUGIN.MINIMUM_LICENSE_REQUIRED); switch (check.state) { - case LICENSE_CHECK_STATE.Expired: + case 'expired': return { showAppLink: true, enableAppLink: false, message: check.message || '', }; - case LICENSE_CHECK_STATE.Invalid: - case LICENSE_CHECK_STATE.Unavailable: + case 'invalid': + case 'unavailable': return { showAppLink: false, enableAppLink: false, message: check.message || '', }; - case LICENSE_CHECK_STATE.Valid: + case 'valid': return { showAppLink: true, enableAppLink: true, diff --git a/x-pack/plugins/actions/server/mocks.ts b/x-pack/plugins/actions/server/mocks.ts index 75396f2aad897..bc4268bb69872 100644 --- a/x-pack/plugins/actions/server/mocks.ts +++ b/x-pack/plugins/actions/server/mocks.ts @@ -21,6 +21,7 @@ const createStartMock = () => { execute: jest.fn(), isActionTypeEnabled: jest.fn(), getActionsClientWithRequest: jest.fn().mockResolvedValue(actionsClientMock.create()), + preconfiguredActions: [], }; return mock; }; diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index 383f84590fbc6..6215b08df81d4 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -31,7 +31,34 @@ describe('Actions Plugin', () => { let pluginsSetup: jest.Mocked; beforeEach(() => { - context = coreMock.createPluginInitializerContext(); + context = coreMock.createPluginInitializerContext({ + preconfigured: [ + { + id: 'my-slack1', + actionTypeId: '.slack', + name: 'Slack #xyz', + description: 'Send a message to the #xyz channel', + config: { + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz', + }, + }, + { + id: 'custom-system-abc-connector', + actionTypeId: 'system-abc-action-type', + description: 'Send a notification to system ABC', + name: 'System ABC', + config: { + xyzConfig1: 'value1', + xyzConfig2: 'value2', + listOfThings: ['a', 'b', 'c', 'd'], + }, + secrets: { + xyzSecret1: 'credential1', + xyzSecret2: 'credential2', + }, + }, + ], + }); plugin = new ActionsPlugin(context); coreSetup = coreMock.createSetup(); @@ -160,7 +187,9 @@ describe('Actions Plugin', () => { let pluginsStart: jest.Mocked; beforeEach(() => { - const context = coreMock.createPluginInitializerContext(); + const context = coreMock.createPluginInitializerContext({ + preconfigured: [], + }); plugin = new ActionsPlugin(context); coreSetup = coreMock.createSetup(); coreStart = coreMock.createStart(); diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index ce31e62bc9b8e..34c9e7aa9e8b8 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -30,7 +30,7 @@ import { LICENSE_TYPE } from '../../licensing/common/types'; import { SpacesPluginSetup, SpacesServiceSetup } from '../../spaces/server'; import { ActionsConfig } from './config'; -import { Services, ActionType } from './types'; +import { Services, ActionType, PreConfiguredAction } from './types'; import { ActionExecutor, TaskRunnerFactory, LicenseState, ILicenseState } from './lib'; import { ActionsClient } from './actions_client'; import { ActionTypeRegistry } from './action_type_registry'; @@ -44,7 +44,7 @@ import { getActionsConfigurationUtilities } from './actions_config'; import { createActionRoute, deleteActionRoute, - findActionRoute, + getAllActionRoute, getActionRoute, updateActionRoute, listActionTypesRoute, @@ -67,6 +67,7 @@ export interface PluginStartContract { isActionTypeEnabled(id: string): boolean; execute(options: ExecuteOptions): Promise; getActionsClientWithRequest(request: KibanaRequest): Promise>; + preconfiguredActions: PreConfiguredAction[]; } export interface ActionsPluginsSetup { @@ -97,6 +98,7 @@ export class ActionsPlugin implements Plugin, Plugi private eventLogger?: IEventLogger; private isESOUsingEphemeralEncryptionKey?: boolean; private readonly telemetryLogger: Logger; + private readonly preconfiguredActions: PreConfiguredAction[]; constructor(initContext: PluginInitializerContext) { this.config = initContext.config @@ -113,6 +115,7 @@ export class ActionsPlugin implements Plugin, Plugi this.logger = initContext.logger.get('actions'); this.telemetryLogger = initContext.logger.get('telemetry'); + this.preconfiguredActions = []; } public async setup(core: CoreSetup, plugins: ActionsPluginsSetup): Promise { @@ -151,8 +154,14 @@ export class ActionsPlugin implements Plugin, Plugi // get executions count const taskRunnerFactory = new TaskRunnerFactory(actionExecutor); - const actionsConfigUtils = getActionsConfigurationUtilities( - (await this.config) as ActionsConfig + const actionsConfig = (await this.config) as ActionsConfig; + const actionsConfigUtils = getActionsConfigurationUtilities(actionsConfig); + + this.preconfiguredActions.push( + ...actionsConfig.preconfigured.map( + preconfiguredAction => + ({ ...preconfiguredAction, isPreconfigured: true } as PreConfiguredAction) + ) ); const actionTypeRegistry = new ActionTypeRegistry({ taskRunnerFactory, @@ -197,7 +206,7 @@ export class ActionsPlugin implements Plugin, Plugi createActionRoute(router, this.licenseState); deleteActionRoute(router, this.licenseState); getActionRoute(router, this.licenseState); - findActionRoute(router, this.licenseState); + getAllActionRoute(router, this.licenseState); updateActionRoute(router, this.licenseState); listActionTypesRoute(router, this.licenseState); executeActionRoute(router, this.licenseState, actionExecutor); @@ -226,6 +235,7 @@ export class ActionsPlugin implements Plugin, Plugi kibanaIndex, adminClient, isESOUsingEphemeralEncryptionKey, + preconfiguredActions, } = this; actionExecutor!.initialize({ @@ -271,8 +281,10 @@ export class ActionsPlugin implements Plugin, Plugi actionTypeRegistry: actionTypeRegistry!, defaultKibanaIndex: await kibanaIndex, scopedClusterClient: adminClient!.asScoped(request), + preconfiguredActions, }); }, + preconfiguredActions, }; } @@ -289,7 +301,12 @@ export class ActionsPlugin implements Plugin, Plugi private createRouteHandlerContext = ( defaultKibanaIndex: string ): IContextProvider, 'actions'> => { - const { actionTypeRegistry, adminClient, isESOUsingEphemeralEncryptionKey } = this; + const { + actionTypeRegistry, + adminClient, + isESOUsingEphemeralEncryptionKey, + preconfiguredActions, + } = this; return async function actionsRouteHandlerContext(context, request) { return { getActionsClient: () => { @@ -303,6 +320,7 @@ export class ActionsPlugin implements Plugin, Plugi actionTypeRegistry: actionTypeRegistry!, defaultKibanaIndex, scopedClusterClient: adminClient!.asScoped(request), + preconfiguredActions, }); }, listTypes: actionTypeRegistry!.list.bind(actionTypeRegistry!), diff --git a/x-pack/plugins/actions/server/routes/delete.ts b/x-pack/plugins/actions/server/routes/delete.ts index cddebb3a8e31e..ffd1f0faabbab 100644 --- a/x-pack/plugins/actions/server/routes/delete.ts +++ b/x-pack/plugins/actions/server/routes/delete.ts @@ -17,7 +17,7 @@ import { IKibanaResponse, KibanaResponseFactory, } from 'kibana/server'; -import { ILicenseState, verifyApiAccess } from '../lib'; +import { ILicenseState, verifyApiAccess, isErrorThatHandlesItsOwnResponse } from '../lib'; import { BASE_ACTION_API_PATH } from '../../common'; const paramSchema = schema.object({ @@ -46,8 +46,15 @@ export const deleteActionRoute = (router: IRouter, licenseState: ILicenseState) } const actionsClient = context.actions.getActionsClient(); const { id } = req.params; - await actionsClient.delete({ id }); - return res.noContent(); + try { + await actionsClient.delete({ id }); + return res.noContent(); + } catch (e) { + if (isErrorThatHandlesItsOwnResponse(e)) { + return e.sendResponse(res); + } + throw e; + } }) ); }; diff --git a/x-pack/plugins/actions/server/routes/find.test.ts b/x-pack/plugins/actions/server/routes/find.test.ts deleted file mode 100644 index 1b130421fa71f..0000000000000 --- a/x-pack/plugins/actions/server/routes/find.test.ts +++ /dev/null @@ -1,152 +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 { findActionRoute } from './find'; -import { mockRouter, RouterMock } from '../../../../../src/core/server/http/router/router.mock'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { verifyApiAccess } from '../lib'; -import { mockHandlerArguments } from './_mock_handler_arguments'; - -jest.mock('../lib/verify_api_access.ts', () => ({ - verifyApiAccess: jest.fn(), -})); - -beforeEach(() => { - jest.resetAllMocks(); -}); - -describe('findActionRoute', () => { - it('finds actions with proper parameters', async () => { - const licenseState = licenseStateMock.create(); - const router: RouterMock = mockRouter.create(); - - findActionRoute(router, licenseState); - - const [config, handler] = router.get.mock.calls[0]; - - expect(config.path).toMatchInlineSnapshot(`"/api/action/_find"`); - expect(config.options).toMatchInlineSnapshot(` - Object { - "tags": Array [ - "access:actions-read", - ], - } - `); - - const findResult = { - page: 1, - perPage: 1, - total: 0, - data: [], - }; - const actionsClient = { - find: jest.fn().mockResolvedValueOnce(findResult), - }; - - const [context, req, res] = mockHandlerArguments( - { actionsClient }, - { - query: { - per_page: 1, - page: 1, - default_search_operator: 'OR', - }, - }, - ['ok'] - ); - - expect(await handler(context, req, res)).toMatchInlineSnapshot(` - Object { - "body": Object { - "data": Array [], - "page": 1, - "perPage": 1, - "total": 0, - }, - } - `); - - expect(actionsClient.find).toHaveBeenCalledTimes(1); - expect(actionsClient.find.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "options": Object { - "defaultSearchOperator": "OR", - "fields": undefined, - "filter": undefined, - "page": 1, - "perPage": 1, - "search": undefined, - "sortField": undefined, - "sortOrder": undefined, - }, - }, - ] - `); - - expect(res.ok).toHaveBeenCalledWith({ - body: findResult, - }); - }); - - it('ensures the license allows finding actions', async () => { - const licenseState = licenseStateMock.create(); - const router: RouterMock = mockRouter.create(); - - findActionRoute(router, licenseState); - - const [, handler] = router.get.mock.calls[0]; - - const actionsClient = { - find: jest.fn().mockResolvedValueOnce({ - page: 1, - perPage: 1, - total: 0, - data: [], - }), - }; - - const [context, req, res] = mockHandlerArguments(actionsClient, { - query: { - per_page: 1, - page: 1, - default_search_operator: 'OR', - }, - }); - - await handler(context, req, res); - - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); - }); - - it('ensures the license check prevents finding actions', async () => { - const licenseState = licenseStateMock.create(); - const router: RouterMock = mockRouter.create(); - - (verifyApiAccess as jest.Mock).mockImplementation(() => { - throw new Error('OMG'); - }); - - findActionRoute(router, licenseState); - - const [, handler] = router.get.mock.calls[0]; - - const [context, req, res] = mockHandlerArguments( - {}, - { - query: { - per_page: 1, - page: 1, - default_search_operator: 'OR', - }, - }, - ['ok'] - ); - expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); - - expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); - }); -}); diff --git a/x-pack/plugins/actions/server/routes/find.ts b/x-pack/plugins/actions/server/routes/find.ts deleted file mode 100644 index 45b967629a2a8..0000000000000 --- a/x-pack/plugins/actions/server/routes/find.ts +++ /dev/null @@ -1,95 +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 { schema, TypeOf } from '@kbn/config-schema'; -import { - IRouter, - RequestHandlerContext, - KibanaRequest, - IKibanaResponse, - KibanaResponseFactory, -} from 'kibana/server'; -import { FindOptions } from '../../../alerting/server'; -import { ILicenseState, verifyApiAccess } from '../lib'; -import { BASE_ACTION_API_PATH } from '../../common'; - -// config definition -const querySchema = schema.object({ - per_page: schema.number({ defaultValue: 20, min: 0 }), - page: schema.number({ defaultValue: 1, min: 1 }), - search: schema.maybe(schema.string()), - default_search_operator: schema.oneOf([schema.literal('OR'), schema.literal('AND')], { - defaultValue: 'OR', - }), - search_fields: schema.maybe(schema.oneOf([schema.arrayOf(schema.string()), schema.string()])), - sort_field: schema.maybe(schema.string()), - sort_order: schema.maybe(schema.oneOf([schema.literal('asc'), schema.literal('desc')])), - has_reference: schema.maybe( - // use nullable as maybe is currently broken - // in config-schema - schema.nullable( - schema.object({ - type: schema.string(), - id: schema.string(), - }) - ) - ), - fields: schema.maybe(schema.arrayOf(schema.string())), - filter: schema.maybe(schema.string()), -}); - -export const findActionRoute = (router: IRouter, licenseState: ILicenseState) => { - router.get( - { - path: `${BASE_ACTION_API_PATH}/_find`, - validate: { - query: querySchema, - }, - options: { - tags: ['access:actions-read'], - }, - }, - router.handleLegacyErrors(async function( - context: RequestHandlerContext, - req: KibanaRequest, any, any>, - res: KibanaResponseFactory - ): Promise> { - verifyApiAccess(licenseState); - if (!context.actions) { - return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); - } - const actionsClient = context.actions.getActionsClient(); - const query = req.query; - const options: FindOptions['options'] = { - perPage: query.per_page, - page: query.page, - search: query.search, - defaultSearchOperator: query.default_search_operator, - sortField: query.sort_field, - fields: query.fields, - filter: query.filter, - sortOrder: query.sort_order, - }; - - if (query.search_fields) { - options.searchFields = Array.isArray(query.search_fields) - ? query.search_fields - : [query.search_fields]; - } - - if (query.has_reference) { - options.hasReference = query.has_reference; - } - - const findResult = await actionsClient.find({ - options, - }); - return res.ok({ - body: findResult, - }); - }) - ); -}; diff --git a/x-pack/plugins/actions/server/routes/get_all.test.ts b/x-pack/plugins/actions/server/routes/get_all.test.ts new file mode 100644 index 0000000000000..6499427b8c1a5 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/get_all.test.ts @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getAllActionRoute } from './get_all'; +import { mockRouter, RouterMock } from '../../../../../src/core/server/http/router/router.mock'; +import { licenseStateMock } from '../lib/license_state.mock'; +import { verifyApiAccess } from '../lib'; +import { mockHandlerArguments } from './_mock_handler_arguments'; + +jest.mock('../lib/verify_api_access.ts', () => ({ + verifyApiAccess: jest.fn(), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('getAllActionRoute', () => { + it('get all actions with proper parameters', async () => { + const licenseState = licenseStateMock.create(); + const router: RouterMock = mockRouter.create(); + + getAllActionRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/action/_getAll"`); + expect(config.options).toMatchInlineSnapshot(` + Object { + "tags": Array [ + "access:actions-read", + ], + } + `); + + const actionsClient = { + getAll: jest.fn().mockResolvedValueOnce([]), + }; + + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); + + expect(await handler(context, req, res)).toMatchInlineSnapshot(` + Object { + "body": Array [], + } + `); + + expect(actionsClient.getAll).toHaveBeenCalledTimes(1); + + expect(res.ok).toHaveBeenCalledWith({ + body: [], + }); + }); + + it('ensures the license allows getting all actions', async () => { + const licenseState = licenseStateMock.create(); + const router: RouterMock = mockRouter.create(); + + getAllActionRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/action/_getAll"`); + expect(config.options).toMatchInlineSnapshot(` + Object { + "tags": Array [ + "access:actions-read", + ], + } + `); + + const actionsClient = { + getAll: jest.fn().mockResolvedValueOnce([]), + }; + + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); + + await handler(context, req, res); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); + + it('ensures the license check prevents getting all actions', async () => { + const licenseState = licenseStateMock.create(); + const router: RouterMock = mockRouter.create(); + + (verifyApiAccess as jest.Mock).mockImplementation(() => { + throw new Error('OMG'); + }); + + getAllActionRoute(router, licenseState); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/action/_getAll"`); + expect(config.options).toMatchInlineSnapshot(` + Object { + "tags": Array [ + "access:actions-read", + ], + } + `); + + const actionsClient = { + getAll: jest.fn().mockResolvedValueOnce([]), + }; + + const [context, req, res] = mockHandlerArguments({ actionsClient }, {}, ['ok']); + + expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`); + + expect(verifyApiAccess).toHaveBeenCalledWith(licenseState); + }); +}); diff --git a/x-pack/plugins/actions/server/routes/get_all.ts b/x-pack/plugins/actions/server/routes/get_all.ts new file mode 100644 index 0000000000000..c70a13bc01c9f --- /dev/null +++ b/x-pack/plugins/actions/server/routes/get_all.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 { + IRouter, + RequestHandlerContext, + KibanaRequest, + IKibanaResponse, + KibanaResponseFactory, +} from 'kibana/server'; +import { ILicenseState, verifyApiAccess } from '../lib'; +import { BASE_ACTION_API_PATH } from '../../common'; + +export const getAllActionRoute = (router: IRouter, licenseState: ILicenseState) => { + router.get( + { + path: `${BASE_ACTION_API_PATH}/_getAll`, + validate: {}, + options: { + tags: ['access:actions-read'], + }, + }, + router.handleLegacyErrors(async function( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + verifyApiAccess(licenseState); + if (!context.actions) { + return res.badRequest({ body: 'RouteHandlerContext is not registered for actions' }); + } + const actionsClient = context.actions.getActionsClient(); + const result = await actionsClient.getAll(); + return res.ok({ + body: result, + }); + }) + ); +}; diff --git a/x-pack/plugins/actions/server/routes/index.ts b/x-pack/plugins/actions/server/routes/index.ts index 33191132bece5..94f9ec1c94364 100644 --- a/x-pack/plugins/actions/server/routes/index.ts +++ b/x-pack/plugins/actions/server/routes/index.ts @@ -6,7 +6,7 @@ export { createActionRoute } from './create'; export { deleteActionRoute } from './delete'; -export { findActionRoute } from './find'; +export { getAllActionRoute } from './get_all'; export { getActionRoute } from './get'; export { updateActionRoute } from './update'; export { listActionTypesRoute } from './list_action_types'; diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index 999e739e77060..92e38d77314f8 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -55,6 +55,11 @@ export interface ActionResult { actionTypeId: string; name: string; config: Record; + isPreconfigured: boolean; +} + +export interface PreConfiguredAction extends ActionResult { + secrets: Record; } export interface FindActionResult extends ActionResult { diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts similarity index 94% rename from src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts rename to x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts index 2574a9e529ebf..e4bd73558b357 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.test.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.test.ts @@ -1,29 +1,18 @@ /* - * 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. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. */ import { DynamicActionManager } from './dynamic_action_manager'; import { ActionStorage, MemoryActionStorage, SerializedEvent } from './dynamic_action_storage'; -import { UiActionsService } from '../service'; -import { ActionFactoryDefinition } from './action_factory_definition'; -import { ActionRegistry } from '../types'; -import { SerializedAction } from './types'; -import { of } from '../../../kibana_utils'; +import { + UiActionsService, + UiActionsActionFactoryDefinition as ActionFactoryDefinition, + UiActionsSerializedAction as SerializedAction, + UiActionsActionInternal as ActionInternal, +} from '../../../../../src/plugins/ui_actions/public'; +import { of } from '../../../../../src/plugins/kibana_utils'; const actionFactoryDefinition1: ActionFactoryDefinition = { id: 'ACTION_FACTORY_1', @@ -82,7 +71,7 @@ const event3: SerializedEvent = { const setup = (events: readonly SerializedEvent[] = []) => { const isCompatible = async () => true; const storage: ActionStorage = new MemoryActionStorage(events); - const actions: ActionRegistry = new Map(); + const actions = new Map(); const uiActions = new UiActionsService({ actions, }); diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.ts similarity index 88% rename from src/plugins/ui_actions/public/actions/dynamic_action_manager.ts rename to x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.ts index 97eb5b05fbbc2..73aff70332fc3 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager.ts @@ -1,31 +1,20 @@ /* - * 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. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. */ import { v4 as uuidv4 } from 'uuid'; import { Subscription } from 'rxjs'; import { ActionStorage, SerializedEvent } from './dynamic_action_storage'; -import { UiActionsService } from '../service'; -import { SerializedAction } from './types'; -import { TriggerContextMapping } from '../types'; -import { ActionDefinition } from './action'; +import { + UiActionsService, + UiActionsSerializedAction as SerializedAction, + TriggerContextMapping, + UiActionsActionDefinition as ActionDefinition, +} from '../../../../../src/plugins/ui_actions/public'; import { defaultState, transitions, selectors, State } from './dynamic_action_manager_state'; -import { StateContainer, createStateContainer } from '../../../kibana_utils'; +import { StateContainer, createStateContainer } from '../../../../../src/plugins/kibana_utils'; const compareEvents = ( a: ReadonlyArray<{ eventId: string }>, diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager_state.ts similarity index 75% rename from src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts rename to x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager_state.ts index 636af076ea39f..9f10eced43a65 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_manager_state.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_manager_state.ts @@ -1,20 +1,7 @@ /* - * 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. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. */ import { SerializedEvent } from './dynamic_action_storage'; diff --git a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_storage.ts similarity index 77% rename from src/plugins/ui_actions/public/actions/dynamic_action_storage.ts rename to x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_storage.ts index 28550a671782e..92d2e8f0c3da7 100644 --- a/src/plugins/ui_actions/public/actions/dynamic_action_storage.ts +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/dynamic_action_storage.ts @@ -1,26 +1,13 @@ /* - * 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. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under 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 { Observable, Subject } from 'rxjs'; -import { SerializedAction } from './types'; +import { UiActionsSerializedAction as SerializedAction } from '../../../../../src/plugins/ui_actions/public'; /** * Serialized representation of event-action pair, used to persist in storage. diff --git a/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/index.ts b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/index.ts new file mode 100644 index 0000000000000..a2f2818d29731 --- /dev/null +++ b/x-pack/plugins/advanced_ui_actions/public/dynamic_actions/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 './dynamic_action_storage'; +export * from './dynamic_action_manager_state'; +export * from './dynamic_action_manager'; diff --git a/x-pack/plugins/advanced_ui_actions/public/index.ts b/x-pack/plugins/advanced_ui_actions/public/index.ts index 55361105dcb0f..9672fb10f9e16 100644 --- a/x-pack/plugins/advanced_ui_actions/public/index.ts +++ b/x-pack/plugins/advanced_ui_actions/public/index.ts @@ -26,3 +26,11 @@ export { Configurable as AdvancedUiActionsConfigurable, CollectConfigProps as AdvancedUiActionsCollectConfigProps, } from './util'; + +export { + AbstractActionStorage as UiActionsEnhancedAbstractActionStorage, + DynamicActionManager as UiActionsEnhancedDynamicActionManager, + DynamicActionManagerParams as UiActionsEnhancedDynamicActionManagerParams, + DynamicActionManagerState as UiActionsEnhancedDynamicActionManagerState, + MemoryActionStorage as UiActionsEnhancedMemoryActionStorage, +} from './dynamic_actions'; diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts index 9d4ea69a63609..2574e73dd4f9a 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerting/common/index.ts @@ -17,6 +17,7 @@ export interface ActionGroup { export interface AlertingFrameworkHealth { isSufficientlySecure: boolean; + hasPermanentEncryptionKey: boolean; } export const BASE_ALERT_API_PATH = '/api/alert'; diff --git a/x-pack/plugins/alerting/kibana.json b/x-pack/plugins/alerting/kibana.json index 02514511e7560..59c4bb2221b0a 100644 --- a/x-pack/plugins/alerting/kibana.json +++ b/x-pack/plugins/alerting/kibana.json @@ -5,6 +5,6 @@ "version": "8.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "alerting"], - "requiredPlugins": ["licensing", "taskManager", "encryptedSavedObjects", "actions"], + "requiredPlugins": ["licensing", "taskManager", "encryptedSavedObjects", "actions", "eventLog"], "optionalPlugins": ["usageCollection", "spaces", "security"] } diff --git a/x-pack/plugins/alerting/server/alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client.test.ts index 0e929ff457fbd..a9ff5ee8ecdc6 100644 --- a/x-pack/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client.test.ts @@ -30,6 +30,7 @@ const alertsClientParams = { invalidateAPIKey: jest.fn(), logger: loggingServiceMock.create().get(), encryptedSavedObjectsPlugin: encryptedSavedObjects, + preconfiguredActions: [], }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client.ts index 5538b44b69fcb..6f8478df58a53 100644 --- a/x-pack/plugins/alerting/server/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client.ts @@ -13,6 +13,7 @@ import { SavedObjectReference, SavedObject, } from 'src/core/server'; +import { PreConfiguredAction } from '../../actions/server'; import { Alert, PartialAlert, @@ -53,6 +54,7 @@ interface ConstructorOptions { getUserName: () => Promise; createAPIKey: () => Promise; invalidateAPIKey: (params: InvalidateAPIKeyParams) => Promise; + preconfiguredActions: PreConfiguredAction[]; } export interface FindOptions { @@ -123,6 +125,7 @@ export class AlertsClient { private readonly invalidateAPIKey: ( params: InvalidateAPIKeyParams ) => Promise; + private preconfiguredActions: PreConfiguredAction[]; encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; constructor({ @@ -136,6 +139,7 @@ export class AlertsClient { createAPIKey, invalidateAPIKey, encryptedSavedObjectsPlugin, + preconfiguredActions, }: ConstructorOptions) { this.logger = logger; this.getUserName = getUserName; @@ -147,6 +151,7 @@ export class AlertsClient { this.createAPIKey = createAPIKey; this.invalidateAPIKey = invalidateAPIKey; this.encryptedSavedObjectsPlugin = encryptedSavedObjectsPlugin; + this.preconfiguredActions = preconfiguredActions; } public async create({ data, options }: CreateOptions): Promise { @@ -659,18 +664,37 @@ export class AlertsClient { private async denormalizeActions( alertActions: NormalizedAlertAction[] ): Promise<{ actions: RawAlert['actions']; references: SavedObjectReference[] }> { - // Fetch action objects in bulk - const actionIds = [...new Set(alertActions.map(alertAction => alertAction.id))]; - const bulkGetOpts = actionIds.map(id => ({ id, type: 'action' })); - const bulkGetResult = await this.savedObjectsClient.bulkGet(bulkGetOpts); const actionMap = new Map(); - for (const action of bulkGetResult.saved_objects) { - if (action.error) { - throw Boom.badRequest( - `Failed to load action ${action.id} (${action.error.statusCode}): ${action.error.message}` - ); + // map preconfigured actions + for (const alertAction of alertActions) { + const action = this.preconfiguredActions.find( + preconfiguredAction => preconfiguredAction.id === alertAction.id + ); + if (action !== undefined) { + actionMap.set(action.id, action); + } + } + // Fetch action objects in bulk + // Excluding preconfigured actions to avoid an not found error, which is already mapped + const actionIds = [ + ...new Set( + alertActions + .filter(alertAction => !actionMap.has(alertAction.id)) + .map(alertAction => alertAction.id) + ), + ]; + if (actionIds.length > 0) { + const bulkGetOpts = actionIds.map(id => ({ id, type: 'action' })); + const bulkGetResult = await this.savedObjectsClient.bulkGet(bulkGetOpts); + + for (const action of bulkGetResult.saved_objects) { + if (action.error) { + throw Boom.badRequest( + `Failed to load action ${action.id} (${action.error.statusCode}): ${action.error.message}` + ); + } + actionMap.set(action.id, action); } - actionMap.set(action.id, action); } // Extract references and set actionTypeId const references: SavedObjectReference[] = []; @@ -681,10 +705,16 @@ export class AlertsClient { name: actionRef, type: 'action', }); + const actionMapValue = actionMap.get(id); + // if action is a save object, than actionTypeId should be under attributes property + // if action is a preconfigured, than actionTypeId is the action property + const actionTypeId = actionIds.find(actionId => actionId === id) + ? actionMapValue.attributes.actionTypeId + : actionMapValue.actionTypeId; return { ...alertAction, actionRef, - actionTypeId: actionMap.get(id).attributes.actionTypeId, + actionTypeId, }; }); return { diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts b/x-pack/plugins/alerting/server/alerts_client_factory.test.ts index 4c74ca54a0d2f..951d18a33b35f 100644 --- a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client_factory.test.ts @@ -28,6 +28,7 @@ const alertsClientFactoryParams: jest.Mocked = { getSpaceId: jest.fn(), spaceIdToNamespace: jest.fn(), encryptedSavedObjectsPlugin: encryptedSavedObjectsMock.createStart(), + preconfiguredActions: [], }; const fakeRequest: Request = { headers: {}, @@ -67,6 +68,7 @@ test('creates an alerts client with proper constructor arguments', async () => { createAPIKey: expect.any(Function), invalidateAPIKey: expect.any(Function), encryptedSavedObjectsPlugin: alertsClientFactoryParams.encryptedSavedObjectsPlugin, + preconfiguredActions: [], }); }); diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.ts b/x-pack/plugins/alerting/server/alerts_client_factory.ts index fd480658e236a..734417e72733e 100644 --- a/x-pack/plugins/alerting/server/alerts_client_factory.ts +++ b/x-pack/plugins/alerting/server/alerts_client_factory.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { PreConfiguredAction } from '../../actions/server'; import { AlertsClient } from './alerts_client'; import { AlertTypeRegistry, SpaceIdToNamespaceFunction } from './types'; import { KibanaRequest, Logger, SavedObjectsClientContract } from '../../../../src/core/server'; @@ -19,6 +20,7 @@ export interface AlertsClientFactoryOpts { getSpaceId: (request: KibanaRequest) => string | undefined; spaceIdToNamespace: SpaceIdToNamespaceFunction; encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; + preconfiguredActions: PreConfiguredAction[]; } export class AlertsClientFactory { @@ -30,6 +32,7 @@ export class AlertsClientFactory { private getSpaceId!: (request: KibanaRequest) => string | undefined; private spaceIdToNamespace!: SpaceIdToNamespaceFunction; private encryptedSavedObjectsPlugin!: EncryptedSavedObjectsPluginStart; + private preconfiguredActions!: PreConfiguredAction[]; public initialize(options: AlertsClientFactoryOpts) { if (this.isInitialized) { @@ -43,6 +46,7 @@ export class AlertsClientFactory { this.securityPluginSetup = options.securityPluginSetup; this.spaceIdToNamespace = options.spaceIdToNamespace; this.encryptedSavedObjectsPlugin = options.encryptedSavedObjectsPlugin; + this.preconfiguredActions = options.preconfiguredActions; } public create( @@ -100,6 +104,7 @@ export class AlertsClientFactory { result: invalidateAPIKeyResult, }; }, + preconfiguredActions: this.preconfiguredActions, }); } } diff --git a/x-pack/plugins/alerting/server/lib/license_state.mock.ts b/x-pack/plugins/alerting/server/lib/license_state.mock.ts index f36f3a9eaeade..aaccbfcc0af0e 100644 --- a/x-pack/plugins/alerting/server/lib/license_state.mock.ts +++ b/x-pack/plugins/alerting/server/lib/license_state.mock.ts @@ -6,7 +6,7 @@ import { of } from 'rxjs'; import { LicenseState } from './license_state'; -import { LICENSE_CHECK_STATE, ILicense } from '../../../licensing/server'; +import { ILicense } from '../../../licensing/server'; export const mockLicenseState = () => { const license: ILicense = { @@ -24,7 +24,7 @@ export const mockLicenseState = () => { }, check() { return { - state: LICENSE_CHECK_STATE.Valid, + state: 'valid', }; }, getFeature() { diff --git a/x-pack/plugins/alerting/server/lib/license_state.test.ts b/x-pack/plugins/alerting/server/lib/license_state.test.ts index 8feb8d74976fd..9bbd619dc4868 100644 --- a/x-pack/plugins/alerting/server/lib/license_state.test.ts +++ b/x-pack/plugins/alerting/server/lib/license_state.test.ts @@ -7,7 +7,6 @@ import expect from '@kbn/expect'; import { LicenseState } from './license_state'; import { licensingMock } from '../../../../plugins/licensing/server/mocks'; -import { LICENSE_CHECK_STATE } from '../../../../plugins/licensing/server'; describe('license_state', () => { let getRawLicense: any; @@ -20,7 +19,7 @@ describe('license_state', () => { beforeEach(() => { const license = licensingMock.createLicense({ license: { status: 'invalid' } }); license.check = jest.fn(() => ({ - state: LICENSE_CHECK_STATE.Invalid, + state: 'invalid', })); getRawLicense.mockReturnValue(license); }); @@ -37,7 +36,7 @@ describe('license_state', () => { beforeEach(() => { const license = licensingMock.createLicense({ license: { status: 'active' } }); license.check = jest.fn(() => ({ - state: LICENSE_CHECK_STATE.Valid, + state: 'valid', })); getRawLicense.mockReturnValue(license); }); diff --git a/x-pack/plugins/alerting/server/lib/license_state.ts b/x-pack/plugins/alerting/server/lib/license_state.ts index 690eaed9e2b89..db60d64db5df4 100644 --- a/x-pack/plugins/alerting/server/lib/license_state.ts +++ b/x-pack/plugins/alerting/server/lib/license_state.ts @@ -7,7 +7,7 @@ import Boom from 'boom'; import { i18n } from '@kbn/i18n'; import { Observable, Subscription } from 'rxjs'; -import { ILicense, LICENSE_CHECK_STATE } from '../../../../plugins/licensing/common/types'; +import { ILicense } from '../../../../plugins/licensing/common/types'; import { assertNever } from '../../../../../src/core/utils'; import { PLUGIN } from '../constants/plugin'; @@ -55,20 +55,20 @@ export class LicenseState { const check = license.check(PLUGIN.ID, PLUGIN.MINIMUM_LICENSE_REQUIRED); switch (check.state) { - case LICENSE_CHECK_STATE.Expired: + case 'expired': return { showAppLink: true, enableAppLink: false, message: check.message || '', }; - case LICENSE_CHECK_STATE.Invalid: - case LICENSE_CHECK_STATE.Unavailable: + case 'invalid': + case 'unavailable': return { showAppLink: false, enableAppLink: false, message: check.message || '', }; - case LICENSE_CHECK_STATE.Valid: + case 'valid': return { showAppLink: true, enableAppLink: true, diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index ec0ed4b761205..74a1f2349180e 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -9,6 +9,7 @@ import { coreMock } from '../../../../src/core/server/mocks'; import { licensingMock } from '../../../plugins/licensing/server/mocks'; import { encryptedSavedObjectsMock } from '../../../plugins/encrypted_saved_objects/server/mocks'; import { taskManagerMock } from '../../task_manager/server/mocks'; +import { eventLogServiceMock } from '../../event_log/server/event_log_service.mock'; describe('Alerting Plugin', () => { describe('setup()', () => { @@ -30,6 +31,7 @@ describe('Alerting Plugin', () => { licensing: licensingMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsSetup, taskManager: taskManagerMock.createSetup(), + eventLog: eventLogServiceMock.create(), } as any ); @@ -67,6 +69,7 @@ describe('Alerting Plugin', () => { licensing: licensingMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsSetup, taskManager: taskManagerMock.createSetup(), + eventLog: eventLogServiceMock.create(), } as any ); @@ -109,6 +112,7 @@ describe('Alerting Plugin', () => { licensing: licensingMock.createSetup(), encryptedSavedObjects: encryptedSavedObjectsSetup, taskManager: taskManagerMock.createSetup(), + eventLog: eventLogServiceMock.create(), } as any ); diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index b0d06d4aeeb74..fdca6c0a9b503 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -56,6 +56,15 @@ import { import { Services } from './types'; import { registerAlertsUsageCollector } from './usage'; import { initializeAlertingTelemetry, scheduleAlertingTelemetry } from './usage/task'; +import { IEventLogger, IEventLogService } from '../../event_log/server'; + +const EVENT_LOG_PROVIDER = 'alerting'; +export const EVENT_LOG_ACTIONS = { + execute: 'execute', + executeAction: 'execute-action', + newInstance: 'new-instance', + resolvedInstance: 'resolved-instance', +}; export interface PluginSetupContract { registerType: AlertTypeRegistry['register']; @@ -73,6 +82,7 @@ export interface AlertingPluginsSetup { licensing: LicensingPluginSetup; spaces?: SpacesPluginSetup; usageCollection?: UsageCollectionSetup; + eventLog: IEventLogService; } export interface AlertingPluginsStart { actions: ActionsPluginStartContract; @@ -93,6 +103,7 @@ export class AlertingPlugin { private readonly alertsClientFactory: AlertsClientFactory; private readonly telemetryLogger: Logger; private readonly kibanaIndex: Promise; + private eventLogger?: IEventLogger; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get('plugins', 'alerting'); @@ -133,6 +144,11 @@ export class AlertingPlugin { ]), }); + plugins.eventLog.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ACTIONS)); + this.eventLogger = plugins.eventLog.getLogger({ + event: { provider: EVENT_LOG_PROVIDER }, + }); + const alertTypeRegistry = new AlertTypeRegistry({ taskManager: plugins.taskManager, taskRunnerFactory: this.taskRunnerFactory, @@ -174,7 +190,7 @@ export class AlertingPlugin { unmuteAllAlertRoute(router, this.licenseState); muteAlertInstanceRoute(router, this.licenseState); unmuteAlertInstanceRoute(router, this.licenseState); - healthRoute(router, this.licenseState); + healthRoute(router, this.licenseState, plugins.encryptedSavedObjects); return { registerType: alertTypeRegistry.register.bind(alertTypeRegistry), @@ -202,6 +218,7 @@ export class AlertingPlugin { getSpaceId(request: KibanaRequest) { return spaces?.getSpaceId(request); }, + preconfiguredActions: plugins.actions.preconfiguredActions, }); taskRunnerFactory.initialize({ @@ -211,6 +228,7 @@ export class AlertingPlugin { actionsPlugin: plugins.actions, encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, getBasePath: this.getBasePath, + eventLogger: this.eventLogger!, }); scheduleAlertingTelemetry(this.telemetryLogger, plugins.taskManager); diff --git a/x-pack/plugins/alerting/server/routes/health.test.ts b/x-pack/plugins/alerting/server/routes/health.test.ts index 9efe020bc10c4..42c83a7c04deb 100644 --- a/x-pack/plugins/alerting/server/routes/health.test.ts +++ b/x-pack/plugins/alerting/server/routes/health.test.ts @@ -10,6 +10,7 @@ import { mockHandlerArguments } from './_mock_handler_arguments'; import { elasticsearchServiceMock } from '../../../../../src/core/server/mocks'; import { verifyApiAccess } from '../lib/license_api_access'; import { mockLicenseState } from '../lib/license_state.mock'; +import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; jest.mock('../lib/license_api_access.ts', () => ({ verifyApiAccess: jest.fn(), @@ -24,7 +25,9 @@ describe('healthRoute', () => { const router: RouterMock = mockRouter.create(); const licenseState = mockLicenseState(); - healthRoute(router, licenseState); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = false; + healthRoute(router, licenseState, encryptedSavedObjects); const [config] = router.get.mock.calls[0]; @@ -35,7 +38,9 @@ describe('healthRoute', () => { const router: RouterMock = mockRouter.create(); const licenseState = mockLicenseState(); - healthRoute(router, licenseState); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = false; + healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; const elasticsearch = elasticsearchServiceMock.createSetup(); @@ -58,11 +63,37 @@ describe('healthRoute', () => { `); }); + it('evaluates whether Encrypted Saved Objects is using an ephemeral encryption key', async () => { + const router: RouterMock = mockRouter.create(); + + const licenseState = mockLicenseState(); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = true; + healthRoute(router, licenseState, encryptedSavedObjects); + const [, handler] = router.get.mock.calls[0]; + + const elasticsearch = elasticsearchServiceMock.createSetup(); + elasticsearch.adminClient.callAsInternalUser.mockReturnValue(Promise.resolve({})); + + const [context, req, res] = mockHandlerArguments({ elasticsearch }, {}, ['ok']); + + expect(await handler(context, req, res)).toMatchInlineSnapshot(` + Object { + "body": Object { + "hasPermanentEncryptionKey": false, + "isSufficientlySecure": true, + }, + } + `); + }); + it('evaluates missing security info from the usage api to mean that the security plugin is disbled', async () => { const router: RouterMock = mockRouter.create(); const licenseState = mockLicenseState(); - healthRoute(router, licenseState); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = false; + healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; const elasticsearch = elasticsearchServiceMock.createSetup(); @@ -73,6 +104,7 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { "body": Object { + "hasPermanentEncryptionKey": true, "isSufficientlySecure": true, }, } @@ -83,7 +115,9 @@ describe('healthRoute', () => { const router: RouterMock = mockRouter.create(); const licenseState = mockLicenseState(); - healthRoute(router, licenseState); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = false; + healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; const elasticsearch = elasticsearchServiceMock.createSetup(); @@ -94,6 +128,7 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { "body": Object { + "hasPermanentEncryptionKey": true, "isSufficientlySecure": true, }, } @@ -104,7 +139,9 @@ describe('healthRoute', () => { const router: RouterMock = mockRouter.create(); const licenseState = mockLicenseState(); - healthRoute(router, licenseState); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = false; + healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; const elasticsearch = elasticsearchServiceMock.createSetup(); @@ -117,6 +154,7 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { "body": Object { + "hasPermanentEncryptionKey": true, "isSufficientlySecure": false, }, } @@ -127,7 +165,9 @@ describe('healthRoute', () => { const router: RouterMock = mockRouter.create(); const licenseState = mockLicenseState(); - healthRoute(router, licenseState); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = false; + healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; const elasticsearch = elasticsearchServiceMock.createSetup(); @@ -140,6 +180,7 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { "body": Object { + "hasPermanentEncryptionKey": true, "isSufficientlySecure": false, }, } @@ -150,7 +191,9 @@ describe('healthRoute', () => { const router: RouterMock = mockRouter.create(); const licenseState = mockLicenseState(); - healthRoute(router, licenseState); + const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup(); + encryptedSavedObjects.usingEphemeralEncryptionKey = false; + healthRoute(router, licenseState, encryptedSavedObjects); const [, handler] = router.get.mock.calls[0]; const elasticsearch = elasticsearchServiceMock.createSetup(); @@ -163,6 +206,7 @@ describe('healthRoute', () => { expect(await handler(context, req, res)).toMatchInlineSnapshot(` Object { "body": Object { + "hasPermanentEncryptionKey": true, "isSufficientlySecure": true, }, } diff --git a/x-pack/plugins/alerting/server/routes/health.ts b/x-pack/plugins/alerting/server/routes/health.ts index 29c2f3c5730f4..fa2358a1f181c 100644 --- a/x-pack/plugins/alerting/server/routes/health.ts +++ b/x-pack/plugins/alerting/server/routes/health.ts @@ -14,6 +14,7 @@ import { import { LicenseState } from '../lib/license_state'; import { verifyApiAccess } from '../lib/license_api_access'; import { AlertingFrameworkHealth } from '../types'; +import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; interface XPackUsageSecurity { security?: { @@ -26,7 +27,11 @@ interface XPackUsageSecurity { }; } -export function healthRoute(router: IRouter, licenseState: LicenseState) { +export function healthRoute( + router: IRouter, + licenseState: LicenseState, + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup +) { router.get( { path: '/api/alert/_health', @@ -54,6 +59,7 @@ export function healthRoute(router: IRouter, licenseState: LicenseState) { const frameworkHealth: AlertingFrameworkHealth = { isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled), + hasPermanentEncryptionKey: !encryptedSavedObjects.usingEphemeralEncryptionKey, }; return res.ok({ diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts index 5bd8382f0a4b2..8d037a1ecee91 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.test.ts @@ -8,6 +8,7 @@ import { AlertType } from '../types'; import { createExecutionHandler } from './create_execution_handler'; import { loggingServiceMock } from '../../../../../src/core/server/mocks'; import { actionsMock } from '../../../actions/server/mocks'; +import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; const alertType: AlertType = { id: 'test', @@ -31,6 +32,7 @@ const createExecutionHandlerParams = { getBasePath: jest.fn().mockReturnValue(undefined), alertType, logger: loggingServiceMock.create().get(), + eventLogger: eventLoggerMock.create(), actions: [ { id: '1', @@ -75,6 +77,37 @@ test('calls actionsPlugin.execute per selected action', async () => { }, ] `); + + const eventLogger = createExecutionHandlerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(1); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "execute-action", + }, + "kibana": Object { + "alerting": Object { + "instance_id": "2", + }, + "namespace": "default", + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + Object { + "id": "1", + "type": "action", + }, + ], + }, + "message": "alert: test:1: 'name-of-alert' instanceId: '2' scheduled actionGroup: 'default' action: test:1", + }, + ], + ] + `); }); test(`doesn't call actionsPlugin.execute for disabled actionTypes`, async () => { diff --git a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts index 5d14f4adc709e..de06c8bbb374a 100644 --- a/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/create_execution_handler.ts @@ -9,6 +9,8 @@ import { AlertAction, State, Context, AlertType } from '../types'; import { Logger } from '../../../../../src/core/server'; import { transformActionParams } from './transform_action_params'; import { PluginStartContract as ActionsPluginStartContract } from '../../../../plugins/actions/server'; +import { IEventLogger, IEvent } from '../../../event_log/server'; +import { EVENT_LOG_ACTIONS } from '../plugin'; interface CreateExecutionHandlerOptions { alertId: string; @@ -20,6 +22,7 @@ interface CreateExecutionHandlerOptions { apiKey: string | null; alertType: AlertType; logger: Logger; + eventLogger: IEventLogger; } interface ExecutionHandlerOptions { @@ -39,6 +42,7 @@ export function createExecutionHandler({ spaceId, apiKey, alertType, + eventLogger, }: CreateExecutionHandlerOptions) { const alertTypeActionGroups = new Set(pluck(alertType.actionGroups, 'id')); return async ({ actionGroup, context, state, alertInstanceId }: ExecutionHandlerOptions) => { @@ -63,19 +67,42 @@ export function createExecutionHandler({ }), }; }); + + const alertLabel = `${alertType.id}:${alertId}: '${alertName}'`; + for (const action of actions) { - if (actionsPlugin.isActionTypeEnabled(action.actionTypeId)) { - await actionsPlugin.execute({ - id: action.id, - params: action.params, - spaceId, - apiKey, - }); - } else { + if (!actionsPlugin.isActionTypeEnabled(action.actionTypeId)) { logger.warn( `Alert "${alertId}" skipped scheduling action "${action.id}" because it is disabled` ); + continue; } + + // TODO would be nice to add the action name here, but it's not available + const actionLabel = `${action.actionTypeId}:${action.id}`; + await actionsPlugin.execute({ + id: action.id, + params: action.params, + spaceId, + apiKey, + }); + + const event: IEvent = { + event: { action: EVENT_LOG_ACTIONS.executeAction }, + kibana: { + alerting: { + instance_id: alertInstanceId, + }, + namespace: spaceId, + saved_objects: [ + { type: 'alert', id: alertId }, + { type: 'action', id: action.id }, + ], + }, + }; + + event.message = `alert: ${alertLabel} instanceId: '${alertInstanceId}' scheduled actionGroup: '${actionGroup}' action: ${actionLabel}`; + eventLogger.logEvent(event); } }; } diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index 5f4669f64f09d..520f8d5c99b16 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -14,6 +14,8 @@ import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_o import { savedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; import { PluginStartContract as ActionsPluginStart } from '../../../actions/server'; import { actionsMock } from '../../../actions/server/mocks'; +import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; +import { IEventLogger } from '../../../event_log/server'; const alertType = { id: 'test', @@ -59,6 +61,7 @@ describe('Task Runner', () => { const taskRunnerFactoryInitializerParams: jest.Mocked & { actionsPlugin: jest.Mocked; + eventLogger: jest.Mocked; } = { getServices: jest.fn().mockReturnValue(services), actionsPlugin: actionsMock.createStart(), @@ -66,6 +69,7 @@ describe('Task Runner', () => { logger: loggingServiceMock.create().get(), spaceIdToNamespace: jest.fn().mockReturnValue(undefined), getBasePath: jest.fn().mockReturnValue(undefined), + eventLogger: eventLoggerMock.create(), }; const mockedAlertTypeSavedObject = { @@ -156,6 +160,26 @@ describe('Task Runner', () => { expect(call.services.alertInstanceFactory).toBeTruthy(); expect(call.services.callCluster).toBeTruthy(); expect(call.services).toBeTruthy(); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(1); + expect(eventLogger.logEvent.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "event": Object { + "action": "execute", + }, + "kibana": Object { + "namespace": undefined, + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + ], + }, + "message": "alert executed: test:1: 'alert-name'", + } + `); }); test('actionsPlugin.execute is called per alert instance that is scheduled', async () => { @@ -194,6 +218,74 @@ describe('Task Runner', () => { }, ] `); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(3); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "execute", + }, + "kibana": Object { + "namespace": undefined, + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + ], + }, + "message": "alert executed: test:1: 'alert-name'", + }, + ], + Array [ + Object { + "event": Object { + "action": "new-instance", + }, + "kibana": Object { + "alerting": Object { + "instance_id": "1", + }, + "namespace": undefined, + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + ], + }, + "message": "test:1: 'alert-name' created new instance: '1'", + }, + ], + Array [ + Object { + "event": Object { + "action": "execute-action", + }, + "kibana": Object { + "alerting": Object { + "instance_id": "1", + }, + "namespace": undefined, + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + Object { + "id": "1", + "type": "action", + }, + ], + }, + "message": "alert: test:1: 'alert-name' instanceId: '1' scheduled actionGroup: 'default' action: undefined:1", + }, + ], + ] + `); }); test('persists alertInstances passed in from state, only if they are scheduled for execution', async () => { @@ -241,6 +333,50 @@ describe('Task Runner', () => { }, } `); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(2); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "execute", + }, + "kibana": Object { + "namespace": undefined, + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + ], + }, + "message": "alert executed: test:1: 'alert-name'", + }, + ], + Array [ + Object { + "event": Object { + "action": "resolved-instance", + }, + "kibana": Object { + "alerting": Object { + "instance_id": "2", + }, + "namespace": undefined, + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + ], + }, + "message": "test:1: 'alert-name' resolved instance: '2'", + }, + ], + ] + `); }); test('validates params before executing the alert type', async () => { @@ -410,6 +546,33 @@ describe('Task Runner', () => { }, } `); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(1); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "error": Object { + "message": "OMG", + }, + "event": Object { + "action": "execute", + }, + "kibana": Object { + "namespace": undefined, + "saved_objects": Array [ + Object { + "id": "1", + "type": "alert", + }, + ], + }, + "message": "alert execution failure: test:1: 'alert-name'", + }, + ], + ] + `); }); test('recovers gracefully when the Alert Task Runner throws an exception when fetching the encrypted attributes', async () => { diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 42768a80a4ccf..2ba56396279ea 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { pick, mapValues, omit } from 'lodash'; +import { pick, mapValues, omit, without } from 'lodash'; import { Logger, SavedObject } from '../../../../../src/core/server'; import { TaskRunnerContext } from './task_runner_factory'; import { ConcreteTaskInstance } from '../../../../plugins/task_manager/server'; @@ -24,6 +24,8 @@ import { import { promiseResult, map, Resultable, asOk, asErr, resolveErr } from '../lib/result_type'; import { taskInstanceToAlertTaskInstance } from './alert_task_instance'; import { AlertInstances } from '../alert_instance/alert_instance'; +import { EVENT_LOG_ACTIONS } from '../plugin'; +import { IEvent, IEventLogger } from '../../../event_log/server'; const FALLBACK_RETRY_INTERVAL: IntervalSchedule = { interval: '5m' }; @@ -124,6 +126,7 @@ export class TaskRunner { actions: actionsWithIds, spaceId, alertType: this.alertType, + eventLogger: this.context.eventLogger, }); } @@ -165,29 +168,61 @@ export class TaskRunner { rawAlertInstance => new AlertInstance(rawAlertInstance) ); - const updatedAlertTypeState = await this.alertType.executor({ - alertId, - services: { - ...services, - alertInstanceFactory: createAlertInstanceFactory(alertInstances), - }, - params, - state: alertTypeState, - startedAt: this.taskInstance.startedAt!, - previousStartedAt: previousStartedAt ? new Date(previousStartedAt) : null, - spaceId, - namespace, - name, - tags, - createdBy, - updatedBy, - }); + const originalAlertInstanceIds = Object.keys(alertInstances); + const eventLogger = this.context.eventLogger; + const alertLabel = `${this.alertType.id}:${alertId}: '${name}'`; + const event: IEvent = { + event: { action: EVENT_LOG_ACTIONS.execute }, + kibana: { namespace, saved_objects: [{ type: 'alert', id: alertId }] }, + }; + eventLogger.startTiming(event); + + let updatedAlertTypeState: void | Record; + try { + updatedAlertTypeState = await this.alertType.executor({ + alertId, + services: { + ...services, + alertInstanceFactory: createAlertInstanceFactory(alertInstances), + }, + params, + state: alertTypeState, + startedAt: this.taskInstance.startedAt!, + previousStartedAt: previousStartedAt ? new Date(previousStartedAt) : null, + spaceId, + namespace, + name, + tags, + createdBy, + updatedBy, + }); + } catch (err) { + eventLogger.stopTiming(event); + event.message = `alert execution failure: ${alertLabel}`; + event.error = event.error || {}; + event.error.message = err.message; + eventLogger.logEvent(event); + throw err; + } + + eventLogger.stopTiming(event); + event.message = `alert executed: ${alertLabel}`; + eventLogger.logEvent(event); // Cleanup alert instances that are no longer scheduling actions to avoid over populating the alertInstances object const instancesWithScheduledActions = pick( alertInstances, (alertInstance: AlertInstance) => alertInstance.hasScheduledActions() ); + const currentAlertInstanceIds = Object.keys(instancesWithScheduledActions); + generateNewAndResolvedInstanceEvents({ + eventLogger, + originalAlertInstanceIds, + currentAlertInstanceIds, + alertId, + alertLabel, + namespace, + }); if (!muteAll) { const enabledAlertInstances = omit( @@ -313,6 +348,48 @@ export class TaskRunner { } } +interface GenerateNewAndResolvedInstanceEventsParams { + eventLogger: IEventLogger; + originalAlertInstanceIds: string[]; + currentAlertInstanceIds: string[]; + alertId: string; + alertLabel: string; + namespace: string | undefined; +} + +function generateNewAndResolvedInstanceEvents(params: GenerateNewAndResolvedInstanceEventsParams) { + const { currentAlertInstanceIds, originalAlertInstanceIds } = params; + const newIds = without(currentAlertInstanceIds, ...originalAlertInstanceIds); + const resolvedIds = without(originalAlertInstanceIds, ...currentAlertInstanceIds); + + for (const id of newIds) { + const message = `${params.alertLabel} created new instance: '${id}'`; + logInstanceEvent(id, EVENT_LOG_ACTIONS.newInstance, message); + } + + for (const id of resolvedIds) { + const message = `${params.alertLabel} resolved instance: '${id}'`; + logInstanceEvent(id, EVENT_LOG_ACTIONS.resolvedInstance, message); + } + + function logInstanceEvent(id: string, action: string, message: string) { + const event: IEvent = { + event: { + action, + }, + kibana: { + namespace: params.namespace, + alerting: { + instance_id: id, + }, + saved_objects: [{ type: 'alert', id: params.alertId }], + }, + message, + }; + params.eventLogger.logEvent(event); + } +} + /** * If an error is thrown, wrap it in an AlertTaskRunResult * so that we can treat each field independantly diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts index fc34cacba2818..1d220f97f127a 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts @@ -10,6 +10,7 @@ import { TaskRunnerContext, TaskRunnerFactory } from './task_runner_factory'; import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_objects/server/mocks'; import { savedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; import { actionsMock } from '../../../actions/server/mocks'; +import { eventLoggerMock } from '../../../event_log/server/event_logger.mock'; const alertType = { id: 'test', @@ -62,6 +63,7 @@ describe('Task Runner Factory', () => { logger: loggingServiceMock.create().get(), spaceIdToNamespace: jest.fn().mockReturnValue(undefined), getBasePath: jest.fn().mockReturnValue(undefined), + eventLogger: eventLoggerMock.create(), }; beforeEach(() => { diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts index 3bad4e475ff49..b58db8c74f7bb 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts @@ -14,11 +14,13 @@ import { SpaceIdToNamespaceFunction, } from '../types'; import { TaskRunner } from './task_runner'; +import { IEventLogger } from '../../../event_log/server'; export interface TaskRunnerContext { logger: Logger; getServices: GetServicesFunction; actionsPlugin: ActionsPluginStartContract; + eventLogger: IEventLogger; encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; spaceIdToNamespace: SpaceIdToNamespaceFunction; getBasePath: GetBasePathFunction; diff --git a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap index 5de82a9ee8788..54dd4704edfc0 100644 --- a/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap +++ b/x-pack/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap @@ -16,6 +16,8 @@ exports[`Error ERROR_EXC_HANDLED 1`] = `undefined`; exports[`Error ERROR_EXC_MESSAGE 1`] = `undefined`; +exports[`Error ERROR_EXC_TYPE 1`] = `undefined`; + exports[`Error ERROR_GROUP_ID 1`] = `"grouping key"`; exports[`Error ERROR_LOG_LEVEL 1`] = `undefined`; @@ -144,6 +146,8 @@ exports[`Span ERROR_EXC_HANDLED 1`] = `undefined`; exports[`Span ERROR_EXC_MESSAGE 1`] = `undefined`; +exports[`Span ERROR_EXC_TYPE 1`] = `undefined`; + exports[`Span ERROR_GROUP_ID 1`] = `undefined`; exports[`Span ERROR_LOG_LEVEL 1`] = `undefined`; @@ -272,6 +276,8 @@ exports[`Transaction ERROR_EXC_HANDLED 1`] = `undefined`; exports[`Transaction ERROR_EXC_MESSAGE 1`] = `undefined`; +exports[`Transaction ERROR_EXC_TYPE 1`] = `undefined`; + exports[`Transaction ERROR_GROUP_ID 1`] = `undefined`; exports[`Transaction ERROR_LOG_LEVEL 1`] = `undefined`; diff --git a/x-pack/plugins/apm/common/agent_configuration/amount_and_unit.ts b/x-pack/plugins/apm/common/agent_configuration/amount_and_unit.ts index 447e529c9c199..d6520ae150539 100644 --- a/x-pack/plugins/apm/common/agent_configuration/amount_and_unit.ts +++ b/x-pack/plugins/apm/common/agent_configuration/amount_and_unit.ts @@ -10,7 +10,8 @@ interface AmountAndUnit { } export function amountAndUnitToObject(value: string): AmountAndUnit { - const [, amount = '', unit = ''] = value.match(/(\d+)?(\w+)?/) || []; + // matches any postive and negative number and its unit. + const [, amount = '', unit = ''] = value.match(/(^-?\d+)?(\w+)?/) || []; return { amount, unit }; } diff --git a/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.test.ts b/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.test.ts index a83ee9262cad6..98d0cb5f028c3 100644 --- a/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.test.ts +++ b/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.test.ts @@ -10,24 +10,56 @@ * you may not use this file except in compliance with the Elastic License. */ -import { durationRt } from './duration_rt'; +import { durationRt, getDurationRt } from './duration_rt'; import { isRight } from 'fp-ts/lib/Either'; describe('durationRt', () => { describe('it should not accept', () => { - [undefined, null, '', 0, 'foo', true, false, '100', 's', 'm', '0h'].map( + [ + undefined, + null, + '', + 0, + 'foo', + true, + false, + '100', + 's', + 'm', + '0ms', + '-1ms' + ].map(input => { + it(`${JSON.stringify(input)}`, () => { + expect(isRight(durationRt.decode(input))).toBe(false); + }); + }); + }); + + describe('it should accept', () => { + ['1000ms', '2s', '3m', '1s'].map(input => { + it(`${JSON.stringify(input)}`, () => { + expect(isRight(durationRt.decode(input))).toBe(true); + }); + }); + }); +}); + +describe('getDurationRt', () => { + const customDurationRt = getDurationRt({ min: -1 }); + describe('it should not accept', () => { + [undefined, null, '', 0, 'foo', true, false, '100', 's', 'm', '-2ms'].map( input => { it(`${JSON.stringify(input)}`, () => { - expect(isRight(durationRt.decode(input))).toBe(false); + expect(isRight(customDurationRt.decode(input))).toBe(false); }); } ); }); - describe('It should accept', () => { - ['1000ms', '2s', '3m'].map(input => { + describe('it should accept', () => { + ['1000ms', '2s', '3m', '1s', '-1s', '0ms'].map(input => { it(`${JSON.stringify(input)}`, () => { - expect(isRight(durationRt.decode(input))).toBe(true); + expect(isRight(customDurationRt.decode(input))).toBe(true); }); }); }); diff --git a/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.ts b/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.ts index 383fd69be9a78..b691276854fb0 100644 --- a/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.ts +++ b/x-pack/plugins/apm/common/agent_configuration/runtime_types/duration_rt.ts @@ -10,24 +10,28 @@ import { amountAndUnitToObject } from '../amount_and_unit'; export const DURATION_UNITS = ['ms', 's', 'm']; -export const durationRt = new t.Type( - 'durationRt', - t.string.is, - (input, context) => { - return either.chain(t.string.validate(input, context), inputAsString => { - const { amount, unit } = amountAndUnitToObject(inputAsString); - const amountAsInt = parseInt(amount, 10); - const isValidUnit = DURATION_UNITS.includes(unit); - const isValid = amountAsInt > 0 && isValidUnit; +export function getDurationRt({ min }: { min: number }) { + return new t.Type( + 'durationRt', + t.string.is, + (input, context) => { + return either.chain(t.string.validate(input, context), inputAsString => { + const { amount, unit } = amountAndUnitToObject(inputAsString); + const amountAsInt = parseInt(amount, 10); + const isValidUnit = DURATION_UNITS.includes(unit); + const isValid = amountAsInt >= min && isValidUnit; - return isValid - ? t.success(inputAsString) - : t.failure( - input, - context, - `Must have numeric amount and a valid unit (${DURATION_UNITS})` - ); - }); - }, - t.identity -); + return isValid + ? t.success(inputAsString) + : t.failure( + input, + context, + `Must have numeric amount and a valid unit (${DURATION_UNITS})` + ); + }); + }, + t.identity + ); +} + +export const durationRt = getDurationRt({ min: 1 }); diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap index 4b74b07fc8e27..49840d2157af7 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/__snapshots__/index.test.ts.snap @@ -118,6 +118,7 @@ Array [ }, Object { "key": "span_frames_min_duration", + "min": -1, "type": "duration", "units": Array [ "ms", @@ -152,8 +153,9 @@ Array [ }, Object { "key": "stress_monitor_gc_stress_threshold", - "type": "boolean", - "validationName": "(\\"true\\" | \\"false\\")", + "type": "float", + "validationError": "Must be a number between 0.000 and 1", + "validationName": "numberFloatRt", }, Object { "key": "stress_monitor_system_cpu_relief_threshold", diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts index 152db37a1bff3..e73aed35e87f9 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { getIntegerRt } from '../runtime_types/integer_rt'; import { captureBodyRt } from '../runtime_types/capture_body_rt'; import { RawSettingDefinition } from './types'; +import { getDurationRt } from '../runtime_types/duration_rt'; /* * Settings added here will show up in the UI and will be validated on the client and server @@ -62,7 +63,7 @@ export const generalSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.captureBody.description', { defaultMessage: - 'For transactions that are HTTP requests, the agent can optionally capture the request body (e.g. POST variables).' + 'For transactions that are HTTP requests, the agent can optionally capture the request body (e.g. POST variables).\nFor transactions that are initiated by receiving a message from a message broker, the agent can capture the textual message body.' } ), options: [ @@ -86,7 +87,7 @@ export const generalSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.captureHeaders.description', { defaultMessage: - 'If set to `true`, the agent will capture request and response headers, including cookies.\n\nNOTE: Setting this to `false` reduces network bandwidth, disk space and object allocations.' + 'If set to `true`, the agent will capture HTTP request and response headers (including cookies), as well as message headers/properties when using messaging frameworks (like Kafka).\n\nNOTE: Setting this to `false` reduces network bandwidth, disk space and object allocations.' } ), excludeAgents: ['js-base', 'rum-js', 'nodejs'] @@ -116,7 +117,7 @@ export const generalSettings: RawSettingDefinition[] = [ }), description: i18n.translate('xpack.apm.agentConfig.recording.description', { defaultMessage: - 'When recording, the agent instruments incoming HTTP requests, tracks errors, and collects and sends metrics. When inactive, the agent works as a noop, not collecting data and not communicating with the APM Server except for polling for updated configuration. As this is a reversible switch, agent threads are not being killed when inactivated, but they will be mostly idle in this state, so the overhead should be negligible. You can use this setting to dynamically control whether Elastic APM is enabled or disabled.' + 'When recording, the agent instruments incoming HTTP requests, tracks errors, and collects and sends metrics. When set to non-recording, the agent works as a noop, not collecting data and not communicating with the APM Server except for polling for updated configuration. As this is a reversible switch, agent threads are not being killed when set to non-recording, but they will be mostly idle in this state, so the overhead should be negligible. You can use this setting to dynamically control whether Elastic APM is enabled or disabled.' }), excludeAgents: ['nodejs'] }, @@ -143,6 +144,7 @@ export const generalSettings: RawSettingDefinition[] = [ { key: 'span_frames_min_duration', type: 'duration', + validation: getDurationRt({ min: -1 }), defaultValue: '5ms', label: i18n.translate('xpack.apm.agentConfig.spanFramesMinDuration.label', { defaultMessage: 'Span frames minimum duration' @@ -154,7 +156,8 @@ export const generalSettings: RawSettingDefinition[] = [ 'In its default settings, the APM agent will collect a stack trace with every recorded span.\nWhile this is very helpful to find the exact place in your code that causes the span, collecting this stack trace does have some overhead. \nWhen setting this option to a negative value, like `-1ms`, stack traces will be collected for all spans. Setting it to a positive value, e.g. `5ms`, will limit stack trace collection to spans with durations equal to or longer than the given value, e.g. 5 milliseconds.\n\nTo disable stack trace collection for spans completely, set the value to `0ms`.' } ), - excludeAgents: ['js-base', 'rum-js', 'nodejs'] + excludeAgents: ['js-base', 'rum-js', 'nodejs'], + min: -1 }, // STACK_TRACE_LIMIT @@ -212,7 +215,7 @@ export const generalSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.transactionSampleRate.description', { defaultMessage: - 'By default, the agent will sample every transaction (e.g. request to your service). To reduce overhead and storage requirements, you can set the sample rate to a value between 0.0 and 1.0. We still record overall time and the result for unsampled transactions, but no context information, labels, or spans.' + 'By default, the agent will sample every transaction (e.g. request to your service). To reduce overhead and storage requirements, you can set the sample rate to a value between 0.0 and 1.0. We still record overall time and the result for unsampled transactions, but not context information, labels, or spans.' } ) } diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts index bb050076b9f9a..2e10c74378549 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/java_settings.ts @@ -20,7 +20,7 @@ export const javaSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.enableLogCorrelation.description', { defaultMessage: - "A boolean specifying if the agent should integrate into SLF4J's MDC to enable trace-log correlation. If set to `true`, the agent will set the `trace.id` and `transaction.id` for the currently active spans and transactions to the MDC. While it's allowed to enable this setting at runtime, you can't disable it without a restart." + "A boolean specifying if the agent should integrate into SLF4J's MDC to enable trace-log correlation. If set to `true`, the agent will set the `trace.id` and `transaction.id` for the currently active spans and transactions to the MDC. Since Java agent version 1.16.0, the agent also adds `error.id` of captured error to the MDC just before the error message is logged. NOTE: While it's allowed to enable this setting at runtime, you can't disable it without a restart." } ), includeAgents: ['java'] @@ -41,7 +41,7 @@ export const javaSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.circuitBreakerEnabled.description', { defaultMessage: - 'A boolean specifying whether the circuit breaker should be enabled or not. When enabled, the agent periodically polls stress monitors to detect system/process/JVM stress state. If ANY of the monitors detects a stress indication, the agent will become inactive, as if the `active` configuration option has been set to `false`, thus reducing resource consumption to a minimum. When inactive, the agent continues polling the same monitors in order to detect whether the stress state has been relieved. If ALL monitors approve that the system/process/JVM is not under stress anymore, the agent will resume and become fully functional.' + 'A boolean specifying whether the circuit breaker should be enabled or not. When enabled, the agent periodically polls stress monitors to detect system/process/JVM stress state. If ANY of the monitors detects a stress indication, the agent will pause, as if the `recording` configuration option has been set to `false`, thus reducing resource consumption to a minimum. When paused, the agent continues polling the same monitors in order to detect whether the stress state has been relieved. If ALL monitors approve that the system/process/JVM is not under stress anymore, the agent will resume and become fully functional.' } ), includeAgents: ['java'] @@ -52,7 +52,7 @@ export const javaSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.stressMonitorGcStressThreshold.label', { defaultMessage: 'Stress monitor gc stress threshold' } ), - type: 'boolean', + type: 'float', category: 'Circuit-Breaker', defaultValue: '0.95', description: i18n.translate( @@ -155,7 +155,7 @@ export const javaSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.profilingInferredSpansEnabled.description', { defaultMessage: - 'Set to `true` to make the agent create spans for method executions based on async-profiler, a sampling aka statistical profiler. Due to the nature of how sampling profilers work, the duration of the inferred spans are not exact, but only estimations. The `profiling_inferred_spans_sampling_interval` lets you fine tune the trade-off between accuracy and overhead. The inferred spans are created after a profiling session has ended. This means there is a delay between the regular and the inferred spans being visible in the UI. This feature is not available on Windows' + 'Set to `true` to make the agent create spans for method executions based on async-profiler, a sampling aka statistical profiler. Due to the nature of how sampling profilers work, the duration of the inferred spans are not exact, but only estimations. The `profiling_inferred_spans_sampling_interval` lets you fine tune the trade-off between accuracy and overhead. The inferred spans are created after a profiling session has ended. This means there is a delay between the regular and the inferred spans being visible in the UI. NOTE: This feature is not available on Windows.' } ), includeAgents: ['java'] @@ -209,7 +209,7 @@ export const javaSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.profilingInferredSpansIncludedClasses.description', { defaultMessage: - 'If set, the agent will only create inferred spans for methods which match this list. Setting a value may slightly increase performance and can reduce clutter by only creating spans for the classes you are interested in. Example: `org.example.myapp.*` This option supports the wildcard `*`, which matches zero or more characters. Examples: `/foo/*/bar/*/baz*`, `*foo*`. Matching is case insensitive by default. Prepending an element with `(?-i)` makes the matching case sensitive.' + 'If set, the agent will only create inferred spans for methods which match this list. Setting a value may slightly reduce overhead and can reduce clutter by only creating spans for the classes you are interested in. This option supports the wildcard `*`, which matches zero or more characters. Example: `org.example.myapp.*`. Matching is case insensitive by default. Prepending an element with `(?-i)` makes the matching case sensitive.' } ), includeAgents: ['java'] @@ -228,7 +228,7 @@ export const javaSettings: RawSettingDefinition[] = [ 'xpack.apm.agentConfig.profilingInferredSpansExcludedClasses.description', { defaultMessage: - 'Excludes classes for which no profiler-inferred spans should be created. This option supports the wildcard `*`, which matches zero or more characters. Examples: `/foo/*/bar/*/baz*`, `*foo*`. Matching is case insensitive by default. Prepending an element with `(?-i)` makes the matching case sensitive.' + 'Excludes classes for which no profiler-inferred spans should be created. This option supports the wildcard `*`, which matches zero or more characters. Matching is case insensitive by default. Prepending an element with `(?-i)` makes the matching case sensitive.' } ), includeAgents: ['java'] diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/types.d.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/types.d.ts index 6b584fc7e2048..282ced346dda0 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/types.d.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/types.d.ts @@ -91,6 +91,7 @@ interface BytesSetting extends BaseSetting { interface DurationSetting extends BaseSetting { type: 'duration'; units?: string[]; + min?: number; } export type RawSettingDefinition = diff --git a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts index bc1b346f50da7..d5c3f91eb9247 100644 --- a/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts +++ b/x-pack/plugins/apm/common/elasticsearch_fieldnames.ts @@ -60,6 +60,7 @@ export const ERROR_LOG_LEVEL = 'error.log.level'; export const ERROR_LOG_MESSAGE = 'error.log.message'; export const ERROR_EXC_MESSAGE = 'error.exception.message'; // only to be used in es queries, since error.exception is now an array export const ERROR_EXC_HANDLED = 'error.exception.handled'; // only to be used in es queries, since error.exception is now an array +export const ERROR_EXC_TYPE = 'error.exception.type'; export const ERROR_PAGE_URL = 'error.page.url'; // METRICS diff --git a/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts index 187a75d0b61f2..d6242507e4011 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_error_rate_alert_type.ts @@ -7,6 +7,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; +import { i18n } from '@kbn/i18n'; import { AlertType, ALERT_TYPES_CONFIG } from '../../../common/alert_types'; import { ESSearchResponse, @@ -46,7 +47,19 @@ export function registerErrorRateAlertType({ validate: { params: paramsSchema }, - + actionVariables: { + context: [ + { + description: i18n.translate( + 'xpack.apm.registerErrorRateAlertType.variables.serviceName', + { + defaultMessage: 'Service name' + } + ), + name: 'serviceName' + } + ] + }, executor: async ({ services, params }) => { const config = await config$.pipe(take(1)).toPromise(); @@ -99,7 +112,9 @@ export function registerErrorRateAlertType({ const alertInstance = services.alertInstanceFactory( AlertType.ErrorRate ); - alertInstance.scheduleActions(alertTypeConfig.defaultActionGroupId); + alertInstance.scheduleActions(alertTypeConfig.defaultActionGroupId, { + serviceName: alertParams.serviceName + }); } return {}; diff --git a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts index 7575a8268bc26..2799da16cf6a9 100644 --- a/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts +++ b/x-pack/plugins/apm/server/lib/alerts/register_transaction_duration_alert_type.ts @@ -7,6 +7,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; +import { i18n } from '@kbn/i18n'; import { AlertType, ALERT_TYPES_CONFIG } from '../../../common/alert_types'; import { ESSearchResponse } from '../../../typings/elasticsearch'; import { @@ -51,7 +52,28 @@ export function registerTransactionDurationAlertType({ validate: { params: paramsSchema }, - + actionVariables: { + context: [ + { + description: i18n.translate( + 'xpack.apm.registerTransactionDurationAlertType.variables.serviceName', + { + defaultMessage: 'Service name' + } + ), + name: 'serviceName' + }, + { + description: i18n.translate( + 'xpack.apm.registerTransactionDurationAlertType.variables.transactionType', + { + defaultMessage: 'Transaction type' + } + ), + name: 'transactionType' + } + ] + }, executor: async ({ services, params }) => { const config = await config$.pipe(take(1)).toPromise(); @@ -131,7 +153,10 @@ export function registerTransactionDurationAlertType({ const alertInstance = services.alertInstanceFactory( AlertType.TransactionDuration ); - alertInstance.scheduleActions(alertTypeConfig.defaultActionGroupId); + alertInstance.scheduleActions(alertTypeConfig.defaultActionGroupId, { + transactionType: alertParams.transactionType, + serviceName: alertParams.serviceName + }); } return {}; diff --git a/x-pack/plugins/apm/server/lib/errors/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/errors/__snapshots__/queries.test.ts.snap index b9ac9d5431700..982ad558dc91d 100644 --- a/x-pack/plugins/apm/server/lib/errors/__snapshots__/queries.test.ts.snap +++ b/x-pack/plugins/apm/server/lib/errors/__snapshots__/queries.test.ts.snap @@ -73,6 +73,7 @@ Object { "error.log.message", "error.exception.message", "error.exception.handled", + "error.exception.type", "error.culprit", "error.grouping_key", "@timestamp", @@ -148,6 +149,7 @@ Object { "error.log.message", "error.exception.message", "error.exception.handled", + "error.exception.type", "error.culprit", "error.grouping_key", "@timestamp", diff --git a/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts b/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts index 8ea6df5a9898a..5221d737866f4 100644 --- a/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts +++ b/x-pack/plugins/apm/server/lib/errors/get_error_groups.ts @@ -8,6 +8,7 @@ import { ERROR_CULPRIT, ERROR_EXC_HANDLED, ERROR_EXC_MESSAGE, + ERROR_EXC_TYPE, ERROR_GROUP_ID, ERROR_LOG_MESSAGE } from '../../../common/elasticsearch_fieldnames'; @@ -67,6 +68,7 @@ export async function getErrorGroups({ ERROR_LOG_MESSAGE, ERROR_EXC_MESSAGE, ERROR_EXC_HANDLED, + ERROR_EXC_TYPE, ERROR_CULPRIT, ERROR_GROUP_ID, '@timestamp' @@ -99,6 +101,7 @@ export async function getErrorGroups({ exception?: Array<{ handled?: boolean; message?: string; + type?: string; }>; culprit: APMError['error']['culprit']; grouping_key: APMError['error']['grouping_key']; @@ -120,7 +123,8 @@ export async function getErrorGroups({ culprit: source.error.culprit, groupId: source.error.grouping_key, latestOccurrenceAt: source['@timestamp'], - handled: source.error.exception?.[0].handled + handled: source.error.exception?.[0].handled, + type: source.error.exception?.[0].type }; }); diff --git a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts index 0fe825e8ace35..d7e28828572d5 100644 --- a/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts +++ b/x-pack/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts @@ -41,9 +41,7 @@ export async function getServiceMapServiceNodeInfo({ const filter: ESFilter[] = [ { range: rangeFilter(start, end) }, { term: { [SERVICE_NAME]: serviceName } }, - ...(environment - ? [{ term: { [SERVICE_ENVIRONMENT]: SERVICE_ENVIRONMENT } }] - : []) + ...(environment ? [{ term: { [SERVICE_ENVIRONMENT]: environment } }] : []) ]; const minutes = Math.abs((end - start) / (1000 * 60)); diff --git a/x-pack/plugins/case/common/api/cases/configure.ts b/x-pack/plugins/case/common/api/cases/configure.ts index 9b210c2aa05ad..d92af587d0e92 100644 --- a/x-pack/plugins/case/common/api/cases/configure.ts +++ b/x-pack/plugins/case/common/api/cases/configure.ts @@ -61,13 +61,6 @@ export type CasesConnectorConfiguration = rt.TypeOf action.actionTypeId === CASE_SERVICE_NOW_ACTION + ); + return response.ok({ body: results }); } catch (error) { return response.customError(wrapError(error)); } diff --git a/x-pack/plugins/dashboard_enhanced/public/plugin.ts b/x-pack/plugins/dashboard_enhanced/public/plugin.ts index 68d3306ad4a56..e0f2aff7c70a8 100644 --- a/x-pack/plugins/dashboard_enhanced/public/plugin.ts +++ b/x-pack/plugins/dashboard_enhanced/public/plugin.ts @@ -6,17 +6,20 @@ import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; +import { EmbeddableSetup, EmbeddableStart } from '../../../../src/plugins/embeddable/public'; import { DashboardDrilldownsService } from './services'; import { DrilldownsSetup, DrilldownsStart } from '../../drilldowns/public'; export interface SetupDependencies { - uiActions: UiActionsSetup; drilldowns: DrilldownsSetup; + embeddable: EmbeddableSetup; + uiActions: UiActionsSetup; } export interface StartDependencies { - uiActions: UiActionsStart; drilldowns: DrilldownsStart; + embeddable: EmbeddableStart; + uiActions: UiActionsStart; } // eslint-disable-next-line diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx index 31ee9e29938cb..dcdefba04d882 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.test.tsx @@ -13,7 +13,7 @@ import { drilldownsPluginMock } from '../../../../../../drilldowns/public/mocks' import { ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; import { uiActionsPluginMock } from '../../../../../../../../src/plugins/ui_actions/public/mocks'; import { TriggerContextMapping } from '../../../../../../../../src/plugins/ui_actions/public'; -import { MockEmbeddable } from '../test_helpers'; +import { MockEmbeddable, enhanceEmbeddable } from '../test_helpers'; const overlays = coreMock.createStart().overlays; const drilldowns = drilldownsPluginMock.createStartContract(); @@ -40,85 +40,91 @@ test('icon exists', () => { ); }); +interface CompatibilityParams { + isEdit?: boolean; + isValueClickTriggerSupported?: boolean; + isEmbeddableEnhanced?: boolean; +} + describe('isCompatible', () => { const drilldownAction = new FlyoutCreateDrilldownAction(actionParams); - function checkCompatibility(params: { - isEdit: boolean; - withUiActions: boolean; - isValueClickTriggerSupported: boolean; - }): Promise { - return drilldownAction.isCompatible({ - embeddable: new MockEmbeddable( - { id: '', viewMode: params.isEdit ? ViewMode.EDIT : ViewMode.VIEW }, - { - supportedTriggers: (params.isValueClickTriggerSupported - ? ['VALUE_CLICK_TRIGGER'] - : []) as Array, - uiActions: params.withUiActions ? uiActions : undefined, // dynamic actions support - } - ), + async function assertCompatibility( + { + isEdit = true, + isValueClickTriggerSupported = true, + isEmbeddableEnhanced = true, + }: CompatibilityParams, + expectedResult: boolean = true + ): Promise { + let embeddable = new MockEmbeddable( + { id: '', viewMode: isEdit ? ViewMode.EDIT : ViewMode.VIEW }, + { + supportedTriggers: (isValueClickTriggerSupported ? ['VALUE_CLICK_TRIGGER'] : []) as Array< + keyof TriggerContextMapping + >, + uiActions, + } + ); + + if (isEmbeddableEnhanced) { + embeddable = enhanceEmbeddable(embeddable); + } + + const result = await drilldownAction.isCompatible({ + embeddable, }); + + expect(result).toBe(expectedResult); } + const assertNonCompatibility = (params: CompatibilityParams) => + assertCompatibility(params, false); + test("compatible if dynamicUiActions enabled, 'VALUE_CLICK_TRIGGER' is supported, in edit mode", async () => { - expect( - await checkCompatibility({ - withUiActions: true, - isEdit: true, - isValueClickTriggerSupported: true, - }) - ).toBe(true); + await assertCompatibility({}); }); - test('not compatible if dynamicUiActions disabled', async () => { - expect( - await checkCompatibility({ - withUiActions: false, - isEdit: true, - isValueClickTriggerSupported: true, - }) - ).toBe(false); + test('not compatible if embeddable is not enhanced', async () => { + await assertNonCompatibility({ + isEmbeddableEnhanced: false, + }); }); test("not compatible if 'VALUE_CLICK_TRIGGER' is not supported", async () => { - expect( - await checkCompatibility({ - withUiActions: true, - isEdit: true, - isValueClickTriggerSupported: false, - }) - ).toBe(false); + await assertNonCompatibility({ + isValueClickTriggerSupported: false, + }); }); test('not compatible if in view mode', async () => { - expect( - await checkCompatibility({ - withUiActions: true, - isEdit: false, - isValueClickTriggerSupported: true, - }) - ).toBe(false); + await assertNonCompatibility({ + isEdit: false, + }); }); }); describe('execute', () => { const drilldownAction = new FlyoutCreateDrilldownAction(actionParams); + test('throws error if no dynamicUiActions', async () => { await expect( drilldownAction.execute({ embeddable: new MockEmbeddable({ id: '' }, {}), }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Can't execute FlyoutCreateDrilldownAction without dynamicActionsManager"` + `"Need embeddable to be EnhancedEmbeddable to execute FlyoutCreateDrilldownAction."` ); }); test('should open flyout', async () => { const spy = jest.spyOn(overlays, 'openFlyout'); + const embeddable = enhanceEmbeddable(new MockEmbeddable({ id: '' }, { uiActions })); + await drilldownAction.execute({ - embeddable: new MockEmbeddable({ id: '' }, { uiActions }), + embeddable, }); + expect(spy).toBeCalled(); }); }); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx index 00e74ea570a11..d901bb8cf4e47 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_create_drilldown/flyout_create_drilldown.tsx @@ -10,6 +10,7 @@ import { CoreStart } from 'src/core/public'; import { ActionByType } from '../../../../../../../../src/plugins/ui_actions/public'; import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; import { DrilldownsStart } from '../../../../../../drilldowns/public'; +import { isEnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; export const OPEN_FLYOUT_ADD_DRILLDOWN = 'OPEN_FLYOUT_ADD_DRILLDOWN'; @@ -37,7 +38,7 @@ export class FlyoutCreateDrilldownAction implements ActionByType -1; @@ -51,10 +52,12 @@ export class FlyoutCreateDrilldownAction implements ActionByType handle.close()} placeContext={context} viewMode={'create'} - dynamicActionManager={dynamicActionManager} + dynamicActionManager={embeddable.enhancements.dynamicActions} /> ), { diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx index a3f11eb976f90..06a3654258291 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.test.tsx @@ -9,11 +9,24 @@ import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { drilldownsPluginMock } from '../../../../../../drilldowns/public/mocks'; import { ViewMode } from '../../../../../../../../src/plugins/embeddable/public'; import { uiActionsPluginMock } from '../../../../../../../../src/plugins/ui_actions/public/mocks'; -import { MockEmbeddable } from '../test_helpers'; +import { EnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; +import { MockEmbeddable, enhanceEmbeddable } from '../test_helpers'; const overlays = coreMock.createStart().overlays; const drilldowns = drilldownsPluginMock.createStartContract(); -const uiActions = uiActionsPluginMock.createStartContract(); +const uiActionsPlugin = uiActionsPluginMock.createPlugin(); +const uiActions = uiActionsPlugin.doStart(); + +uiActionsPlugin.setup.registerActionFactory({ + id: 'foo', + CollectConfig: {} as any, + createConfig: () => ({}), + isConfigValid: () => true, + create: () => ({ + id: 'test', + execute: async () => {}, + }), +}); const actionParams: FlyoutEditDrilldownParams = { drilldowns: () => Promise.resolve(drilldowns), @@ -39,63 +52,93 @@ test('MenuItem exists', () => { }); describe('isCompatible', () => { - const drilldownAction = new FlyoutEditDrilldownAction(actionParams); + function setupIsCompatible({ + isEdit = true, + isEmbeddableEnhanced = true, + }: { + isEdit?: boolean; + isEmbeddableEnhanced?: boolean; + } = {}) { + const action = new FlyoutEditDrilldownAction(actionParams); + const input = { + id: '', + viewMode: isEdit ? ViewMode.EDIT : ViewMode.VIEW, + }; + const embeddable = new MockEmbeddable(input, { + uiActions, + }); + const context = { + embeddable: (isEmbeddableEnhanced + ? enhanceEmbeddable(embeddable, uiActions) + : embeddable) as EnhancedEmbeddable, + }; + + return { + action, + context, + }; + } + + test('not compatible if no drilldowns', async () => { + const { action, context } = setupIsCompatible(); + expect(await action.isCompatible(context)).toBe(false); + }); + + test('not compatible if embeddable is not enhanced', async () => { + const { action, context } = setupIsCompatible({ isEmbeddableEnhanced: false }); + expect(await action.isCompatible(context)).toBe(false); + }); + + describe('when has at least one drilldown', () => { + test('is compatible in edit mode', async () => { + const { action, context } = setupIsCompatible(); - function checkCompatibility(params: { - isEdit: boolean; - withUiActions: boolean; - }): Promise { - return drilldownAction.isCompatible({ - embeddable: new MockEmbeddable( + await context.embeddable.enhancements.dynamicActions.createEvent( { - id: '', - viewMode: params.isEdit ? ViewMode.EDIT : ViewMode.VIEW, + config: {}, + factoryId: 'foo', + name: '', }, - { - uiActions: params.withUiActions ? uiActions : undefined, // dynamic actions support - } - ), + ['VALUE_CLICK_TRIGGER'] + ); + + expect(await action.isCompatible(context)).toBe(true); }); - } - // TODO: need proper DynamicActionsMock and ActionFactory mock - test.todo('compatible if dynamicUiActions enabled, in edit view, and have at least 1 drilldown'); + test('not compatible in view mode', async () => { + const { action, context } = setupIsCompatible({ isEdit: false }); - test('not compatible if dynamicUiActions disabled', async () => { - expect( - await checkCompatibility({ - withUiActions: false, - isEdit: true, - }) - ).toBe(false); - }); + await context.embeddable.enhancements.dynamicActions.createEvent( + { + config: {}, + factoryId: 'foo', + name: '', + }, + ['VALUE_CLICK_TRIGGER'] + ); - test('not compatible if no drilldowns', async () => { - expect( - await checkCompatibility({ - withUiActions: true, - isEdit: true, - }) - ).toBe(false); + expect(await action.isCompatible(context)).toBe(false); + }); }); }); describe('execute', () => { const drilldownAction = new FlyoutEditDrilldownAction(actionParams); + test('throws error if no dynamicUiActions', async () => { await expect( drilldownAction.execute({ embeddable: new MockEmbeddable({ id: '' }, {}), }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Can't execute FlyoutEditDrilldownAction without dynamicActionsManager"` + `"Need embeddable to be EnhancedEmbeddable to execute FlyoutEditDrilldownAction."` ); }); test('should open flyout', async () => { const spy = jest.spyOn(overlays, 'openFlyout'); await drilldownAction.execute({ - embeddable: new MockEmbeddable({ id: '' }, { uiActions }), + embeddable: enhanceEmbeddable(new MockEmbeddable({ id: '' }, { uiActions })), }); expect(spy).toBeCalled(); }); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx index 816b757592a72..23a6689bd8d9d 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/flyout_edit_drilldown.tsx @@ -15,6 +15,7 @@ import { EmbeddableContext, ViewMode } from '../../../../../../../../src/plugins import { DrilldownsStart } from '../../../../../../drilldowns/public'; import { txtDisplayName } from './i18n'; import { MenuItem } from './menu_item'; +import { isEnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; export const OPEN_FLYOUT_EDIT_DRILLDOWN = 'OPEN_FLYOUT_EDIT_DRILLDOWN'; @@ -42,16 +43,19 @@ export class FlyoutEditDrilldownAction implements ActionByType 0; + if (!isEnhancedEmbeddable(embeddable)) return false; + return embeddable.enhancements.dynamicActions.state.get().events.length > 0; } public async execute(context: EmbeddableContext) { const overlays = await this.params.overlays(); const drilldowns = await this.params.drilldowns(); - const dynamicActionManager = context.embeddable.dynamicActions; - if (!dynamicActionManager) { - throw new Error(`Can't execute FlyoutEditDrilldownAction without dynamicActionsManager`); + const { embeddable } = context; + + if (!isEnhancedEmbeddable(embeddable)) { + throw new Error( + 'Need embeddable to be EnhancedEmbeddable to execute FlyoutEditDrilldownAction.' + ); } const handle = overlays.openFlyout( @@ -60,7 +64,7 @@ export class FlyoutEditDrilldownAction implements ActionByType handle.close()} placeContext={context} viewMode={'manage'} - dynamicActionManager={dynamicActionManager} + dynamicActionManager={embeddable.enhancements.dynamicActions} /> ), { diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx index be693fadf9282..c57bafc88e978 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.test.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { render, cleanup, act } from '@testing-library/react/pure'; import { MenuItem } from './menu_item'; import { createStateContainer } from '../../../../../../../../src/plugins/kibana_utils/common'; -import { DynamicActionManager } from '../../../../../../../../src/plugins/ui_actions/public'; -import { IEmbeddable } from '../../../../../../../../src/plugins/embeddable/public/lib/embeddables'; +import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../../../../../advanced_ui_actions/public'; +import { EnhancedEmbeddable } from '../../../../../../embeddable_enhanced/public'; import '@testing-library/jest-dom'; afterEach(cleanup); @@ -20,8 +20,10 @@ test('', () => { ); diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx index 4f99fca511b07..ddcea0028409c 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/flyout_edit_drilldown/menu_item.tsx @@ -6,15 +6,12 @@ import React from 'react'; import { EuiNotificationBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { EmbeddableContext } from '../../../../../../../../src/plugins/embeddable/public'; -import { txtDisplayName } from './i18n'; import { useContainerState } from '../../../../../../../../src/plugins/kibana_utils/common'; +import { EnhancedEmbeddableContext } from '../../../../../../embeddable_enhanced/public'; +import { txtDisplayName } from './i18n'; -export const MenuItem: React.FC<{ context: EmbeddableContext }> = ({ context }) => { - if (!context.embeddable.dynamicActions) - throw new Error('Flyout edit drillldown context menu item requires `dynamicActions`'); - - const { events } = useContainerState(context.embeddable.dynamicActions.state); +export const MenuItem: React.FC<{ context: EnhancedEmbeddableContext }> = ({ context }) => { + const { events } = useContainerState(context.embeddable.enhancements.dynamicActions.state); const count = events.length; return ( diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts index 9b156b0ba85b4..07751f383fe15 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/test_helpers.ts @@ -4,11 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Embeddable, EmbeddableInput } from '../../../../../../../src/plugins/embeddable/public/'; +import { Embeddable, EmbeddableInput } from '../../../../../../../src/plugins/embeddable/public'; +import { EnhancedEmbeddable } from '../../../../../embeddable_enhanced/public'; +import { + UiActionsEnhancedMemoryActionStorage as MemoryActionStorage, + UiActionsEnhancedDynamicActionManager as DynamicActionManager, +} from '../../../../../advanced_ui_actions/public'; import { TriggerContextMapping, UiActionsStart, } from '../../../../../../../src/plugins/ui_actions/public'; +import { uiActionsPluginMock } from '../../../../../../../src/plugins/ui_actions/public/mocks'; export class MockEmbeddable extends Embeddable { public readonly type = 'mock'; @@ -26,3 +32,17 @@ export class MockEmbeddable extends Embeddable { return this.triggers; } } + +export const enhanceEmbeddable = ( + embeddable: E, + uiActions: UiActionsStart = uiActionsPluginMock.createStartContract() +): EnhancedEmbeddable => { + (embeddable as EnhancedEmbeddable).enhancements = { + dynamicActions: new DynamicActionManager({ + storage: new MemoryActionStorage(), + isCompatible: async () => true, + uiActions, + }), + }; + return embeddable as EnhancedEmbeddable; +}; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts index 4bdf03dff3531..3c7089937488b 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/dashboard_drilldowns_services.ts @@ -6,10 +6,8 @@ import { CoreSetup } from 'src/core/public'; import { SetupDependencies } from '../../plugin'; -import { - CONTEXT_MENU_TRIGGER, - EmbeddableContext, -} from '../../../../../../src/plugins/embeddable/public'; +import { CONTEXT_MENU_TRIGGER } from '../../../../../../src/plugins/embeddable/public'; +import { EnhancedEmbeddableContext } from '../../../../embeddable_enhanced/public'; import { FlyoutCreateDrilldownAction, FlyoutEditDrilldownAction, @@ -21,8 +19,8 @@ import { DashboardToDashboardDrilldown } from './dashboard_to_dashboard_drilldow declare module '../../../../../../src/plugins/ui_actions/public' { export interface ActionContextMapping { - [OPEN_FLYOUT_ADD_DRILLDOWN]: EmbeddableContext; - [OPEN_FLYOUT_EDIT_DRILLDOWN]: EmbeddableContext; + [OPEN_FLYOUT_ADD_DRILLDOWN]: EnhancedEmbeddableContext; + [OPEN_FLYOUT_EDIT_DRILLDOWN]: EnhancedEmbeddableContext; } } diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts index 88c576c70bdf0..a8a42fe11e7ce 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts @@ -71,7 +71,7 @@ describe('ES search strategy', () => { expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request'); const { method, path, body } = mockApiCaller.mock.calls[0][1]; expect(method).toBe('POST'); - expect(path).toBe('logstash-*/_async_search'); + expect(path).toBe('/logstash-*/_async_search'); expect(body).toEqual({ query: {} }); }); @@ -94,7 +94,7 @@ describe('ES search strategy', () => { expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request'); const { method, path, body } = mockApiCaller.mock.calls[0][1]; expect(method).toBe('GET'); - expect(path).toBe('_async_search/foo'); + expect(path).toBe('/_async_search/foo'); expect(body).toEqual(undefined); }); @@ -117,7 +117,7 @@ describe('ES search strategy', () => { expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request'); const { method, path } = mockApiCaller.mock.calls[0][1]; expect(method).toBe('POST'); - expect(path).toBe('foo-%E7%A8%8B/_async_search'); + expect(path).toBe('/foo-%E7%A8%8B/_async_search'); }); it('calls the rollup API if the index is a rollup type', async () => { @@ -139,6 +139,6 @@ describe('ES search strategy', () => { expect(mockApiCaller.mock.calls[0][0]).toBe('transport.request'); const { method, path } = mockApiCaller.mock.calls[0][1]; expect(method).toBe('POST'); - expect(path).toBe('foo-%E7%A8%8B/_rollup_search'); + expect(path).toBe('/foo-%E7%A8%8B/_rollup_search'); }); }); 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 301f184af7d81..6b329bccab4a7 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 @@ -45,7 +45,7 @@ export const enhancedEsSearchStrategyProvider: TSearchStrategyProvider = async id => { const method = 'DELETE'; - const path = encodeURI(`_async_search/${id}`); + const path = encodeURI(`/_async_search/${id}`); await caller('transport.request', { method, path }); }; @@ -66,7 +66,7 @@ async function asyncSearch( const { body = undefined, index = undefined, ...queryParams } = request.id ? {} : params; const method = request.id ? 'GET' : 'POST'; - const path = encodeURI(request.id ? `_async_search/${request.id}` : `${index}/_async_search`); + const path = encodeURI(request.id ? `/_async_search/${request.id}` : `/${index}/_async_search`); // Wait up to 1s for the response to return const query = toSnakeCase({ waitForCompletionTimeout: '1s', ...queryParams }); @@ -87,7 +87,7 @@ async function rollupSearch( ) { const { body, index, ...params } = request.params; const method = 'POST'; - const path = encodeURI(`${index}/_rollup_search`); + const path = encodeURI(`/${index}/_rollup_search`); const query = toSnakeCase(params); const rawResponse = await ((caller( diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx index f22ccc2f26f02..f4dcbf64d895e 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/connected_flyout_manage_drilldowns.tsx @@ -9,13 +9,13 @@ import useMountedState from 'react-use/lib/useMountedState'; import { AdvancedUiActionsActionFactory as ActionFactory, AdvancedUiActionsStart, + UiActionsEnhancedDynamicActionManager as DynamicActionManager, } from '../../../../advanced_ui_actions/public'; import { NotificationsStart } from '../../../../../../src/core/public'; import { DrilldownWizardConfig, FlyoutDrilldownWizard } from '../flyout_drilldown_wizard'; import { FlyoutListManageDrilldowns } from '../flyout_list_manage_drilldowns'; import { IStorageWrapper } from '../../../../../../src/plugins/kibana_utils/public'; import { - DynamicActionManager, UiActionsSerializedEvent, UiActionsSerializedAction, VALUE_CLICK_TRIGGER, diff --git a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts index b8deaa8b842bc..780c91eee56c3 100644 --- a/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts +++ b/x-pack/plugins/drilldowns/public/components/connected_flyout_manage_drilldowns/test_data.ts @@ -6,8 +6,10 @@ import uuid from 'uuid'; import { - DynamicActionManager, - DynamicActionManagerState, + UiActionsEnhancedDynamicActionManager as DynamicActionManager, + UiActionsEnhancedDynamicActionManagerState as DynamicActionManagerState, +} from '../../../../advanced_ui_actions/public'; +import { UiActionsSerializedAction, TriggerContextMapping, } from '../../../../../../src/plugins/ui_actions/public'; diff --git a/x-pack/plugins/embeddable_enhanced/README.md b/x-pack/plugins/embeddable_enhanced/README.md new file mode 100644 index 0000000000000..a0be90731fdb0 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/README.md @@ -0,0 +1 @@ +# X-Pack part of `embeddable` plugin diff --git a/x-pack/plugins/embeddable_enhanced/kibana.json b/x-pack/plugins/embeddable_enhanced/kibana.json new file mode 100644 index 0000000000000..9f63a55d5731d --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "embeddableEnhanced", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": ["uiActions", "embeddable"] +} diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts similarity index 93% rename from src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts rename to x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts index 83fd3f184e098..f2aa0f8ed8fdf 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.test.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts @@ -1,28 +1,17 @@ /* - * 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. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. */ -import { Embeddable } from './embeddable'; -import { EmbeddableInput } from './i_embeddable'; -import { ViewMode } from '../types'; +import { + Embeddable, + EmbeddableInput, + ViewMode, +} from '../../../../../src/plugins/embeddable/public'; import { EmbeddableActionStorage } from './embeddable_action_storage'; -import { UiActionsSerializedEvent } from '../../../../ui_actions/public'; -import { of } from '../../../../kibana_utils/common'; +import { UiActionsSerializedEvent } from '../../../../../src/plugins/ui_actions/public'; +import { of } from '../../../../../src/plugins/kibana_utils/public'; class TestEmbeddable extends Embeddable { public readonly type = 'test'; diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts similarity index 54% rename from src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts rename to x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts index fad5b4d535d6c..ad4f82cc529b5 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_action_storage.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.ts @@ -1,36 +1,21 @@ /* - * 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. + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. */ -import { - UiActionsAbstractActionStorage, - UiActionsSerializedEvent, -} from '../../../../ui_actions/public'; -import { Embeddable } from '..'; +import { UiActionsSerializedEvent as SerializedEvent } from '../../../../../src/plugins/ui_actions/public'; +import { UiActionsEnhancedAbstractActionStorage as AbstractActionStorage } from '../../../advanced_ui_actions/public'; +import { IEmbeddable } from '../../../../../src/plugins/embeddable/public'; -export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { - constructor(private readonly embbeddable: Embeddable) { +export class EmbeddableActionStorage extends AbstractActionStorage { + constructor(private readonly embbeddable: IEmbeddable) { super(); } - async create(event: UiActionsSerializedEvent) { + async create(event: SerializedEvent) { const input = this.embbeddable.getInput(); - const events = (input.events || []) as UiActionsSerializedEvent[]; + const events = (input.events || []) as SerializedEvent[]; const exists = !!events.find(({ eventId }) => eventId === event.eventId); if (exists) { @@ -45,9 +30,9 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { }); } - async update(event: UiActionsSerializedEvent) { + async update(event: SerializedEvent) { const input = this.embbeddable.getInput(); - const events = (input.events || []) as UiActionsSerializedEvent[]; + const events = (input.events || []) as SerializedEvent[]; const index = events.findIndex(({ eventId }) => eventId === event.eventId); if (index === -1) { @@ -65,7 +50,7 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { async remove(eventId: string) { const input = this.embbeddable.getInput(); - const events = (input.events || []) as UiActionsSerializedEvent[]; + const events = (input.events || []) as SerializedEvent[]; const index = events.findIndex(event => eventId === event.eventId); if (index === -1) { @@ -81,9 +66,9 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { }); } - async read(eventId: string): Promise { + async read(eventId: string): Promise { const input = this.embbeddable.getInput(); - const events = (input.events || []) as UiActionsSerializedEvent[]; + const events = (input.events || []) as SerializedEvent[]; const event = events.find(ev => eventId === ev.eventId); if (!event) { @@ -98,10 +83,10 @@ export class EmbeddableActionStorage extends UiActionsAbstractActionStorage { private __list() { const input = this.embbeddable.getInput(); - return (input.events || []) as UiActionsSerializedEvent[]; + return (input.events || []) as SerializedEvent[]; } - async list(): Promise { + async list(): Promise { return this.__list(); } } diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/index.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/index.ts new file mode 100644 index 0000000000000..fabbc60a13f67 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './is_enhanced_embeddable'; +export * from './embeddable_action_storage'; diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/is_enhanced_embeddable.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/is_enhanced_embeddable.ts new file mode 100644 index 0000000000000..f29430dc6a3de --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/is_enhanced_embeddable.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEmbeddable } from '../../../../../src/plugins/embeddable/public'; +import { EnhancedEmbeddable } from '../types'; + +export const isEnhancedEmbeddable = ( + maybeEnhancedEmbeddable: E +): maybeEnhancedEmbeddable is EnhancedEmbeddable => + typeof (maybeEnhancedEmbeddable as EnhancedEmbeddable) + ?.enhancements?.dynamicActions === 'object'; diff --git a/x-pack/plugins/embeddable_enhanced/public/index.ts b/x-pack/plugins/embeddable_enhanced/public/index.ts new file mode 100644 index 0000000000000..059acf9644820 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'src/core/public'; +import { EmbeddableEnhancedPlugin } from './plugin'; + +export { + SetupContract as EmbeddableEnhancedSetupContract, + SetupDependencies as EmbeddableEnhancedSetupDependencies, + StartContract as EmbeddableEnhancedStartContract, + StartDependencies as EmbeddableEnhancedStartDependencies, +} from './plugin'; + +export function plugin(context: PluginInitializerContext) { + return new EmbeddableEnhancedPlugin(context); +} + +export { EnhancedEmbeddable, EnhancedEmbeddableContext } from './types'; +export { isEnhancedEmbeddable } from './embeddables'; diff --git a/x-pack/plugins/embeddable_enhanced/public/mocks.ts b/x-pack/plugins/embeddable_enhanced/public/mocks.ts new file mode 100644 index 0000000000000..d048d1248b6ff --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/mocks.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EmbeddableEnhancedSetupContract, EmbeddableEnhancedStartContract } from '.'; + +export type Setup = jest.Mocked; +export type Start = jest.Mocked; + +const createSetupContract = (): Setup => { + const setupContract: Setup = {}; + + return setupContract; +}; + +const createStartContract = (): Start => { + const startContract: Start = {}; + + return startContract; +}; + +export const embeddableEnhancedPluginMock = { + createSetupContract, + createStartContract, +}; diff --git a/x-pack/plugins/embeddable_enhanced/public/plugin.ts b/x-pack/plugins/embeddable_enhanced/public/plugin.ts new file mode 100644 index 0000000000000..5a1f07a480937 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/plugin.ts @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; +import { SavedObjectAttributes } from 'kibana/public'; +import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; +import { + EmbeddableFactory, + EmbeddableFactoryDefinition, + EmbeddableInput, + EmbeddableOutput, + EmbeddableSetup, + EmbeddableStart, + IEmbeddable, + defaultEmbeddableFactoryProvider, + EmbeddableContext, +} from '../../../../src/plugins/embeddable/public'; +import { EnhancedEmbeddable } from './types'; +import { EmbeddableActionStorage } from './embeddables/embeddable_action_storage'; +import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../advanced_ui_actions/public'; + +export interface SetupDependencies { + embeddable: EmbeddableSetup; + uiActions: UiActionsSetup; +} + +export interface StartDependencies { + embeddable: EmbeddableStart; + uiActions: UiActionsStart; +} + +// eslint-disable-next-line +export interface SetupContract {} + +// eslint-disable-next-line +export interface StartContract {} + +export class EmbeddableEnhancedPlugin + implements Plugin { + constructor(protected readonly context: PluginInitializerContext) {} + + private uiActions?: StartDependencies['uiActions']; + + public setup(core: CoreSetup, plugins: SetupDependencies): SetupContract { + this.setCustomEmbeddableFactoryProvider(plugins); + + return {}; + } + + public start(core: CoreStart, plugins: StartDependencies): StartContract { + this.uiActions = plugins.uiActions; + + return {}; + } + + public stop() {} + + private setCustomEmbeddableFactoryProvider(plugins: SetupDependencies) { + plugins.embeddable.setCustomEmbeddableFactoryProvider( + < + I extends EmbeddableInput = EmbeddableInput, + O extends EmbeddableOutput = EmbeddableOutput, + E extends IEmbeddable = IEmbeddable, + T extends SavedObjectAttributes = SavedObjectAttributes + >( + def: EmbeddableFactoryDefinition + ): EmbeddableFactory => { + const factory: EmbeddableFactory = defaultEmbeddableFactoryProvider( + def + ); + return { + ...factory, + create: async (...args) => { + const embeddable = await factory.create(...args); + if (!embeddable) return embeddable; + return this.enhanceEmbeddableWithDynamicActions(embeddable); + }, + createFromSavedObject: async (...args) => { + const embeddable = await factory.createFromSavedObject(...args); + if (!embeddable) return embeddable; + return this.enhanceEmbeddableWithDynamicActions(embeddable); + }, + }; + } + ); + } + + private enhanceEmbeddableWithDynamicActions( + embeddable: E + ): EnhancedEmbeddable { + const enhancedEmbeddable = embeddable as EnhancedEmbeddable; + + const storage = new EmbeddableActionStorage(embeddable); + const dynamicActions = new DynamicActionManager({ + isCompatible: async (context: unknown) => + (context as EmbeddableContext).embeddable.runtimeId === embeddable.runtimeId, + storage, + uiActions: this.uiActions!, + }); + + dynamicActions.start().catch(error => { + /* eslint-disable */ + console.log('Failed to start embeddable dynamic actions', embeddable); + console.error(error); + /* eslint-enable */ + }); + + const stop = () => { + dynamicActions.stop().catch(error => { + /* eslint-disable */ + console.log('Failed to stop embeddable dynamic actions', embeddable); + console.error(error); + /* eslint-enable */ + }); + }; + + embeddable.getInput$().subscribe({ + next: () => { + storage.reload$.next(); + }, + error: stop, + complete: stop, + }); + + enhancedEmbeddable.enhancements = { + ...enhancedEmbeddable.enhancements, + dynamicActions, + }; + + return enhancedEmbeddable; + } +} diff --git a/x-pack/plugins/embeddable_enhanced/public/types.ts b/x-pack/plugins/embeddable_enhanced/public/types.ts new file mode 100644 index 0000000000000..924605be332b2 --- /dev/null +++ b/x-pack/plugins/embeddable_enhanced/public/types.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 { IEmbeddable } from '../../../../src/plugins/embeddable/public'; +import { UiActionsEnhancedDynamicActionManager as DynamicActionManager } from '../../advanced_ui_actions/public'; + +export type EnhancedEmbeddable = E & { + enhancements: { + /** + * Default implementation of dynamic action manager for embeddables. + */ + dynamicActions: DynamicActionManager; + }; +}; + +export interface EnhancedEmbeddableContext { + embeddable: EnhancedEmbeddable; +} diff --git a/x-pack/plugins/encrypted_saved_objects/README.md b/x-pack/plugins/encrypted_saved_objects/README.md index a352989870079..6085b52d392a4 100644 --- a/x-pack/plugins/encrypted_saved_objects/README.md +++ b/x-pack/plugins/encrypted_saved_objects/README.md @@ -100,10 +100,10 @@ $ node scripts/jest.js In one shell, from `kibana-root-folder/x-pack`: ```bash -$ node scripts/functional_tests_server.js --config test/plugin_api_integration/config.js +$ node scripts/functional_tests_server.js --config test/encrypted_saved_objects_api_integration/config.ts ``` In another shell, from `kibana-root-folder/x-pack`: ```bash -$ node ../scripts/functional_test_runner.js --config test/plugin_api_integration/config.js --grep="{TEST_NAME}" +$ node ../scripts/functional_test_runner.js --config test/encrypted_saved_objects_api_integration/config.ts --grep="{TEST_NAME}" ``` diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts index e1e1a8224aa7b..be33238a26ee7 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.test.ts @@ -19,9 +19,10 @@ beforeEach(() => { mockAuditLogger = encryptedSavedObjectsAuditLoggerMock.create(); // Call actual `@elastic/node-crypto` by default, but allow to override implementation in tests. - jest - .requireMock('@elastic/node-crypto') - .mockImplementation((...args: any[]) => jest.requireActual('@elastic/node-crypto')(...args)); + jest.requireMock('@elastic/node-crypto').mockImplementation((...args: any[]) => { + const { default: nodeCrypto } = jest.requireActual('@elastic/node-crypto'); + return nodeCrypto(...args); + }); service = new EncryptedSavedObjectsService( 'encryption-key-abc', diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts index 94c1684529577..eea2b12354d9b 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encrypted_saved_objects_service.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore -import nodeCrypto from '@elastic/node-crypto'; +import nodeCrypto, { Crypto } from '@elastic/node-crypto'; import stringify from 'json-stable-stringify'; import typeDetect from 'type-detect'; import { Logger } from 'src/core/server'; @@ -49,10 +48,7 @@ export function descriptorToArray(descriptor: SavedObjectDescriptor) { * attributes. */ export class EncryptedSavedObjectsService { - private readonly crypto: Readonly<{ - encrypt(valueToEncrypt: T, aad?: string): Promise; - decrypt(valueToDecrypt: string, aad?: string): Promise; - }>; + private readonly crypto: Readonly; /** * Map of all registered saved object types where the `key` is saved object type and the `value` @@ -229,10 +225,10 @@ export class EncryptedSavedObjectsService { } try { - decryptedAttributes[attributeName] = await this.crypto.decrypt( + decryptedAttributes[attributeName] = (await this.crypto.decrypt( attributeValue, encryptionAAD - ); + )) as string; } catch (err) { this.logger.error(`Failed to decrypt "${attributeName}" attribute: ${err.message || err}`); this.audit.decryptAttributeFailure(attributeName, descriptor); diff --git a/x-pack/plugins/endpoint/common/generate_data.ts b/x-pack/plugins/endpoint/common/generate_data.ts index 0ec105129b7ac..7c24bd9d77148 100644 --- a/x-pack/plugins/endpoint/common/generate_data.ts +++ b/x-pack/plugins/endpoint/common/generate_data.ts @@ -7,6 +7,11 @@ import uuid from 'uuid'; import seedrandom from 'seedrandom'; import { AlertEvent, EndpointEvent, HostMetadata, OSFields, HostFields } from './types'; +// FIXME: move types/model to top-level +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { PolicyData } from '../public/applications/endpoint/types'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { generatePolicy } from '../public/applications/endpoint/models/policy'; export type Event = AlertEvent | EndpointEvent; @@ -452,6 +457,39 @@ export class EndpointDocGenerator { } } + /** + * Generates an Ingest `datasource` that includes the Endpoint Policy data + */ + public generatePolicyDatasource(): PolicyData { + return { + id: this.seededUUIDv4(), + name: 'Endpoint Policy', + description: 'Policy to protect the worlds data', + config_id: this.seededUUIDv4(), + enabled: true, + output_id: '', + inputs: [ + { + type: 'endpoint', + enabled: true, + streams: [], + config: { + policy: { + value: generatePolicy(), + }, + }, + }, + ], + namespace: 'default', + package: { + name: 'endpoint', + title: 'Elastic Endpoint', + version: '1.0.0', + }, + revision: 1, + }; + } + private randomN(n: number): number { return Math.floor(this.random() * n); } diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts index e8e1281a88925..a614526d92a3f 100644 --- a/x-pack/plugins/endpoint/common/types.ts +++ b/x-pack/plugins/endpoint/common/types.ts @@ -86,7 +86,7 @@ export interface AlertResultList { export interface HostResultList { /* the hosts restricted by the page size */ - hosts: HostMetadata[]; + hosts: HostInfo[]; /* the total number of unique hosts in the index */ total: number; /* the page size requested */ @@ -252,6 +252,32 @@ export type AlertData = AlertEvent & AlertMetadata; export type AlertDetails = AlertData & AlertState; +/** + * The status of the host + */ +export enum HostStatus { + /** + * Default state of the host when no host information is present or host information cannot + * be retrieved. e.g. API error + */ + ERROR = 'error', + + /** + * Host is online as indicated by its checkin status during the last checkin window + */ + ONLINE = 'online', + + /** + * Host is offline as indicated by its checkin status during the last checkin window + */ + OFFLINE = 'offline', +} + +export type HostInfo = Immutable<{ + metadata: HostMetadata; + host_status: HostStatus; +}>; + export type HostMetadata = Immutable<{ '@timestamp': number; event: { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx index fa9055e0d9bbd..89a6302351a54 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/index.tsx @@ -7,13 +7,9 @@ import * as React from 'react'; import ReactDOM from 'react-dom'; import { CoreStart, AppMountParameters, ScopedHistory } from 'kibana/public'; -import { I18nProvider, FormattedMessage } from '@kbn/i18n/react'; -import { Route, Switch, Router } from 'react-router-dom'; -import { Provider } from 'react-redux'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Route, Switch } from 'react-router-dom'; import { Store } from 'redux'; -import { useObservable } from 'react-use'; -import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; -import { RouteCapture } from './view/route_capture'; import { EndpointPluginStartDependencies } from '../../plugin'; import { appStoreFactory } from './store'; import { AlertIndex } from './view/alerts'; @@ -21,7 +17,7 @@ import { HostList } from './view/hosts'; import { PolicyList } from './view/policy'; import { PolicyDetails } from './view/policy'; import { HeaderNavigation } from './components/header_nav'; -import { EuiThemeProvider } from '../../../../../legacy/common/eui_styled_components'; +import { AppRootProvider } from './view/app_root_provider'; /** * This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle. @@ -49,54 +45,31 @@ interface RouterProps { } const AppRoot: React.FunctionComponent = React.memo( - ({ - history, - store, - coreStart: { http, notifications, uiSettings, application }, - depsStart: { data }, - }) => { - const isDarkMode = useObservable(uiSettings.get$('theme:darkMode')); - + ({ history, store, coreStart, depsStart }) => { return ( - - - - - - - - - ( -

- -

- )} - /> - - - - - ( - - )} - /> -
-
-
-
-
-
-
+ + + + ( +

+ +

+ )} + /> + + + + + ( + + )} + /> +
+
); } ); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/mocks/app_context_render.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/mocks/app_context_render.tsx new file mode 100644 index 0000000000000..af34205e2310f --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/mocks/app_context_render.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { createMemoryHistory } from 'history'; +import { render as reactRender, RenderOptions, RenderResult } from '@testing-library/react'; +import { appStoreFactory } from '../store'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { EndpointPluginStartDependencies } from '../../../plugin'; +import { depsStartMock } from './dependencies_start_mock'; +import { AppRootProvider } from '../view/app_root_provider'; + +type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult; + +/** + * Mocked app root context renderer + */ +interface AppContextTestRender { + store: ReturnType; + history: ReturnType; + coreStart: ReturnType; + depsStart: EndpointPluginStartDependencies; + /** + * A wrapper around `AppRootContext` component. Uses the mocked modules as input to the + * `AppRootContext` + */ + AppWrapper: React.FC; + /** + * Renders the given UI within the created `AppWrapper` providing the given UI a mocked + * endpoint runtime context environment + */ + render: UiRender; +} + +/** + * Creates a mocked endpoint app context custom renderer that can be used to render + * component that depend upon the application's surrounding context providers. + * Factory also returns the content that was used to create the custom renderer, allowing + * for further customization. + */ +export const createAppRootMockRenderer = (): AppContextTestRender => { + const history = createMemoryHistory(); + const coreStart = coreMock.createStart({ basePath: '/mock' }); + const depsStart = depsStartMock(); + const store = appStoreFactory({ coreStart, depsStart }); + const AppWrapper: React.FunctionComponent<{ children: React.ReactElement }> = ({ children }) => ( + + {children} + + ); + const render: UiRender = (ui, options) => { + // @ts-ignore + return reactRender(ui, { + wrapper: AppWrapper, + ...options, + }); + }; + + return { + store, + history, + coreStart, + depsStart, + AppWrapper, + render, + }; +}; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/mocks.ts b/x-pack/plugins/endpoint/public/applications/endpoint/mocks/dependencies_start_mock.ts similarity index 96% rename from x-pack/plugins/endpoint/public/applications/endpoint/mocks.ts rename to x-pack/plugins/endpoint/public/applications/endpoint/mocks/dependencies_start_mock.ts index e1a90b4a416dc..00cf0bca57e66 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/mocks.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/mocks/dependencies_start_mock.ts @@ -7,7 +7,7 @@ import { dataPluginMock, Start as DataPublicStartMock, -} from '../../../../../../src/plugins/data/public/mocks'; +} from '../../../../../../../src/plugins/data/public/mocks'; type DataMock = Omit & { indexPatterns: Omit & { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/mocks/index.ts b/x-pack/plugins/endpoint/public/applications/endpoint/mocks/index.ts new file mode 100644 index 0000000000000..65e78f27943ba --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/mocks/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './dependencies_start_mock'; +export * from './app_context_render'; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts index dee35aa3b895a..4dafa68ddb647 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/action.ts @@ -5,7 +5,7 @@ */ import { HostListPagination, ServerApiError } from '../../types'; -import { HostResultList, HostMetadata } from '../../../../../common/types'; +import { HostResultList, HostInfo } from '../../../../../common/types'; interface ServerReturnedHostList { type: 'serverReturnedHostList'; @@ -14,7 +14,7 @@ interface ServerReturnedHostList { interface ServerReturnedHostDetails { type: 'serverReturnedHostDetails'; - payload: HostMetadata; + payload: HostInfo; } interface ServerFailedToReturnHostDetails { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts index 9aff66cdfb75e..6148934343635 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/index.test.ts @@ -52,7 +52,7 @@ describe('HostList store concerns', () => { }); const currentState = store.getState(); - expect(currentState.hosts).toEqual(payload.hosts); + expect(currentState.hosts).toEqual(payload.hosts.map(hostInfo => hostInfo.metadata)); expect(currentState.pageSize).toEqual(payload.request_page_size); expect(currentState.pageIndex).toEqual(payload.request_page_index); expect(currentState.total).toEqual(payload.total); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts index a1973a38b6534..8c8578426aa29 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.test.ts @@ -58,6 +58,6 @@ describe('host list middleware', () => { paging_properties: [{ page_index: 0 }, { page_size: 10 }], }), }); - expect(listData(getState())).toEqual(apiResponse.hosts); + expect(listData(getState())).toEqual(apiResponse.hosts.map(hostInfo => hostInfo.metadata)); }); }); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts index db39ecf448312..d4c2602e34387 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/mock_host_result_list.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HostResultList } from '../../../../../common/types'; +import { HostResultList, HostStatus } from '../../../../../common/types'; import { EndpointDocGenerator } from '../../../../../common/generate_data'; export const mockHostResultList: (options?: { @@ -27,7 +27,10 @@ export const mockHostResultList: (options?: { const hosts = []; for (let index = 0; index < actualCountToReturn; index++) { const generator = new EndpointDocGenerator('seed'); - hosts.push(generator.generateHostMetadata()); + hosts.push({ + metadata: generator.generateHostMetadata(), + host_status: HostStatus.ERROR, + }); } const mock: HostResultList = { hosts, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts index fd70317a9f37e..ad6741dab7be7 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/reducer.ts @@ -34,7 +34,7 @@ export const hostListReducer: Reducer = ( } = action.payload; return { ...state, - hosts, + hosts: hosts.map(hostInfo => hostInfo.metadata), total, pageSize, pageIndex, @@ -43,7 +43,7 @@ export const hostListReducer: Reducer = ( } else if (action.type === 'serverReturnedHostDetails') { return { ...state, - details: action.payload, + details: action.payload.metadata, }; } else if (action.type === 'serverFailedToReturnHostDetails') { return { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/reducer.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/reducer.ts index af9b49cea18ad..fb3e26157ef32 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/reducer.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/policy_details/reducer.ts @@ -74,8 +74,12 @@ export const policyDetailsReducer: Reducer = ( ...state, location: action.payload, }; + const isCurrentlyOnDetailsPage = isOnPolicyDetailsPage(newState); + const wasPreviouslyOnDetailsPage = isOnPolicyDetailsPage(state); - if (isOnPolicyDetailsPage(newState)) { + // Did user just enter the Detail page? if so, then set the loading indicator and return new state + if (isCurrentlyOnDetailsPage && !wasPreviouslyOnDetailsPage) { + newState.isLoading = true; return newState; } return { diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/app_root_provider.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/app_root_provider.tsx new file mode 100644 index 0000000000000..ca27831ee90b5 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/app_root_provider.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, ReactNode, useMemo } from 'react'; +import { Provider } from 'react-redux'; +import { I18nProvider } from '@kbn/i18n/react'; +import { Router } from 'react-router-dom'; +import { History } from 'history'; +import { CoreStart } from 'kibana/public'; +import { useObservable } from 'react-use'; +import { EuiThemeProvider } from '../../../../../../legacy/common/eui_styled_components'; +import { KibanaContextProvider } from '../../../../../../../src/plugins/kibana_react/public'; +import { appStoreFactory } from '../store'; +import { RouteCapture } from './route_capture'; +import { EndpointPluginStartDependencies } from '../../../plugin'; + +/** + * Provides the context for rendering the endpoint app + */ +export const AppRootProvider = memo<{ + store: ReturnType; + history: History; + coreStart: CoreStart; + depsStart: EndpointPluginStartDependencies; + children: ReactNode | ReactNode[]; +}>( + ({ + store, + history, + coreStart: { http, notifications, uiSettings, application }, + depsStart: { data }, + children, + }) => { + const isDarkMode = useObservable(uiSettings.get$('theme:darkMode')); + const services = useMemo(() => ({ http, notifications, application, data }), [ + application, + data, + http, + notifications, + ]); + return ( + + + + + + {children} + + + + + + ); + } +); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/agents_summary.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/agents_summary.tsx index d0751cf9fb886..a3e30eb891db4 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/agents_summary.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/agents_summary.tsx @@ -61,7 +61,7 @@ export const AgentsSummary = memo(props => { }, []); return ( - + {stats.map(({ key, title, health }) => { return ( diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.test.tsx new file mode 100644 index 0000000000000..2ecc2b117bf01 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.test.tsx @@ -0,0 +1,239 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { createAppRootMockRenderer } from '../../mocks'; +import { PolicyDetails } from './policy_details'; +import { EndpointDocGenerator } from '../../../../../common/generate_data'; + +describe('Policy Details', () => { + type FindReactWrapperResponse = ReturnType['find']>; + + const sleep = (ms = 100) => new Promise(wakeup => setTimeout(wakeup, ms)); + const generator = new EndpointDocGenerator(); + const { history, AppWrapper, coreStart } = createAppRootMockRenderer(); + const http = coreStart.http; + const render = (ui: Parameters[0]) => mount(ui, { wrappingComponent: AppWrapper }); + let policyDatasource: ReturnType; + let policyView: ReturnType; + + beforeEach(() => jest.clearAllMocks()); + + afterEach(() => { + if (policyView) { + policyView.unmount(); + } + }); + + describe('when displayed with invalid id', () => { + beforeEach(() => { + http.get.mockReturnValue(Promise.reject(new Error('policy not found'))); + history.push('/policy/1'); + policyView = render(); + }); + + it('should show loader followed by error message', () => { + expect(policyView.find('EuiLoadingSpinner').length).toBe(1); + policyView.update(); + const callout = policyView.find('EuiCallOut'); + expect(callout).toHaveLength(1); + expect(callout.prop('color')).toEqual('danger'); + expect(callout.text()).toEqual('policy not found'); + }); + }); + describe('when displayed with valid id', () => { + let asyncActions: Promise = Promise.resolve(); + + beforeEach(() => { + policyDatasource = generator.generatePolicyDatasource(); + policyDatasource.id = '1'; + + http.get.mockImplementation((...args) => { + const [path] = args; + if (typeof path === 'string') { + // GET datasouce + if (path === '/api/ingest_manager/datasources/1') { + asyncActions = asyncActions.then(async (): Promise => await sleep()); + return Promise.resolve({ + item: policyDatasource, + success: true, + }); + } + + // GET Agent status for agent config + if (path === '/api/ingest_manager/fleet/agent-status') { + asyncActions = asyncActions.then(async () => await sleep()); + return Promise.resolve({ + results: { events: 0, total: 5, online: 3, error: 1, offline: 1 }, + success: true, + }); + } + } + + return Promise.reject(new Error('unknown API call!')); + }); + history.push('/policy/1'); + policyView = render(); + }); + + it('should display back to list button and policy title', () => { + policyView.update(); + const pageHeaderLeft = policyView.find( + 'EuiPageHeaderSection[data-test-subj="pageViewHeaderLeft"]' + ); + + const backToListButton = pageHeaderLeft.find('EuiButtonEmpty'); + expect(backToListButton.prop('iconType')).toBe('arrowLeft'); + expect(backToListButton.prop('href')).toBe('/mock/app/endpoint/policy'); + expect(backToListButton.text()).toBe('Back to policy list'); + + const pageTitle = pageHeaderLeft.find('[data-test-subj="pageViewHeaderLeftTitle"]'); + expect(pageTitle).toHaveLength(1); + expect(pageTitle.text()).toEqual(policyDatasource.name); + }); + it('should navigate to list if back to link is clicked', async () => { + policyView.update(); + const backToListButton = policyView.find( + 'EuiPageHeaderSection[data-test-subj="pageViewHeaderLeft"] EuiButtonEmpty' + ); + expect(history.location.pathname).toEqual('/policy/1'); + backToListButton.simulate('click'); + expect(history.location.pathname).toEqual('/policy'); + }); + it('should display agent stats', async () => { + await asyncActions; + policyView.update(); + const headerRight = policyView.find( + 'EuiPageHeaderSection[data-test-subj="pageViewHeaderRight"]' + ); + const agentsSummary = headerRight.find('EuiFlexGroup[data-test-subj="policyAgentsSummary"]'); + expect(agentsSummary).toHaveLength(1); + expect(agentsSummary.text()).toBe('Hosts5Online3Offline1Error1'); + }); + it('should display cancel button', async () => { + await asyncActions; + policyView.update(); + const cancelbutton = policyView.find( + 'EuiButtonEmpty[data-test-subj="policyDetailsCancelButton"]' + ); + expect(cancelbutton).toHaveLength(1); + expect(cancelbutton.text()).toEqual('Cancel'); + }); + it('should redirect to policy list when cancel button is clicked', async () => { + await asyncActions; + policyView.update(); + const cancelbutton = policyView.find( + 'EuiButtonEmpty[data-test-subj="policyDetailsCancelButton"]' + ); + expect(history.location.pathname).toEqual('/policy/1'); + cancelbutton.simulate('click'); + expect(history.location.pathname).toEqual('/policy'); + }); + it('should display save button', async () => { + await asyncActions; + policyView.update(); + const saveButton = policyView.find('EuiButton[data-test-subj="policyDetailsSaveButton"]'); + expect(saveButton).toHaveLength(1); + expect(saveButton.text()).toEqual('Save'); + }); + describe('when the save button is clicked', () => { + let saveButton: FindReactWrapperResponse; + let confirmModal: FindReactWrapperResponse; + let modalCancelButton: FindReactWrapperResponse; + let modalConfirmButton: FindReactWrapperResponse; + + beforeEach(async () => { + await asyncActions; + policyView.update(); + saveButton = policyView.find('EuiButton[data-test-subj="policyDetailsSaveButton"]'); + saveButton.simulate('click'); + policyView.update(); + confirmModal = policyView.find( + 'EuiConfirmModal[data-test-subj="policyDetailsConfirmModal"]' + ); + modalCancelButton = confirmModal.find('button[data-test-subj="confirmModalCancelButton"]'); + modalConfirmButton = confirmModal.find( + 'button[data-test-subj="confirmModalConfirmButton"]' + ); + http.put.mockImplementation((...args) => { + asyncActions = asyncActions.then(async () => await sleep()); + const [path] = args; + if (typeof path === 'string') { + if (path === '/api/ingest_manager/datasources/1') { + return Promise.resolve({ + item: policyDatasource, + success: true, + }); + } + } + + return Promise.reject(new Error('unknown PUT path!')); + }); + }); + + it('should show a modal confirmation', () => { + expect(confirmModal).toHaveLength(1); + expect(confirmModal.find('div[data-test-subj="confirmModalTitleText"]').text()).toEqual( + 'Save and deploy changes' + ); + expect(modalCancelButton.text()).toEqual('Cancel'); + expect(modalConfirmButton.text()).toEqual('Save and deploy changes'); + }); + it('should show info callout if policy is in use', () => { + const warningCallout = confirmModal.find( + 'EuiCallOut[data-test-subj="policyDetailsWarningCallout"]' + ); + expect(warningCallout).toHaveLength(1); + expect(warningCallout.text()).toEqual( + 'This action will update 5 hostsSaving these changes will apply the updates to all active endpoints assigned to this policy' + ); + }); + it('should close dialog if cancel button is clicked', () => { + modalCancelButton.simulate('click'); + expect( + policyView.find('EuiConfirmModal[data-test-subj="policyDetailsConfirmModal"]') + ).toHaveLength(0); + }); + it('should update policy and show success notification when confirm button is clicked', async () => { + modalConfirmButton.simulate('click'); + policyView.update(); + // Modal should be closed + expect( + policyView.find('EuiConfirmModal[data-test-subj="policyDetailsConfirmModal"]') + ).toHaveLength(0); + + // API should be called + await asyncActions; + expect(http.put.mock.calls[0][0]).toEqual(`/api/ingest_manager/datasources/1`); + policyView.update(); + + // Toast notification should be shown + const toastAddMock = coreStart.notifications.toasts.add.mock; + expect(toastAddMock.calls).toHaveLength(1); + expect(toastAddMock.calls[0][0]).toMatchObject({ + color: 'success', + iconType: 'check', + }); + }); + it('should show an error notification toast if update fails', async () => { + policyDatasource.id = 'invalid'; + modalConfirmButton.simulate('click'); + + await asyncActions; + policyView.update(); + + // Toast notification should be shown + const toastAddMock = coreStart.notifications.toasts.add.mock; + expect(toastAddMock.calls).toHaveLength(1); + expect(toastAddMock.calls[0][0]).toMatchObject({ + color: 'danger', + iconType: 'alert', + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx index 2dba301bf4537..bc56e5e6f6329 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_details.tsx @@ -149,7 +149,10 @@ export const PolicyDetails = React.memo(() => { - + @@ -157,6 +160,7 @@ export const PolicyDetails = React.memo(() => { 0 && ( <> { +): Promise { const query = getESQueryHostMetadataByID(id); const response = (await context.core.elasticsearch.dataClient.callAsCurrentUser( 'search', @@ -101,7 +98,7 @@ export async function getHostData( return undefined; } - return response.hits.hits[0]._source; + return enrichHostMetadata(response.hits.hits[0]._source); } function mapToHostResultList( @@ -116,7 +113,7 @@ function mapToHostResultList( hosts: searchResponse.hits.hits .map(response => response.inner_hits.most_recent.hits.hits) .flatMap(data => data as HitSource) - .map(entry => entry._source), + .map(entry => enrichHostMetadata(entry._source)), total: totalNumberOfHosts, }; } else { @@ -128,3 +125,10 @@ function mapToHostResultList( }; } } + +function enrichHostMetadata(hostMetadata: HostMetadata): HostInfo { + return { + metadata: hostMetadata, + host_status: HostStatus.ERROR, + }; +} diff --git a/x-pack/plugins/endpoint/server/routes/metadata.test.ts b/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts similarity index 94% rename from x-pack/plugins/endpoint/server/routes/metadata.test.ts rename to x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts index 65e07edbcde24..9bd251735cc04 100644 --- a/x-pack/plugins/endpoint/server/routes/metadata.test.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/metadata.test.ts @@ -17,12 +17,12 @@ import { httpServerMock, httpServiceMock, loggingServiceMock, -} from '../../../../../src/core/server/mocks'; -import { HostMetadata, HostResultList } from '../../common/types'; +} from '../../../../../../src/core/server/mocks'; +import { HostInfo, HostMetadata, HostResultList, HostStatus } from '../../../common/types'; import { SearchResponse } from 'elasticsearch'; -import { registerEndpointRoutes } from './metadata'; -import { EndpointConfigSchema } from '../config'; -import * as data from '../test_data/all_metadata_data.json'; +import { EndpointConfigSchema } from '../../config'; +import * as data from '../../test_data/all_metadata_data.json'; +import { registerEndpointRoutes } from './index'; describe('test endpoint route', () => { let routerMock: jest.Mocked; @@ -230,7 +230,7 @@ describe('test endpoint route', () => { expect(message).toEqual('Endpoint Not Found'); }); - it('should return a single endpoint', async () => { + it('should return a single endpoint with status error', async () => { const mockRequest = httpServerMock.createKibanaRequest({ params: { id: (data as any).hits.hits[0]._id }, }); @@ -257,8 +257,9 @@ describe('test endpoint route', () => { expect(mockScopedClient.callAsCurrentUser).toBeCalled(); expect(routeConfig.options).toEqual({ authRequired: true }); expect(mockResponse.ok).toBeCalled(); - const result = mockResponse.ok.mock.calls[0][0]?.body as HostMetadata; - expect(result).toHaveProperty('endpoint'); + const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo; + expect(result).toHaveProperty('metadata.endpoint'); + expect(result.host_status).toEqual(HostStatus.ERROR); }); }); }); diff --git a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.test.ts b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts similarity index 97% rename from x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.test.ts rename to x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts index 0966b52c79f7d..2514d5aa85811 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.test.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.test.ts @@ -5,10 +5,7 @@ */ import { httpServerMock, loggingServiceMock } from '../../../../../../src/core/server/mocks'; import { EndpointConfigSchema } from '../../config'; -import { - kibanaRequestToMetadataListESQuery, - getESQueryHostMetadataByID, -} from './metadata_query_builders'; +import { kibanaRequestToMetadataListESQuery, getESQueryHostMetadataByID } from './query_builders'; import { EndpointAppConstants } from '../../../common/types'; describe('query builder', () => { diff --git a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.ts b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.ts similarity index 100% rename from x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.ts rename to x-pack/plugins/endpoint/server/routes/metadata/query_builders.ts index 57b0a4ef10519..bd07604fe9ad2 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/metadata_query_builders.ts +++ b/x-pack/plugins/endpoint/server/routes/metadata/query_builders.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { KibanaRequest } from 'kibana/server'; -import { EndpointAppConstants } from '../../../common/types'; -import { EndpointAppContext } from '../../types'; import { esKuery } from '../../../../../../src/plugins/data/server'; +import { EndpointAppContext } from '../../types'; +import { EndpointAppConstants } from '../../../common/types'; export const kibanaRequestToMetadataListESQuery = async ( request: KibanaRequest, diff --git a/x-pack/plugins/event_log/common/index.ts b/x-pack/plugins/event_log/common/index.ts new file mode 100644 index 0000000000000..3ee274916c127 --- /dev/null +++ b/x-pack/plugins/event_log/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 const BASE_EVENT_LOG_API_PATH = '/api/event_log'; diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json index d0e4652c2828e..ab1b4096d17f2 100644 --- a/x-pack/plugins/event_log/generated/mappings.json +++ b/x-pack/plugins/event_log/generated/mappings.json @@ -55,6 +55,12 @@ "user": { "properties": { "name": { + "fields": { + "text": { + "norms": false, + "type": "text" + } + }, "ignore_above": 1024, "type": "keyword" } @@ -70,6 +76,14 @@ "type": "keyword", "ignore_above": 1024 }, + "alerting": { + "properties": { + "instance_id": { + "type": "keyword", + "ignore_above": 1024 + } + } + }, "saved_objects": { "properties": { "store": { diff --git a/x-pack/plugins/event_log/generated/schemas.ts b/x-pack/plugins/event_log/generated/schemas.ts index a040ede891bfd..b731093b33b06 100644 --- a/x-pack/plugins/event_log/generated/schemas.ts +++ b/x-pack/plugins/event_log/generated/schemas.ts @@ -18,7 +18,7 @@ type DeepPartial = { [P in keyof T]?: T[P] extends Array ? Array> : DeepPartial; }; -export const ECS_VERSION = '1.3.1'; +export const ECS_VERSION = '1.5.0'; // types and config-schema describing the es structures export type IValidatedEvent = TypeOf; @@ -57,6 +57,11 @@ export const EventSchema = schema.maybe( schema.object({ server_uuid: ecsString(), namespace: ecsString(), + alerting: schema.maybe( + schema.object({ + instance_id: ecsString(), + }) + ), saved_objects: schema.maybe( schema.arrayOf( schema.object({ diff --git a/x-pack/plugins/event_log/scripts/mappings.js b/x-pack/plugins/event_log/scripts/mappings.js index 43fd0c78183a1..9e721b06ec335 100644 --- a/x-pack/plugins/event_log/scripts/mappings.js +++ b/x-pack/plugins/event_log/scripts/mappings.js @@ -11,6 +11,15 @@ exports.EcsKibanaExtensionsMappings = { type: 'keyword', ignore_above: 1024, }, + // alerting specific fields + alerting: { + properties: { + instance_id: { + type: 'keyword', + ignore_above: 1024, + }, + }, + }, // relevant kibana space namespace: { type: 'keyword', @@ -53,6 +62,7 @@ exports.EcsEventLogProperties = [ 'user.name', 'kibana.server_uuid', 'kibana.namespace', + 'kibana.alerting.instance_id', 'kibana.saved_objects.store', 'kibana.saved_objects.id', 'kibana.saved_objects.name', diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts index 87e8fb0f521a9..bd57958b0cb88 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.mock.ts @@ -15,6 +15,7 @@ const createClusterClientMock = () => { createIndexTemplate: jest.fn(), doesAliasExist: jest.fn(), createIndex: jest.fn(), + queryEventsBySavedObject: jest.fn(), }; return mock; }; diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts index b61196439ee4f..ae26d7a7ece07 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts @@ -7,6 +7,8 @@ import { ClusterClient, Logger } from '../../../../../src/core/server'; import { elasticsearchServiceMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; import { ClusterClientAdapter, IClusterClientAdapter } from './cluster_client_adapter'; +import moment from 'moment'; +import { findOptionsSchema } from '../event_log_client'; type EsClusterClient = Pick, 'callAsInternalUser' | 'asScoped'>; @@ -195,3 +197,230 @@ describe('createIndex', () => { await clusterClientAdapter.createIndex('foo'); }); }); + +describe('queryEventsBySavedObject', () => { + const DEFAULT_OPTIONS = findOptionsSchema.validate({}); + + test('should call cluster with proper arguments', async () => { + clusterClient.callAsInternalUser.mockResolvedValue({ + hits: { + hits: [], + total: { value: 0 }, + }, + }); + await clusterClientAdapter.queryEventsBySavedObject( + 'index-name', + 'saved-object-type', + 'saved-object-id', + DEFAULT_OPTIONS + ); + + const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; + expect(method).toEqual('search'); + expect(query).toMatchObject({ + index: 'index-name', + body: { + from: 0, + size: 10, + sort: { 'event.start': { order: 'asc' } }, + query: { + bool: { + must: [ + { + nested: { + path: 'kibana.saved_objects', + query: { + bool: { + must: [ + { + term: { + 'kibana.saved_objects.type': { + value: 'saved-object-type', + }, + }, + }, + { + term: { + 'kibana.saved_objects.id': { + value: 'saved-object-id', + }, + }, + }, + ], + }, + }, + }, + }, + ], + }, + }, + }, + }); + }); + + test('should call cluster with sort', async () => { + clusterClient.callAsInternalUser.mockResolvedValue({ + hits: { + hits: [], + total: { value: 0 }, + }, + }); + await clusterClientAdapter.queryEventsBySavedObject( + 'index-name', + 'saved-object-type', + 'saved-object-id', + { ...DEFAULT_OPTIONS, sort_field: 'event.end', sort_order: 'desc' } + ); + + const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; + expect(method).toEqual('search'); + expect(query).toMatchObject({ + index: 'index-name', + body: { + sort: { 'event.end': { order: 'desc' } }, + }, + }); + }); + + test('supports open ended date', async () => { + clusterClient.callAsInternalUser.mockResolvedValue({ + hits: { + hits: [], + total: { value: 0 }, + }, + }); + + const start = moment() + .subtract(1, 'days') + .toISOString(); + + await clusterClientAdapter.queryEventsBySavedObject( + 'index-name', + 'saved-object-type', + 'saved-object-id', + { ...DEFAULT_OPTIONS, start } + ); + + const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; + expect(method).toEqual('search'); + expect(query).toMatchObject({ + index: 'index-name', + body: { + query: { + bool: { + must: [ + { + nested: { + path: 'kibana.saved_objects', + query: { + bool: { + must: [ + { + term: { + 'kibana.saved_objects.type': { + value: 'saved-object-type', + }, + }, + }, + { + term: { + 'kibana.saved_objects.id': { + value: 'saved-object-id', + }, + }, + }, + ], + }, + }, + }, + }, + { + range: { + 'event.start': { + gte: start, + }, + }, + }, + ], + }, + }, + }, + }); + }); + + test('supports optional date range', async () => { + clusterClient.callAsInternalUser.mockResolvedValue({ + hits: { + hits: [], + total: { value: 0 }, + }, + }); + + const start = moment() + .subtract(1, 'days') + .toISOString(); + const end = moment() + .add(1, 'days') + .toISOString(); + + await clusterClientAdapter.queryEventsBySavedObject( + 'index-name', + 'saved-object-type', + 'saved-object-id', + { ...DEFAULT_OPTIONS, start, end } + ); + + const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; + expect(method).toEqual('search'); + expect(query).toMatchObject({ + index: 'index-name', + body: { + query: { + bool: { + must: [ + { + nested: { + path: 'kibana.saved_objects', + query: { + bool: { + must: [ + { + term: { + 'kibana.saved_objects.type': { + value: 'saved-object-type', + }, + }, + }, + { + term: { + 'kibana.saved_objects.id': { + value: 'saved-object-id', + }, + }, + }, + ], + }, + }, + }, + }, + { + range: { + 'event.start': { + gte: start, + }, + }, + }, + { + range: { + 'event.end': { + lte: end, + }, + }, + }, + ], + }, + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts index d585fd4f539b5..36bc94edfca4e 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts @@ -4,7 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { reject, isUndefined } from 'lodash'; import { Logger, ClusterClient } from '../../../../../src/core/server'; +import { IEvent } from '../types'; +import { FindOptionsType } from '../event_log_client'; export type EsClusterClient = Pick; export type IClusterClientAdapter = PublicMethodsOf; @@ -14,6 +17,13 @@ export interface ConstructorOpts { clusterClient: EsClusterClient; } +export interface QueryEventsBySavedObjectResult { + page: number; + per_page: number; + total: number; + data: IEvent[]; +} + export class ClusterClientAdapter { private readonly logger: Logger; private readonly clusterClient: EsClusterClient; @@ -107,6 +117,87 @@ export class ClusterClientAdapter { } } + public async queryEventsBySavedObject( + index: string, + type: string, + id: string, + { page, per_page: perPage, start, end, sort_field, sort_order }: FindOptionsType + ): Promise { + try { + const { + hits: { + hits, + total: { value: total }, + }, + } = await this.callEs('search', { + index, + body: { + size: perPage, + from: (page - 1) * perPage, + sort: { [sort_field]: { order: sort_order } }, + query: { + bool: { + must: reject( + [ + { + nested: { + path: 'kibana.saved_objects', + query: { + bool: { + must: [ + { + term: { + 'kibana.saved_objects.type': { + value: type, + }, + }, + }, + { + term: { + 'kibana.saved_objects.id': { + value: id, + }, + }, + }, + ], + }, + }, + }, + }, + start && { + range: { + 'event.start': { + gte: start, + }, + }, + }, + end && { + range: { + 'event.end': { + lte: end, + }, + }, + }, + ], + isUndefined + ), + }, + }, + }, + }); + return { + page, + per_page: perPage, + total, + data: hits.map((hit: any) => hit._source) as IEvent[], + }; + } catch (err) { + throw new Error( + `querying for Event Log by for type "${type}" and id "${id}" failed with: ${err.message}` + ); + } + } + private async callEs(operation: string, body?: any): Promise { try { this.debug(`callEs(${operation}) calls:`, body); diff --git a/x-pack/plugins/event_log/server/event_log_client.mock.ts b/x-pack/plugins/event_log/server/event_log_client.mock.ts new file mode 100644 index 0000000000000..31cab802555d0 --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_client.mock.ts @@ -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 { IEventLogClient } from './types'; + +const createEventLogClientMock = () => { + const mock: jest.Mocked = { + findEventsBySavedObject: jest.fn(), + }; + return mock; +}; + +export const eventLogClientMock = { + create: createEventLogClientMock, +}; diff --git a/x-pack/plugins/event_log/server/event_log_client.test.ts b/x-pack/plugins/event_log/server/event_log_client.test.ts new file mode 100644 index 0000000000000..6d4c9b67abc1b --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_client.test.ts @@ -0,0 +1,292 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EventLogClient } from './event_log_client'; +import { contextMock } from './es/context.mock'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; +import { merge } from 'lodash'; +import moment from 'moment'; + +describe('EventLogStart', () => { + describe('findEventsBySavedObject', () => { + test('verifies that the user can access the specified saved object', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'saved-object-id', + type: 'saved-object-type', + attributes: {}, + references: [], + }); + + await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id'); + + expect(savedObjectsClient.get).toHaveBeenCalledWith('saved-object-type', 'saved-object-id'); + }); + + test('throws when the user doesnt have permission to access the specified saved object', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockRejectedValue(new Error('Fail')); + + expect( + eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id') + ).rejects.toMatchInlineSnapshot(`[Error: Fail]`); + }); + + test('fetches all event that reference the saved object', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'saved-object-id', + type: 'saved-object-type', + attributes: {}, + references: [], + }); + + const expectedEvents = [ + fakeEvent({ + kibana: { + saved_objects: [ + { + id: 'saved-object-id', + type: 'saved-object-type', + }, + { + type: 'action', + id: '1', + }, + ], + }, + }), + fakeEvent({ + kibana: { + saved_objects: [ + { + id: 'saved-object-id', + type: 'saved-object-type', + }, + { + type: 'action', + id: '2', + }, + ], + }, + }), + ]; + + const result = { + page: 0, + per_page: 10, + total: expectedEvents.length, + data: expectedEvents, + }; + esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue(result); + + expect( + await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id') + ).toEqual(result); + + expect(esContext.esAdapter.queryEventsBySavedObject).toHaveBeenCalledWith( + esContext.esNames.alias, + 'saved-object-type', + 'saved-object-id', + { + page: 1, + per_page: 10, + sort_field: 'event.start', + sort_order: 'asc', + } + ); + }); + + test('fetches all events in time frame that reference the saved object', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'saved-object-id', + type: 'saved-object-type', + attributes: {}, + references: [], + }); + + const expectedEvents = [ + fakeEvent({ + kibana: { + saved_objects: [ + { + id: 'saved-object-id', + type: 'saved-object-type', + }, + { + type: 'action', + id: '1', + }, + ], + }, + }), + fakeEvent({ + kibana: { + saved_objects: [ + { + id: 'saved-object-id', + type: 'saved-object-type', + }, + { + type: 'action', + id: '2', + }, + ], + }, + }), + ]; + + const result = { + page: 0, + per_page: 10, + total: expectedEvents.length, + data: expectedEvents, + }; + esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue(result); + + const start = moment() + .subtract(1, 'days') + .toISOString(); + const end = moment() + .add(1, 'days') + .toISOString(); + + expect( + await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id', { + start, + end, + }) + ).toEqual(result); + + expect(esContext.esAdapter.queryEventsBySavedObject).toHaveBeenCalledWith( + esContext.esNames.alias, + 'saved-object-type', + 'saved-object-id', + { + page: 1, + per_page: 10, + sort_field: 'event.start', + sort_order: 'asc', + start, + end, + } + ); + }); + + test('validates that the start date is valid', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'saved-object-id', + type: 'saved-object-type', + attributes: {}, + references: [], + }); + + esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue({ + page: 0, + per_page: 0, + total: 0, + data: [], + }); + + expect( + eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id', { + start: 'not a date string', + }) + ).rejects.toMatchInlineSnapshot(`[Error: [start]: Invalid Date]`); + }); + + test('validates that the end date is valid', async () => { + const esContext = contextMock.create(); + const savedObjectsClient = savedObjectsClientMock.create(); + const eventLogClient = new EventLogClient({ + esContext, + savedObjectsClient, + }); + + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'saved-object-id', + type: 'saved-object-type', + attributes: {}, + references: [], + }); + + esContext.esAdapter.queryEventsBySavedObject.mockResolvedValue({ + page: 0, + per_page: 0, + total: 0, + data: [], + }); + + expect( + eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id', { + end: 'not a date string', + }) + ).rejects.toMatchInlineSnapshot(`[Error: [end]: Invalid Date]`); + }); + }); +}); + +function fakeEvent(overrides = {}) { + return merge( + { + event: { + provider: 'actions', + action: 'execute', + start: '2020-03-30T14:55:47.054Z', + end: '2020-03-30T14:55:47.055Z', + duration: 1000000, + }, + kibana: { + namespace: 'default', + saved_objects: [ + { + type: 'action', + id: '968f1b82-0414-4a10-becc-56b6473e4a29', + }, + ], + server_uuid: '5b2de169-2785-441b-ae8c-186a1936b17d', + }, + message: 'action executed: .server-log:968f1b82-0414-4a10-becc-56b6473e4a29: logger', + '@timestamp': '2020-03-30T14:55:47.055Z', + ecs: { + version: '1.3.1', + }, + }, + overrides + ); +} diff --git a/x-pack/plugins/event_log/server/event_log_client.ts b/x-pack/plugins/event_log/server/event_log_client.ts new file mode 100644 index 0000000000000..765f0895f8e0d --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_client.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 { Observable } from 'rxjs'; +import { ClusterClient, SavedObjectsClientContract } from 'src/core/server'; + +import { schema, TypeOf } from '@kbn/config-schema'; +import { EsContext } from './es'; +import { IEventLogClient } from './types'; +import { QueryEventsBySavedObjectResult } from './es/cluster_client_adapter'; +export type PluginClusterClient = Pick; +export type AdminClusterClient$ = Observable; + +interface EventLogServiceCtorParams { + esContext: EsContext; + savedObjectsClient: SavedObjectsClientContract; +} + +const optionalDateFieldSchema = schema.maybe( + schema.string({ + validate(value) { + if (isNaN(Date.parse(value))) { + return 'Invalid Date'; + } + }, + }) +); + +export const findOptionsSchema = schema.object({ + per_page: schema.number({ defaultValue: 10, min: 0 }), + page: schema.number({ defaultValue: 1, min: 1 }), + start: optionalDateFieldSchema, + end: optionalDateFieldSchema, + sort_field: schema.oneOf( + [ + schema.literal('event.start'), + schema.literal('event.end'), + schema.literal('event.provider'), + schema.literal('event.duration'), + schema.literal('event.action'), + schema.literal('message'), + ], + { + defaultValue: 'event.start', + } + ), + sort_order: schema.oneOf([schema.literal('asc'), schema.literal('desc')], { + defaultValue: 'asc', + }), +}); +// page & perPage are required, other fields are optional +// using schema.maybe allows us to set undefined, but not to make the field optional +export type FindOptionsType = Pick< + TypeOf, + 'page' | 'per_page' | 'sort_field' | 'sort_order' +> & + Partial>; + +// note that clusterClient may be null, indicating we can't write to ES +export class EventLogClient implements IEventLogClient { + private esContext: EsContext; + private savedObjectsClient: SavedObjectsClientContract; + + constructor({ esContext, savedObjectsClient }: EventLogServiceCtorParams) { + this.esContext = esContext; + this.savedObjectsClient = savedObjectsClient; + } + + async findEventsBySavedObject( + type: string, + id: string, + options?: Partial + ): Promise { + // verify the user has the required permissions to view this saved object + await this.savedObjectsClient.get(type, id); + return await this.esContext.esAdapter.queryEventsBySavedObject( + this.esContext.esNames.alias, + type, + id, + findOptionsSchema.validate(options ?? {}) + ); + } +} diff --git a/x-pack/plugins/event_log/server/event_log_start_service.mock.ts b/x-pack/plugins/event_log/server/event_log_start_service.mock.ts new file mode 100644 index 0000000000000..e99ec777b473b --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_start_service.mock.ts @@ -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 { IEventLogClientService } from './types'; + +const createEventLogServiceMock = () => { + const mock: jest.Mocked = { + getClient: jest.fn(), + }; + return mock; +}; + +export const eventLogStartServiceMock = { + create: createEventLogServiceMock, +}; diff --git a/x-pack/plugins/event_log/server/event_log_start_service.test.ts b/x-pack/plugins/event_log/server/event_log_start_service.test.ts new file mode 100644 index 0000000000000..a8d75bc6c2e5a --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_start_service.test.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 { EventLogClientService } from './event_log_start_service'; +import { contextMock } from './es/context.mock'; +import { KibanaRequest } from 'kibana/server'; +import { savedObjectsServiceMock } from 'src/core/server/saved_objects/saved_objects_service.mock'; +import { savedObjectsClientMock } from 'src/core/server/mocks'; + +jest.mock('./event_log_client'); + +describe('EventLogClientService', () => { + const esContext = contextMock.create(); + + describe('getClient', () => { + test('creates a client with a scoped SavedObjects client', () => { + const savedObjectsService = savedObjectsServiceMock.createStartContract(); + const request = fakeRequest(); + + const eventLogStartService = new EventLogClientService({ + esContext, + savedObjectsService, + }); + + eventLogStartService.getClient(request); + + expect(savedObjectsService.getScopedClient).toHaveBeenCalledWith(request); + + const [{ value: savedObjectsClient }] = savedObjectsService.getScopedClient.mock.results; + + expect(jest.requireMock('./event_log_client').EventLogClient).toHaveBeenCalledWith({ + esContext, + savedObjectsClient, + }); + }); + }); +}); + +function fakeRequest(): KibanaRequest { + const savedObjectsClient = savedObjectsClientMock.create(); + return { + headers: {}, + getBasePath: () => '', + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + getSavedObjectsClient: () => savedObjectsClient, + } as any; +} diff --git a/x-pack/plugins/event_log/server/event_log_start_service.ts b/x-pack/plugins/event_log/server/event_log_start_service.ts new file mode 100644 index 0000000000000..5938f7a2e614e --- /dev/null +++ b/x-pack/plugins/event_log/server/event_log_start_service.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import { Observable } from 'rxjs'; +import { + ClusterClient, + KibanaRequest, + SavedObjectsServiceStart, + SavedObjectsClientContract, +} from 'src/core/server'; + +import { EsContext } from './es'; +import { IEventLogClientService } from './types'; +import { EventLogClient } from './event_log_client'; +export type PluginClusterClient = Pick; +export type AdminClusterClient$ = Observable; + +interface EventLogServiceCtorParams { + esContext: EsContext; + savedObjectsService: SavedObjectsServiceStart; +} + +// note that clusterClient may be null, indicating we can't write to ES +export class EventLogClientService implements IEventLogClientService { + private esContext: EsContext; + private savedObjectsService: SavedObjectsServiceStart; + + constructor({ esContext, savedObjectsService }: EventLogServiceCtorParams) { + this.esContext = esContext; + this.savedObjectsService = savedObjectsService; + } + + getClient( + request: KibanaRequest, + savedObjectsClient: SavedObjectsClientContract = this.savedObjectsService.getScopedClient( + request + ) + ) { + return new EventLogClient({ + esContext: this.esContext, + savedObjectsClient, + }); + } +} diff --git a/x-pack/plugins/event_log/server/index.ts b/x-pack/plugins/event_log/server/index.ts index 81a56faa49964..b7fa25cb6eb9c 100644 --- a/x-pack/plugins/event_log/server/index.ts +++ b/x-pack/plugins/event_log/server/index.ts @@ -8,6 +8,6 @@ import { PluginInitializerContext } from 'src/core/server'; import { ConfigSchema } from './types'; import { Plugin } from './plugin'; -export { IEventLogService, IEventLogger, IEvent } from './types'; +export { IEventLogService, IEventLogger, IEventLogClientService, IEvent } from './types'; export const config = { schema: ConfigSchema }; export const plugin = (context: PluginInitializerContext) => new Plugin(context); diff --git a/x-pack/plugins/event_log/server/mocks.ts b/x-pack/plugins/event_log/server/mocks.ts index aad6cf3e24561..2f632a52d2f36 100644 --- a/x-pack/plugins/event_log/server/mocks.ts +++ b/x-pack/plugins/event_log/server/mocks.ts @@ -5,8 +5,9 @@ */ import { eventLogServiceMock } from './event_log_service.mock'; +import { eventLogStartServiceMock } from './event_log_start_service.mock'; -export { eventLogServiceMock }; +export { eventLogServiceMock, eventLogStartServiceMock }; export { eventLoggerMock } from './event_logger.mock'; const createSetupMock = () => { @@ -14,7 +15,7 @@ const createSetupMock = () => { }; const createStartMock = () => { - return undefined; + return eventLogStartServiceMock.create(); }; export const eventLogMock = { diff --git a/x-pack/plugins/event_log/server/plugin.ts b/x-pack/plugins/event_log/server/plugin.ts index fdb08b2d090a6..2cc41354b4fbc 100644 --- a/x-pack/plugins/event_log/server/plugin.ts +++ b/x-pack/plugins/event_log/server/plugin.ts @@ -14,11 +14,21 @@ import { PluginInitializerContext, ClusterClient, SharedGlobalConfig, + IContextProvider, + RequestHandler, } from 'src/core/server'; -import { IEventLogConfig, IEventLogService, IEventLogger, IEventLogConfig$ } from './types'; +import { + IEventLogConfig, + IEventLogService, + IEventLogger, + IEventLogConfig$, + IEventLogClientService, +} from './types'; +import { findRoute } from './routes'; import { EventLogService } from './event_log_service'; import { createEsContext, EsContext } from './es'; +import { EventLogClientService } from './event_log_start_service'; export type PluginClusterClient = Pick; @@ -29,13 +39,14 @@ const ACTIONS = { stopping: 'stopping', }; -export class Plugin implements CorePlugin { +export class Plugin implements CorePlugin { private readonly config$: IEventLogConfig$; private systemLogger: Logger; private eventLogService?: IEventLogService; private esContext?: EsContext; private eventLogger?: IEventLogger; private globalConfig$: Observable; + private eventLogClientService?: EventLogClientService; constructor(private readonly context: PluginInitializerContext) { this.systemLogger = this.context.logger.get(); @@ -71,10 +82,17 @@ export class Plugin implements CorePlugin { event: { provider: PROVIDER }, }); + core.http.registerRouteHandlerContext('eventLog', this.createRouteHandlerContext()); + + // Routes + const router = core.http.createRouter(); + // Register routes + findRoute(router); + return this.eventLogService; } - async start(core: CoreStart) { + async start(core: CoreStart): Promise { this.systemLogger.debug('starting plugin'); if (!this.esContext) throw new Error('esContext not initialized'); @@ -91,8 +109,26 @@ export class Plugin implements CorePlugin { event: { action: ACTIONS.starting }, message: 'eventLog starting', }); + + this.eventLogClientService = new EventLogClientService({ + esContext: this.esContext, + savedObjectsService: core.savedObjects, + }); + return this.eventLogClientService; } + private createRouteHandlerContext = (): IContextProvider< + RequestHandler, + 'eventLog' + > => { + return async (context, request) => { + return { + getEventLogClient: () => + this.eventLogClientService!.getClient(request, context.core.savedObjects.client), + }; + }; + }; + stop() { this.systemLogger.debug('stopping plugin'); diff --git a/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts new file mode 100644 index 0000000000000..6640683bf6005 --- /dev/null +++ b/x-pack/plugins/event_log/server/routes/_mock_handler_arguments.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandlerContext, KibanaRequest, KibanaResponseFactory } from 'kibana/server'; +import { identity, merge } from 'lodash'; +import { httpServerMock } from '../../../../../src/core/server/mocks'; +import { IEventLogClient } from '../types'; + +export function mockHandlerArguments( + eventLogClient: IEventLogClient, + req: any, + res?: Array> +): [RequestHandlerContext, KibanaRequest, KibanaResponseFactory] { + return [ + ({ + eventLog: { + getEventLogClient() { + return eventLogClient; + }, + }, + } as unknown) as RequestHandlerContext, + req as KibanaRequest, + mockResponseFactory(res), + ]; +} + +export const mockResponseFactory = (resToMock: Array> = []) => { + const factory: jest.Mocked = httpServerMock.createResponseFactory(); + resToMock.forEach((key: string) => { + if (key in factory) { + Object.defineProperty(factory, key, { + value: jest.fn(identity), + }); + } + }); + return (factory as unknown) as KibanaResponseFactory; +}; + +export function fakeEvent(overrides = {}) { + return merge( + { + event: { + provider: 'actions', + action: 'execute', + start: '2020-03-30T14:55:47.054Z', + end: '2020-03-30T14:55:47.055Z', + duration: 1000000, + }, + kibana: { + namespace: 'default', + saved_objects: [ + { + type: 'action', + id: '968f1b82-0414-4a10-becc-56b6473e4a29', + }, + ], + server_uuid: '5b2de169-2785-441b-ae8c-186a1936b17d', + }, + message: 'action executed: .server-log:968f1b82-0414-4a10-becc-56b6473e4a29: logger', + '@timestamp': '2020-03-30T14:55:47.055Z', + ecs: { + version: '1.3.1', + }, + }, + overrides + ); +} diff --git a/x-pack/plugins/event_log/server/routes/find.test.ts b/x-pack/plugins/event_log/server/routes/find.test.ts new file mode 100644 index 0000000000000..844a84dc117a9 --- /dev/null +++ b/x-pack/plugins/event_log/server/routes/find.test.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 { findRoute } from './find'; +import { mockRouter, RouterMock } from '../../../../../src/core/server/http/router/router.mock'; +import { mockHandlerArguments, fakeEvent } from './_mock_handler_arguments'; +import { eventLogClientMock } from '../event_log_client.mock'; + +const eventLogClient = eventLogClientMock.create(); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('find', () => { + it('finds events with proper parameters', async () => { + const router: RouterMock = mockRouter.create(); + + findRoute(router); + + const [config, handler] = router.get.mock.calls[0]; + + expect(config.path).toMatchInlineSnapshot(`"/api/event_log/{type}/{id}/_find"`); + + const events = [fakeEvent(), fakeEvent()]; + const result = { + page: 0, + per_page: 10, + total: events.length, + data: events, + }; + eventLogClient.findEventsBySavedObject.mockResolvedValueOnce(result); + + const [context, req, res] = mockHandlerArguments( + eventLogClient, + { + params: { id: '1', type: 'action' }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1); + + const [type, id] = eventLogClient.findEventsBySavedObject.mock.calls[0]; + expect(type).toEqual(`action`); + expect(id).toEqual(`1`); + + expect(res.ok).toHaveBeenCalledWith({ + body: result, + }); + }); + + it('supports optional pagination parameters', async () => { + const router: RouterMock = mockRouter.create(); + + findRoute(router); + + const [, handler] = router.get.mock.calls[0]; + eventLogClient.findEventsBySavedObject.mockResolvedValueOnce({ + page: 0, + per_page: 10, + total: 0, + data: [], + }); + + const [context, req, res] = mockHandlerArguments( + eventLogClient, + { + params: { id: '1', type: 'action' }, + query: { page: 3, per_page: 10 }, + }, + ['ok'] + ); + + await handler(context, req, res); + + expect(eventLogClient.findEventsBySavedObject).toHaveBeenCalledTimes(1); + + const [type, id, options] = eventLogClient.findEventsBySavedObject.mock.calls[0]; + expect(type).toEqual(`action`); + expect(id).toEqual(`1`); + expect(options).toMatchObject({}); + + expect(res.ok).toHaveBeenCalledWith({ + body: { + page: 0, + per_page: 10, + total: 0, + data: [], + }, + }); + }); +}); diff --git a/x-pack/plugins/event_log/server/routes/find.ts b/x-pack/plugins/event_log/server/routes/find.ts new file mode 100644 index 0000000000000..cb170e50fb447 --- /dev/null +++ b/x-pack/plugins/event_log/server/routes/find.ts @@ -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 { schema, TypeOf } from '@kbn/config-schema'; +import { + IRouter, + RequestHandlerContext, + KibanaRequest, + IKibanaResponse, + KibanaResponseFactory, +} from 'kibana/server'; +import { BASE_EVENT_LOG_API_PATH } from '../../common'; +import { findOptionsSchema, FindOptionsType } from '../event_log_client'; + +const paramSchema = schema.object({ + type: schema.string(), + id: schema.string(), +}); + +export const findRoute = (router: IRouter) => { + router.get( + { + path: `${BASE_EVENT_LOG_API_PATH}/{type}/{id}/_find`, + validate: { + params: paramSchema, + query: findOptionsSchema, + }, + }, + router.handleLegacyErrors(async function( + context: RequestHandlerContext, + req: KibanaRequest, FindOptionsType, any, any>, + res: KibanaResponseFactory + ): Promise> { + if (!context.eventLog) { + return res.badRequest({ body: 'RouteHandlerContext is not registered for eventLog' }); + } + const eventLogClient = context.eventLog.getEventLogClient(); + const { + params: { id, type }, + query, + } = req; + return res.ok({ + body: await eventLogClient.findEventsBySavedObject(type, id, query), + }); + }) + ); +}; diff --git a/x-pack/typings/elastic__node_crypto.d.ts b/x-pack/plugins/event_log/server/routes/index.ts similarity index 86% rename from x-pack/typings/elastic__node_crypto.d.ts rename to x-pack/plugins/event_log/server/routes/index.ts index 463b662d5b207..85d9b3e0db8cd 100644 --- a/x-pack/typings/elastic__node_crypto.d.ts +++ b/x-pack/plugins/event_log/server/routes/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -declare module '@elastic/node-crypto'; +export { findRoute } from './find'; diff --git a/x-pack/plugins/event_log/server/types.ts b/x-pack/plugins/event_log/server/types.ts index f606bb2be6c6c..baf53ef447914 100644 --- a/x-pack/plugins/event_log/server/types.ts +++ b/x-pack/plugins/event_log/server/types.ts @@ -8,7 +8,10 @@ import { Observable } from 'rxjs'; import { schema, TypeOf } from '@kbn/config-schema'; export { IEvent, IValidatedEvent, EventSchema, ECS_VERSION } from '../generated/schemas'; +import { KibanaRequest } from 'kibana/server'; import { IEvent } from '../generated/schemas'; +import { FindOptionsType } from './event_log_client'; +import { QueryEventsBySavedObjectResult } from './es/cluster_client_adapter'; export const ConfigSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), @@ -19,6 +22,14 @@ export const ConfigSchema = schema.object({ export type IEventLogConfig = TypeOf; export type IEventLogConfig$ = Observable>; +declare module 'src/core/server' { + interface RequestHandlerContext { + eventLog?: { + getEventLogClient: () => IEventLogClient; + }; + } +} + // the object exposed by plugin.setup() export interface IEventLogService { isEnabled(): boolean; @@ -31,6 +42,18 @@ export interface IEventLogService { getLogger(properties: IEvent): IEventLogger; } +export interface IEventLogClientService { + getClient(request: KibanaRequest): IEventLogClient; +} + +export interface IEventLogClient { + findEventsBySavedObject( + type: string, + id: string, + options?: Partial + ): Promise; +} + export interface IEventLogger { logEvent(properties: IEvent): void; startTiming(event: IEvent): void; diff --git a/x-pack/plugins/features/common/feature.ts b/x-pack/plugins/features/common/feature.ts index 82fcc33f5c8ce..ef32a8a80a0bd 100644 --- a/x-pack/plugins/features/common/feature.ts +++ b/x-pack/plugins/features/common/feature.ts @@ -7,6 +7,7 @@ import { RecursiveReadonly } from '@kbn/utility-types'; import { FeatureKibanaPrivileges } from './feature_kibana_privileges'; import { SubFeatureConfig, SubFeature } from './sub_feature'; +import { ReservedKibanaPrivilege } from './reserved_kibana_privilege'; /** * Interface for registering a feature. @@ -122,8 +123,8 @@ export interface FeatureConfig { * @private */ reserved?: { - privilege: FeatureKibanaPrivileges; description: string; + privileges: ReservedKibanaPrivilege[]; }; } diff --git a/x-pack/plugins/features/common/reserved_kibana_privilege.ts b/x-pack/plugins/features/common/reserved_kibana_privilege.ts new file mode 100644 index 0000000000000..0186011382e84 --- /dev/null +++ b/x-pack/plugins/features/common/reserved_kibana_privilege.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 { FeatureKibanaPrivileges } from '.'; + +export interface ReservedKibanaPrivilege { + id: string; + privilege: FeatureKibanaPrivileges; +} diff --git a/x-pack/plugins/features/server/feature_registry.test.ts b/x-pack/plugins/features/server/feature_registry.test.ts index 5b4f7728c9f31..2039f8f6acda2 100644 --- a/x-pack/plugins/features/server/feature_registry.test.ts +++ b/x-pack/plugins/features/server/feature_registry.test.ts @@ -110,19 +110,24 @@ describe('FeatureRegistry', () => { ], privilegesTooltip: 'some fancy tooltip', reserved: { - privilege: { - catalogue: ['foo'], - management: { - foo: ['bar'], - }, - app: ['app1'], - savedObject: { - all: ['space', 'etc', 'telemetry'], - read: ['canvas', 'config', 'url'], + privileges: [ + { + id: 'reserved', + privilege: { + catalogue: ['foo'], + management: { + foo: ['bar'], + }, + app: ['app1'], + savedObject: { + all: ['space', 'etc', 'telemetry'], + read: ['canvas', 'config', 'url'], + }, + api: ['someApiEndpointTag', 'anotherEndpointTag'], + ui: ['allowsFoo', 'showBar', 'showBaz'], + }, }, - api: ['someApiEndpointTag', 'anotherEndpointTag'], - ui: ['allowsFoo', 'showBar', 'showBaz'], - }, + ], description: 'some completely adequate description', }, }; @@ -264,13 +269,18 @@ describe('FeatureRegistry', () => { privileges: null, reserved: { description: 'foo', - privilege: { - ui: [], - savedObject: { - all: [], - read: [], + privileges: [ + { + id: 'reserved', + privilege: { + ui: [], + savedObject: { + all: [], + read: [], + }, + }, }, - }, + ], }, }; @@ -278,7 +288,7 @@ describe('FeatureRegistry', () => { featureRegistry.register(feature); const result = featureRegistry.getAll(); - const reservedPrivilege = result[0]!.reserved!.privilege; + const reservedPrivilege = result[0]!.reserved!.privileges[0].privilege; expect(reservedPrivilege.savedObject.all).toEqual(['telemetry']); expect(reservedPrivilege.savedObject.read).toEqual(['config', 'url']); }); @@ -520,14 +530,19 @@ describe('FeatureRegistry', () => { privileges: null, reserved: { description: 'something', - privilege: { - savedObject: { - all: [], - read: [], + privileges: [ + { + id: 'reserved', + privilege: { + savedObject: { + all: [], + read: [], + }, + ui: [], + app: ['foo', 'bar', 'baz'], + }, }, - ui: [], - app: ['foo', 'bar', 'baz'], - }, + ], }, }; @@ -546,14 +561,19 @@ describe('FeatureRegistry', () => { privileges: null, reserved: { description: 'something', - privilege: { - savedObject: { - all: [], - read: [], + privileges: [ + { + id: 'reserved', + privilege: { + savedObject: { + all: [], + read: [], + }, + ui: [], + app: ['foo', 'bar'], + }, }, - ui: [], - app: ['foo', 'bar'], - }, + ], }, }; @@ -666,15 +686,20 @@ describe('FeatureRegistry', () => { privileges: null, reserved: { description: 'something', - privilege: { - catalogue: ['foo', 'bar', 'baz'], - savedObject: { - all: [], - read: [], + privileges: [ + { + id: 'reserved', + privilege: { + catalogue: ['foo', 'bar', 'baz'], + savedObject: { + all: [], + read: [], + }, + ui: [], + app: [], + }, }, - ui: [], - app: [], - }, + ], }, }; @@ -694,15 +719,20 @@ describe('FeatureRegistry', () => { privileges: null, reserved: { description: 'something', - privilege: { - catalogue: ['foo', 'bar'], - savedObject: { - all: [], - read: [], + privileges: [ + { + id: 'reserved', + privilege: { + catalogue: ['foo', 'bar'], + savedObject: { + all: [], + read: [], + }, + ui: [], + app: [], + }, }, - ui: [], - app: [], - }, + ], }, }; @@ -840,18 +870,23 @@ describe('FeatureRegistry', () => { privileges: null, reserved: { description: 'something', - privilege: { - catalogue: ['bar'], - management: { - kibana: ['hey-there'], - }, - savedObject: { - all: [], - read: [], + privileges: [ + { + id: 'reserved', + privilege: { + catalogue: ['bar'], + management: { + kibana: ['hey-there'], + }, + savedObject: { + all: [], + read: [], + }, + ui: [], + app: [], + }, }, - ui: [], - app: [], - }, + ], }, }; @@ -874,18 +909,23 @@ describe('FeatureRegistry', () => { privileges: null, reserved: { description: 'something', - privilege: { - catalogue: ['bar'], - management: { - kibana: ['hey-there'], - }, - savedObject: { - all: [], - read: [], + privileges: [ + { + id: 'reserved', + privilege: { + catalogue: ['bar'], + management: { + kibana: ['hey-there'], + }, + savedObject: { + all: [], + read: [], + }, + ui: [], + app: [], + }, }, - ui: [], - app: [], - }, + ], }, }; @@ -896,6 +936,78 @@ describe('FeatureRegistry', () => { ); }); + it('allows multiple reserved feature privileges to be registered', () => { + const feature: FeatureConfig = { + id: 'test-feature', + name: 'Test Feature', + app: [], + privileges: null, + reserved: { + description: 'my reserved privileges', + privileges: [ + { + id: 'a_reserved_1', + privilege: { + savedObject: { + all: [], + read: [], + }, + ui: [], + app: [], + }, + }, + { + id: 'a_reserved_2', + privilege: { + savedObject: { + all: [], + read: [], + }, + ui: [], + app: [], + }, + }, + ], + }, + }; + + const featureRegistry = new FeatureRegistry(); + featureRegistry.register(feature); + const result = featureRegistry.getAll(); + expect(result).toHaveLength(1); + expect(result[0].reserved?.privileges).toHaveLength(2); + }); + + it('does not allow reserved privilege ids to start with "reserved_"', () => { + const feature: FeatureConfig = { + id: 'test-feature', + name: 'Test Feature', + app: [], + privileges: null, + reserved: { + description: 'my reserved privileges', + privileges: [ + { + id: 'reserved_1', + privilege: { + savedObject: { + all: [], + read: [], + }, + ui: [], + app: [], + }, + }, + ], + }, + }; + + const featureRegistry = new FeatureRegistry(); + expect(() => featureRegistry.register(feature)).toThrowErrorMatchingInlineSnapshot( + `"child \\"reserved\\" fails because [child \\"privileges\\" fails because [\\"privileges\\" at position 0 fails because [child \\"id\\" fails because [\\"id\\" with value \\"reserved_1\\" fails to match the required pattern: /^(?!reserved_)[a-zA-Z0-9_-]+$/]]]]"` + ); + }); + it('cannot register feature after getAll has been called', () => { const feature1: FeatureConfig = { id: 'test-feature', diff --git a/x-pack/plugins/features/server/feature_registry.ts b/x-pack/plugins/features/server/feature_registry.ts index 73a353cd27471..6140b7ac87ce0 100644 --- a/x-pack/plugins/features/server/feature_registry.ts +++ b/x-pack/plugins/features/server/feature_registry.ts @@ -39,9 +39,9 @@ export class FeatureRegistry { function applyAutomaticPrivilegeGrants(feature: FeatureConfig): FeatureConfig { const allPrivilege = feature.privileges?.all; const readPrivilege = feature.privileges?.read; - const reservedPrivilege = feature.reserved?.privilege; + const reservedPrivileges = (feature.reserved?.privileges ?? []).map(rp => rp.privilege); - applyAutomaticAllPrivilegeGrants(allPrivilege, reservedPrivilege); + applyAutomaticAllPrivilegeGrants(allPrivilege, ...reservedPrivileges); applyAutomaticReadPrivilegeGrants(readPrivilege); return feature; diff --git a/x-pack/plugins/features/server/feature_schema.ts b/x-pack/plugins/features/server/feature_schema.ts index fdeceb30b4e3d..403d9586bf160 100644 --- a/x-pack/plugins/features/server/feature_schema.ts +++ b/x-pack/plugins/features/server/feature_schema.ts @@ -18,6 +18,7 @@ const prohibitedFeatureIds: Array = ['catalogue', 'managem const featurePrivilegePartRegex = /^[a-zA-Z0-9_-]+$/; const subFeaturePrivilegePartRegex = /^[a-zA-Z0-9_-]+$/; const managementSectionIdRegex = /^[a-zA-Z0-9_-]+$/; +const reservedFeaturePrrivilegePartRegex = /^(?!reserved_)[a-zA-Z0-9_-]+$/; export const uiCapabilitiesRegex = /^[a-zA-Z0-9:_-]+$/; const managementSchema = Joi.object().pattern( @@ -118,8 +119,17 @@ const schema = Joi.object({ }), privilegesTooltip: Joi.string(), reserved: Joi.object({ - privilege: privilegeSchema.required(), description: Joi.string().required(), + privileges: Joi.array() + .items( + Joi.object({ + id: Joi.string() + .regex(reservedFeaturePrrivilegePartRegex) + .required(), + privilege: privilegeSchema.required(), + }) + ) + .required(), }), }); @@ -209,7 +219,9 @@ export function validateFeature(feature: FeatureConfig) { privilegeEntries.push(...Object.entries(feature.privileges)); } if (feature.reserved) { - privilegeEntries.push(['reserved', feature.reserved.privilege]); + feature.reserved.privileges.forEach(reservedPrivilege => { + privilegeEntries.push([reservedPrivilege.id, reservedPrivilege.privilege]); + }); } if (privilegeEntries.length === 0) { diff --git a/x-pack/plugins/graph/common/check_license.ts b/x-pack/plugins/graph/common/check_license.ts index a918f53776b17..f9a663f35ed47 100644 --- a/x-pack/plugins/graph/common/check_license.ts +++ b/x-pack/plugins/graph/common/check_license.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { ILicense, LICENSE_CHECK_STATE } from '../../licensing/common/types'; +import { ILicense } from '../../licensing/common/types'; import { assertNever } from '../../../../src/core/utils'; export interface GraphLicenseInformation { @@ -43,20 +43,20 @@ export function checkLicense(license: ILicense | undefined): GraphLicenseInforma const check = license.check('graph', 'platinum'); switch (check.state) { - case LICENSE_CHECK_STATE.Expired: + case 'expired': return { showAppLink: true, enableAppLink: false, message: check.message || '', }; - case LICENSE_CHECK_STATE.Invalid: - case LICENSE_CHECK_STATE.Unavailable: + case 'invalid': + case 'unavailable': return { showAppLink: false, enableAppLink: false, message: check.message || '', }; - case LICENSE_CHECK_STATE.Valid: + case 'valid': return { showAppLink: true, enableAppLink: true, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts index 520b62083e7d3..e0cffb7f0969a 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/template_form.helpers.ts @@ -44,7 +44,7 @@ export const formSetup = async (initTestBed: SetupFunc) => { testBed.find('editFieldUpdateButton').simulate('click'); }; - const clickRemoveButtonAtField = (index: number) => { + const deleteMappingsFieldAt = (index: number) => { testBed .find('removeFieldButton') .at(index) @@ -54,7 +54,7 @@ export const formSetup = async (initTestBed: SetupFunc) => { }; const clickCancelCreateFieldButton = () => { - testBed.find('createFieldWrapper.cancelButton').simulate('click'); + testBed.find('createFieldForm.cancelButton').simulate('click'); }; const completeStepOne = async ({ @@ -154,7 +154,7 @@ export const formSetup = async (initTestBed: SetupFunc) => { const { find, form, component } = testBed; form.setInputValue('nameParameterInput', name); - find('createFieldWrapper.mockComboBox').simulate('change', [ + find('createFieldForm.mockComboBox').simulate('change', [ { label: type, value: type, @@ -164,7 +164,7 @@ export const formSetup = async (initTestBed: SetupFunc) => { await nextTick(50); component.update(); - find('createFieldWrapper.addButton').simulate('click'); + find('createFieldForm.addButton').simulate('click'); await nextTick(); component.update(); @@ -178,7 +178,7 @@ export const formSetup = async (initTestBed: SetupFunc) => { clickSubmitButton, clickEditButtonAtField, clickEditFieldUpdateButton, - clickRemoveButtonAtField, + deleteMappingsFieldAt, clickCancelCreateFieldButton, completeStepOne, completeStepTwo, @@ -196,16 +196,16 @@ export type TestSubjects = | 'backButton' | 'codeEditorContainer' | 'confirmModalConfirmButton' - | 'createFieldWrapper.addPropertyButton' - | 'createFieldWrapper.addButton' - | 'createFieldWrapper.addFieldButton' - | 'createFieldWrapper.addMultiFieldButton' - | 'createFieldWrapper.cancelButton' - | 'createFieldWrapper.mockComboBox' + | 'createFieldForm.addPropertyButton' + | 'createFieldForm.addButton' + | 'createFieldForm.addFieldButton' + | 'createFieldForm.addMultiFieldButton' + | 'createFieldForm.cancelButton' + | 'createFieldForm.mockComboBox' | 'editFieldButton' | 'editFieldUpdateButton' | 'fieldsListItem' - | 'fieldTypeComboBox' + | 'fieldType' | 'indexPatternsField' | 'indexPatternsWarning' | 'indexPatternsWarningDescription' diff --git a/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx b/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx index ad8e8c22a87fa..ec810faf687be 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx +++ b/x-pack/plugins/index_management/__jest__/client_integration/template_create.test.tsx @@ -178,7 +178,7 @@ describe('', () => { actions.clickCancelCreateFieldButton(); // Remove first field - actions.clickRemoveButtonAtField(0); + actions.deleteMappingsFieldAt(0); expect(find('fieldsListItem').length).toBe(1); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts index 6d64cb73da4bd..fa6bee56349e9 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/index.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { setup as mappingsEditorSetup } from './mappings_editor.helpers'; +import { setup as mappingsEditorSetup, MappingsEditorTestBed } from './mappings_editor.helpers'; export { nextTick, @@ -15,3 +15,5 @@ export { export const componentHelpers = { mappingsEditor: { setup: mappingsEditorSetup }, }; + +export { MappingsEditorTestBed }; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.ts b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.ts deleted file mode 100644 index acb416654023e..0000000000000 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { registerTestBed } from '../../../../../../../../../test_utils'; -import { MappingsEditor } from '../../../mappings_editor'; - -export const setup = (props: any) => - registerTestBed(MappingsEditor, { - memoryRouter: { - wrapComponent: false, - }, - defaultProps: props, - })(); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.tsx new file mode 100644 index 0000000000000..c8c8ef8bfe9b3 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.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 React from 'react'; +import { registerTestBed, TestBed, nextTick } from '../../../../../../../../../test_utils'; +import { MappingsEditor } from '../../../mappings_editor'; + +jest.mock('@elastic/eui', () => ({ + ...jest.requireActual('@elastic/eui'), + // Mocking EuiComboBox, as it utilizes "react-virtualized" for rendering search suggestions, + // which does not produce a valid component wrapper + EuiComboBox: (props: any) => ( + { + props.onChange([syntheticEvent['0']]); + }} + /> + ), + // Mocking EuiCodeEditor, which uses React Ace under the hood + EuiCodeEditor: (props: any) => ( + { + props.onChange(e.jsonContent); + }} + /> + ), +})); + +const createActions = (testBed: TestBed) => { + const { find, waitFor, form, component } = testBed; + + const addField = async (name: string, type: string) => { + const currentCount = find('fieldsListItem').length; + + form.setInputValue('nameParameterInput', name); + find('createFieldForm.fieldType').simulate('change', [ + { + label: type, + value: type, + }, + ]); + + await nextTick(); + component.update(); + + find('createFieldForm.addButton').simulate('click'); + + // We wait until there is one more field in the DOM + await waitFor('fieldsListItem', currentCount + 1); + }; + + const selectTab = async (tab: 'fields' | 'templates' | 'advanced') => { + const index = ['fields', 'templates', 'advanced'].indexOf(tab); + const tabIdToContentMap: { [key: string]: TestSubjects } = { + fields: 'documentFields', + templates: 'dynamicTemplates', + advanced: 'advancedConfiguration', + }; + + const tabElement = find('formTab').at(index); + if (tabElement.length === 0) { + throw new Error(`Tab not found: "${tab}"`); + } + tabElement.simulate('click'); + + await waitFor(tabIdToContentMap[tab]); + }; + + const updateJsonEditor = async (testSubject: TestSubjects, value: object) => { + find(testSubject).simulate('change', { jsonContent: JSON.stringify(value) }); + }; + + const getJsonEditorValue = (testSubject: TestSubjects) => { + const value = find(testSubject).props()['data-currentvalue']; + if (typeof value === 'string') { + try { + return JSON.parse(value); + } catch { + return { errorParsingJson: true, props: find(testSubject).props() }; + } + } + return value; + }; + + return { + selectTab, + addField, + updateJsonEditor, + getJsonEditorValue, + }; +}; + +export const setup = async (props: any = { onUpdate() {} }): Promise => { + const testBed = await registerTestBed(MappingsEditor, { + memoryRouter: { + wrapComponent: false, + }, + defaultProps: props, + })(); + + return { + ...testBed, + actions: createActions(testBed), + }; +}; + +export type MappingsEditorTestBed = TestBed & { + actions: ReturnType; +}; + +export type TestSubjects = + | 'formTab' + | 'mappingsEditor' + | 'fieldsListItem' + | 'fieldName' + | 'mappingTypesDetectedCallout' + | 'documentFields' + | 'dynamicTemplates' + | 'advancedConfiguration' + | 'advancedConfiguration.numericDetection' + | 'advancedConfiguration.numericDetection.input' + | 'advancedConfiguration.dynamicMappingsToggle' + | 'advancedConfiguration.dynamicMappingsToggle.input' + | 'dynamicTemplatesEditor' + | 'nameParameterInput' + | 'createFieldForm.fieldType' + | 'createFieldForm.addButton'; diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 723c105d403b8..0cf5bf3f4453f 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -3,8 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { act } from 'react-dom/test-utils'; -import { componentHelpers } from './helpers'; +import { componentHelpers, MappingsEditorTestBed, nextTick, getRandomString } from './helpers'; const { setup } = componentHelpers.mappingsEditor; const mockOnUpdate = () => undefined; @@ -52,4 +53,125 @@ describe('', () => { expect(exists('documentFields')).toBe(true); }); }); + + describe('tabs', () => { + const defaultMappings = { + properties: {}, + dynamic_templates: [{ before: 'foo' }], + }; + let testBed: MappingsEditorTestBed; + + beforeEach(async () => { + testBed = await setup({ defaultValue: defaultMappings, onUpdate() {} }); + }); + + test('should keep the changes when switching tabs', async () => { + const { + actions: { addField, selectTab, updateJsonEditor, getJsonEditorValue }, + component, + find, + exists, + form, + } = testBed; + + // ------------------------------------- + // Document fields Tab: add a new field + // ------------------------------------- + expect(find('fieldsListItem').length).toEqual(0); // Check that we start with an empty list + + const newField = { name: getRandomString(), type: 'text' }; + await act(async () => { + await addField(newField.name, newField.type); + }); + + expect(find('fieldsListItem').length).toEqual(1); + + let field = find('fieldsListItem').at(0); + expect(find('fieldName', field).text()).toEqual(newField.name); + + // ------------------------------------- + // Navigate to dynamic templates tab + // ------------------------------------- + await act(async () => { + await selectTab('templates'); + }); + + let templatesValue = getJsonEditorValue('dynamicTemplatesEditor'); + expect(templatesValue).toEqual(defaultMappings.dynamic_templates); + + // Update the dynamic templates editor value + const updatedValueTemplates = [{ after: 'bar' }]; + + await act(async () => { + await updateJsonEditor('dynamicTemplatesEditor', updatedValueTemplates); + await nextTick(); + component.update(); + }); + + templatesValue = getJsonEditorValue('dynamicTemplatesEditor'); + expect(templatesValue).toEqual(updatedValueTemplates); + + // ------------------------------------------------------ + // Switch to advanced settings tab and make some changes + // ------------------------------------------------------ + await act(async () => { + await selectTab('advanced'); + }); + + let isDynamicMappingsEnabled = find( + 'advancedConfiguration.dynamicMappingsToggle.input' + ).props()['aria-checked']; + expect(isDynamicMappingsEnabled).toBe(true); + + let isNumericDetectionVisible = exists('advancedConfiguration.numericDetection'); + expect(isNumericDetectionVisible).toBe(true); + + // Turn off dynamic mappings + await act(async () => { + form.toggleEuiSwitch('advancedConfiguration.dynamicMappingsToggle.input'); + await nextTick(); + component.update(); + await nextTick(); + }); + + isDynamicMappingsEnabled = find('advancedConfiguration.dynamicMappingsToggle.input').props()[ + 'aria-checked' + ]; + expect(isDynamicMappingsEnabled).toBe(false); + + isNumericDetectionVisible = exists('advancedConfiguration.numericDetection'); + expect(isNumericDetectionVisible).toBe(false); + + // ---------------------------------------------------------------------------- + // Go back to dynamic templates tab and make sure our changes are still there + // ---------------------------------------------------------------------------- + await act(async () => { + await selectTab('templates'); + }); + + templatesValue = getJsonEditorValue('dynamicTemplatesEditor'); + expect(templatesValue).toEqual(updatedValueTemplates); + + // ----------------------------------------------------------- + // Go back to fields and make sure our created field is there + // ----------------------------------------------------------- + await act(async () => { + await selectTab('fields'); + }); + field = find('fieldsListItem').at(0); + expect(find('fieldName', field).text()).toEqual(newField.name); + + // Go back to advanced settings tab make sure dynamic mappings is disabled + await act(async () => { + await selectTab('advanced'); + }); + + isDynamicMappingsEnabled = find('advancedConfiguration.dynamicMappingsToggle.input').props()[ + 'aria-checked' + ]; + expect(isDynamicMappingsEnabled).toBe(false); + isNumericDetectionVisible = exists('advancedConfiguration.numericDetection'); + expect(isNumericDetectionVisible).toBe(false); + }); + }); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx index 9b0b8420f9ea9..6b33d4450c3ae 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/configuration_form.tsx @@ -121,7 +121,7 @@ export const ConfigurationForm = React.memo(({ defaultValue }: Props) => { // Avoid reseting the form on component mount. didMountRef.current = true; } - }, [defaultValue, form]); + }, [defaultValue]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { return () => { @@ -131,7 +131,12 @@ export const ConfigurationForm = React.memo(({ defaultValue }: Props) => { }, [dispatch]); return ( -
+ diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/dynamic_mapping_section/dynamic_mapping_section.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/dynamic_mapping_section/dynamic_mapping_section.tsx index e1b08c831f168..cb9b464d270ce 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/dynamic_mapping_section/dynamic_mapping_section.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/dynamic_mapping_section/dynamic_mapping_section.tsx @@ -43,7 +43,11 @@ export const DynamicMappingSection = () => ( }} /> - + } > @@ -62,7 +66,10 @@ export const DynamicMappingSection = () => ( if (enabled) { return ( <> - + {dateDetection && ( ); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx index b41f35b983885..041c88b34976a 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/create_field/create_field.tsx @@ -5,7 +5,6 @@ */ import React, { useEffect, useCallback } from 'react'; import classNames from 'classnames'; -import * as _ from 'lodash'; import { i18n } from '@kbn/i18n'; @@ -273,7 +272,12 @@ export const CreateField = React.memo(function CreateFieldComponent({ return ( - +
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx index 75a083d64b6db..23cff2a0d9aba 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/document_fields/fields/edit_field/edit_field_header_form.tsx @@ -90,7 +90,6 @@ export const EditFieldHeaderForm = React.memo( {/* Field sub type (if any) - will never be the case if we have an "other" type */} {hasSubType && ( - {' '} )} - + {source.name} diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx index da4b8e6f6eef2..95630a6981843 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx @@ -51,7 +51,8 @@ const openModalWithJsonContent = ({ find, waitFor }: TestBed) => async (json: an }); }; -describe('', () => { +// FLAKY: https://github.com/elastic/kibana/issues/59030 +describe.skip('', () => { test('it should forward valid mapping definition', async () => { const mappingsToLoad = { properties: { diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/templates_form/templates_form.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/templates_form/templates_form.tsx index f32fcb3956e1c..3c4d6b08ebe44 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/templates_form/templates_form.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/templates_form/templates_form.tsx @@ -80,7 +80,7 @@ export const TemplatesForm = React.memo(({ defaultValue }: Props) => { // Avoid reseting the form on component mount. didMountRef.current = true; } - }, [defaultValue, form]); + }, [defaultValue]); // eslint-disable-line react-hooks/exhaustive-deps useEffect(() => { return () => { @@ -90,7 +90,7 @@ export const TemplatesForm = React.memo(({ defaultValue }: Props) => { }, [dispatch]); return ( - <> +
{ component={JsonEditorField} componentProps={{ euiCodeEditorProps: { + ['data-test-subj']: 'dynamicTemplatesEditor', height: '600px', 'aria-label': i18n.translate( 'xpack.idxMgmt.mappingsEditor.dynamicTemplatesEditorAriaLabel', @@ -124,6 +125,6 @@ export const TemplatesForm = React.memo(({ defaultValue }: Props) => { }} /> - +
); }); diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx index b6345a7140e15..316fee55526a3 100644 --- a/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx +++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/mappings_editor.tsx @@ -122,6 +122,7 @@ export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSetting changeTab('fields', state)} isSelected={selectedTab === 'fields'} + data-test-subj="formTab" > {i18n.translate('xpack.idxMgmt.mappingsEditor.fieldsTabLabel', { defaultMessage: 'Mapped fields', @@ -130,6 +131,7 @@ export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSetting changeTab('templates', state)} isSelected={selectedTab === 'templates'} + data-test-subj="formTab" > {i18n.translate('xpack.idxMgmt.mappingsEditor.templatesTabLabel', { defaultMessage: 'Dynamic templates', @@ -138,6 +140,7 @@ export const MappingsEditor = React.memo(({ onUpdate, defaultValue, indexSetting changeTab('advanced', state)} isSelected={selectedTab === 'advanced'} + data-test-subj="formTab" > {i18n.translate('xpack.idxMgmt.mappingsEditor.advancedTabLabel', { defaultMessage: 'Advanced options', diff --git a/x-pack/plugins/index_management/public/application/mount_management_section.ts b/x-pack/plugins/index_management/public/application/mount_management_section.ts new file mode 100644 index 0000000000000..c47b0603dc1c8 --- /dev/null +++ b/x-pack/plugins/index_management/public/application/mount_management_section.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup } from 'src/core/public'; +import { ManagementAppMountParams } from 'src/plugins/management/public/'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; + +import { ExtensionsService } from '../services'; +import { IndexMgmtMetricsType } from '../types'; +import { AppDependencies } from './app_context'; +import { breadcrumbService } from './services/breadcrumbs'; +import { documentationService } from './services/documentation'; +import { HttpService, NotificationService, UiMetricService } from './services'; + +import { renderApp } from '.'; + +interface InternalServices { + httpService: HttpService; + notificationService: NotificationService; + uiMetricService: UiMetricService; + extensionsService: ExtensionsService; +} + +export async function mountManagementSection( + coreSetup: CoreSetup, + usageCollection: UsageCollectionSetup, + services: InternalServices, + params: ManagementAppMountParams +) { + const { element, setBreadcrumbs } = params; + const [core] = await coreSetup.getStartServices(); + const { docLinks, fatalErrors } = core; + + breadcrumbService.setup(setBreadcrumbs); + documentationService.setup(docLinks); + + const appDependencies: AppDependencies = { + core: { + fatalErrors, + }, + plugins: { + usageCollection, + }, + services, + }; + + return renderApp(element, { core, dependencies: appDependencies }); +} diff --git a/x-pack/plugins/index_management/public/plugin.ts b/x-pack/plugins/index_management/public/plugin.ts index c1b26fe3ca56b..4aa06d286e3c4 100644 --- a/x-pack/plugins/index_management/public/plugin.ts +++ b/x-pack/plugins/index_management/public/plugin.ts @@ -10,10 +10,7 @@ import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/p import { ManagementSetup } from '../../../../src/plugins/management/public'; import { UIM_APP_NAME, PLUGIN } from '../common/constants'; -import { AppDependencies } from './application'; import { httpService } from './application/services/http'; -import { breadcrumbService } from './application/services/breadcrumbs'; -import { documentationService } from './application/services/documentation'; import { notificationService } from './application/services/notification'; import { UiMetricService } from './application/services/ui_metric'; @@ -44,7 +41,7 @@ export class IndexMgmtUIPlugin { } public setup(coreSetup: CoreSetup, plugins: PluginsDependencies): IndexMgmtSetup { - const { http, notifications, getStartServices } = coreSetup; + const { http, notifications } = coreSetup; const { usageCollection, management } = plugins; httpService.setup(http); @@ -55,30 +52,15 @@ export class IndexMgmtUIPlugin { id: PLUGIN.id, title: i18n.translate('xpack.idxMgmt.appTitle', { defaultMessage: 'Index Management' }), order: 1, - mount: async ({ element, setBreadcrumbs }) => { - const [core] = await getStartServices(); - const { docLinks, fatalErrors } = core; - - breadcrumbService.setup(setBreadcrumbs); - documentationService.setup(docLinks); - - const appDependencies: AppDependencies = { - core: { - fatalErrors, - }, - plugins: { - usageCollection, - }, - services: { - uiMetricService: this.uiMetricService, - extensionsService: this.extensionsService, - httpService, - notificationService, - }, + mount: async params => { + const { mountManagementSection } = await import('./application/mount_management_section'); + const services = { + httpService, + notificationService, + uiMetricService: this.uiMetricService, + extensionsService: this.extensionsService, }; - - const { renderApp } = await import('./application'); - return renderApp(element, { core, dependencies: appDependencies }); + return mountManagementSection(coreSetup, usageCollection, services, params); }, }); diff --git a/x-pack/plugins/index_management/server/services/license.ts b/x-pack/plugins/index_management/server/services/license.ts index c490aa7b57ed9..31d3654c51e3e 100644 --- a/x-pack/plugins/index_management/server/services/license.ts +++ b/x-pack/plugins/index_management/server/services/license.ts @@ -12,7 +12,7 @@ import { } from 'kibana/server'; import { LicensingPluginSetup } from '../../../licensing/server'; -import { LicenseType, LICENSE_CHECK_STATE } from '../../../licensing/common/types'; +import { LicenseType } from '../../../licensing/common/types'; export interface LicenseStatus { isValid: boolean; @@ -37,7 +37,7 @@ export class License { ) { licensing.license$.subscribe(license => { const { state, message } = license.check(pluginId, minimumLicenseType); - const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + const hasRequiredLicense = state === 'valid'; if (hasRequiredLicense) { this.licenseStatus = { isValid: true }; diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx index cd3ba43c3607c..2e43ede2480ce 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/expression.tsx @@ -17,6 +17,11 @@ import { import { IFieldType } from 'src/plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { + MetricExpressionParams, + Comparator, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../server/lib/alerting/metric_threshold/types'; import { euiStyled } from '../../../../../observability/public'; import { WhenExpression, @@ -36,16 +41,6 @@ import { MetricsExplorerSeries } from '../../../../common/http_api/metrics_explo import { useSource } from '../../../containers/source'; import { MetricsExplorerGroupBy } from '../../metrics_explorer/group_by'; -export interface MetricExpression { - aggType?: string; - metric?: string; - comparator?: Comparator; - threshold?: number[]; - timeSize?: number; - timeUnit?: TimeUnit; - indexPattern?: string; -} - interface AlertContextMeta { currentOptions?: Partial; series?: MetricsExplorerSeries; @@ -57,14 +52,35 @@ interface Props { criteria: MetricExpression[]; groupBy?: string; filterQuery?: string; + sourceId?: string; }; alertsContext: AlertsContextValue; setAlertParams(key: string, value: any): void; setAlertProperty(key: string, value: any): void; } -type Comparator = '>' | '>=' | 'between' | '<' | '<='; type TimeUnit = 's' | 'm' | 'h' | 'd'; +type MetricExpression = Omit & { + metric?: string; +}; + +enum AGGREGATION_TYPES { + COUNT = 'count', + AVERAGE = 'avg', + SUM = 'sum', + MIN = 'min', + MAX = 'max', + RATE = 'rate', + CARDINALITY = 'cardinality', +} + +const defaultExpression = { + aggType: AGGREGATION_TYPES.AVERAGE, + comparator: Comparator.GT, + threshold: [], + timeSize: 1, + timeUnit: 'm', +} as MetricExpression; export const Expressions: React.FC = props => { const { setAlertParams, alertParams, errors, alertsContext } = props; @@ -87,18 +103,6 @@ export const Expressions: React.FC = props => { } }, [alertsContext.metadata]); - const defaultExpression = useMemo( - () => ({ - aggType: AGGREGATION_TYPES.AVERAGE, - comparator: '>', - threshold: [], - timeSize: 1, - timeUnit: 'm', - indexPattern: source?.configuration.metricAlias, - }), - [source] - ); - const updateParams = useCallback( (id, e: MetricExpression) => { const exp = alertParams.criteria ? alertParams.criteria.slice() : []; @@ -112,7 +116,7 @@ export const Expressions: React.FC = props => { const exp = alertParams.criteria.slice(); exp.push(defaultExpression); setAlertParams('criteria', exp); - }, [setAlertParams, alertParams.criteria, defaultExpression]); + }, [setAlertParams, alertParams.criteria]); const removeExpression = useCallback( (id: number) => { @@ -179,11 +183,10 @@ export const Expressions: React.FC = props => { 'criteria', md.currentOptions.metrics.map(metric => ({ metric: metric.field, - comparator: '>', + comparator: Comparator.GT, threshold: [], timeSize, timeUnit, - indexPattern: source?.configuration.metricAlias, aggType: metric.aggregation, })) ); @@ -201,6 +204,7 @@ export const Expressions: React.FC = props => { setAlertParams('groupBy', md.currentOptions.groupBy); } + setAlertParams('sourceId', source?.id); } }, [alertsContext.metadata, defaultExpression, source]); // eslint-disable-line react-hooks/exhaustive-deps @@ -316,26 +320,31 @@ interface ExpressionRowProps { const StyledExpressionRow = euiStyled(EuiFlexGroup)` display: flex; flex-wrap: wrap; - margin: 0 -${props => props.theme.eui.euiSizeXS}; + margin: 0 -4px; `; const StyledExpression = euiStyled.div` - padding: 0 ${props => props.theme.eui.euiSizeXS}; + padding: 0 4px; `; export const ExpressionRow: React.FC = props => { const { setAlertParams, expression, errors, expressionId, remove, fields, canDelete } = props; - const { aggType = AGGREGATION_TYPES.MAX, metric, comparator = '>', threshold = [] } = expression; + const { + aggType = AGGREGATION_TYPES.MAX, + metric, + comparator = Comparator.GT, + threshold = [], + } = expression; const updateAggType = useCallback( (at: string) => { - setAlertParams(expressionId, { ...expression, aggType: at }); + setAlertParams(expressionId, { ...expression, aggType: at as MetricExpression['aggType'] }); }, [expressionId, expression, setAlertParams] ); const updateMetric = useCallback( - (m?: string) => { + (m?: MetricExpression['metric']) => { setAlertParams(expressionId, { ...expression, metric: m }); }, [expressionId, expression, setAlertParams] @@ -384,7 +393,7 @@ export const ExpressionRow: React.FC = props => { )} '} + thresholdComparator={comparator || Comparator.GT} threshold={threshold} onChangeSelectedThresholdComparator={updateComparator} onChangeSelectedThreshold={updateThreshold} @@ -411,16 +420,6 @@ export const ExpressionRow: React.FC = props => { ); }; -enum AGGREGATION_TYPES { - COUNT = 'count', - AVERAGE = 'avg', - SUM = 'sum', - MIN = 'min', - MAX = 'max', - RATE = 'rate', - CARDINALITY = 'cardinality', -} - export const aggregationType: { [key: string]: any } = { avg: { text: i18n.translate('xpack.infra.metrics.alertFlyout.aggregationText.avg', { diff --git a/x-pack/plugins/infra/public/components/alerting/metrics/validation.tsx b/x-pack/plugins/infra/public/components/alerting/metrics/validation.tsx index 0f5b07f8c0e13..d84e46d08a287 100644 --- a/x-pack/plugins/infra/public/components/alerting/metrics/validation.tsx +++ b/x-pack/plugins/infra/public/components/alerting/metrics/validation.tsx @@ -6,15 +6,14 @@ import { i18n } from '@kbn/i18n'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths - -import { MetricExpression } from './expression'; +import { MetricExpressionParams } from '../../../../server/lib/alerting/metric_threshold/types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ValidationResult } from '../../../../../triggers_actions_ui/public/types'; export function validateMetricThreshold({ criteria, }: { - criteria: MetricExpression[]; + criteria: MetricExpressionParams[]; }): ValidationResult { const validationResult = { errors: {} }; const errors: { @@ -24,6 +23,7 @@ export function validateMetricThreshold({ timeWindowSize: string[]; threshold0: string[]; threshold1: string[]; + metric: string[]; }; } = {}; validationResult.errors = errors; @@ -42,6 +42,7 @@ export function validateMetricThreshold({ timeWindowSize: [], threshold0: [], threshold1: [], + metric: [], }; if (!c.aggType) { errors[id].aggField.push( @@ -74,6 +75,14 @@ export function validateMetricThreshold({ }) ); } + + if (!c.metric && c.aggType !== 'count') { + errors[id].metric.push( + i18n.translate('xpack.infra.metrics.alertFlyout.error.metricRequired', { + defaultMessage: 'Metric is required.', + }) + ); + } }); return validationResult; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts index 09f1702349542..38cd0cec145f9 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts @@ -16,7 +16,8 @@ const executor = createMetricThresholdExecutor('test') as (opts: { const alertInstances = new Map(); const services = { - callCluster(_: string, { body }: any) { + callCluster(_: string, { body, index }: any) { + if (index === 'alternatebeat-*') return mocks.changedSourceIdResponse; const metric = body.query.bool.filter[1]?.exists.field; if (body.aggs.groupings) { if (body.aggs.groupings.composite.after) { @@ -55,6 +56,13 @@ const services = { }, }; }, + savedObjectsClient: { + get(_: string, sourceId: string) { + if (sourceId === 'alternate') + return { id: 'alternate', attributes: { metricAlias: 'alternatebeat-*' } }; + return { id: 'default', attributes: { metricAlias: 'metricbeat-*' } }; + }, + }, }; const baseCriterion = { @@ -62,15 +70,15 @@ const baseCriterion = { metric: 'test.metric.1', timeSize: 1, timeUnit: 'm', - indexPattern: 'metricbeat-*', }; describe('The metric threshold alert type', () => { describe('querying the entire infrastructure', () => { const instanceID = 'test-*'; - const execute = (comparator: Comparator, threshold: number[]) => + const execute = (comparator: Comparator, threshold: number[], sourceId: string = 'default') => executor({ services, params: { + sourceId, criteria: [ { ...baseCriterion, @@ -134,6 +142,14 @@ describe('The metric threshold alert type', () => { expect(mostRecentAction.action.thresholdOf.condition0).toStrictEqual([0.75]); expect(mostRecentAction.action.metricOf.condition0).toBe('test.metric.1'); }); + test('fetches the index pattern dynamically', async () => { + await execute(Comparator.LT, [17], 'alternate'); + expect(alertInstances.get(instanceID).mostRecentAction.id).toBe(FIRED_ACTIONS.id); + expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.ALERT); + await execute(Comparator.LT, [1.5], 'alternate'); + expect(alertInstances.get(instanceID).mostRecentAction).toBe(undefined); + expect(alertInstances.get(instanceID).state.alertState).toBe(AlertStates.OK); + }); }); describe('querying with a groupBy parameter', () => { diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts index 60bba61b75ef1..c5ea65f7a4d1a 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.ts @@ -5,6 +5,8 @@ */ import { mapValues } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { convertSavedObjectToSavedSourceConfiguration } from '../../sources/sources'; +import { infraSourceConfigurationSavedObjectType } from '../../sources/saved_object_mappings'; import { InfraDatabaseSearchResponse } from '../../adapters/framework/adapter_types'; import { createAfterKeyHandler } from '../../../utils/create_afterkey_handler'; import { getAllCompositeData } from '../../../utils/get_all_composite_data'; @@ -15,6 +17,7 @@ import { getIntervalInSeconds } from '../../../utils/get_interval_in_seconds'; import { getDateHistogramOffset } from '../../snapshot/query_helpers'; const TOTAL_BUCKETS = 5; +const DEFAULT_INDEX_PATTERN = 'metricbeat-*'; interface Aggregation { aggregatedIntervals: { @@ -165,18 +168,42 @@ export const getElasticsearchMetricQuery = ( }; }; +const getIndexPattern: ( + services: AlertServices, + sourceId?: string +) => Promise = async function({ savedObjectsClient }, sourceId = 'default') { + try { + const sourceConfiguration = await savedObjectsClient.get( + infraSourceConfigurationSavedObjectType, + sourceId + ); + const { metricAlias } = convertSavedObjectToSavedSourceConfiguration( + sourceConfiguration + ).configuration; + return metricAlias || DEFAULT_INDEX_PATTERN; + } catch (e) { + if (e.output.statusCode === 404) { + return DEFAULT_INDEX_PATTERN; + } else { + throw e; + } + } +}; + const getMetric: ( services: AlertServices, params: MetricExpressionParams, + index: string, groupBy: string | undefined, filterQuery: string | undefined ) => Promise> = async function( - { callCluster }, + { savedObjectsClient, callCluster }, params, + index, groupBy, filterQuery ) { - const { indexPattern, aggType } = params; + const { aggType } = params; const searchBody = getElasticsearchMetricQuery(params, groupBy, filterQuery); try { @@ -189,7 +216,7 @@ const getMetric: ( response => response.aggregations?.groupings?.after_key ); const compositeBuckets = (await getAllCompositeData( - body => callCluster('search', { body, index: indexPattern }), + body => callCluster('search', { body, index }), searchBody, bucketSelector, afterKeyHandler @@ -204,7 +231,7 @@ const getMetric: ( } const result = await callCluster('search', { body: searchBody, - index: indexPattern, + index, }); return { '*': getCurrentValueFromAggregations(result.aggregations, aggType) }; } catch (e) { @@ -236,16 +263,18 @@ const mapToConditionsLookup = ( export const createMetricThresholdExecutor = (alertUUID: string) => async function({ services, params }: AlertExecutorOptions) { - const { criteria, groupBy, filterQuery } = params as { + const { criteria, groupBy, filterQuery, sourceId } = params as { criteria: MetricExpressionParams[]; groupBy: string | undefined; filterQuery: string | undefined; + sourceId?: string; }; const alertResults = await Promise.all( criteria.map(criterion => (async () => { - const currentValues = await getMetric(services, criterion, groupBy, filterQuery); + const index = await getIndexPattern(services, sourceId); + const currentValues = await getMetric(services, criterion, index, groupBy, filterQuery); const { threshold, comparator } = criterion; const comparisonFunction = comparatorMap[comparator]; return mapValues(currentValues, value => ({ diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts index 2ab3a3322661d..8808219cabaa7 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts @@ -29,7 +29,6 @@ export async function registerMetricThresholdAlertType(alertingPlugin: PluginSet ]), timeUnit: schema.string(), timeSize: schema.number(), - indexPattern: schema.string(), }; const nonCountCriterion = schema.object({ @@ -89,6 +88,7 @@ export async function registerMetricThresholdAlertType(alertingPlugin: PluginSet criteria: schema.arrayOf(schema.oneOf([countCriterion, nonCountCriterion])), groupBy: schema.maybe(schema.string()), filterQuery: schema.maybe(schema.string()), + sourceId: schema.string(), }), }, defaultActionGroupId: FIRED_ACTIONS.id, diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts index e87ffcfb2b912..66e0a363c8983 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/test_mocks.ts @@ -26,6 +26,17 @@ const bucketsB = [ }, ]; +const bucketsC = [ + { + doc_count: 2, + aggregatedValue: { value: 0.5 }, + }, + { + doc_count: 3, + aggregatedValue: { value: 16.0 }, + }, +]; + export const basicMetricResponse = { aggregations: { aggregatedIntervals: { @@ -108,3 +119,11 @@ export const compositeEndResponse = { aggregations: {}, hits: { total: { value: 0 } }, }; + +export const changedSourceIdResponse = { + aggregations: { + aggregatedIntervals: { + buckets: bucketsC, + }, + }, +}; diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts index 557a071ec9175..abed691f109c0 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/types.ts @@ -28,7 +28,7 @@ export type TimeUnit = 's' | 'm' | 'h' | 'd'; interface BaseMetricExpressionParams { timeSize: number; timeUnit: TimeUnit; - indexPattern: string; + sourceId?: string; threshold: number[]; comparator: Comparator; } diff --git a/x-pack/plugins/infra/server/lib/sources/sources.ts b/x-pack/plugins/infra/server/lib/sources/sources.ts index 5b9207a88e6e4..c7ff6c9638204 100644 --- a/x-pack/plugins/infra/server/lib/sources/sources.ts +++ b/x-pack/plugins/infra/server/lib/sources/sources.ts @@ -231,7 +231,7 @@ const mergeSourceConfiguration = ( first ); -const convertSavedObjectToSavedSourceConfiguration = (savedObject: unknown) => +export const convertSavedObjectToSavedSourceConfiguration = (savedObject: unknown) => pipe( SourceConfigurationSavedObjectRuntimeType.decode(savedObject), map(savedSourceConfiguration => ({ diff --git a/x-pack/plugins/ingest_manager/common/constants/agent.ts b/x-pack/plugins/ingest_manager/common/constants/agent.ts index fe6f7f57e2899..0b462fb4c0319 100644 --- a/x-pack/plugins/ingest_manager/common/constants/agent.ts +++ b/x-pack/plugins/ingest_manager/common/constants/agent.ts @@ -5,8 +5,8 @@ */ export const AGENT_SAVED_OBJECT_TYPE = 'agents'; - export const AGENT_EVENT_SAVED_OBJECT_TYPE = 'agent_events'; +export const AGENT_ACTION_SAVED_OBJECT_TYPE = 'agent_actions'; export const AGENT_TYPE_PERMANENT = 'PERMANENT'; export const AGENT_TYPE_EPHEMERAL = 'EPHEMERAL'; diff --git a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts index ab039be8e7c22..b897c03e89f82 100644 --- a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts +++ b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.test.ts @@ -41,7 +41,7 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => { }, { id: 'test-logs-bar', - enabled: false, + enabled: true, dataset: 'bar', config: { barVar: { value: 'bar-value' }, @@ -119,7 +119,7 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => { }, { id: 'test-logs-bar', - enabled: false, + enabled: true, dataset: 'bar', barVar: 'bar-value', barVar2: [1, 2], @@ -140,6 +140,44 @@ describe('Ingest Manager - storedDatasourceToAgentDatasource', () => { }); }); + it('returns agent datasource config without disabled streams', () => { + expect( + storedDatasourceToAgentDatasource({ + ...mockDatasource, + inputs: [ + { + ...mockInput, + streams: [{ ...mockInput.streams[0] }, { ...mockInput.streams[1], enabled: false }], + }, + ], + }) + ).toEqual({ + id: 'mock-datasource', + namespace: 'default', + enabled: true, + use_output: 'default', + inputs: [ + { + type: 'test-logs', + enabled: true, + inputVar: 'input-value', + inputVar3: { + testField: 'test', + }, + streams: [ + { + id: 'test-logs-foo', + enabled: true, + dataset: 'foo', + fooVar: 'foo-value', + fooVar2: [1, 2], + }, + ], + }, + ], + }); + }); + it('returns agent datasource config without disabled inputs', () => { expect( storedDatasourceToAgentDatasource({ diff --git a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts index f58eaacb7be67..20bbbec8919d6 100644 --- a/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts +++ b/x-pack/plugins/ingest_manager/common/services/datasource_to_agent_datasource.ts @@ -51,14 +51,16 @@ export const storedDatasourceToAgentDatasource = ( const fullInput = { ...input, ...Object.entries(input.config || {}).reduce(configReducer, {}), - streams: input.streams.map(stream => { - const fullStream = { - ...stream, - ...Object.entries(stream.config || {}).reduce(configReducer, {}), - }; - delete fullStream.config; - return fullStream; - }), + streams: input.streams + .filter(stream => stream.enabled) + .map(stream => { + const fullStream = { + ...stream, + ...Object.entries(stream.config || {}).reduce(configReducer, {}), + }; + delete fullStream.config; + return fullStream; + }), }; delete fullInput.config; return fullInput; diff --git a/x-pack/plugins/ingest_manager/common/services/package_to_config.test.ts b/x-pack/plugins/ingest_manager/common/services/package_to_config.test.ts index 357f407811880..5fa7af2dda79a 100644 --- a/x-pack/plugins/ingest_manager/common/services/package_to_config.test.ts +++ b/x-pack/plugins/ingest_manager/common/services/package_to_config.test.ts @@ -24,6 +24,7 @@ describe('Ingest Manager - packageToConfig', () => { visualization: [], search: [], 'index-pattern': [], + map: [], }, }, status: InstallationStatus.notInstalled, 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 aa5729a101e11..4d03a30f9a590 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/agent.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/agent.ts @@ -16,15 +16,21 @@ export type AgentStatus = 'offline' | 'error' | 'online' | 'inactive' | 'warning export interface NewAgentAction { type: 'CONFIG_CHANGE' | 'DATA_DUMP' | 'RESUME' | 'PAUSE'; - data?: string; + data?: any; sent_at?: string; } export type AgentAction = NewAgentAction & { id: string; + agent_id: string; created_at: string; } & SavedObjectAttributes; +export interface AgentActionSOAttributes extends NewAgentAction, SavedObjectAttributes { + created_at: string; + agent_id: string; +} + export interface AgentEvent { type: 'STATE' | 'ERROR' | 'ACTION_RESULT' | 'ACTION'; subtype: // State @@ -62,7 +68,6 @@ interface AgentBase { config_revision?: number; config_newest_revision?: number; last_checkin?: string; - actions: AgentAction[]; } export interface Agent extends AgentBase { diff --git a/x-pack/plugins/ingest_manager/common/types/models/epm.ts b/x-pack/plugins/ingest_manager/common/types/models/epm.ts index 28786530db018..efa6621001038 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/epm.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/epm.ts @@ -28,6 +28,7 @@ export enum KibanaAssetType { visualization = 'visualization', search = 'search', indexPattern = 'index-pattern', + map = 'map', } export enum ElasticsearchAssetType { diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_config.tsx index 356739af1ff9a..0e8763cb2d4c0 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_config.tsx @@ -6,26 +6,38 @@ import React, { useState, Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { - EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiText, + EuiTextColor, EuiSpacer, EuiButtonEmpty, EuiTitle, + EuiIconTip, } from '@elastic/eui'; import { DatasourceInput, RegistryVarsEntry } from '../../../../types'; -import { isAdvancedVar } from '../services'; +import { isAdvancedVar, DatasourceConfigValidationResults, validationHasErrors } from '../services'; import { DatasourceInputVarField } from './datasource_input_var_field'; export const DatasourceInputConfig: React.FunctionComponent<{ packageInputVars?: RegistryVarsEntry[]; datasourceInput: DatasourceInput; updateDatasourceInput: (updatedInput: Partial) => void; -}> = ({ packageInputVars, datasourceInput, updateDatasourceInput }) => { + inputVarsValidationResults: DatasourceConfigValidationResults; + forceShowErrors?: boolean; +}> = ({ + packageInputVars, + datasourceInput, + updateDatasourceInput, + inputVarsValidationResults, + forceShowErrors, +}) => { // Showing advanced options toggle state const [isShowingAdvanced, setIsShowingAdvanced] = useState(false); + // Errors state + const hasErrors = forceShowErrors && validationHasErrors(inputVarsValidationResults); + const requiredVars: RegistryVarsEntry[] = []; const advancedVars: RegistryVarsEntry[] = []; @@ -40,15 +52,36 @@ export const DatasourceInputConfig: React.FunctionComponent<{ } return ( - - + + -

- -

+ + +

+ + + +

+
+ {hasErrors ? ( + + + } + position="right" + type="alert" + iconProps={{ color: 'danger' }} + /> + + ) : null} +
@@ -60,7 +93,7 @@ export const DatasourceInputConfig: React.FunctionComponent<{

- + {requiredVars.map(varDef => { const { name: varName, type: varType } = varDef; @@ -81,6 +114,8 @@ export const DatasourceInputConfig: React.FunctionComponent<{ }, }); }} + errors={inputVarsValidationResults.config![varName]} + forceShowErrors={forceShowErrors} /> ); @@ -123,6 +158,8 @@ export const DatasourceInputConfig: React.FunctionComponent<{ }, }); }} + errors={inputVarsValidationResults.config![varName]} + forceShowErrors={forceShowErrors} /> ); @@ -132,6 +169,6 @@ export const DatasourceInputConfig: React.FunctionComponent<{ ) : null}
-
+
); }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_panel.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_panel.tsx index 74b08f48df12d..6b0c68ccb7d3f 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_panel.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_panel.tsx @@ -17,8 +17,10 @@ import { EuiButtonIcon, EuiHorizontalRule, EuiSpacer, + EuiIconTip, } from '@elastic/eui'; import { DatasourceInput, DatasourceInputStream, RegistryInput } from '../../../../types'; +import { DatasourceInputValidationResults, validationHasErrors } from '../services'; import { DatasourceInputConfig } from './datasource_input_config'; import { DatasourceInputStreamConfig } from './datasource_input_stream_config'; @@ -32,10 +34,21 @@ export const DatasourceInputPanel: React.FunctionComponent<{ packageInput: RegistryInput; datasourceInput: DatasourceInput; updateDatasourceInput: (updatedInput: Partial) => void; -}> = ({ packageInput, datasourceInput, updateDatasourceInput }) => { + inputValidationResults: DatasourceInputValidationResults; + forceShowErrors?: boolean; +}> = ({ + packageInput, + datasourceInput, + updateDatasourceInput, + inputValidationResults, + forceShowErrors, +}) => { // Showing streams toggle state const [isShowingStreams, setIsShowingStreams] = useState(false); + // Errors state + const hasErrors = forceShowErrors && validationHasErrors(inputValidationResults); + return ( {/* Header / input-level toggle */} @@ -43,9 +56,32 @@ export const DatasourceInputPanel: React.FunctionComponent<{ -

{packageInput.title || packageInput.type}

- + + + +

+ + {packageInput.title || packageInput.type} + +

+
+
+ {hasErrors ? ( + + + } + position="right" + type="alert" + iconProps={{ color: 'danger' }} + /> + + ) : null} +
} checked={datasourceInput.enabled} onChange={e => { @@ -122,6 +158,8 @@ export const DatasourceInputPanel: React.FunctionComponent<{ packageInputVars={packageInput.vars} datasourceInput={datasourceInput} updateDatasourceInput={updateDatasourceInput} + inputVarsValidationResults={{ config: inputValidationResults.config }} + forceShowErrors={forceShowErrors} /> @@ -165,6 +203,10 @@ export const DatasourceInputPanel: React.FunctionComponent<{ updateDatasourceInput(updatedInput); }} + inputStreamValidationResults={ + inputValidationResults.streams![datasourceInputStream.id] + } + forceShowErrors={forceShowErrors} /> diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_stream_config.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_stream_config.tsx index 3bf5b2bb4c0f0..43e8f5a2c060d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_stream_config.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_stream_config.tsx @@ -7,26 +7,38 @@ import React, { useState, Fragment } from 'react'; import ReactMarkdown from 'react-markdown'; import { FormattedMessage } from '@kbn/i18n/react'; import { - EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiSwitch, EuiText, EuiSpacer, EuiButtonEmpty, + EuiTextColor, + EuiIconTip, } from '@elastic/eui'; import { DatasourceInputStream, RegistryStream, RegistryVarsEntry } from '../../../../types'; -import { isAdvancedVar } from '../services'; +import { isAdvancedVar, DatasourceConfigValidationResults, validationHasErrors } from '../services'; import { DatasourceInputVarField } from './datasource_input_var_field'; export const DatasourceInputStreamConfig: React.FunctionComponent<{ packageInputStream: RegistryStream; datasourceInputStream: DatasourceInputStream; updateDatasourceInputStream: (updatedStream: Partial) => void; -}> = ({ packageInputStream, datasourceInputStream, updateDatasourceInputStream }) => { + inputStreamValidationResults: DatasourceConfigValidationResults; + forceShowErrors?: boolean; +}> = ({ + packageInputStream, + datasourceInputStream, + updateDatasourceInputStream, + inputStreamValidationResults, + forceShowErrors, +}) => { // Showing advanced options toggle state const [isShowingAdvanced, setIsShowingAdvanced] = useState(false); + // Errors state + const hasErrors = forceShowErrors && validationHasErrors(inputStreamValidationResults); + const requiredVars: RegistryVarsEntry[] = []; const advancedVars: RegistryVarsEntry[] = []; @@ -41,10 +53,33 @@ export const DatasourceInputStreamConfig: React.FunctionComponent<{ } return ( - - + + + + + {packageInputStream.title || packageInputStream.dataset} + + + {hasErrors ? ( + + + } + position="right" + type="alert" + iconProps={{ color: 'danger' }} + /> + + ) : null} + + } checked={datasourceInputStream.enabled} onChange={e => { const enabled = e.target.checked; @@ -62,7 +97,7 @@ export const DatasourceInputStreamConfig: React.FunctionComponent<{ ) : null} - + {requiredVars.map(varDef => { const { name: varName, type: varType } = varDef; @@ -83,6 +118,8 @@ export const DatasourceInputStreamConfig: React.FunctionComponent<{ }, }); }} + errors={inputStreamValidationResults.config![varName]} + forceShowErrors={forceShowErrors} /> ); @@ -125,6 +162,8 @@ export const DatasourceInputStreamConfig: React.FunctionComponent<{ }, }); }} + errors={inputStreamValidationResults.config![varName]} + forceShowErrors={forceShowErrors} /> ); @@ -134,6 +173,6 @@ export const DatasourceInputStreamConfig: React.FunctionComponent<{ ) : null}
- + ); }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_var_field.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_var_field.tsx index bcb99eed88ac0..846a807f9240d 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_var_field.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/datasource_input_var_field.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 from 'react'; +import React, { useState } from 'react'; import ReactMarkdown from 'react-markdown'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFormRow, EuiFieldText, EuiComboBox, EuiText, EuiCodeEditor } from '@elastic/eui'; @@ -16,12 +16,20 @@ export const DatasourceInputVarField: React.FunctionComponent<{ varDef: RegistryVarsEntry; value: any; onChange: (newValue: any) => void; -}> = ({ varDef, value, onChange }) => { + errors?: string[] | null; + forceShowErrors?: boolean; +}> = ({ varDef, value, onChange, errors: varErrors, forceShowErrors }) => { + const [isDirty, setIsDirty] = useState(false); + const { multi, required, type, title, name, description } = varDef; + const isInvalid = (isDirty || forceShowErrors) && !!varErrors; + const errors = isInvalid ? varErrors : null; + const renderField = () => { - if (varDef.multi) { + if (multi) { return ( ({ label: val }))} onCreateOption={(newVal: any) => { onChange([...value, newVal]); @@ -29,10 +37,11 @@ export const DatasourceInputVarField: React.FunctionComponent<{ onChange={(newVals: any[]) => { onChange(newVals.map(val => val.label)); }} + onBlur={() => setIsDirty(true)} /> ); } - if (varDef.type === 'yaml') { + if (type === 'yaml') { return ( onChange(newVal)} + onBlur={() => setIsDirty(true)} /> ); } return ( onChange(e.target.value)} + onBlur={() => setIsDirty(true)} /> ); }; return ( ) : null } - helpText={} + helpText={} > {renderField()} diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/index.ts index e5f18e1449d1b..3bfca75668911 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/components/index.ts @@ -5,3 +5,4 @@ */ export { CreateDatasourcePageLayout } from './layout'; export { DatasourceInputPanel } from './datasource_input_panel'; +export { DatasourceInputVarField } from './datasource_input_var_field'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx index 23d0f3317a667..7815ab9cd1d6e 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/index.tsx @@ -21,6 +21,7 @@ import { useLinks as useEPMLinks } from '../../epm/hooks'; import { CreateDatasourcePageLayout } from './components'; import { CreateDatasourceFrom, CreateDatasourceStep } from './types'; import { CREATE_DATASOURCE_STEP_PATHS } from './constants'; +import { DatasourceValidationResults, validateDatasource } from './services'; import { StepSelectPackage } from './step_select_package'; import { StepSelectConfig } from './step_select_config'; import { StepConfigureDatasource } from './step_configure_datasource'; @@ -51,6 +52,9 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { inputs: [], }); + // Datasource validation state + const [validationResults, setValidationResults] = useState(); + // Update package info method const updatePackageInfo = (updatedPackageInfo: PackageInfo | undefined) => { if (updatedPackageInfo) { @@ -84,9 +88,18 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { ...updatedFields, }; setDatasource(newDatasource); - // eslint-disable-next-line no-console console.debug('Datasource updated', newDatasource); + updateDatasourceValidation(newDatasource); + }; + + const updateDatasourceValidation = (newDatasource?: NewDatasource) => { + if (packageInfo) { + const newValidationResult = validateDatasource(newDatasource || datasource, packageInfo); + setValidationResults(newValidationResult); + // eslint-disable-next-line no-console + console.debug('Datasource validation results', newValidationResult); + } }; // Cancel url @@ -202,6 +215,7 @@ export const CreateDatasourcePage: React.FunctionComponent = () => { packageInfo={packageInfo} datasource={datasource} updateDatasource={updateDatasource} + validationResults={validationResults!} backLink={ {from === 'config' ? ( diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/index.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/index.ts index 44e5bfa41cb9b..d99f0712db3c3 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/index.ts +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/index.ts @@ -4,3 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ export { isAdvancedVar } from './is_advanced_var'; +export { + DatasourceValidationResults, + DatasourceConfigValidationResults, + DatasourceInputValidationResults, + validateDatasource, + validationHasErrors, +} from './validate_datasource'; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.test.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.test.ts new file mode 100644 index 0000000000000..a45fabeb5ed6a --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.test.ts @@ -0,0 +1,504 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + PackageInfo, + InstallationStatus, + NewDatasource, + RegistryDatasource, +} from '../../../../types'; +import { validateDatasource, validationHasErrors } from './validate_datasource'; + +describe('Ingest Manager - validateDatasource()', () => { + const mockPackage = ({ + name: 'mock-package', + title: 'Mock package', + version: '0.0.0', + description: 'description', + type: 'mock', + categories: [], + requirement: { kibana: { versions: '' }, elasticsearch: { versions: '' } }, + format_version: '', + download: '', + path: '', + assets: { + kibana: { + dashboard: [], + visualization: [], + search: [], + 'index-pattern': [], + }, + }, + status: InstallationStatus.notInstalled, + datasources: [ + { + name: 'datasource1', + title: 'Datasource 1', + description: 'test datasource', + inputs: [ + { + type: 'foo', + title: 'Foo', + vars: [ + { default: 'foo-input-var-value', name: 'foo-input-var-name', type: 'text' }, + { + default: 'foo-input2-var-value', + name: 'foo-input2-var-name', + required: true, + type: 'text', + }, + { name: 'foo-input3-var-name', type: 'text', required: true, multi: true }, + ], + streams: [ + { + dataset: 'foo', + input: 'foo', + title: 'Foo', + vars: [{ name: 'var-name', type: 'yaml' }], + }, + ], + }, + { + type: 'bar', + title: 'Bar', + vars: [ + { + default: ['value1', 'value2'], + name: 'bar-input-var-name', + type: 'text', + multi: true, + }, + { name: 'bar-input2-var-name', required: true, type: 'text' }, + ], + streams: [ + { + dataset: 'bar', + input: 'bar', + title: 'Bar', + vars: [{ name: 'var-name', type: 'yaml', required: true }], + }, + { + dataset: 'bar2', + input: 'bar2', + title: 'Bar 2', + vars: [{ default: 'bar2-var-value', name: 'var-name', type: 'text' }], + }, + ], + }, + { + type: 'with-no-config-or-streams', + title: 'With no config or streams', + streams: [], + }, + { + type: 'with-disabled-streams', + title: 'With disabled streams', + streams: [ + { + dataset: 'disabled', + input: 'disabled', + title: 'Disabled', + enabled: false, + vars: [{ multi: true, required: true, name: 'var-name', type: 'text' }], + }, + { dataset: 'disabled2', input: 'disabled2', title: 'Disabled 2', enabled: false }, + ], + }, + ], + }, + ], + } as unknown) as PackageInfo; + + const validDatasource: NewDatasource = { + name: 'datasource1-1', + config_id: 'test-config', + enabled: true, + output_id: 'test-output', + inputs: [ + { + type: 'foo', + enabled: true, + config: { + 'foo-input-var-name': { value: 'foo-input-var-value', type: 'text' }, + 'foo-input2-var-name': { value: 'foo-input2-var-value', type: 'text' }, + 'foo-input3-var-name': { value: ['test'], type: 'text' }, + }, + streams: [ + { + id: 'foo-foo', + dataset: 'foo', + enabled: true, + config: { 'var-name': { value: 'test_yaml: value', type: 'yaml' } }, + }, + ], + }, + { + type: 'bar', + enabled: true, + config: { + 'bar-input-var-name': { value: ['value1', 'value2'], type: 'text' }, + 'bar-input2-var-name': { value: 'test', type: 'text' }, + }, + streams: [ + { + id: 'bar-bar', + dataset: 'bar', + enabled: true, + config: { 'var-name': { value: 'test_yaml: value', type: 'yaml' } }, + }, + { + id: 'bar-bar2', + dataset: 'bar2', + enabled: true, + config: { 'var-name': { value: undefined, type: 'text' } }, + }, + ], + }, + { + type: 'with-no-config-or-streams', + enabled: true, + streams: [], + }, + { + type: 'with-disabled-streams', + enabled: true, + streams: [ + { + id: 'with-disabled-streams-disabled', + dataset: 'disabled', + enabled: false, + config: { 'var-name': { value: undefined, type: 'text' } }, + }, + { + id: 'with-disabled-streams-disabled2', + dataset: 'disabled2', + enabled: false, + }, + ], + }, + ], + }; + + const invalidDatasource: NewDatasource = { + ...validDatasource, + name: '', + inputs: [ + { + type: 'foo', + enabled: true, + config: { + 'foo-input-var-name': { value: undefined, type: 'text' }, + 'foo-input2-var-name': { value: '', type: 'text' }, + 'foo-input3-var-name': { value: [], type: 'text' }, + }, + streams: [ + { + id: 'foo-foo', + dataset: 'foo', + enabled: true, + config: { 'var-name': { value: 'invalidyaml: test\n foo bar:', type: 'yaml' } }, + }, + ], + }, + { + type: 'bar', + enabled: true, + config: { + 'bar-input-var-name': { value: 'invalid value for multi', type: 'text' }, + 'bar-input2-var-name': { value: undefined, type: 'text' }, + }, + streams: [ + { + id: 'bar-bar', + dataset: 'bar', + enabled: true, + config: { 'var-name': { value: ' \n\n', type: 'yaml' } }, + }, + { + id: 'bar-bar2', + dataset: 'bar2', + enabled: true, + config: { 'var-name': { value: undefined, type: 'text' } }, + }, + ], + }, + { + type: 'with-no-config-or-streams', + enabled: true, + streams: [], + }, + { + type: 'with-disabled-streams', + enabled: true, + streams: [ + { + id: 'with-disabled-streams-disabled', + dataset: 'disabled', + enabled: false, + config: { + 'var-name': { + value: 'invalid value but not checked due to not enabled', + type: 'text', + }, + }, + }, + { + id: 'with-disabled-streams-disabled2', + dataset: 'disabled2', + enabled: false, + }, + ], + }, + ], + }; + + const noErrorsValidationResults = { + name: null, + description: null, + inputs: { + foo: { + config: { + 'foo-input-var-name': null, + 'foo-input2-var-name': null, + 'foo-input3-var-name': null, + }, + streams: { 'foo-foo': { config: { 'var-name': null } } }, + }, + bar: { + config: { 'bar-input-var-name': null, 'bar-input2-var-name': null }, + streams: { + 'bar-bar': { config: { 'var-name': null } }, + 'bar-bar2': { config: { 'var-name': null } }, + }, + }, + 'with-disabled-streams': { + streams: { 'with-disabled-streams-disabled': { config: { 'var-name': null } } }, + }, + }, + }; + + it('returns no errors for valid datasource configuration', () => { + expect(validateDatasource(validDatasource, mockPackage)).toEqual(noErrorsValidationResults); + }); + + it('returns errors for invalid datasource configuration', () => { + expect(validateDatasource(invalidDatasource, mockPackage)).toEqual({ + name: ['Name is required'], + description: null, + inputs: { + foo: { + config: { + 'foo-input-var-name': null, + 'foo-input2-var-name': ['foo-input2-var-name is required'], + 'foo-input3-var-name': ['foo-input3-var-name is required'], + }, + streams: { 'foo-foo': { config: { 'var-name': ['Invalid YAML format'] } } }, + }, + bar: { + config: { + 'bar-input-var-name': ['Invalid format'], + 'bar-input2-var-name': ['bar-input2-var-name is required'], + }, + streams: { + 'bar-bar': { config: { 'var-name': ['var-name is required'] } }, + 'bar-bar2': { config: { 'var-name': null } }, + }, + }, + 'with-disabled-streams': { + streams: { 'with-disabled-streams-disabled': { config: { 'var-name': null } } }, + }, + }, + }); + }); + + it('returns no errors for disabled inputs', () => { + const disabledInputs = invalidDatasource.inputs.map(input => ({ ...input, enabled: false })); + expect(validateDatasource({ ...validDatasource, inputs: disabledInputs }, mockPackage)).toEqual( + noErrorsValidationResults + ); + }); + + it('returns only datasource and input-level errors for disabled streams', () => { + const inputsWithDisabledStreams = invalidDatasource.inputs.map(input => + input.streams + ? { + ...input, + streams: input.streams.map(stream => ({ ...stream, enabled: false })), + } + : input + ); + expect( + validateDatasource({ ...invalidDatasource, inputs: inputsWithDisabledStreams }, mockPackage) + ).toEqual({ + name: ['Name is required'], + description: null, + inputs: { + foo: { + config: { + 'foo-input-var-name': null, + 'foo-input2-var-name': ['foo-input2-var-name is required'], + 'foo-input3-var-name': ['foo-input3-var-name is required'], + }, + streams: { 'foo-foo': { config: { 'var-name': null } } }, + }, + bar: { + config: { + 'bar-input-var-name': ['Invalid format'], + 'bar-input2-var-name': ['bar-input2-var-name is required'], + }, + streams: { + 'bar-bar': { config: { 'var-name': null } }, + 'bar-bar2': { config: { 'var-name': null } }, + }, + }, + 'with-disabled-streams': { + streams: { 'with-disabled-streams-disabled': { config: { 'var-name': null } } }, + }, + }, + }); + }); + + it('returns no errors for packages with no datasources', () => { + expect( + validateDatasource(validDatasource, { + ...mockPackage, + datasources: undefined, + }) + ).toEqual({ + name: null, + description: null, + inputs: null, + }); + expect( + validateDatasource(validDatasource, { + ...mockPackage, + datasources: [], + }) + ).toEqual({ + name: null, + description: null, + inputs: null, + }); + }); + + it('returns no errors for packages with no inputs', () => { + expect( + validateDatasource(validDatasource, { + ...mockPackage, + datasources: [{} as RegistryDatasource], + }) + ).toEqual({ + name: null, + description: null, + inputs: null, + }); + expect( + validateDatasource(validDatasource, { + ...mockPackage, + datasources: [({ inputs: [] } as unknown) as RegistryDatasource], + }) + ).toEqual({ + name: null, + description: null, + inputs: null, + }); + }); +}); + +describe('Ingest Manager - validationHasErrors()', () => { + it('returns true for stream validation results with errors', () => { + expect( + validationHasErrors({ + config: { foo: ['foo error'], bar: null }, + }) + ).toBe(true); + }); + + it('returns false for stream validation results with no errors', () => { + expect( + validationHasErrors({ + config: { foo: null, bar: null }, + }) + ).toBe(false); + }); + + it('returns true for input validation results with errors', () => { + expect( + validationHasErrors({ + config: { foo: ['foo error'], bar: null }, + streams: { stream1: { config: { foo: null, bar: null } } }, + }) + ).toBe(true); + expect( + validationHasErrors({ + config: { foo: null, bar: null }, + streams: { stream1: { config: { foo: ['foo error'], bar: null } } }, + }) + ).toBe(true); + }); + + it('returns false for input validation results with no errors', () => { + expect( + validationHasErrors({ + config: { foo: null, bar: null }, + streams: { stream1: { config: { foo: null, bar: null } } }, + }) + ).toBe(false); + }); + + it('returns true for datasource validation results with errors', () => { + expect( + validationHasErrors({ + name: ['name error'], + description: null, + inputs: { + input1: { + config: { foo: null, bar: null }, + streams: { stream1: { config: { foo: null, bar: null } } }, + }, + }, + }) + ).toBe(true); + expect( + validationHasErrors({ + name: null, + description: null, + inputs: { + input1: { + config: { foo: ['foo error'], bar: null }, + streams: { stream1: { config: { foo: null, bar: null } } }, + }, + }, + }) + ).toBe(true); + expect( + validationHasErrors({ + name: null, + description: null, + inputs: { + input1: { + config: { foo: null, bar: null }, + streams: { stream1: { config: { foo: ['foo error'], bar: null } } }, + }, + }, + }) + ).toBe(true); + }); + + it('returns false for datasource validation results with no errors', () => { + expect( + validationHasErrors({ + name: null, + description: null, + inputs: { + input1: { + config: { foo: null, bar: null }, + streams: { stream1: { config: { foo: null, bar: null } } }, + }, + }, + }) + ).toBe(false); + }); +}); diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.ts b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.ts new file mode 100644 index 0000000000000..518e2bfc1af07 --- /dev/null +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/services/validate_datasource.ts @@ -0,0 +1,232 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { safeLoad } from 'js-yaml'; +import { getFlattenedObject } from '../../../../services'; +import { + NewDatasource, + DatasourceInput, + DatasourceInputStream, + DatasourceConfigRecordEntry, + PackageInfo, + RegistryInput, + RegistryVarsEntry, +} from '../../../../types'; + +type Errors = string[] | null; + +type ValidationEntry = Record; + +export interface DatasourceConfigValidationResults { + config?: ValidationEntry; +} + +export type DatasourceInputValidationResults = DatasourceConfigValidationResults & { + streams?: Record; +}; + +export interface DatasourceValidationResults { + name: Errors; + description: Errors; + inputs: Record | null; +} + +/* + * Returns validation information for a given datasource configuration and package info + * Note: this method assumes that `datasource` is correctly structured for the given package + */ +export const validateDatasource = ( + datasource: NewDatasource, + packageInfo: PackageInfo +): DatasourceValidationResults => { + const validationResults: DatasourceValidationResults = { + name: null, + description: null, + inputs: {}, + }; + + if (!datasource.name.trim()) { + validationResults.name = [ + i18n.translate('xpack.ingestManager.datasourceValidation.nameRequiredErrorMessage', { + defaultMessage: 'Name is required', + }), + ]; + } + + if ( + !packageInfo.datasources || + packageInfo.datasources.length === 0 || + !packageInfo.datasources[0] || + !packageInfo.datasources[0].inputs || + packageInfo.datasources[0].inputs.length === 0 + ) { + validationResults.inputs = null; + return validationResults; + } + + const registryInputsByType: Record< + string, + RegistryInput + > = packageInfo.datasources[0].inputs.reduce((inputs, registryInput) => { + inputs[registryInput.type] = registryInput; + return inputs; + }, {} as Record); + + // Validate each datasource input with either its own config fields or streams + datasource.inputs.forEach(input => { + if (!input.config && !input.streams) { + return; + } + + const inputValidationResults: DatasourceInputValidationResults = { + config: undefined, + streams: {}, + }; + + const inputVarsByName = (registryInputsByType[input.type].vars || []).reduce( + (vars, registryVar) => { + vars[registryVar.name] = registryVar; + return vars; + }, + {} as Record + ); + + // Validate input-level config fields + const inputConfigs = Object.entries(input.config || {}); + if (inputConfigs.length) { + inputValidationResults.config = inputConfigs.reduce((results, [name, configEntry]) => { + results[name] = input.enabled + ? validateDatasourceConfig(configEntry, inputVarsByName[name]) + : null; + return results; + }, {} as ValidationEntry); + } else { + delete inputValidationResults.config; + } + + // Validate each input stream with config fields + if (input.streams.length) { + input.streams.forEach(stream => { + if (!stream.config) { + return; + } + + const streamValidationResults: DatasourceConfigValidationResults = { + config: undefined, + }; + + const streamVarsByName = ( + ( + registryInputsByType[input.type].streams.find( + registryStream => registryStream.dataset === stream.dataset + ) || {} + ).vars || [] + ).reduce((vars, registryVar) => { + vars[registryVar.name] = registryVar; + return vars; + }, {} as Record); + + // Validate stream-level config fields + streamValidationResults.config = Object.entries(stream.config).reduce( + (results, [name, configEntry]) => { + results[name] = + input.enabled && stream.enabled + ? validateDatasourceConfig(configEntry, streamVarsByName[name]) + : null; + return results; + }, + {} as ValidationEntry + ); + + inputValidationResults.streams![stream.id] = streamValidationResults; + }); + } else { + delete inputValidationResults.streams; + } + + if (inputValidationResults.config || inputValidationResults.streams) { + validationResults.inputs![input.type] = inputValidationResults; + } + }); + + if (Object.entries(validationResults.inputs!).length === 0) { + validationResults.inputs = null; + } + return validationResults; +}; + +const validateDatasourceConfig = ( + configEntry: DatasourceConfigRecordEntry, + varDef: RegistryVarsEntry +): string[] | null => { + const errors = []; + const { value } = configEntry; + let parsedValue: any = value; + + if (typeof value === 'string') { + parsedValue = value.trim(); + } + + if (varDef.required) { + if (parsedValue === undefined || (typeof parsedValue === 'string' && !parsedValue)) { + errors.push( + i18n.translate('xpack.ingestManager.datasourceValidation.requiredErrorMessage', { + defaultMessage: '{fieldName} is required', + values: { + fieldName: varDef.title || varDef.name, + }, + }) + ); + } + } + + if (varDef.type === 'yaml') { + try { + parsedValue = safeLoad(value); + } catch (e) { + errors.push( + i18n.translate('xpack.ingestManager.datasourceValidation.invalidYamlFormatErrorMessage', { + defaultMessage: 'Invalid YAML format', + }) + ); + } + } + + if (varDef.multi) { + if (parsedValue && !Array.isArray(parsedValue)) { + errors.push( + i18n.translate('xpack.ingestManager.datasourceValidation.invalidArrayErrorMessage', { + defaultMessage: 'Invalid format', + }) + ); + } + if ( + varDef.required && + (!parsedValue || (Array.isArray(parsedValue) && parsedValue.length === 0)) + ) { + errors.push( + i18n.translate('xpack.ingestManager.datasourceValidation.requiredErrorMessage', { + defaultMessage: '{fieldName} is required', + values: { + fieldName: varDef.title || varDef.name, + }, + }) + ); + } + } + + return errors.length ? errors : null; +}; + +export const validationHasErrors = ( + validationResults: + | DatasourceValidationResults + | DatasourceInputValidationResults + | DatasourceConfigValidationResults +) => { + const flattenedValidation = getFlattenedObject(validationResults); + return !!Object.entries(flattenedValidation).find(([, value]) => !!value); +}; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_configure_datasource.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_configure_datasource.tsx index b45beef4a8b5e..105d6c66a5704 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_configure_datasource.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/agent_config/create_datasource_page/step_configure_datasource.tsx @@ -9,17 +9,16 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSteps, EuiPanel, - EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiFormRow, - EuiFieldText, EuiButtonEmpty, EuiSpacer, EuiEmptyPrompt, EuiText, EuiButton, EuiComboBox, + EuiCallOut, } from '@elastic/eui'; import { AgentConfig, @@ -28,21 +27,37 @@ import { NewDatasource, DatasourceInput, } from '../../../types'; +import { Loading } from '../../../components'; import { packageToConfigDatasourceInputs } from '../../../services'; -import { DatasourceInputPanel } from './components'; +import { DatasourceValidationResults, validationHasErrors } from './services'; +import { DatasourceInputPanel, DatasourceInputVarField } from './components'; export const StepConfigureDatasource: React.FunctionComponent<{ agentConfig: AgentConfig; packageInfo: PackageInfo; datasource: NewDatasource; updateDatasource: (fields: Partial) => void; + validationResults: DatasourceValidationResults; backLink: JSX.Element; cancelUrl: string; onNext: () => void; -}> = ({ agentConfig, packageInfo, datasource, updateDatasource, backLink, cancelUrl, onNext }) => { +}> = ({ + agentConfig, + packageInfo, + datasource, + updateDatasource, + validationResults, + backLink, + cancelUrl, + onNext, +}) => { // Form show/hide states const [isShowingAdvancedDefine, setIsShowingAdvancedDefine] = useState(false); + // Form submit state + const [submitAttempted, setSubmitAttempted] = useState(false); + const hasErrors = validationResults ? validationHasErrors(validationResults) : false; + // Update datasource's package and config info useEffect(() => { const dsPackage = datasource.package; @@ -81,56 +96,56 @@ export const StepConfigureDatasource: React.FunctionComponent<{ }, [datasource.package, datasource.config_id, agentConfig, packageInfo, updateDatasource]); // Step A, define datasource - const DefineDatasource = ( + const renderDefineDatasource = () => ( - - - - } - > - - updateDatasource({ - name: e.target.value, - }) - } - /> - + + + { + updateDatasource({ + name: newValue, + }); + }} + errors={validationResults!.name} + forceShowErrors={submitAttempted} + /> - - - } - labelAppend={ - - - - } - > - - updateDatasource({ - description: e.target.value, - }) - } - /> - + + { + updateDatasource({ + description: newValue, + }); + }} + errors={validationResults!.description} + forceShowErrors={submitAttempted} + /> - + - - + + - + + ) : null} @@ -182,7 +198,7 @@ export const StepConfigureDatasource: React.FunctionComponent<{ // Step B, configure inputs (and their streams) // Assume packages only export one datasource for now - const ConfigureInputs = + const renderConfigureInputs = () => packageInfo.datasources && packageInfo.datasources[0] && packageInfo.datasources[0].inputs && @@ -208,6 +224,8 @@ export const StepConfigureDatasource: React.FunctionComponent<{ inputs: newInputs, }); }} + inputValidationResults={validationResults!.inputs![datasourceInput.type]} + forceShowErrors={submitAttempted} /> ) : null; @@ -232,7 +250,7 @@ export const StepConfigureDatasource: React.FunctionComponent<{
); - return ( + return validationResults ? ( @@ -251,7 +269,7 @@ export const StepConfigureDatasource: React.FunctionComponent<{ defaultMessage: 'Define your datasource', } ), - children: DefineDatasource, + children: renderDefineDatasource(), }, { title: i18n.translate( @@ -260,13 +278,34 @@ export const StepConfigureDatasource: React.FunctionComponent<{ defaultMessage: 'Choose the data you want to collect', } ), - children: ConfigureInputs, + children: renderConfigureInputs(), }, ]} /> + {hasErrors && submitAttempted ? ( + + +

+ +

+
+ +
+ ) : null} @@ -278,7 +317,17 @@ export const StepConfigureDatasource: React.FunctionComponent<{ - onNext()}> + { + setSubmitAttempted(true); + if (!hasErrors) { + onNext(); + } + }} + > + ) : ( + ); }; diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx index 3a6dfe4a87daf..685199245df18 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/epm/constants.tsx @@ -24,6 +24,7 @@ export const AssetTitleMap: Record = { search: 'Saved Search', visualization: 'Visualization', input: 'Agent input', + map: 'Map', }; export const ServiceTitleMap: Record = { @@ -36,6 +37,7 @@ export const AssetIcons: Record = { 'index-pattern': 'indexPatternApp', search: 'searchProfilerApp', visualization: 'visualizeApp', + map: 'mapApp', }; export const ServiceIcons: Record = { 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 0aa08602e4d4d..5ebd1300baf65 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 @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +export { getFlattenedObject } from '../../../../../../../src/core/utils'; + export { agentConfigRouteService, datasourceRouteService, 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 333a9b049fa85..32615278b67d7 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 @@ -16,6 +16,7 @@ export { NewDatasource, DatasourceInput, DatasourceInputStream, + DatasourceConfigRecordEntry, // API schemas - Agent Config GetAgentConfigsResponse, GetAgentConfigsResponseItem, @@ -56,6 +57,7 @@ export { RegistryVarsEntry, RegistryInput, RegistryStream, + RegistryDatasource, PackageList, PackageListItem, PackagesGroupedByStatus, @@ -70,4 +72,5 @@ export { DeletePackageResponse, DetailViewPanelName, InstallStatus, + InstallationStatus, } from '../../../../common'; diff --git a/x-pack/plugins/ingest_manager/server/constants/index.ts b/x-pack/plugins/ingest_manager/server/constants/index.ts index f6ee475614c5e..6ac92ca5d2a91 100644 --- a/x-pack/plugins/ingest_manager/server/constants/index.ts +++ b/x-pack/plugins/ingest_manager/server/constants/index.ts @@ -20,8 +20,9 @@ export { INSTALL_SCRIPT_API_ROUTES, SETUP_API_ROUTE, // Saved object types - AGENT_EVENT_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE, + AGENT_EVENT_SAVED_OBJECT_TYPE, + AGENT_ACTION_SAVED_OBJECT_TYPE, AGENT_CONFIG_SAVED_OBJECT_TYPE, DATASOURCE_SAVED_OBJECT_TYPE, OUTPUT_SAVED_OBJECT_TYPE, diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts index a20ba4a880537..76247c338a24f 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.test.ts @@ -78,7 +78,7 @@ describe('test actions handlers', () => { getAgent: jest.fn().mockReturnValueOnce({ id: 'agent', }), - updateAgentActions: jest.fn().mockReturnValueOnce(agentAction), + createAgentAction: jest.fn().mockReturnValueOnce(agentAction), } as jest.Mocked; const postNewAgentActionHandler = postNewAgentActionHandlerBuilder(actionsService); diff --git a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts index 2b9c230803593..8eb427e5739b0 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/actions_handlers.ts @@ -28,11 +28,11 @@ export const postNewAgentActionHandlerBuilder = function( const newAgentAction = request.body.action as NewAgentAction; - const savedAgentAction = await actionsService.updateAgentActions( - soClient, - agent, - newAgentAction - ); + const savedAgentAction = await actionsService.createAgentAction(soClient, { + created_at: new Date().toISOString(), + ...newAgentAction, + agent_id: agent.id, + }); const body: PostNewAgentActionResponse = { success: true, 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 adff1fda11200..89c827abe30ec 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/handlers.ts @@ -187,8 +187,9 @@ export const postAgentCheckinHandler: RequestHandler< action: 'checkin', success: true, actions: actions.map(a => ({ + agent_id: agent.id, type: a.type, - data: a.data ? JSON.parse(a.data) : a.data, + data: a.data, id: a.id, created_at: a.created_at, })), 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 d461027017842..ac27e47db155e 100644 --- a/x-pack/plugins/ingest_manager/server/routes/agent/index.ts +++ b/x-pack/plugins/ingest_manager/server/routes/agent/index.ts @@ -122,7 +122,7 @@ export const registerRoutes = (router: IRouter) => { }, postNewAgentActionHandlerBuilder({ getAgent: AgentService.getAgent, - updateAgentActions: AgentService.updateAgentActions, + createAgentAction: AgentService.createAgentAction, }) ); diff --git a/x-pack/plugins/ingest_manager/server/saved_objects.ts b/x-pack/plugins/ingest_manager/server/saved_objects.ts index 9f3035e1aac17..6800cb4056700 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects.ts @@ -10,6 +10,7 @@ import { PACKAGES_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE, + AGENT_ACTION_SAVED_OBJECT_TYPE, ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, } from './constants'; @@ -38,17 +39,16 @@ export const savedObjectMappings = { default_api_key: { type: 'keyword' }, updated_at: { type: 'date' }, current_error_events: { type: 'text' }, + }, + }, + [AGENT_ACTION_SAVED_OBJECT_TYPE]: { + properties: { + agent_id: { type: 'keyword' }, + type: { type: 'keyword' }, // FIXME_INGEST https://github.com/elastic/kibana/issues/56554 - actions: { - type: 'nested', - properties: { - id: { type: 'keyword' }, - type: { type: 'keyword' }, - data: { type: 'text' }, - sent_at: { type: 'date' }, - created_at: { type: 'date' }, - }, - }, + data: { type: 'flattened' }, + sent_at: { type: 'date' }, + created_at: { type: 'date' }, }, }, [AGENT_EVENT_SAVED_OBJECT_TYPE]: { @@ -148,6 +148,7 @@ export const savedObjectMappings = { properties: { name: { type: 'keyword' }, version: { type: 'keyword' }, + internal: { type: 'boolean' }, installed: { type: 'nested', properties: { diff --git a/x-pack/plugins/ingest_manager/server/services/agent_config.ts b/x-pack/plugins/ingest_manager/server/services/agent_config.ts index a941494072ae3..309ddca3784c2 100644 --- a/x-pack/plugins/ingest_manager/server/services/agent_config.ts +++ b/x-pack/plugins/ingest_manager/server/services/agent_config.ts @@ -319,9 +319,9 @@ class AgentConfigService { return outputs; }, {} as FullAgentConfig['outputs']), }, - datasources: (config.datasources as Datasource[]).map(ds => - storedDatasourceToAgentDatasource(ds) - ), + datasources: (config.datasources as Datasource[]) + .filter(datasource => datasource.enabled) + .map(ds => storedDatasourceToAgentDatasource(ds)), revision: config.revision, }; 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 3c07463e3af5d..b4c1f09015a69 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 @@ -3,29 +3,46 @@ * 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 { SavedObjectsBulkResponse } from 'kibana/server'; import { savedObjectsClientMock } from '../../../../../../src/core/server/saved_objects/service/saved_objects_client.mock'; -import { Agent, AgentAction, AgentEvent } from '../../../common/types/models'; +import { + Agent, + AgentAction, + AgentActionSOAttributes, + AgentEvent, +} from '../../../common/types/models'; import { AGENT_TYPE_PERMANENT } from '../../../common/constants'; import { acknowledgeAgentActions } from './acks'; -import { isBoom } from 'boom'; describe('test agent acks services', () => { it('should succeed on valid and matched actions', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + + mockSavedObjectsClient.bulkGet.mockReturnValue( + Promise.resolve({ + saved_objects: [ + { + id: 'action1', + references: [], + type: 'agent_actions', + attributes: { + type: 'CONFIG_CHANGE', + agent_id: 'id', + 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', + }, + }, + ], + } as SavedObjectsBulkResponse) + ); + const agentActions = await acknowledgeAgentActions( mockSavedObjectsClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, - actions: [ - { - type: 'CONFIG_CHANGE', - id: 'action1', - 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', - }, - ], } as unknown) as Agent, [ { @@ -41,6 +58,7 @@ describe('test agent acks services', () => { ({ type: 'CONFIG_CHANGE', id: 'action1', + agent_id: 'id', 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', @@ -50,21 +68,26 @@ describe('test agent acks services', () => { it('should fail for actions that cannot be found on agent actions list', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + mockSavedObjectsClient.bulkGet.mockReturnValue( + Promise.resolve({ + saved_objects: [ + { + id: 'action1', + error: { + message: 'Not found', + statusCode: 404, + }, + }, + ], + } as SavedObjectsBulkResponse) + ); + try { await acknowledgeAgentActions( mockSavedObjectsClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, - actions: [ - { - type: 'CONFIG_CHANGE', - id: 'action1', - 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', - }, - ], } as unknown) as Agent, [ ({ @@ -78,27 +101,38 @@ describe('test agent acks services', () => { ); expect(true).toBeFalsy(); } catch (e) { - expect(isBoom(e)).toBeTruthy(); + expect(Boom.isBoom(e)).toBeTruthy(); } }); it('should fail for events that have types not in the allowed acknowledgement type list', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); + + mockSavedObjectsClient.bulkGet.mockReturnValue( + Promise.resolve({ + saved_objects: [ + { + id: 'action1', + references: [], + type: 'agent_actions', + attributes: { + type: 'CONFIG_CHANGE', + agent_id: 'id', + 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', + }, + }, + ], + } as SavedObjectsBulkResponse) + ); + try { await acknowledgeAgentActions( mockSavedObjectsClient, ({ id: 'id', type: AGENT_TYPE_PERMANENT, - actions: [ - { - type: 'CONFIG_CHANGE', - id: 'action1', - 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', - }, - ], } as unknown) as Agent, [ ({ @@ -112,7 +146,7 @@ describe('test agent acks services', () => { ); expect(true).toBeFalsy(); } catch (e) { - expect(isBoom(e)).toBeTruthy(); + expect(Boom.isBoom(e)).toBeTruthy(); } }); }); 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 cf9a47979ae8b..24c3b322aad7f 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/acks.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.ts @@ -17,8 +17,14 @@ import { AgentEvent, AgentEventSOAttributes, AgentSOAttributes, + AgentActionSOAttributes, } from '../../types'; -import { AGENT_EVENT_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE } from '../../constants'; +import { + AGENT_EVENT_SAVED_OBJECT_TYPE, + AGENT_SAVED_OBJECT_TYPE, + AGENT_ACTION_SAVED_OBJECT_TYPE, +} from '../../constants'; +import { getAgentActionByIds } from './actions'; const ALLOWED_ACKNOWLEDGEMENT_TYPE: string[] = ['ACTION_RESULT']; @@ -27,50 +33,81 @@ export async function acknowledgeAgentActions( agent: Agent, agentEvents: AgentEvent[] ): Promise { - const now = new Date().toISOString(); - - const agentActionMap: Map = new Map( - agent.actions.map(agentAction => [agentAction.id, agentAction]) - ); - - const matchedUpdatedActions: AgentAction[] = []; - - agentEvents.forEach(agentEvent => { + for (const agentEvent of agentEvents) { if (!isAllowedType(agentEvent.type)) { throw Boom.badRequest(`${agentEvent.type} not allowed for acknowledgment only ACTION_RESULT`); } - if (agentActionMap.has(agentEvent.action_id!)) { - const action = agentActionMap.get(agentEvent.action_id!) as AgentAction; - if (!action.sent_at) { - action.sent_at = now; - } - matchedUpdatedActions.push(action); - } else { - throw Boom.badRequest('all actions should belong to current agent'); + } + + const actionIds = agentEvents + .map(event => event.action_id) + .filter(actionId => actionId !== undefined) as string[]; + + let actions; + try { + actions = await getAgentActionByIds(soClient, actionIds); + } catch (error) { + if (Boom.isBoom(error) && error.output.statusCode === 404) { + throw Boom.badRequest(`One or more actions cannot be found`); + } + throw error; + } + + for (const action of actions) { + if (action.agent_id !== agent.id) { + throw Boom.badRequest(`${action.id} not found`); } - }); + } + + if (actions.length === 0) { + return []; + } + const configRevision = getLatestConfigRevison(agent, actions); - if (matchedUpdatedActions.length > 0) { - const configRevision = matchedUpdatedActions.reduce((acc, action) => { - if (action.type !== 'CONFIG_CHANGE') { - return acc; - } - const data = action.data ? JSON.parse(action.data as string) : {}; + await soClient.bulkUpdate([ + buildUpdateAgentConfigRevision(agent.id, configRevision), + ...buildUpdateAgentActionSentAt(actionIds), + ]); - if (data?.config?.id !== agent.config_id) { - return acc; - } + return actions; +} - return data?.config?.revision > acc ? data?.config?.revision : acc; - }, agent.config_revision || 0); +function getLatestConfigRevison(agent: Agent, actions: AgentAction[]) { + return actions.reduce((acc, action) => { + if (action.type !== 'CONFIG_CHANGE') { + return acc; + } + const data = action.data || {}; - await soClient.update(AGENT_SAVED_OBJECT_TYPE, agent.id, { - actions: matchedUpdatedActions, + if (data?.config?.id !== agent.config_id) { + return acc; + } + + return data?.config?.revision > acc ? data?.config?.revision : acc; + }, agent.config_revision || 0); +} + +function buildUpdateAgentConfigRevision(agentId: string, configRevision: number) { + return { + type: AGENT_SAVED_OBJECT_TYPE, + id: agentId, + attributes: { config_revision: configRevision, - }); - } + }, + }; +} - return matchedUpdatedActions; +function buildUpdateAgentActionSentAt( + actionsIds: string[], + sentAt: string = new Date().toISOString() +) { + return actionsIds.map(actionId => ({ + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + id: actionId, + attributes: { + sent_at: sentAt, + }, + })); } function isAllowedType(eventType: string): boolean { diff --git a/x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts index b500aeb825fec..f2e671c6dbaa8 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/actions.test.ts @@ -4,64 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createAgentAction, updateAgentActions } from './actions'; -import { Agent, AgentAction, NewAgentAction } from '../../../common/types/models'; +import { createAgentAction } from './actions'; +import { SavedObject } from 'kibana/server'; +import { AgentAction, AgentActionSOAttributes } from '../../../common/types/models'; import { savedObjectsClientMock } from '../../../../../../src/core/server/saved_objects/service/saved_objects_client.mock'; -import { AGENT_TYPE_PERMANENT } from '../../../common/constants'; - -interface UpdatedActions { - actions: AgentAction[]; -} describe('test agent actions services', () => { - it('should update agent current actions with new action', async () => { + it('should create a new action', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); - const newAgentAction: NewAgentAction = { + const newAgentAction: AgentActionSOAttributes = { + agent_id: 'agentid', type: 'CONFIG_CHANGE', data: 'data', sent_at: '2020-03-14T19:45:02.620Z', + created_at: '2020-03-14T19:45:02.620Z', }; - - await updateAgentActions( - mockSavedObjectsClient, - ({ - id: 'id', - type: AGENT_TYPE_PERMANENT, - actions: [ - { - type: 'CONFIG_CHANGE', - id: 'action1', - 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', - }, - ], - } as unknown) as Agent, - newAgentAction + mockSavedObjectsClient.create.mockReturnValue( + Promise.resolve({ + attributes: {}, + } as SavedObject) ); - - const updatedAgentActions = (mockSavedObjectsClient.update.mock - .calls[0][2] as unknown) as UpdatedActions; - - expect(updatedAgentActions.actions.length).toEqual(2); - const actualAgentAction = updatedAgentActions.actions.find(action => action?.data === 'data'); - expect(actualAgentAction?.type).toEqual(newAgentAction.type); - expect(actualAgentAction?.data).toEqual(newAgentAction.data); - expect(actualAgentAction?.sent_at).toEqual(newAgentAction.sent_at); - }); - - it('should create agent action from new agent action model', async () => { - const newAgentAction: NewAgentAction = { - type: 'CONFIG_CHANGE', - data: 'data', - sent_at: '2020-03-14T19:45:02.620Z', - }; - const now = new Date(); - const agentAction = createAgentAction(now, newAgentAction); - - expect(agentAction.type).toEqual(newAgentAction.type); - expect(agentAction.data).toEqual(newAgentAction.data); - expect(agentAction.sent_at).toEqual(newAgentAction.sent_at); + await createAgentAction(mockSavedObjectsClient, newAgentAction); + + const createdAction = (mockSavedObjectsClient.create.mock + .calls[0][1] as unknown) as AgentAction; + expect(createdAction).toBeDefined(); + expect(createdAction?.type).toEqual(newAgentAction.type); + expect(createdAction?.data).toEqual(newAgentAction.data); + expect(createdAction?.sent_at).toEqual(newAgentAction.sent_at); }); }); 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 2f8ed9f504453..a8ef0820f8d9f 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/actions.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/actions.ts @@ -5,46 +5,52 @@ */ import { SavedObjectsClientContract } from 'kibana/server'; -import uuid from 'uuid'; -import { - Agent, - AgentAction, - AgentSOAttributes, - NewAgentAction, -} from '../../../common/types/models'; -import { AGENT_SAVED_OBJECT_TYPE } from '../../../common/constants'; - -export async function updateAgentActions( +import { Agent, AgentAction, AgentActionSOAttributes } from '../../../common/types/models'; +import { AGENT_ACTION_SAVED_OBJECT_TYPE } from '../../../common/constants'; +import { savedObjectToAgentAction } from './saved_objects'; + +export async function createAgentAction( soClient: SavedObjectsClientContract, - agent: Agent, - newAgentAction: NewAgentAction + newAgentAction: AgentActionSOAttributes ): Promise { - const agentAction = createAgentAction(new Date(), newAgentAction); + const so = await soClient.create(AGENT_ACTION_SAVED_OBJECT_TYPE, { + ...newAgentAction, + }); - agent.actions.push(agentAction); + return savedObjectToAgentAction(so); +} - await soClient.update(AGENT_SAVED_OBJECT_TYPE, agent.id, { - actions: agent.actions, +export async function getAgentActionsForCheckin( + soClient: SavedObjectsClientContract, + agentId: string +): Promise { + const res = await soClient.find({ + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + filter: `not ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.sent_at: * and ${AGENT_ACTION_SAVED_OBJECT_TYPE}.attributes.agent_id:${agentId}`, }); - return agentAction; + return res.saved_objects.map(savedObjectToAgentAction); } -export function createAgentAction(createdAt: Date, newAgentAction: NewAgentAction): AgentAction { - const agentAction = { - id: uuid.v4(), - created_at: createdAt.toISOString(), - }; - - return Object.assign(agentAction, newAgentAction); +export async function getAgentActionByIds( + soClient: SavedObjectsClientContract, + actionIds: string[] +) { + const res = await soClient.bulkGet( + actionIds.map(actionId => ({ + id: actionId, + type: AGENT_ACTION_SAVED_OBJECT_TYPE, + })) + ); + + return res.saved_objects.map(savedObjectToAgentAction); } export interface ActionsService { getAgent: (soClient: SavedObjectsClientContract, agentId: string) => Promise; - updateAgentActions: ( + createAgentAction: ( soClient: SavedObjectsClientContract, - agent: Agent, - newAgentAction: NewAgentAction + newAgentAction: AgentActionSOAttributes ) => Promise; } diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts index d3e10fcb6b63f..d98052ea87e86 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin.test.ts @@ -14,13 +14,13 @@ function getAgent(data: Partial) { describe('Agent checkin service', () => { describe('shouldCreateConfigAction', () => { it('should return false if the agent do not have an assigned config', () => { - const res = shouldCreateConfigAction(getAgent({})); + const res = shouldCreateConfigAction(getAgent({}), []); expect(res).toBeFalsy(); }); it('should return true if this is agent first checkin', () => { - const res = shouldCreateConfigAction(getAgent({ config_id: 'config1' })); + const res = shouldCreateConfigAction(getAgent({ config_id: 'config1' }), []); expect(res).toBeTruthy(); }); @@ -32,7 +32,8 @@ describe('Agent checkin service', () => { last_checkin: '2018-01-02T00:00:00', config_revision: 1, config_newest_revision: 1, - }) + }), + [] ); expect(res).toBeFalsy(); @@ -45,20 +46,21 @@ describe('Agent checkin service', () => { last_checkin: '2018-01-02T00:00:00', config_revision: 1, config_newest_revision: 2, - actions: [ - { - id: 'action1', - type: 'CONFIG_CHANGE', - created_at: new Date().toISOString(), - data: JSON.stringify({ - config: { - id: 'config1', - revision: 2, - }, - }), - }, - ], - }) + }), + [ + { + id: 'action1', + agent_id: 'agent1', + type: 'CONFIG_CHANGE', + created_at: new Date().toISOString(), + data: JSON.stringify({ + config: { + id: 'config1', + revision: 2, + }, + }), + }, + ] ); expect(res).toBeFalsy(); @@ -71,31 +73,33 @@ describe('Agent checkin service', () => { last_checkin: '2018-01-02T00:00:00', config_revision: 1, config_newest_revision: 2, - actions: [ - { - id: 'action1', - type: 'CONFIG_CHANGE', - created_at: new Date().toISOString(), - data: JSON.stringify({ - config: { - id: 'config2', - revision: 2, - }, - }), - }, - { - id: 'action1', - type: 'CONFIG_CHANGE', - created_at: new Date().toISOString(), - data: JSON.stringify({ - config: { - id: 'config1', - revision: 1, - }, - }), - }, - ], - }) + }), + [ + { + id: 'action1', + agent_id: 'agent1', + type: 'CONFIG_CHANGE', + created_at: new Date().toISOString(), + data: JSON.stringify({ + config: { + id: 'config2', + revision: 2, + }, + }), + }, + { + id: 'action1', + agent_id: 'agent1', + type: 'CONFIG_CHANGE', + created_at: new Date().toISOString(), + data: JSON.stringify({ + config: { + id: 'config1', + revision: 1, + }, + }), + }, + ] ); expect(res).toBeTruthy(); @@ -108,7 +112,8 @@ describe('Agent checkin service', () => { last_checkin: '2018-01-02T00:00:00', config_revision: 1, config_newest_revision: 2, - }) + }), + [] ); expect(res).toBeTruthy(); diff --git a/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts b/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts index d80fff5d8eceb..9a2b3f22b9431 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/checkin.ts @@ -5,7 +5,6 @@ */ import { SavedObjectsClientContract, SavedObjectsBulkCreateObject } from 'src/core/server'; -import uuid from 'uuid'; import { Agent, AgentEvent, @@ -17,6 +16,7 @@ import { import { agentConfigService } from '../agent_config'; import * as APIKeysService from '../api_keys'; import { AGENT_SAVED_OBJECT_TYPE, AGENT_EVENT_SAVED_OBJECT_TYPE } from '../../constants'; +import { getAgentActionsForCheckin, createAgentAction } from './actions'; export async function agentCheckin( soClient: SavedObjectsClientContract, @@ -34,10 +34,10 @@ export async function agentCheckin( last_checkin: new Date().toISOString(), }; - const actions = filterActionsForCheckin(agent); + const actions = await getAgentActionsForCheckin(soClient, agent.id); // Generate new agent config if config is updated - if (agent.config_id && shouldCreateConfigAction(agent)) { + if (agent.config_id && shouldCreateConfigAction(agent, actions)) { const config = await agentConfigService.getFullConfig(soClient, agent.config_id); if (config) { // Assign output API keys @@ -52,18 +52,14 @@ export async function agentCheckin( // Mutate the config to set the api token for this agent config.outputs.default.api_key = agent.default_api_key || updateData.default_api_key; - const configChangeAction: AgentAction = { - id: uuid.v4(), + const configChangeAction = await createAgentAction(soClient, { + agent_id: agent.id, type: 'CONFIG_CHANGE', + data: { config } as any, created_at: new Date().toISOString(), - data: JSON.stringify({ - config, - }), sent_at: undefined, - }; + }); actions.push(configChangeAction); - // persist new action - updateData.actions = actions; } } if (localMetadata) { @@ -149,7 +145,7 @@ function isActionEvent(event: AgentEvent) { ); } -export function shouldCreateConfigAction(agent: Agent): boolean { +export function shouldCreateConfigAction(agent: Agent, actions: AgentAction[]): boolean { if (!agent.config_id) { return false; } @@ -167,7 +163,7 @@ export function shouldCreateConfigAction(agent: Agent): boolean { return false; } - const isActionAlreadyGenerated = !!agent.actions.find(action => { + const isActionAlreadyGenerated = !!actions.find(action => { if (!action.data || action.type !== 'CONFIG_CHANGE') { return false; } @@ -181,7 +177,3 @@ export function shouldCreateConfigAction(agent: Agent): boolean { return !isActionAlreadyGenerated; } - -function filterActionsForCheckin(agent: Agent): AgentAction[] { - return agent.actions.filter((a: AgentAction) => !a.sent_at); -} diff --git a/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts b/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts index 52547e9bcb0fb..a34d2e03e9b3d 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/enroll.ts @@ -35,7 +35,6 @@ export async function enroll( user_provided_metadata: JSON.stringify(metadata?.userProvided ?? {}), local_metadata: JSON.stringify(metadata?.local ?? {}), current_error_events: undefined, - actions: [], access_api_key_id: undefined, last_checkin: undefined, default_api_key: undefined, diff --git a/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts b/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts index dbe268818713d..aa88520740687 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/saved_objects.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import Boom from 'boom'; import { SavedObject } from 'src/core/server'; -import { Agent, AgentSOAttributes } from '../../types'; +import { Agent, AgentSOAttributes, AgentAction, AgentActionSOAttributes } from '../../types'; export function savedObjectToAgent(so: SavedObject): Agent { if (so.error) { @@ -24,3 +25,18 @@ export function savedObjectToAgent(so: SavedObject): Agent { status: undefined, }; } + +export function savedObjectToAgentAction(so: SavedObject): AgentAction { + if (so.error) { + if (so.error.statusCode === 404) { + throw Boom.notFound(so.error.message); + } + + throw new Error(so.error.message); + } + + return { + id: so.id, + ...so.attributes, + }; +} diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts index 929f2518ee748..e3aef6077dbc3 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.test.ts @@ -80,3 +80,103 @@ describe('getField searches recursively for nested field in fields given an arra expect(getField(searchFields, ['2', '2-2', '2-2-1'])?.name).toBe('2-2-1'); }); }); + +describe('processFields', () => { + const flattenedFields = [ + { + name: 'a.a', + type: 'text', + }, + { + name: 'a.b', + type: 'text', + }, + ]; + const expandedFields = [ + { + name: 'a', + type: 'group', + fields: [ + { + name: 'a', + type: 'text', + }, + { + name: 'b', + type: 'text', + }, + ], + }, + ]; + test('correctly expands flattened fields', () => { + expect(JSON.stringify(processFields(flattenedFields))).toEqual(JSON.stringify(expandedFields)); + }); + test('leaves expanded fields unchanged', () => { + expect(JSON.stringify(processFields(expandedFields))).toEqual(JSON.stringify(expandedFields)); + }); + + const mixedFieldsA = [ + { + name: 'a.a', + type: 'group', + fields: [ + { + name: 'a', + type: 'text', + }, + { + name: 'b', + type: 'text', + }, + ], + }, + ]; + + const mixedFieldsB = [ + { + name: 'a', + type: 'group', + fields: [ + { + name: 'a.a', + type: 'text', + }, + { + name: 'a.b', + type: 'text', + }, + ], + }, + ]; + + const mixedFieldsExpanded = [ + { + name: 'a', + type: 'group', + fields: [ + { + name: 'a', + type: 'group', + fields: [ + { + name: 'a', + type: 'text', + }, + { + name: 'b', + type: 'text', + }, + ], + }, + ], + }, + ]; + test('correctly expands a mix of expanded and flattened fields', () => { + expect(JSON.stringify(processFields(mixedFieldsA))).toEqual( + JSON.stringify(mixedFieldsExpanded) + ); + expect(JSON.stringify(processFields(mixedFieldsB))).toEqual( + JSON.stringify(mixedFieldsExpanded) + ); + }); +}); diff --git a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts index 4a1a84baf6599..810896bb50389 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/fields/field.ts @@ -52,13 +52,12 @@ export type Fields = Field[]; * expandFields takes the given fields read from yaml and expands them. * There are dotted fields in the field.yml like `foo.bar`. These should * be stored as an field within a 'group' field. - * - * Note: This function modifies the passed fields array. */ -export function expandFields(fields: Fields) { +export function expandFields(fields: Fields): Fields { + const newFields: Fields = []; + fields.forEach((field, key) => { const fieldName = field.name; - // If the field name contains a dot, it means we need to // - take the first part of the name // - create a field of type 'group' with this first part @@ -71,30 +70,29 @@ export function expandFields(fields: Fields) { const groupFieldName = nameParts[0]; // Put back together the parts again for the new field name - const restFieldName = nameParts.slice(1).join('.'); + const nestedFieldName = nameParts.slice(1).join('.'); // keep all properties of the original field, but give it the shortened name - field.name = restFieldName; + const nestedField = { ...field, name: nestedFieldName }; // create a new field of type group with the original field in the fields array const groupField: Field = { name: groupFieldName, type: 'group', - fields: [field], + fields: expandFields([nestedField]), }; - // check child fields further down the tree - if (groupField.fields) { - expandFields(groupField.fields); - } // Replace the original field in the array with the new one - fields[key] = groupField; + newFields.push(groupField); } else { // even if this field doesn't have dots to expand, its child fields further down the tree might - if (field.fields) { - expandFields(field.fields); + const newField = { ...field }; + if (newField.fields) { + newField.fields = expandFields(newField.fields); } + newFields.push(newField); } }); + return newFields; } /** * dedupFields takes the given fields and merges sibling fields with the @@ -180,8 +178,8 @@ export const getField = (fields: Fields, pathNames: string[]): Field | undefined }; export function processFields(fields: Fields): Fields { - expandFields(fields); - const dedupedFields = dedupFields(fields); + const expandedFields = expandFields(fields); + const dedupedFields = dedupFields(expandedFields); return validateFields(dedupedFields, dedupedFields); } diff --git a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts index d655b81f8cdef..e963ea138dfd5 100644 --- a/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts +++ b/x-pack/plugins/ingest_manager/server/services/epm/packages/get.ts @@ -31,17 +31,17 @@ export async function getPackages( Object.assign({}, item, { title: item.title || nameAsTitle(item.name) }) ); }); - const searchObjects = registryItems.map(({ name, version }) => ({ + // get the installed packages + const results = await savedObjectsClient.find({ type: PACKAGES_SAVED_OBJECT_TYPE, - id: `${name}-${version}`, - })); - const results = await savedObjectsClient.bulkGet(searchObjects); - const savedObjects = results.saved_objects.filter(o => !o.error); // ignore errors for now + }); + // filter out any internal packages + const savedObjectsVisible = results.saved_objects.filter(o => !o.attributes.internal); const packageList = registryItems .map(item => createInstallableFrom( item, - savedObjects.find(({ id }) => id === `${item.name}-${item.version}`) + savedObjectsVisible.find(({ attributes }) => attributes.name === item.name) ) ) .sort(sortByName); 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 3cce238f582f4..82523e37509d1 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 @@ -87,7 +87,7 @@ export async function installPackage(options: { }): Promise { const { savedObjectsClient, pkgkey, callCluster } = options; const registryPackageInfo = await Registry.fetchInfo(pkgkey); - const { name: pkgName, version: pkgVersion } = registryPackageInfo; + const { name: pkgName, version: pkgVersion, internal = false } = registryPackageInfo; const installKibanaAssetsPromise = installKibanaAssets({ savedObjectsClient, @@ -116,6 +116,7 @@ export async function installPackage(options: { pkgkey, pkgName, pkgVersion, + internal, toSave, }); return toSave; @@ -145,9 +146,10 @@ export async function saveInstallationReferences(options: { pkgkey: string; pkgName: string; pkgVersion: string; + internal: boolean; toSave: AssetReference[]; }) { - const { savedObjectsClient, pkgkey, pkgName, pkgVersion, toSave } = options; + const { savedObjectsClient, pkgkey, pkgName, pkgVersion, internal, toSave } = options; const installation = await getInstallation({ savedObjectsClient, pkgkey }); const savedRefs = installation?.installed || []; const mergeRefsReducer = (current: AssetReference[], pending: AssetReference) => { @@ -159,7 +161,7 @@ export async function saveInstallationReferences(options: { const toInstall = toSave.reduce(mergeRefsReducer, savedRefs); await savedObjectsClient.create( PACKAGES_SAVED_OBJECT_TYPE, - { installed: toInstall, name: pkgName, version: pkgVersion }, + { installed: toInstall, name: pkgName, version: pkgVersion, internal }, { id: pkgkey, overwrite: true } ); diff --git a/x-pack/plugins/ingest_manager/server/types/index.tsx b/x-pack/plugins/ingest_manager/server/types/index.tsx index 59c7f152e5cbc..1cd5622c0c7b0 100644 --- a/x-pack/plugins/ingest_manager/server/types/index.tsx +++ b/x-pack/plugins/ingest_manager/server/types/index.tsx @@ -14,6 +14,7 @@ export { AgentEvent, AgentEventSOAttributes, AgentAction, + AgentActionSOAttributes, Datasource, NewDatasource, FullAgentConfigDatasource, 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 f70b3cf0ed092..f18846348432b 100644 --- a/x-pack/plugins/ingest_manager/server/types/models/agent.ts +++ b/x-pack/plugins/ingest_manager/server/types/models/agent.ts @@ -60,6 +60,6 @@ export const NewAgentActionSchema = schema.object({ schema.literal('RESUME'), schema.literal('PAUSE'), ]), - data: schema.maybe(schema.string()), + data: schema.maybe(schema.any()), sent_at: schema.maybe(schema.string()), }); diff --git a/x-pack/plugins/licensing/README.md b/x-pack/plugins/licensing/README.md index 7a155dfd405c5..a058d42fe908b 100644 --- a/x-pack/plugins/licensing/README.md +++ b/x-pack/plugins/licensing/README.md @@ -59,7 +59,7 @@ chrome.navLinks.update('myPlugin', { "requiredPlugins": ["licensing"], // my_plugin/server/plugin.ts -import { LicensingPluginSetup, LICENSE_CHECK_STATE } from '../licensing/server' +import { LicensingPluginSetup } from '../licensing/server' interface SetupDeps { licensing: LicensingPluginSetup; @@ -69,7 +69,7 @@ class MyPlugin { setup(core: CoreSetup, deps: SetupDeps) { deps.licensing.license$.subscribe(license => { const { state, message } = license.check('myPlugin', 'gold') - const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + const hasRequiredLicense = state === 'valid'; if (hasRequiredLicense && license.getFeature('name').isAvailable) { // enable some server side logic } else { @@ -81,12 +81,12 @@ class MyPlugin { } // my_plugin/public/plugin.ts -import { LicensingPluginSetup, LICENSE_CHECK_STATE } from '../licensing/public' +import { LicensingPluginSetup } from '../licensing/public' class MyPlugin { setup(core: CoreSetup, deps: SetupDeps) { deps.licensing.license$.subscribe(license => { const { state, message } = license.check('myPlugin', 'gold') - const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + const hasRequiredLicense = state === 'valid'; const showLinks = hasRequiredLicense && license.getFeature('name').isAvailable; chrome.navLinks.update('myPlugin', { diff --git a/x-pack/plugins/licensing/common/license.test.ts b/x-pack/plugins/licensing/common/license.test.ts index 622572a6a95ba..648b0a2180928 100644 --- a/x-pack/plugins/licensing/common/license.test.ts +++ b/x-pack/plugins/licensing/common/license.test.ts @@ -5,7 +5,6 @@ */ import { License } from './license'; -import { LICENSE_CHECK_STATE } from './types'; import { licenseMock } from './licensing.mock'; describe('License', () => { @@ -86,21 +85,21 @@ describe('License', () => { describe('check', () => { it('provides availability status', () => { - expect(basicLicense.check('ccr', 'gold').state).toBe(LICENSE_CHECK_STATE.Invalid); + expect(basicLicense.check('ccr', 'gold').state).toBe('invalid'); - expect(goldLicense.check('ccr', 'gold').state).toBe(LICENSE_CHECK_STATE.Valid); - expect(goldLicense.check('ccr', 'basic').state).toBe(LICENSE_CHECK_STATE.Valid); + expect(goldLicense.check('ccr', 'gold').state).toBe('valid'); + expect(goldLicense.check('ccr', 'basic').state).toBe('valid'); - expect(basicExpiredLicense.check('ccr', 'gold').state).toBe(LICENSE_CHECK_STATE.Expired); + expect(basicExpiredLicense.check('ccr', 'gold').state).toBe('expired'); - expect(errorLicense.check('ccr', 'basic').state).toBe(LICENSE_CHECK_STATE.Unavailable); - expect(errorLicense.check('ccr', 'gold').state).toBe(LICENSE_CHECK_STATE.Unavailable); + expect(errorLicense.check('ccr', 'basic').state).toBe('unavailable'); + expect(errorLicense.check('ccr', 'gold').state).toBe('unavailable'); - expect(unavailableLicense.check('ccr', 'basic').state).toBe(LICENSE_CHECK_STATE.Unavailable); - expect(unavailableLicense.check('ccr', 'gold').state).toBe(LICENSE_CHECK_STATE.Unavailable); + expect(unavailableLicense.check('ccr', 'basic').state).toBe('unavailable'); + expect(unavailableLicense.check('ccr', 'gold').state).toBe('unavailable'); - expect(enterpriseLicense.check('ccr', 'gold').state).toBe(LICENSE_CHECK_STATE.Valid); - expect(enterpriseLicense.check('ccr', 'enterprise').state).toBe(LICENSE_CHECK_STATE.Valid); + expect(enterpriseLicense.check('ccr', 'gold').state).toBe('valid'); + expect(enterpriseLicense.check('ccr', 'enterprise').state).toBe('valid'); }); it('throws in case of unknown license type', () => { diff --git a/x-pack/plugins/licensing/common/license.ts b/x-pack/plugins/licensing/common/license.ts index 41f3c73db9c06..bb3480a482f0f 100644 --- a/x-pack/plugins/licensing/common/license.ts +++ b/x-pack/plugins/licensing/common/license.ts @@ -9,7 +9,7 @@ import { LicenseType, ILicense, LicenseStatus, - LICENSE_CHECK_STATE, + LicenseCheck, LICENSE_TYPE, PublicLicenseJSON, PublicLicense, @@ -98,10 +98,10 @@ export class License implements ILicense { return LICENSE_TYPE[minimumLicenseRequired] <= LICENSE_TYPE[type]; } - check(pluginName: string, minimumLicenseRequired: LicenseType) { + check(pluginName: string, minimumLicenseRequired: LicenseType): LicenseCheck { if (!this.isAvailable) { return { - state: LICENSE_CHECK_STATE.Unavailable, + state: 'unavailable', message: i18n.translate('xpack.licensing.check.errorUnavailableMessage', { defaultMessage: 'You cannot use {pluginName} because license information is not available at this time.', @@ -112,7 +112,7 @@ export class License implements ILicense { if (!this.isActive) { return { - state: LICENSE_CHECK_STATE.Expired, + state: 'expired', message: i18n.translate('xpack.licensing.check.errorExpiredMessage', { defaultMessage: 'You cannot use {pluginName} because your {licenseType} license has expired.', @@ -123,7 +123,7 @@ export class License implements ILicense { if (!this.hasAtLeast(minimumLicenseRequired)) { return { - state: LICENSE_CHECK_STATE.Invalid, + state: 'invalid', message: i18n.translate('xpack.licensing.check.errorUnsupportedMessage', { defaultMessage: 'Your {licenseType} license does not support {pluginName}. Please upgrade your license.', @@ -132,7 +132,7 @@ export class License implements ILicense { }; } - return { state: LICENSE_CHECK_STATE.Valid }; + return { state: 'valid' }; } getFeature(name: string) { diff --git a/x-pack/plugins/licensing/common/licensing.mock.ts b/x-pack/plugins/licensing/common/licensing.mock.ts index bbe63d5c0d70a..bf8b85e3e981b 100644 --- a/x-pack/plugins/licensing/common/licensing.mock.ts +++ b/x-pack/plugins/licensing/common/licensing.mock.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { ILicense, PublicLicense, PublicFeatures, LICENSE_CHECK_STATE } from './types'; +import { ILicense, PublicLicense, PublicFeatures } from './types'; import { License } from './license'; function createLicense({ @@ -51,7 +51,7 @@ const createLicenseMock = () => { check: jest.fn(), hasAtLeast: jest.fn(), }; - mock.check.mockReturnValue({ state: LICENSE_CHECK_STATE.Valid }); + mock.check.mockReturnValue({ state: 'valid' }); mock.hasAtLeast.mockReturnValue(true); return mock; }; diff --git a/x-pack/plugins/licensing/common/types.ts b/x-pack/plugins/licensing/common/types.ts index 78c31963da9b1..f589ba7e9e44f 100644 --- a/x-pack/plugins/licensing/common/types.ts +++ b/x-pack/plugins/licensing/common/types.ts @@ -4,12 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export enum LICENSE_CHECK_STATE { - Unavailable = 'UNAVAILABLE', - Invalid = 'INVALID', - Expired = 'EXPIRED', - Valid = 'VALID', -} +export type LicenseCheckState = 'unavailable' | 'invalid' | 'valid' | 'expired'; export enum LICENSE_TYPE { basic = 10, @@ -90,7 +85,7 @@ export interface LicenseCheck { /** * The state of checking the results of a license type meeting the license minimum. */ - state: LICENSE_CHECK_STATE; + state: LicenseCheckState; /** * A message containing the reason for a license type not being valid. */ diff --git a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts index ca0e474491780..ceba2fe56db12 100644 --- a/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts +++ b/x-pack/plugins/maps/common/descriptor_types/data_request_descriptor_types.d.ts @@ -5,6 +5,7 @@ */ /* eslint-disable @typescript-eslint/consistent-type-definitions */ +import { RENDER_AS, SORT_ORDER, SCALING_TYPES } from '../constants'; import { MapExtent, MapQuery } from './map_descriptor'; // Global map state passed to every layer. @@ -18,12 +19,26 @@ export type MapFilters = { zoom: number; }; +type ESSearchSourceSyncMeta = { + sortField: string; + sortOrder: SORT_ORDER; + scalingType: SCALING_TYPES; + topHitsSplitField: string; + topHitsSize: number; +}; + +type ESGeoGridSourceSyncMeta = { + requestType: RENDER_AS; +}; + +export type VectorSourceSyncMeta = ESSearchSourceSyncMeta | ESGeoGridSourceSyncMeta; + export type VectorSourceRequestMeta = MapFilters & { applyGlobalQuery: boolean; fieldNames: string[]; geogridPrecision: number; sourceQuery: MapQuery; - sourceMeta: unknown; + sourceMeta: VectorSourceSyncMeta; }; export type VectorStyleRequestMeta = MapFilters & { diff --git a/x-pack/plugins/maps/public/actions/map_actions.d.ts b/x-pack/plugins/maps/public/actions/map_actions.d.ts new file mode 100644 index 0000000000000..debead3ad5c45 --- /dev/null +++ b/x-pack/plugins/maps/public/actions/map_actions.d.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. + */ +/* eslint-disable @typescript-eslint/consistent-type-definitions */ + +import { Filter, Query, TimeRange } from 'src/plugins/data/public'; +import { AnyAction } from 'redux'; +import { LAYER_TYPE } from '../../common/constants'; +import { + DataMeta, + MapFilters, + MapCenterAndZoom, + MapRefreshConfig, +} from '../../common/descriptor_types'; + +export type SyncContext = { + startLoading(dataId: string, requestToken: symbol, meta: DataMeta): void; + stopLoading(dataId: string, requestToken: symbol, data: unknown, meta: DataMeta): void; + onLoadError(dataId: string, requestToken: symbol, errorMessage: string): void; + updateSourceData(newData: unknown): void; + isRequestStillActive(dataId: string, requestToken: symbol): boolean; + registerCancelCallback(requestToken: symbol, callback: () => void): void; + dataFilters: MapFilters; +}; + +export function updateSourceProp( + layerId: string, + propName: string, + value: unknown, + newLayerType?: LAYER_TYPE +): void; + +export function setGotoWithCenter(config: MapCenterAndZoom): AnyAction; + +export function replaceLayerList(layerList: unknown[]): AnyAction; + +export type QueryGroup = { + filters: Filter[]; + query?: Query; + timeFilters?: TimeRange; + refresh?: boolean; +}; + +export function setQuery(query: QueryGroup): AnyAction; + +export function setRefreshConfig(config: MapRefreshConfig): AnyAction; + +export function disableScrollZoom(): AnyAction; + +export function disableInteractive(): AnyAction; + +export function disableTooltipControl(): AnyAction; + +export function hideToolbarOverlay(): AnyAction; + +export function hideLayerControl(): AnyAction; + +export function hideViewControl(): AnyAction; + +export function setHiddenLayers(hiddenLayerIds: string[]): AnyAction; + +export function addLayerWithoutDataSync(layerDescriptor: unknown): AnyAction; diff --git a/x-pack/legacy/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap b/x-pack/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap rename to x-pack/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap b/x-pack/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap rename to x-pack/plugins/maps/public/components/__snapshots__/tooltip_selector.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/components/__snapshots__/validated_range.test.js.snap b/x-pack/plugins/maps/public/components/__snapshots__/validated_range.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/__snapshots__/validated_range.test.js.snap rename to x-pack/plugins/maps/public/components/__snapshots__/validated_range.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.js b/x-pack/plugins/maps/public/components/add_tooltip_field_popover.js similarity index 98% rename from x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.js rename to x-pack/plugins/maps/public/components/add_tooltip_field_popover.js index 07bc54663c1d8..984ace4fd8708 100644 --- a/x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.js +++ b/x-pack/plugins/maps/public/components/add_tooltip_field_popover.js @@ -17,7 +17,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { FieldIcon } from '../../../../../../src/plugins/kibana_react/public'; +import { FieldIcon } from '../../../../../src/plugins/kibana_react/public'; const sortByLabel = (a, b) => { return a.label.localeCompare(b.label); diff --git a/x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.test.js b/x-pack/plugins/maps/public/components/add_tooltip_field_popover.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/add_tooltip_field_popover.test.js rename to x-pack/plugins/maps/public/components/add_tooltip_field_popover.test.js diff --git a/x-pack/legacy/plugins/maps/public/components/metric_editor.js b/x-pack/plugins/maps/public/components/metric_editor.js similarity index 94% rename from x-pack/legacy/plugins/maps/public/components/metric_editor.js rename to x-pack/plugins/maps/public/components/metric_editor.js index 530f402592b2b..d1affe2f42190 100644 --- a/x-pack/legacy/plugins/maps/public/components/metric_editor.js +++ b/x-pack/plugins/maps/public/components/metric_editor.js @@ -24,8 +24,13 @@ function filterFieldsForAgg(fields, aggType) { return getTermsFields(fields); } + const metricAggFieldTypes = ['number']; + if (aggType !== AGG_TYPE.SUM) { + metricAggFieldTypes.push('date'); + } + return fields.filter(field => { - return field.aggregatable && field.type === 'number'; + return field.aggregatable && metricAggFieldTypes.includes(field.type); }); } diff --git a/x-pack/legacy/plugins/maps/public/components/metric_select.js b/x-pack/plugins/maps/public/components/metric_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/metric_select.js rename to x-pack/plugins/maps/public/components/metric_select.js diff --git a/x-pack/legacy/plugins/maps/public/components/metrics_editor.js b/x-pack/plugins/maps/public/components/metrics_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/components/metrics_editor.js rename to x-pack/plugins/maps/public/components/metrics_editor.js diff --git a/x-pack/legacy/plugins/maps/public/components/no_index_pattern_callout.js b/x-pack/plugins/maps/public/components/no_index_pattern_callout.js similarity index 85% rename from x-pack/legacy/plugins/maps/public/components/no_index_pattern_callout.js rename to x-pack/plugins/maps/public/components/no_index_pattern_callout.js index 3266f13155ca7..1319607546808 100644 --- a/x-pack/legacy/plugins/maps/public/components/no_index_pattern_callout.js +++ b/x-pack/plugins/maps/public/components/no_index_pattern_callout.js @@ -4,14 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; - +import { getHttp } from '../kibana_services'; import React from 'react'; import { EuiCallOut, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; export function NoIndexPatternCallout() { + const http = getHttp(); return ( - + - + { + const image = new Image(); + if (isCrossOriginUrl(imgUrl)) { + image.crossOrigin = 'Anonymous'; + } + image.onload = el => { + const imgData = getImageData(el.currentTarget); + resolve(imgData); + }; + image.onerror = e => { + reject(e); + }; + image.src = imgUrl; + }); +} + +export function addSpriteSheetToMapFromImageData(json, imgData, mbMap) { + for (const imageId in json) { + if (!(json.hasOwnProperty(imageId) && !mbMap.hasImage(imageId))) { + continue; + } + const { width, height, x, y, sdf, pixelRatio } = json[imageId]; + if (typeof width !== 'number' || typeof height !== 'number') { + continue; + } + + const data = new RGBAImage({ width, height }); + RGBAImage.copy(imgData, data, { x, y }, { x: 0, y: 0 }, { width, height }); + mbMap.addImage(imageId, data, { pixelRatio, sdf }); + } +} diff --git a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js b/x-pack/plugins/maps/public/elasticsearch_geo_utils.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.js rename to x-pack/plugins/maps/public/elasticsearch_geo_utils.js diff --git a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js b/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js similarity index 99% rename from x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js rename to x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js index fb4b0a6e29e6c..5db7556be4639 100644 --- a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js +++ b/x-pack/plugins/maps/public/elasticsearch_geo_utils.test.js @@ -20,7 +20,7 @@ import { convertMapExtentToPolygon, roundCoordinates, } from './elasticsearch_geo_utils'; -import { indexPatterns } from '../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../src/plugins/data/public'; const geoFieldName = 'location'; const mapExtent = { diff --git a/x-pack/legacy/plugins/maps/public/index_pattern_util.js b/x-pack/plugins/maps/public/index_pattern_util.js similarity index 95% rename from x-pack/legacy/plugins/maps/public/index_pattern_util.js rename to x-pack/plugins/maps/public/index_pattern_util.js index 30a0a6826db83..6cb02c7605e28 100644 --- a/x-pack/legacy/plugins/maps/public/index_pattern_util.js +++ b/x-pack/plugins/maps/public/index_pattern_util.js @@ -5,7 +5,7 @@ */ import { getIndexPatternService } from './kibana_services'; -import { indexPatterns } from '../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../src/plugins/data/public'; import { ES_GEO_FIELD_TYPE } from '../common/constants'; export async function getIndexPatternsFromIds(indexPatternIds = []) { diff --git a/x-pack/legacy/plugins/maps/public/index_pattern_util.test.js b/x-pack/plugins/maps/public/index_pattern_util.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/index_pattern_util.test.js rename to x-pack/plugins/maps/public/index_pattern_util.test.js diff --git a/x-pack/plugins/maps/public/kibana_services.js b/x-pack/plugins/maps/public/kibana_services.js index 1073e44fa711e..d2ddecfdf915b 100644 --- a/x-pack/plugins/maps/public/kibana_services.js +++ b/x-pack/plugins/maps/public/kibana_services.js @@ -3,7 +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. */ +import { esFilters, search } from '../../../../src/plugins/data/public'; + +export { SearchSource } from '../../../../src/plugins/data/public'; + +export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER; +const { getRequestInspectorStats, getResponseInspectorStats } = search; + +let indexPatternService; +export const setIndexPatternService = dataIndexPatterns => + (indexPatternService = dataIndexPatterns); +export const getIndexPatternService = () => indexPatternService; + +let autocompleteService; +export const setAutocompleteService = dataAutoComplete => (autocompleteService = dataAutoComplete); +export const getAutocompleteService = () => autocompleteService; + +let licenseId; +export const setLicenseId = latestLicenseId => (licenseId = latestLicenseId); +export const getLicenseId = () => { + return licenseId; +}; + +let inspector; +export const setInspector = newInspector => (inspector = newInspector); +export const getInspector = () => { + return inspector; +}; + +let fileUploadPlugin; +export const setFileUpload = fileUpload => (fileUploadPlugin = fileUpload); +export const getFileUploadComponent = () => { + return fileUploadPlugin.JsonUploadAndParse; +}; let getInjectedVar; export const setInjectedVarFunc = getInjectedVarFunc => (getInjectedVar = getInjectedVarFunc); export const getInjectedVarFunc = () => getInjectedVar; + +let uiSettings; +export const setUiSettings = coreUiSettings => (uiSettings = coreUiSettings); +export const getUiSettings = () => uiSettings; + +let indexPatternSelectComponent; +export const setIndexPatternSelect = indexPatternSelect => + (indexPatternSelectComponent = indexPatternSelect); +export const getIndexPatternSelectComponent = () => indexPatternSelectComponent; + +let coreHttp; +export const setHttp = http => (coreHttp = http); +export const getHttp = () => coreHttp; + +let dataTimeFilter; +export const setTimeFilter = timeFilter => (dataTimeFilter = timeFilter); +export const getTimeFilter = () => dataTimeFilter; + +let toast; +export const setToasts = notificationToast => (toast = notificationToast); +export const getToasts = () => toast; + +export async function fetchSearchSourceAndRecordWithInspector({ + searchSource, + requestId, + requestName, + requestDesc, + inspectorAdapters, + abortSignal, +}) { + const inspectorRequest = inspectorAdapters.requests.start(requestName, { + id: requestId, + description: requestDesc, + }); + let resp; + try { + inspectorRequest.stats(getRequestInspectorStats(searchSource)); + searchSource.getSearchRequestBody().then(body => { + inspectorRequest.json(body); + }); + resp = await searchSource.fetch({ abortSignal }); + inspectorRequest.stats(getResponseInspectorStats(searchSource, resp)).ok({ json: resp }); + } catch (error) { + inspectorRequest.error({ error }); + throw error; + } + + return resp; +} diff --git a/x-pack/plugins/maps/public/layers/_index.scss b/x-pack/plugins/maps/public/layers/_index.scss new file mode 100644 index 0000000000000..29a5761255278 --- /dev/null +++ b/x-pack/plugins/maps/public/layers/_index.scss @@ -0,0 +1 @@ +@import 'styles/index'; diff --git a/x-pack/legacy/plugins/maps/public/layers/blended_vector_layer.ts b/x-pack/plugins/maps/public/layers/blended_vector_layer.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/blended_vector_layer.ts rename to x-pack/plugins/maps/public/layers/blended_vector_layer.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/ems_file_field.ts b/x-pack/plugins/maps/public/layers/fields/ems_file_field.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/ems_file_field.ts rename to x-pack/plugins/maps/public/layers/fields/ems_file_field.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.test.ts b/x-pack/plugins/maps/public/layers/fields/es_agg_field.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.test.ts rename to x-pack/plugins/maps/public/layers/fields/es_agg_field.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.ts b/x-pack/plugins/maps/public/layers/fields/es_agg_field.ts similarity index 99% rename from x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.ts rename to x-pack/plugins/maps/public/layers/fields/es_agg_field.ts index 65f952ca01038..34f7dd4b9578f 100644 --- a/x-pack/legacy/plugins/maps/public/layers/fields/es_agg_field.ts +++ b/x-pack/plugins/maps/public/layers/fields/es_agg_field.ts @@ -90,7 +90,7 @@ export class ESAggField implements IESAggField { async createTooltipProperty(value: string | undefined): Promise { const indexPattern = await this._source.getIndexPattern(); const tooltipProperty = new TooltipProperty(this.getName(), await this.getLabel(), value); - return new ESAggTooltipProperty(tooltipProperty, indexPattern, this); + return new ESAggTooltipProperty(tooltipProperty, indexPattern, this, this.getAggType()); } getValueAggDsl(indexPattern: IndexPattern): unknown | null { diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.ts b/x-pack/plugins/maps/public/layers/fields/es_doc_field.ts similarity index 96% rename from x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.ts rename to x-pack/plugins/maps/public/layers/fields/es_doc_field.ts index 4401452841a46..b7647d881fcf6 100644 --- a/x-pack/legacy/plugins/maps/public/layers/fields/es_doc_field.ts +++ b/x-pack/plugins/maps/public/layers/fields/es_doc_field.ts @@ -8,8 +8,8 @@ import { FIELD_ORIGIN } from '../../../common/constants'; import { ESTooltipProperty } from '../tooltips/es_tooltip_property'; import { ITooltipProperty, TooltipProperty } from '../tooltips/tooltip_property'; import { COLOR_PALETTE_MAX_SIZE } from '../../../common/constants'; -import { indexPatterns } from '../../../../../../../src/plugins/data/public'; -import { IFieldType } from '../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../src/plugins/data/public'; +import { IFieldType } from '../../../../../../src/plugins/data/public'; import { IField, AbstractField } from './field'; import { IESSource } from '../sources/es_source'; import { IVectorSource } from '../sources/vector_source'; diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/field.ts b/x-pack/plugins/maps/public/layers/fields/field.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/field.ts rename to x-pack/plugins/maps/public/layers/fields/field.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/kibana_region_field.ts b/x-pack/plugins/maps/public/layers/fields/kibana_region_field.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/kibana_region_field.ts rename to x-pack/plugins/maps/public/layers/fields/kibana_region_field.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/fields/top_term_percentage_field.ts b/x-pack/plugins/maps/public/layers/fields/top_term_percentage_field.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/fields/top_term_percentage_field.ts rename to x-pack/plugins/maps/public/layers/fields/top_term_percentage_field.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js b/x-pack/plugins/maps/public/layers/heatmap_layer.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/heatmap_layer.js rename to x-pack/plugins/maps/public/layers/heatmap_layer.js diff --git a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.js b/x-pack/plugins/maps/public/layers/joins/inner_join.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/joins/inner_join.js rename to x-pack/plugins/maps/public/layers/joins/inner_join.js diff --git a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js b/x-pack/plugins/maps/public/layers/joins/inner_join.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js rename to x-pack/plugins/maps/public/layers/joins/inner_join.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/joins/join.ts b/x-pack/plugins/maps/public/layers/joins/join.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/joins/join.ts rename to x-pack/plugins/maps/public/layers/joins/join.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.d.ts b/x-pack/plugins/maps/public/layers/layer.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/layer.d.ts rename to x-pack/plugins/maps/public/layers/layer.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.js b/x-pack/plugins/maps/public/layers/layer.js similarity index 99% rename from x-pack/legacy/plugins/maps/public/layers/layer.js rename to x-pack/plugins/maps/public/layers/layer.js index e9616be89b601..26bce872b3c2c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/layer.js +++ b/x-pack/plugins/maps/public/layers/layer.js @@ -15,7 +15,7 @@ import { } from '../../common/constants'; import uuid from 'uuid/v4'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { copyPersistentState } from '../../../../../plugins/maps/public/reducers/util.js'; +import { copyPersistentState } from '../reducers/util.js'; import { i18n } from '@kbn/i18n'; export class AbstractLayer { diff --git a/x-pack/legacy/plugins/maps/public/layers/layer_wizard_registry.ts b/x-pack/plugins/maps/public/layers/layer_wizard_registry.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/layer_wizard_registry.ts rename to x-pack/plugins/maps/public/layers/layer_wizard_registry.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/load_layer_wizards.js b/x-pack/plugins/maps/public/layers/load_layer_wizards.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/load_layer_wizards.js rename to x-pack/plugins/maps/public/layers/load_layer_wizards.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/create_client_file_source_editor.js b/x-pack/plugins/maps/public/layers/sources/client_file_source/create_client_file_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/create_client_file_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/client_file_source/create_client_file_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js b/x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js rename to x-pack/plugins/maps/public/layers/sources/client_file_source/geojson_file_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/index.js b/x-pack/plugins/maps/public/layers/sources/client_file_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/client_file_source/index.js rename to x-pack/plugins/maps/public/layers/sources/client_file_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.d.ts b/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.test.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.test.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/index.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/index.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/update_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js rename to x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.test.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.test.js rename to x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/index.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/index.js rename to x-pack/plugins/maps/public/layers/sources/ems_tms_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js rename to x-pack/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_unavailable_message.js b/x-pack/plugins/maps/public/layers/sources/ems_unavailable_message.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/ems_unavailable_message.js rename to x-pack/plugins/maps/public/layers/sources/ems_unavailable_message.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_agg_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/es_agg_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js b/x-pack/plugins/maps/public/layers/sources/es_agg_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.js rename to x-pack/plugins/maps/public/layers/sources/es_agg_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.test.ts b/x-pack/plugins/maps/public/layers/sources/es_agg_source.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_agg_source.test.ts rename to x-pack/plugins/maps/public/layers/sources/es_agg_source.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/convert_to_geojson.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js similarity index 97% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js index 4aec390bec745..265606dc87e0f 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js @@ -9,7 +9,6 @@ import React, { Fragment, Component } from 'react'; import PropTypes from 'prop-types'; import { SingleFieldSelect } from '../../../components/single_field_select'; -import { RENDER_AS } from '../../../../common/constants'; import { getIndexPatternService, getIndexPatternSelectComponent } from '../../../kibana_services'; import { NoIndexPatternCallout } from '../../../components/no_index_pattern_callout'; import { i18n } from '@kbn/i18n'; @@ -155,7 +154,7 @@ export class CreateSourceEditor extends Component { } _renderRenderAsSelect() { - if (this.state.requestType === RENDER_AS.HEATMAP || !this.state.indexPattern) { + if (!this.state.indexPattern) { return null; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js similarity index 99% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js index dec802ac3cf1a..04f944396ab35 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js @@ -70,6 +70,12 @@ export class ESGeoGridSource extends AbstractESAggSource { ); } + getSyncMeta() { + return { + requestType: this._descriptor.requestType, + }; + } + async getImmutableProperties() { let indexPatternTitle = this.getIndexPatternId(); try { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/geo_tile_utils.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/index.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/index.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx similarity index 89% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx index c82781ede186f..899f4a797ea75 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/render_as_select.tsx @@ -27,7 +27,12 @@ const options = [ export function RenderAsSelect(props: { renderAs: RENDER_AS; onChange: (newValue: RENDER_AS) => void; + isColumnCompressed?: boolean; }) { + if (props.renderAs === RENDER_AS.HEATMAP) { + return null; + } + function onChange(selectedOptions: Array>) { if (!selectedOptions || !selectedOptions.length) { return; @@ -46,6 +51,7 @@ export function RenderAsSelect(props: { label={i18n.translate('xpack.maps.source.esGeoGrid.showAsLabel', { defaultMessage: 'Show as', })} + display={props.isColumnCompressed ? 'columnCompressed' : 'row'} > ); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/resolution_editor.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/resolution_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/resolution_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/resolution_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js similarity index 89% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js index 269c2a8b8633a..c0d6cba3a024a 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_geo_grid_source/update_source_editor.js @@ -14,7 +14,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui'; import { isMetricCountable } from '../../util/is_metric_countable'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; +import { RenderAsSelect } from './render_as_select'; export class UpdateSourceEditor extends Component { state = { @@ -65,6 +66,10 @@ export class UpdateSourceEditor extends Component { this.props.onChange({ propName: 'resolution', value: e }); }; + _onRequestTypeSelect = requestType => { + this.props.onChange({ propName: 'requestType', value: requestType }); + }; + _renderMetricsPanel() { const metricsFilter = this.props.renderAs === RENDER_AS.HEATMAP @@ -113,6 +118,11 @@ export class UpdateSourceEditor extends Component { resolution={this.props.resolution} onChange={this._onResolutionChange} /> + diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js rename to x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts rename to x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/convert_to_lines.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js similarity index 98% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js rename to x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js index da2b663746b9d..ea3a2d2fe634d 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js @@ -25,7 +25,7 @@ import { convertToLines } from './convert_to_lines'; import { AbstractESAggSource } from '../es_agg_source'; import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; import { COLOR_GRADIENTS } from '../../styles/color_utils'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; import { registerSource } from '../source_registry'; const MAX_GEOTILE_LEVEL = 29; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js similarity index 96% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js index ce1f53c33ba53..dea59a1c82f8a 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_pew_pew_source/update_source_editor.js @@ -11,7 +11,7 @@ import { getIndexPatternService } from '../../../kibana_services'; import { i18n } from '@kbn/i18n'; import { EuiPanel, EuiTitle, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; export class UpdateSourceEditor extends Component { state = { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap b/x-pack/plugins/maps/public/layers/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap rename to x-pack/plugins/maps/public/layers/sources/es_search_source/__snapshots__/scaling_form.test.tsx.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap b/x-pack/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap rename to x-pack/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/constants.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/constants.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/constants.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/constants.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js similarity index 98% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js index 73bea574ace28..aeb3835354f07 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js @@ -15,7 +15,7 @@ import { NoIndexPatternCallout } from '../../../components/no_index_pattern_call import { i18n } from '@kbn/i18n'; import { ES_GEO_FIELD_TYPE, SCALING_TYPES } from '../../../../common/constants'; import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; import { ScalingForm } from './scaling_form'; import { getTermsFields } from '../../../index_pattern_util'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts rename to x-pack/plugins/maps/public/layers/sources/es_search_source/es_search_source.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/index.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/index.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/load_index_settings.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/load_index_settings.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/load_index_settings.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/load_index_settings.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/scaling_form.test.tsx b/x-pack/plugins/maps/public/layers/sources/es_search_source/scaling_form.test.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/scaling_form.test.tsx rename to x-pack/plugins/maps/public/layers/sources/es_search_source/scaling_form.test.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx b/x-pack/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx similarity index 97% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx rename to x-pack/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx index c5950f1132974..d86fc6d4026e6 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/scaling_form.tsx @@ -22,8 +22,6 @@ import { SingleFieldSelect } from '../../../components/single_field_select'; // @ts-ignore import { indexPatternService } from '../../../kibana_services'; // @ts-ignore -import { getTermsFields, getSourceFields } from '../../../index_pattern_util'; -// @ts-ignore import { ValidatedRange } from '../../../components/validated_range'; import { DEFAULT_MAX_INNER_RESULT_WINDOW, @@ -33,7 +31,7 @@ import { } from '../../../../common/constants'; // @ts-ignore import { loadIndexSettings } from './load_index_settings'; -import { IFieldType } from '../../../../../../../../src/plugins/data/public'; +import { IFieldType } from '../../../../../../../src/plugins/data/public'; import { OnSourceChangeArgs } from '../../../connected_components/layer_panel/view'; interface Props { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js similarity index 98% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js index 9c92ec5801e49..cb6255afd0a42 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js +++ b/x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.js @@ -16,7 +16,7 @@ import { getTermsFields, getSourceFields } from '../../../index_pattern_util'; import { SORT_ORDER } from '../../../../common/constants'; import { ESDocField } from '../../fields/es_doc_field'; import { FormattedMessage } from '@kbn/i18n/react'; -import { indexPatterns } from '../../../../../../../../src/plugins/data/public'; +import { indexPatterns } from '../../../../../../../src/plugins/data/public'; import { ScalingForm } from './scaling_form'; export class UpdateSourceEditor extends Component { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.test.js b/x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/update_source_editor.test.js rename to x-pack/plugins/maps/public/layers/sources/es_search_source/update_source_editor.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_source.d.ts similarity index 92% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/es_source.d.ts index ffd1d343b59e0..65851d0e7bd38 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.d.ts +++ b/x-pack/plugins/maps/public/layers/sources/es_source.d.ts @@ -6,7 +6,7 @@ import { AbstractVectorSource } from './vector_source'; import { IVectorSource } from './vector_source'; -import { IndexPattern, SearchSource } from '../../../../../../../src/plugins/data/public'; +import { IndexPattern, SearchSource } from '../../../../../../src/plugins/data/public'; import { VectorSourceRequestMeta } from '../../../common/descriptor_types'; export interface IESSource extends IVectorSource { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js b/x-pack/plugins/maps/public/layers/sources/es_source.js similarity index 99% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_source.js rename to x-pack/plugins/maps/public/layers/sources/es_source.js index 441d52d23398a..d90a802a38344 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_source.js +++ b/x-pack/plugins/maps/public/layers/sources/es_source.js @@ -17,7 +17,7 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import uuid from 'uuid/v4'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { copyPersistentState } from '../../../../../../plugins/maps/public/reducers/util'; +import { copyPersistentState } from '../../reducers/util'; import { ES_GEO_FIELD_TYPE } from '../../../common/constants'; import { DataRequestAbortError } from '../util/data_request'; import { expandToTileBoundaries } from './es_geo_grid_source/geo_tile_utils'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.d.ts b/x-pack/plugins/maps/public/layers/sources/es_term_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/es_term_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js b/x-pack/plugins/maps/public/layers/sources/es_term_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js rename to x-pack/plugins/maps/public/layers/sources/es_term_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js b/x-pack/plugins/maps/public/layers/sources/es_term_source.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js rename to x-pack/plugins/maps/public/layers/sources/es_term_source.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js rename to x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js b/x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js rename to x-pack/plugins/maps/public/layers/sources/kibana_regionmap_source/kibana_regionmap_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js b/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js rename to x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js b/x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js rename to x-pack/plugins/maps/public/layers/sources/kibana_tilemap_source/kibana_tilemap_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source.d.ts b/x-pack/plugins/maps/public/layers/sources/source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/source.d.ts rename to x-pack/plugins/maps/public/layers/sources/source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source.js b/x-pack/plugins/maps/public/layers/sources/source.js similarity index 95% rename from x-pack/legacy/plugins/maps/public/layers/sources/source.js rename to x-pack/plugins/maps/public/layers/sources/source.js index b6b6c10831bb5..3029a5c091202 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/source.js +++ b/x-pack/plugins/maps/public/layers/sources/source.js @@ -5,7 +5,7 @@ */ // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { copyPersistentState } from '../../../../../../plugins/maps/public/reducers/util'; +import { copyPersistentState } from '../../reducers/util'; export class AbstractSource { static isIndexingSource = false; @@ -111,10 +111,6 @@ export class AbstractSource { return 0; } - getSyncMeta() { - return {}; - } - isJoinable() { return false; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/source_registry.ts b/x-pack/plugins/maps/public/layers/sources/source_registry.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/source_registry.ts rename to x-pack/plugins/maps/public/layers/sources/source_registry.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/tms_source.d.ts b/x-pack/plugins/maps/public/layers/sources/tms_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/tms_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/tms_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/tms_source.js b/x-pack/plugins/maps/public/layers/sources/tms_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/tms_source.js rename to x-pack/plugins/maps/public/layers/sources/tms_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/vector_feature_types.js b/x-pack/plugins/maps/public/layers/sources/vector_feature_types.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/vector_feature_types.js rename to x-pack/plugins/maps/public/layers/sources/vector_feature_types.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.d.ts b/x-pack/plugins/maps/public/layers/sources/vector_source.d.ts similarity index 93% rename from x-pack/legacy/plugins/maps/public/layers/sources/vector_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/vector_source.d.ts index 1400654297e01..d597e64277186 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.d.ts +++ b/x-pack/plugins/maps/public/layers/sources/vector_source.d.ts @@ -12,6 +12,7 @@ import { ESSearchSourceResponseMeta, MapExtent, VectorSourceRequestMeta, + VectorSourceSyncMeta, } from '../../../common/descriptor_types'; export type GeoJsonFetchMeta = ESSearchSourceResponseMeta; @@ -31,6 +32,7 @@ export interface IVectorSource extends ISource { getFields(): Promise; getFieldByName(fieldName: string): IField; + getSyncMeta(): VectorSourceSyncMeta; } export class AbstractVectorSource extends AbstractSource implements IVectorSource { @@ -43,4 +45,5 @@ export class AbstractVectorSource extends AbstractSource implements IVectorSourc getFields(): Promise; getFieldByName(fieldName: string): IField; + getSyncMeta(): VectorSourceSyncMeta; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js b/x-pack/plugins/maps/public/layers/sources/vector_source.js similarity index 99% rename from x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js rename to x-pack/plugins/maps/public/layers/sources/vector_source.js index 7ff1c735c8613..7f97b1b21d189 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/vector_source.js +++ b/x-pack/plugins/maps/public/layers/sources/vector_source.js @@ -151,4 +151,8 @@ export class AbstractVectorSource extends AbstractSource { getSourceTooltipContent(/* sourceDataRequest */) { return { tooltipContent: null, areResultsTrimmed: false }; } + + getSyncMeta() { + return {}; + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/index.js b/x-pack/plugins/maps/public/layers/sources/wms_source/index.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/wms_source/index.js rename to x-pack/plugins/maps/public/layers/sources/wms_source/index.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_client.js b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_client.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_client.js rename to x-pack/plugins/maps/public/layers/sources/wms_source/wms_client.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_client.test.js b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_client.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_client.test.js rename to x-pack/plugins/maps/public/layers/sources/wms_source/wms_client.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_create_source_editor.js b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_create_source_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_create_source_editor.js rename to x-pack/plugins/maps/public/layers/sources/wms_source/wms_create_source_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_source.js b/x-pack/plugins/maps/public/layers/sources/wms_source/wms_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/wms_source/wms_source.js rename to x-pack/plugins/maps/public/layers/sources/wms_source/wms_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.d.ts b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.d.ts rename to x-pack/plugins/maps/public/layers/sources/xyz_tms_source.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.js rename to x-pack/plugins/maps/public/layers/sources/xyz_tms_source.js diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.test.ts b/x-pack/plugins/maps/public/layers/sources/xyz_tms_source.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/sources/xyz_tms_source.test.ts rename to x-pack/plugins/maps/public/layers/sources/xyz_tms_source.test.ts diff --git a/x-pack/plugins/maps/public/layers/styles/_index.scss b/x-pack/plugins/maps/public/layers/styles/_index.scss new file mode 100644 index 0000000000000..a1c4c297a3ac1 --- /dev/null +++ b/x-pack/plugins/maps/public/layers/styles/_index.scss @@ -0,0 +1,4 @@ +@import 'components/color_gradient'; +@import 'vector/components/style_prop_editor'; +@import 'vector/components/color/color_stops'; +@import 'vector/components/symbol/icon_select'; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/abstract_style.js b/x-pack/plugins/maps/public/layers/styles/abstract_style.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/abstract_style.js rename to x-pack/plugins/maps/public/layers/styles/abstract_style.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js b/x-pack/plugins/maps/public/layers/styles/color_utils.js similarity index 98% rename from x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js rename to x-pack/plugins/maps/public/layers/styles/color_utils.js index 09c7d76db1691..23b61b07bf871 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.js +++ b/x-pack/plugins/maps/public/layers/styles/color_utils.js @@ -10,7 +10,7 @@ import chroma from 'chroma-js'; import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { ColorGradient } from './components/color_gradient'; import { COLOR_PALETTE_MAX_SIZE } from '../../../common/constants'; -import { vislibColorMaps } from '../../../../../../../src/plugins/charts/public'; +import { vislibColorMaps } from '../../../../../../src/plugins/charts/public'; const GRADIENT_INTERVALS = 8; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/color_utils.test.js b/x-pack/plugins/maps/public/layers/styles/color_utils.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/color_utils.test.js rename to x-pack/plugins/maps/public/layers/styles/color_utils.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/components/_color_gradient.scss b/x-pack/plugins/maps/public/layers/styles/components/_color_gradient.scss similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/components/_color_gradient.scss rename to x-pack/plugins/maps/public/layers/styles/components/_color_gradient.scss diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/components/color_gradient.js b/x-pack/plugins/maps/public/layers/styles/components/color_gradient.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/components/color_gradient.js rename to x-pack/plugins/maps/public/layers/styles/components/color_gradient.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js b/x-pack/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js rename to x-pack/plugins/maps/public/layers/styles/components/ranged_style_legend_row.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap b/x-pack/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap rename to x-pack/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_constants.js b/x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_constants.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_constants.js rename to x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_constants.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.js b/x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.js rename to x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.test.js b/x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.test.js rename to x-pack/plugins/maps/public/layers/styles/heatmap/components/heatmap_style_editor.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js b/x-pack/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js rename to x-pack/plugins/maps/public/layers/styles/heatmap/components/legend/heatmap_legend.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/heatmap_style.js b/x-pack/plugins/maps/public/layers/styles/heatmap/heatmap_style.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/heatmap/heatmap_style.js rename to x-pack/plugins/maps/public/layers/styles/heatmap/heatmap_style.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/_style_prop_editor.scss b/x-pack/plugins/maps/public/layers/styles/vector/components/_style_prop_editor.scss similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/_style_prop_editor.scss rename to x-pack/plugins/maps/public/layers/styles/vector/components/_style_prop_editor.scss diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss b/x-pack/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/_color_stops.scss diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/color_map_select.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_ordinal.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_utils.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_utils.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_utils.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/color_stops_utils.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/mb_validated_color_picker.tsx b/x-pack/plugins/maps/public/layers/styles/vector/components/color/mb_validated_color_picker.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/mb_validated_color_picker.tsx rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/mb_validated_color_picker.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/categorical_field_meta_popover.tsx b/x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/categorical_field_meta_popover.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/categorical_field_meta_popover.tsx rename to x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/categorical_field_meta_popover.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/field_meta_popover.tsx b/x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/field_meta_popover.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/field_meta_popover.tsx rename to x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/field_meta_popover.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx b/x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx rename to x-pack/plugins/maps/public/layers/styles/vector/components/field_meta/ordinal_field_meta_popover.tsx diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js b/x-pack/plugins/maps/public/layers/styles/vector/components/field_select.js similarity index 97% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/field_select.js index 2f5de507657a5..ed2e7a4eab7ec 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/field_select.js +++ b/x-pack/plugins/maps/public/layers/styles/vector/components/field_select.js @@ -10,7 +10,7 @@ import React from 'react'; import { EuiComboBox, EuiHighlight, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FIELD_ORIGIN } from '../../../../../common/constants'; import { i18n } from '@kbn/i18n'; -import { FieldIcon } from '../../../../../../../../../src/plugins/kibana_react/public'; +import { FieldIcon } from '../../../../../../../../src/plugins/kibana_react/public'; function renderOption(option, searchValue, contentClassName) { return ( diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js b/x-pack/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/category.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/category.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/category.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/category.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/circle_icon.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/circle_icon.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/circle_icon.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/circle_icon.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/extract_color_from_style_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/line_icon.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/line_icon.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/line_icon.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/line_icon.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/polygon_icon.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/polygon_icon.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/polygon_icon.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/polygon_icon.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js b/x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js b/x-pack/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js similarity index 92% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js index 5de7b462136e1..ec847e2a5384e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js +++ b/x-pack/plugins/maps/public/layers/styles/vector/components/size/size_range_selector.js @@ -6,7 +6,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { ValidatedDualRange } from '../../../../../../../../../../src/plugins/kibana_react/public'; +import { ValidatedDualRange } from '../../../../../../../../../src/plugins/kibana_react/public'; import { MIN_SIZE, MAX_SIZE } from '../../vector_style_defaults'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/stop_input.js b/x-pack/plugins/maps/public/layers/styles/vector/components/stop_input.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/stop_input.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/stop_input.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_map_select.js b/x-pack/plugins/maps/public/layers/styles/vector/components/style_map_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_map_select.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/style_map_select.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_option_shapes.js b/x-pack/plugins/maps/public/layers/styles/vector/components/style_option_shapes.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_option_shapes.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/style_option_shapes.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.test.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js b/x-pack/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js rename to x-pack/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap b/x-pack/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap rename to x-pack/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_orientation_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js similarity index 95% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js index 8b3f670bfa528..71ac25f0c6e61 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js +++ b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js @@ -99,9 +99,13 @@ export class DynamicSizeProperty extends DynamicStyleProperty { } } - syncCircleStrokeWidthWithMb(mbLayerId, mbMap) { - const lineWidth = this.getMbSizeExpression(); - mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', lineWidth); + syncCircleStrokeWidthWithMb(mbLayerId, mbMap, hasNoRadius) { + if (hasNoRadius) { + mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', 0); + } else { + const lineWidth = this.getMbSizeExpression(); + mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', lineWidth); + } } syncCircleRadiusWithMb(mbLayerId, mbMap) { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts similarity index 96% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts index 88eb489c4996e..72ca7def73908 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts +++ b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.d.ts @@ -21,7 +21,6 @@ export interface IDynamicStyleProperty extends IStyleProperty { getField(): IField | undefined; getFieldName(): string; getFieldOrigin(): FIELD_ORIGIN | undefined; - getComputedFieldName(): string | undefined; getRangeFieldMeta(): RangeFieldMeta; getCategoryFieldMeta(): CategoryFieldMeta; isFieldMetaEnabled(): boolean; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_color_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_color_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_color_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_color_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_orientation_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_orientation_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_orientation_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_orientation_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_size_property.js similarity index 85% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_size_property.js index 2383a5932cb9b..d86556c6218cf 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js +++ b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_size_property.js @@ -35,8 +35,12 @@ export class StaticSizeProperty extends StaticStyleProperty { mbMap.setLayoutProperty(symbolLayerId, 'icon-size', this._options.size / halfIconPixels); } - syncCircleStrokeWidthWithMb(mbLayerId, mbMap) { - mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', this._options.size); + syncCircleStrokeWidthWithMb(mbLayerId, mbMap, hasNoRadius) { + if (hasNoRadius) { + mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', 0); + } else { + mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', this._options.size); + } } syncCircleRadiusWithMb(mbLayerId, mbMap) { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_style_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_style_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_style_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_style_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_text_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/static_text_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_text_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/static_text_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.ts b/x-pack/plugins/maps/public/layers/styles/vector/properties/style_property.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.ts rename to x-pack/plugins/maps/public/layers/styles/vector/properties/style_property.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js b/x-pack/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js rename to x-pack/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_meta.ts b/x-pack/plugins/maps/public/layers/styles/vector/style_meta.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/style_meta.ts rename to x-pack/plugins/maps/public/layers/styles/vector/style_meta.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js b/x-pack/plugins/maps/public/layers/styles/vector/style_util.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js rename to x-pack/plugins/maps/public/layers/styles/vector/style_util.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js b/x-pack/plugins/maps/public/layers/styles/vector/style_util.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/style_util.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.js b/x-pack/plugins/maps/public/layers/styles/vector/symbol_utils.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.js rename to x-pack/plugins/maps/public/layers/styles/vector/symbol_utils.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.test.js b/x-pack/plugins/maps/public/layers/styles/vector/symbol_utils.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/symbol_utils.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.d.ts b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.d.ts rename to x-pack/plugins/maps/public/layers/styles/vector/vector_style.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js similarity index 99% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js rename to x-pack/plugins/maps/public/layers/styles/vector/vector_style.js index ae5d148e43cfd..b044c98d44d41 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.js @@ -543,8 +543,11 @@ export class VectorStyle extends AbstractStyle { setMBPaintPropertiesForPoints({ alpha, mbMap, pointLayerId }) { this._fillColorStyleProperty.syncCircleColorWithMb(pointLayerId, mbMap, alpha); this._lineColorStyleProperty.syncCircleStrokeWithMb(pointLayerId, mbMap, alpha); - this._lineWidthStyleProperty.syncCircleStrokeWidthWithMb(pointLayerId, mbMap); - this._iconSizeStyleProperty.syncCircleRadiusWithMb(pointLayerId, mbMap); + const hasNoRadius = + !this._iconSizeStyleProperty.isDynamic() && + this._iconSizeStyleProperty.getOptions().size === 0; + this._lineWidthStyleProperty.syncCircleStrokeWidthWithMb(pointLayerId, mbMap, hasNoRadius); + this._iconSizeStyleProperty.syncCircleRadiusWithMb(pointLayerId, mbMap, hasNoRadius); } setMBPropertiesForLabelText({ alpha, mbMap, textLayerId }) { diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js b/x-pack/plugins/maps/public/layers/styles/vector/vector_style.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js rename to x-pack/plugins/maps/public/layers/styles/vector/vector_style.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.ts b/x-pack/plugins/maps/public/layers/styles/vector/vector_style_defaults.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.ts rename to x-pack/plugins/maps/public/layers/styles/vector/vector_style_defaults.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/tile_layer.d.ts b/x-pack/plugins/maps/public/layers/tile_layer.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/tile_layer.d.ts rename to x-pack/plugins/maps/public/layers/tile_layer.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/tile_layer.js b/x-pack/plugins/maps/public/layers/tile_layer.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/tile_layer.js rename to x-pack/plugins/maps/public/layers/tile_layer.js diff --git a/x-pack/legacy/plugins/maps/public/layers/tile_layer.test.ts b/x-pack/plugins/maps/public/layers/tile_layer.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/tile_layer.test.ts rename to x-pack/plugins/maps/public/layers/tile_layer.test.ts diff --git a/x-pack/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.ts b/x-pack/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.ts new file mode 100644 index 0000000000000..acd05475f9762 --- /dev/null +++ b/x-pack/plugins/maps/public/layers/tooltips/es_agg_tooltip_property.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 { ESTooltipProperty } from './es_tooltip_property'; +import { AGG_TYPE } from '../../../common/constants'; +import { ITooltipProperty } from './tooltip_property'; +import { IField } from '../fields/field'; +import { IndexPattern } from '../../../../../../src/plugins/data/public'; + +export class ESAggTooltipProperty extends ESTooltipProperty { + private readonly _aggType: AGG_TYPE; + + constructor( + tooltipProperty: ITooltipProperty, + indexPattern: IndexPattern, + field: IField, + aggType: AGG_TYPE + ) { + super(tooltipProperty, indexPattern, field); + this._aggType = aggType; + } + + isFilterable(): boolean { + return this._aggType === AGG_TYPE.TERMS; + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_tooltip_property.ts b/x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.ts similarity index 95% rename from x-pack/legacy/plugins/maps/public/layers/tooltips/es_tooltip_property.ts rename to x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.ts index 8fd7e173435ce..5c35009881920 100644 --- a/x-pack/legacy/plugins/maps/public/layers/tooltips/es_tooltip_property.ts +++ b/x-pack/plugins/maps/public/layers/tooltips/es_tooltip_property.ts @@ -7,8 +7,8 @@ import _ from 'lodash'; import { ITooltipProperty } from './tooltip_property'; import { IField } from '../fields/field'; -import { esFilters, IFieldType, IndexPattern } from '../../../../../../../src/plugins/data/public'; -import { PhraseFilter } from '../../../../../../../src/plugins/data/public'; +import { esFilters, IFieldType, IndexPattern } from '../../../../../../src/plugins/data/public'; +import { PhraseFilter } from '../../../../../../src/plugins/data/public'; export class ESTooltipProperty implements ITooltipProperty { private readonly _tooltipProperty: ITooltipProperty; diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/join_tooltip_property.ts b/x-pack/plugins/maps/public/layers/tooltips/join_tooltip_property.ts similarity index 96% rename from x-pack/legacy/plugins/maps/public/layers/tooltips/join_tooltip_property.ts rename to x-pack/plugins/maps/public/layers/tooltips/join_tooltip_property.ts index 02f0920ce3c61..4af236f6e9e36 100644 --- a/x-pack/legacy/plugins/maps/public/layers/tooltips/join_tooltip_property.ts +++ b/x-pack/plugins/maps/public/layers/tooltips/join_tooltip_property.ts @@ -6,7 +6,7 @@ import { ITooltipProperty } from './tooltip_property'; import { IJoin } from '../joins/join'; -import { PhraseFilter } from '../../../../../../../src/plugins/data/public'; +import { PhraseFilter } from '../../../../../../src/plugins/data/public'; export class JoinTooltipProperty implements ITooltipProperty { private readonly _tooltipProperty: ITooltipProperty; diff --git a/x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts b/x-pack/plugins/maps/public/layers/tooltips/tooltip_property.ts similarity index 92% rename from x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts rename to x-pack/plugins/maps/public/layers/tooltips/tooltip_property.ts index 46e27bbd770a1..7d680dfe9cae0 100644 --- a/x-pack/legacy/plugins/maps/public/layers/tooltips/tooltip_property.ts +++ b/x-pack/plugins/maps/public/layers/tooltips/tooltip_property.ts @@ -5,8 +5,8 @@ */ import _ from 'lodash'; -import { PhraseFilter } from '../../../../../../../src/plugins/data/public'; -import { TooltipFeature } from '../../../../../../plugins/maps/common/descriptor_types'; +import { PhraseFilter } from '../../../../../../src/plugins/data/public'; +import { TooltipFeature } from '../../../../../plugins/maps/common/descriptor_types'; export interface ITooltipProperty { getPropertyKey(): string; diff --git a/x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.test.ts b/x-pack/plugins/maps/public/layers/util/assign_feature_ids.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.test.ts rename to x-pack/plugins/maps/public/layers/util/assign_feature_ids.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.ts b/x-pack/plugins/maps/public/layers/util/assign_feature_ids.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/assign_feature_ids.ts rename to x-pack/plugins/maps/public/layers/util/assign_feature_ids.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js b/x-pack/plugins/maps/public/layers/util/can_skip_fetch.test.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js rename to x-pack/plugins/maps/public/layers/util/can_skip_fetch.test.js diff --git a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.ts b/x-pack/plugins/maps/public/layers/util/can_skip_fetch.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.ts rename to x-pack/plugins/maps/public/layers/util/can_skip_fetch.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/data_request.ts b/x-pack/plugins/maps/public/layers/util/data_request.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/data_request.ts rename to x-pack/plugins/maps/public/layers/util/data_request.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.test.ts b/x-pack/plugins/maps/public/layers/util/es_agg_utils.test.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.test.ts rename to x-pack/plugins/maps/public/layers/util/es_agg_utils.test.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.ts b/x-pack/plugins/maps/public/layers/util/es_agg_utils.ts similarity index 95% rename from x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.ts rename to x-pack/plugins/maps/public/layers/util/es_agg_utils.ts index 9d4f24f80d6cd..329a2a6fc64fb 100644 --- a/x-pack/legacy/plugins/maps/public/layers/util/es_agg_utils.ts +++ b/x-pack/plugins/maps/public/layers/util/es_agg_utils.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; -import { IndexPattern, IFieldType } from '../../../../../../../src/plugins/data/public'; +import { IndexPattern, IFieldType } from '../../../../../../src/plugins/data/public'; import { TOP_TERM_PERCENTAGE_SUFFIX } from '../../../common/constants'; export function getField(indexPattern: IndexPattern, fieldName: string) { diff --git a/x-pack/legacy/plugins/maps/public/layers/util/is_metric_countable.ts b/x-pack/plugins/maps/public/layers/util/is_metric_countable.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/is_metric_countable.ts rename to x-pack/plugins/maps/public/layers/util/is_metric_countable.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/is_refresh_only_query.ts b/x-pack/plugins/maps/public/layers/util/is_refresh_only_query.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/is_refresh_only_query.ts rename to x-pack/plugins/maps/public/layers/util/is_refresh_only_query.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/util/mb_filter_expressions.ts b/x-pack/plugins/maps/public/layers/util/mb_filter_expressions.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/util/mb_filter_expressions.ts rename to x-pack/plugins/maps/public/layers/util/mb_filter_expressions.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.d.ts b/x-pack/plugins/maps/public/layers/vector_layer.d.ts similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/vector_layer.d.ts rename to x-pack/plugins/maps/public/layers/vector_layer.d.ts diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/plugins/maps/public/layers/vector_layer.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/vector_layer.js rename to x-pack/plugins/maps/public/layers/vector_layer.js diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_tile_layer.js b/x-pack/plugins/maps/public/layers/vector_tile_layer.js similarity index 100% rename from x-pack/legacy/plugins/maps/public/layers/vector_tile_layer.js rename to x-pack/plugins/maps/public/layers/vector_tile_layer.js diff --git a/x-pack/legacy/plugins/maps/public/meta.js b/x-pack/plugins/maps/public/meta.js similarity index 76% rename from x-pack/legacy/plugins/maps/public/meta.js rename to x-pack/plugins/maps/public/meta.js index 4d81785ff7a0a..d4612554cf00b 100644 --- a/x-pack/legacy/plugins/maps/public/meta.js +++ b/x-pack/plugins/maps/public/meta.js @@ -11,20 +11,19 @@ import { EMS_GLYPHS_PATH, EMS_APP_NAME, } from '../common/constants'; -import chrome from 'ui/chrome'; import { i18n } from '@kbn/i18n'; import { EMSClient } from '@elastic/ems-client'; -import { getLicenseId } from './kibana_services'; +import { getInjectedVarFunc, getLicenseId } from './kibana_services'; import fetch from 'node-fetch'; const GIS_API_RELATIVE = `../${GIS_API_PATH}`; export function getKibanaRegionList() { - return chrome.getInjected('regionmapLayers'); + return getInjectedVarFunc()('regionmapLayers'); } export function getKibanaTileMap() { - return chrome.getInjected('tilemap'); + return getInjectedVarFunc()('tilemap'); } function relativeToAbsolute(url) { @@ -41,27 +40,27 @@ let emsClient = null; let latestLicenseId = null; export function getEMSClient() { if (!emsClient) { - const isEmsEnabled = chrome.getInjected('isEmsEnabled', true); + const isEmsEnabled = getInjectedVarFunc()('isEmsEnabled', true); if (isEmsEnabled) { - const proxyElasticMapsServiceInMaps = chrome.getInjected( + const proxyElasticMapsServiceInMaps = getInjectedVarFunc()( 'proxyElasticMapsServiceInMaps', false ); const proxyPath = ''; const tileApiUrl = proxyElasticMapsServiceInMaps ? relativeToAbsolute(`${GIS_API_RELATIVE}/${EMS_TILES_CATALOGUE_PATH}`) - : chrome.getInjected('emsTileApiUrl'); + : getInjectedVarFunc()('emsTileApiUrl'); const fileApiUrl = proxyElasticMapsServiceInMaps ? relativeToAbsolute(`${GIS_API_RELATIVE}/${EMS_FILES_CATALOGUE_PATH}`) - : chrome.getInjected('emsFileApiUrl'); + : getInjectedVarFunc()('emsFileApiUrl'); emsClient = new EMSClient({ language: i18n.getLocale(), - appVersion: chrome.getInjected('kbnPkgVersion'), + appVersion: getInjectedVarFunc()('kbnPkgVersion'), appName: EMS_APP_NAME, tileApiUrl, fileApiUrl, - landingPageUrl: chrome.getInjected('emsLandingPageUrl'), + landingPageUrl: getInjectedVarFunc()('emsLandingPageUrl'), fetchFunction: fetchFunction, //import this from client-side, so the right instance is returned (bootstrapped from common/* would not work proxyPath, }); @@ -87,13 +86,13 @@ export function getEMSClient() { } export function getGlyphUrl() { - if (!chrome.getInjected('isEmsEnabled', true)) { + if (!getInjectedVarFunc()('isEmsEnabled', true)) { return ''; } - return chrome.getInjected('proxyElasticMapsServiceInMaps', false) + return getInjectedVarFunc()('proxyElasticMapsServiceInMaps', false) ? relativeToAbsolute(`../${GIS_API_PATH}/${EMS_TILES_CATALOGUE_PATH}/${EMS_GLYPHS_PATH}`) + `/{fontstack}/{range}` - : chrome.getInjected('emsFontLibraryUrl', true); + : getInjectedVarFunc()('emsFontLibraryUrl', true); } export function isRetina() { diff --git a/x-pack/legacy/plugins/maps/public/meta.test.js b/x-pack/plugins/maps/public/meta.test.js similarity index 57% rename from x-pack/legacy/plugins/maps/public/meta.test.js rename to x-pack/plugins/maps/public/meta.test.js index 64dd73fe109ff..d83f2adb35ef7 100644 --- a/x-pack/legacy/plugins/maps/public/meta.test.js +++ b/x-pack/plugins/maps/public/meta.test.js @@ -9,39 +9,24 @@ import { getEMSClient } from './meta'; jest.mock('@elastic/ems-client'); -jest.mock('ui/chrome', () => ({ - getBasePath: () => { - return ''; - }, - getInjected(key) { - if (key === 'proxyElasticMapsServiceInMaps') { - return false; - } else if (key === 'isEmsEnabled') { - return true; - } else if (key === 'emsFileApiUrl') { - return 'https://file-api'; - } else if (key === 'emsTileApiUrl') { - return 'https://tile-api'; - } - }, - getUiSettingsClient: () => { - return { - get: () => { - return ''; - }, +describe('default use without proxy', () => { + beforeEach(() => { + require('./kibana_services').getInjectedVarFunc = () => key => { + if (key === 'proxyElasticMapsServiceInMaps') { + return false; + } else if (key === 'isEmsEnabled') { + return true; + } else if (key === 'emsFileApiUrl') { + return 'https://file-api'; + } else if (key === 'emsTileApiUrl') { + return 'https://tile-api'; + } }; - }, -})); - -jest.mock('./kibana_services', () => { - return { - getLicenseId() { + require('./kibana_services').getLicenseId = () => { return 'foobarlicenseid'; - }, - }; -}); + }; + }); -describe('default use without proxy', () => { it('should construct EMSClient with absolute file and tile API urls', async () => { getEMSClient(); const mockEmsClientCall = EMSClient.mock.calls[0]; diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 506b0c426f0fa..9437c2512ded4 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -8,6 +8,20 @@ import { Plugin, CoreSetup, CoreStart } from 'src/core/public'; import { Setup as InspectorSetupContract } from 'src/plugins/inspector/public'; // @ts-ignore import { MapView } from './inspector/views/map_view'; +import { + setAutocompleteService, + setFileUpload, + setHttp, + setIndexPatternSelect, + setIndexPatternService, + setInjectedVarFunc, + setInspector, + setLicenseId, + setTimeFilter, + setToasts, + setUiSettings, + // @ts-ignore +} from './kibana_services'; export interface MapsPluginSetupDependencies { inspector: InspectorSetupContract; @@ -15,6 +29,29 @@ export interface MapsPluginSetupDependencies { // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface MapsPluginStartDependencies {} +export const bindSetupCoreAndPlugins = (core: CoreSetup, plugins: any) => { + const { licensing } = plugins; + const { injectedMetadata, http } = core; + if (licensing) { + licensing.license$.subscribe(({ uid }: { uid: string }) => setLicenseId(uid)); + } + setInjectedVarFunc(injectedMetadata.getInjectedVar); + setHttp(http); + setUiSettings(core.uiSettings); + setInjectedVarFunc(core.injectedMetadata.getInjectedVar); + setToasts(core.notifications.toasts); +}; + +export const bindStartCoreAndPlugins = (core: CoreStart, plugins: any) => { + const { file_upload, data, inspector } = plugins; + setInspector(inspector); + setFileUpload(file_upload); + setIndexPatternSelect(data.ui.IndexPatternSelect); + setTimeFilter(data.query.timefilter.timefilter); + setIndexPatternService(data.indexPatterns); + setAutocompleteService(data.autocomplete); +}; + /** * These are the interfaces with your public contracts. You should export these * for other plugins to use in _their_ `SetupDeps`/`StartDeps` interfaces. diff --git a/x-pack/plugins/ml/common/constants/file_datavisualizer.ts b/x-pack/plugins/ml/common/constants/file_datavisualizer.ts index d72e4d63cc47e..81d51bfa25816 100644 --- a/x-pack/plugins/ml/common/constants/file_datavisualizer.ts +++ b/x-pack/plugins/ml/common/constants/file_datavisualizer.ts @@ -5,6 +5,8 @@ */ export const MAX_BYTES = 104857600; +export const ABSOLUTE_MAX_BYTES = MAX_BYTES * 5; +export const FILE_SIZE_DISPLAY_FORMAT = '0,0.[0] b'; // Value to use in the Elasticsearch index mapping meta data to identify the // index as having been created by the ML File Data Visualizer. diff --git a/x-pack/plugins/ml/common/license/ml_license.ts b/x-pack/plugins/ml/common/license/ml_license.ts index 31a1b44d297ed..2a60887310447 100644 --- a/x-pack/plugins/ml/common/license/ml_license.ts +++ b/x-pack/plugins/ml/common/license/ml_license.ts @@ -5,7 +5,7 @@ */ import { Observable, Subscription } from 'rxjs'; -import { ILicense, LICENSE_CHECK_STATE } from '../../../licensing/common/types'; +import { ILicense } from '../../../licensing/common/types'; import { PLUGIN_ID } from '../constants/app'; export const MINIMUM_LICENSE = 'basic'; @@ -38,10 +38,8 @@ export class MlLicense { this._isSecurityEnabled = securityIsEnabled; this._hasLicenseExpired = this._license.status === 'expired'; this._isMlEnabled = this._license.getFeature(PLUGIN_ID).isEnabled; - this._isMinimumLicense = - this._license.check(PLUGIN_ID, MINIMUM_LICENSE).state === LICENSE_CHECK_STATE.Valid; - this._isFullLicense = - this._license.check(PLUGIN_ID, MINIMUM_FULL_LICENSE).state === LICENSE_CHECK_STATE.Valid; + this._isMinimumLicense = this._license.check(PLUGIN_ID, MINIMUM_LICENSE).state === 'valid'; + this._isFullLicense = this._license.check(PLUGIN_ID, MINIMUM_FULL_LICENSE).state === 'valid'; if (this._initialized === false && postInitFunctions !== undefined) { postInitFunctions.forEach(f => f(this)); diff --git a/x-pack/plugins/ml/common/types/errors.ts b/x-pack/plugins/ml/common/types/errors.ts index 63e222490082b..284250bd5ce55 100644 --- a/x-pack/plugins/ml/common/types/errors.ts +++ b/x-pack/plugins/ml/common/types/errors.ts @@ -9,6 +9,7 @@ export interface ErrorResponse { statusCode: number; error: string; message: string; + attributes?: any; }; name: string; } diff --git a/x-pack/plugins/ml/common/types/file_datavisualizer.ts b/x-pack/plugins/ml/common/types/file_datavisualizer.ts index bc03f82673a1f..f771547b97811 100644 --- a/x-pack/plugins/ml/common/types/file_datavisualizer.ts +++ b/x-pack/plugins/ml/common/types/file_datavisualizer.ts @@ -4,6 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ +export interface InputOverrides { + [key: string]: string; +} + +export type FormattedOverrides = InputOverrides & { + column_names: string[]; + has_header_row: boolean; + should_trim_fields: boolean; +}; + +export interface AnalysisResult { + results: FindFileStructureResponse; + overrides?: FormattedOverrides; +} + export interface FindFileStructureResponse { charset: string; has_header_row: boolean; @@ -28,4 +43,54 @@ export interface FindFileStructureResponse { need_client_timezone: boolean; num_lines_analyzed: number; column_names: string[]; + explanation?: string[]; + grok_pattern?: string; + multiline_start_pattern?: string; + exclude_lines_pattern?: string; + java_timestamp_formats?: string[]; + joda_timestamp_formats?: string[]; + timestamp_field?: string; + should_trim_fields?: boolean; +} + +export interface ImportResponse { + success: boolean; + id: string; + index?: string; + pipelineId?: string; + docCount: number; + failures: ImportFailure[]; + error?: any; + ingestError?: boolean; +} + +export interface ImportFailure { + item: number; + reason: string; + doc: Doc; +} + +export interface Doc { + message: string; +} + +export interface Settings { + pipeline?: string; + index: string; + body: any[]; + [key: string]: any; +} + +export interface Mappings { + [key: string]: any; +} + +export interface IngestPipelineWrapper { + id: string; + pipeline: IngestPipeline; +} + +export interface IngestPipeline { + description: string; + processors: any[]; } diff --git a/x-pack/plugins/ml/common/types/ml_config.ts b/x-pack/plugins/ml/common/types/ml_config.ts new file mode 100644 index 0000000000000..8fd9fd22bad8a --- /dev/null +++ b/x-pack/plugins/ml/common/types/ml_config.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { MAX_BYTES } from '../constants/file_datavisualizer'; + +export const configSchema = schema.object({ + file_data_visualizer: schema.object({ + max_file_size_bytes: schema.number({ defaultValue: MAX_BYTES }), + }), +}); + +export type MlConfigType = TypeOf; diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index e9796fcbb0fe4..f1facd18b9da5 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -15,10 +15,14 @@ import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/p import { setDependencyCache, clearCache } from './util/dependency_cache'; import { setLicenseCache } from './license'; import { MlSetupDependencies, MlStartDependencies } from '../plugin'; +import { MlConfigType } from '../../common/types/ml_config'; import { MlRouter } from './routing'; -type MlDependencies = MlSetupDependencies & MlStartDependencies; +type MlDependencies = MlSetupDependencies & + MlStartDependencies & { + mlConfig: MlConfigType; + }; interface AppProps { coreStart: CoreStart; @@ -74,6 +78,7 @@ export const renderApp = ( http: coreStart.http, security: deps.security, urlGenerators: deps.share.urlGenerators, + mlConfig: deps.mlConfig, }); const mlLicense = setLicenseCache(deps.licensing); diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx index fbdb47c87c7ef..9758dd969b443 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/results_table.tsx @@ -211,6 +211,7 @@ export const ResultsTable: FC = React.memo( switch (type) { case ES_FIELD_TYPES.BOOLEAN: column.dataType = ES_FIELD_TYPES.BOOLEAN; + column.render = d => (d ? 'true' : 'false'); break; case ES_FIELD_TYPES.DATE: column.align = 'right'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx index 8d53a9278a1af..a35be5400f46b 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/results_table.tsx @@ -213,6 +213,7 @@ export const ResultsTable: FC = React.memo( switch (type) { case ES_FIELD_TYPES.BOOLEAN: column.dataType = ES_FIELD_TYPES.BOOLEAN; + column.render = d => (d ? 'true' : 'false'); break; case ES_FIELD_TYPES.DATE: column.align = 'right'; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx index e5f30a50ed8f0..0c83dfb6a2346 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx @@ -250,7 +250,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta dependentVariableOptions: [] as State['form']['dependentVariableOptions'], }; - await newJobCapsService.initializeFromIndexPattern(indexPattern); + await newJobCapsService.initializeFromIndexPattern(indexPattern, false, false); // Get fields and filter for supported types for job type const { fields } = newJobCapsService; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/form_options_validation.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/form_options_validation.ts index 9c0bc69f4b41f..4bd03fec7cc72 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/form_options_validation.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/form_options_validation.ts @@ -10,7 +10,7 @@ import { ANALYSIS_CONFIG_TYPE } from '../../../../common/analytics'; import { AnalyticsJobType } from '../../hooks/use_create_analytics_form/state'; import { BASIC_NUMERICAL_TYPES, EXTENDED_NUMERICAL_TYPES } from '../../../../common/fields'; -const CATEGORICAL_TYPES = new Set(['ip', 'keyword', 'text']); +const CATEGORICAL_TYPES = new Set(['ip', 'keyword']); // List of system fields we want to ignore for the numeric field check. export const OMIT_FIELDS: string[] = ['_source', '_type', '_index', '_id', '_version', '_score']; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts index 01a39d2ef9f3b..e121268e65e86 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts @@ -8,6 +8,7 @@ import { EuiComboBoxOptionOption } from '@elastic/eui'; import { DeepPartial, DeepReadonly } from '../../../../../../../common/types/common'; import { checkPermission } from '../../../../../privilege/check_privilege'; import { mlNodesAvailable } from '../../../../../ml_nodes_check'; +import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; import { isClassificationAnalysis, @@ -158,6 +159,55 @@ export const getInitialState = (): State => ({ estimatedModelMemoryLimit: '', }); +const getExcludesFields = (excluded: string[]) => { + const { fields } = newJobCapsService; + const updatedExcluded: string[] = []; + // Loop through excluded fields to check for multiple types of same field + for (let i = 0; i < excluded.length; i++) { + const fieldName = excluded[i]; + let mainField; + + // No dot in fieldName - it is the main field + if (fieldName.includes('.') === false) { + mainField = fieldName; + } else { + // Dot in fieldName - check if there's a field whose name equals the fieldName with the last dot suffix removed + const regex = /\.[^.]*$/; + const suffixRemovedField = fieldName.replace(regex, ''); + const fieldMatch = newJobCapsService.getFieldById(suffixRemovedField); + + // There's a match - set as the main field + if (fieldMatch !== null) { + mainField = suffixRemovedField; + } else { + // No main field to be found - add the fieldName to updatedExcluded array if it's not already there + if (updatedExcluded.includes(fieldName) === false) { + updatedExcluded.push(fieldName); + } + } + } + + if (mainField !== undefined) { + // Add the main field to the updatedExcluded array if it's not already there + if (updatedExcluded.includes(mainField) === false) { + updatedExcluded.push(mainField); + } + // Create regex to find all other fields whose names begin with main field followed by a dot + const regex = new RegExp(`${mainField}\\..+`); + + // Loop through fields and add fields matching the pattern to updatedExcluded array + for (let j = 0; j < fields.length; j++) { + const field = fields[j].name; + if (updatedExcluded.includes(field) === false && field.match(regex) !== null) { + updatedExcluded.push(field); + } + } + } + } + + return updatedExcluded; +}; + export const getJobConfigFromFormState = ( formState: State['form'] ): DeepPartial => { @@ -175,7 +225,7 @@ export const getJobConfigFromFormState = ( index: formState.destinationIndex, }, analyzed_fields: { - excludes: formState.excludes, + excludes: getExcludesFields(formState.excludes), }, analysis: { outlier_detection: {}, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx index a8bb5a0a8fe10..2d6505f5ce1f7 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/datavisualizer_selector.tsx @@ -26,6 +26,7 @@ import { isFullLicense } from '../license'; import { useTimefilter, useMlKibana } from '../contexts/kibana'; import { NavigationMenu } from '../components/navigation_menu'; +import { getMaxBytesFormatted } from './file_based/components/utils'; function startTrialDescription() { return ( @@ -59,6 +60,8 @@ export const DatavisualizerSelector: FC = () => { licenseManagement.enabled === true && isFullLicense() === false; + const maxFileSize = getMaxBytesFormatted(); + return ( @@ -102,7 +105,8 @@ export const DatavisualizerSelector: FC = () => { description={ } betaBadgeLabel={i18n.translate( diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/about_panel.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/about_panel.tsx similarity index 91% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/about_panel.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/about_panel.tsx index edecc925591d3..2bddf0de0499d 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/about_panel.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/about_panel.tsx @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiFlexGroup, @@ -23,7 +23,11 @@ import { import { WelcomeContent } from './welcome_content'; -export function AboutPanel({ onFilePickerChange }) { +interface Props { + onFilePickerChange(files: FileList | null): void; +} + +export const AboutPanel: FC = ({ onFilePickerChange }) => { return ( @@ -54,9 +58,9 @@ export function AboutPanel({ onFilePickerChange }) { ); -} +}; -export function LoadingPanel() { +export const LoadingPanel: FC = () => { return ( @@ -79,4 +83,4 @@ export function LoadingPanel() { ); -} +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/welcome_content.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/welcome_content.tsx similarity index 88% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/welcome_content.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/welcome_content.tsx index 73d12122879f8..49b2396aeca13 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/welcome_content.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/about_panel/welcome_content.tsx @@ -5,7 +5,8 @@ */ import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiFlexGroup, @@ -18,8 +19,18 @@ import { } from '@elastic/eui'; import { ExperimentalBadge } from '../experimental_badge'; +import { getMaxBytesFormatted } from '../utils'; + +export const WelcomeContent: FC = () => { + const toolTipContent = i18n.translate( + 'xpack.ml.fileDatavisualizer.welcomeContent.experimentalFeatureTooltip', + { + defaultMessage: "Experimental feature. We'd love to hear your feedback.", + } + ); + + const maxFileSize = getMaxBytesFormatted(); -export function WelcomeContent() { return ( @@ -32,16 +43,7 @@ export function WelcomeContent() { id="xpack.ml.fileDatavisualizer.welcomeContent.visualizeDataFromLogFileTitle" defaultMessage="Visualize data from a log file {experimentalBadge}" values={{ - experimentalBadge: ( - - } - /> - ), + experimentalBadge: , }} /> @@ -118,7 +120,8 @@ export function WelcomeContent() {

@@ -144,4 +147,4 @@ export function WelcomeContent() {
); -} +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/analysis_summary/analysis_summary.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/analysis_summary/analysis_summary.tsx similarity index 89% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/analysis_summary/analysis_summary.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/analysis_summary/analysis_summary.tsx index b3bf4ea4daf83..b80db3b27fa7e 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/analysis_summary/analysis_summary.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/analysis_summary/analysis_summary.tsx @@ -5,11 +5,12 @@ */ import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiTitle, EuiSpacer, EuiDescriptionList } from '@elastic/eui'; +import { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer'; -export function AnalysisSummary({ results }) { +export const AnalysisSummary: FC<{ results: FindFileStructureResponse }> = ({ results }) => { const items = createDisplayItems(results); return ( @@ -28,10 +29,10 @@ export function AnalysisSummary({ results }) { ); -} +}; -function createDisplayItems(results) { - const items = [ +function createDisplayItems(results: FindFileStructureResponse) { + const items: Array<{ title: any; description: string | number }> = [ { title: ( = ({ tooltipContent }) => { return ( ); -} +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/experimental_badge/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/explanation_flyout.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/explanation_flyout.tsx new file mode 100644 index 0000000000000..8e44a296126b6 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/explanation_flyout.tsx @@ -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 React, { FC } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlyout, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiFlyoutHeader, + EuiButtonEmpty, + EuiTitle, + EuiFlyoutBody, + EuiSpacer, + EuiText, + EuiSubSteps, +} from '@elastic/eui'; +import { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer'; + +interface Props { + results: FindFileStructureResponse; + closeFlyout(): void; +} +export const ExplanationFlyout: FC = ({ results, closeFlyout }) => { + const explanation = results.explanation!; + return ( + + + +

+ +

+
+
+ + + + + + + + + + + + +
+ ); +}; + +const Content: FC<{ explanation: string[] }> = ({ explanation }) => ( + <> + + + + + +
    + {explanation.map((e, i) => ( +
  • + {e} + +
  • + ))} +
+
+
+ +); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/index.ts new file mode 100644 index 0000000000000..e288050403887 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/explanation_flyout/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 { ExplanationFlyout } from './explanation_flyout'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.tsx similarity index 83% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.tsx index ed5ab57a2588d..6564b9a1f4d83 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/file_contents.tsx @@ -5,13 +5,19 @@ */ import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiTitle, EuiSpacer } from '@elastic/eui'; import { MLJobEditor, ML_EDITOR_MODE } from '../../../../jobs/jobs_list/components/ml_job_editor'; -export function FileContents({ data, format, numberOfLines }) { +interface Props { + data: string; + format: string; + numberOfLines: number; +} + +export const FileContents: FC = ({ data, format, numberOfLines }) => { let mode = ML_EDITOR_MODE.TEXT; if (format === ML_EDITOR_MODE.JSON) { mode = ML_EDITOR_MODE.JSON; @@ -35,7 +41,7 @@ export function FileContents({ data, format, numberOfLines }) { id="xpack.ml.fileDatavisualizer.fileContents.firstLinesDescription" defaultMessage="First {numberOfLines, plural, zero {# line} one {# line} other {# lines}}" values={{ - numberOfLines: numberOfLines, + numberOfLines, }} />
@@ -51,9 +57,9 @@ export function FileContents({ data, format, numberOfLines }) { /> ); -} +}; -function limitByNumberOfLines(data, numberOfLines) { +function limitByNumberOfLines(data: string, numberOfLines: number) { return data .split('\n') .slice(0, numberOfLines) diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_contents/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js index 12e5a14b51871..d1b615a878b2b 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_datavisualizer_view.js @@ -17,16 +17,17 @@ import { BottomBar } from '../bottom_bar'; import { ResultsView } from '../results_view'; import { FileCouldNotBeRead, FileTooLarge } from './file_error_callouts'; import { EditFlyout } from '../edit_flyout'; +import { ExplanationFlyout } from '../explanation_flyout'; import { ImportView } from '../import_view'; -import { MAX_BYTES } from '../../../../../../common/constants/file_datavisualizer'; -import { isErrorResponse } from '../../../../../../common/types/errors'; import { + getMaxBytes, readFile, createUrlOverrides, processResults, reduceData, hasImportPermission, } from '../utils'; + import { MODE } from './constants'; const UPLOAD_SIZE_MB = 5; @@ -42,12 +43,14 @@ export class FileDataVisualizerView extends Component { fileSize: 0, fileTooLarge: false, fileCouldNotBeRead: false, - serverErrorMessage: '', + serverError: null, loading: false, loaded: false, results: undefined, + explanation: undefined, mode: MODE.READ, isEditFlyoutVisible: false, + isExplanationFlyoutVisible: false, bottomBarVisible: false, hasPermissionToImport: false, }; @@ -55,6 +58,7 @@ export class FileDataVisualizerView extends Component { this.overrides = {}; this.previousOverrides = {}; this.originalSettings = {}; + this.maxFileUploadBytes = getMaxBytes(); } async componentDidMount() { @@ -78,8 +82,9 @@ export class FileDataVisualizerView extends Component { fileSize: 0, fileTooLarge: false, fileCouldNotBeRead: false, - serverErrorMessage: '', + serverError: null, results: undefined, + explanation: undefined, }, () => { if (files.length) { @@ -90,7 +95,7 @@ export class FileDataVisualizerView extends Component { }; async loadFile(file) { - if (file.size <= MAX_BYTES) { + if (file.size <= this.maxFileUploadBytes) { try { const fileContents = await readFile(file); const data = fileContents.data; @@ -102,7 +107,6 @@ export class FileDataVisualizerView extends Component { await this.loadSettings(data); } catch (error) { - console.error(error); this.setState({ loaded: false, loading: false, @@ -128,7 +132,7 @@ export class FileDataVisualizerView extends Component { console.log('overrides', overrides); const { analyzeFile } = ml.fileDatavisualizer; const resp = await analyzeFile(lessData, overrides); - const serverSettings = processResults(resp.results); + const serverSettings = processResults(resp); const serverOverrides = resp.overrides; this.previousOverrides = this.overrides; @@ -172,26 +176,19 @@ export class FileDataVisualizerView extends Component { this.setState({ results: resp.results, + explanation: resp.explanation, loaded: true, loading: false, fileCouldNotBeRead: isRetry, }); } catch (error) { - console.error(error); - - let serverErrorMsg; - if (isErrorResponse(error) === true) { - serverErrorMsg = `${error.body.error}: ${error.body.message}`; - } else { - serverErrorMsg = JSON.stringify(error, null, 2); - } - this.setState({ results: undefined, + explanation: undefined, loaded: false, loading: false, fileCouldNotBeRead: true, - serverErrorMessage: serverErrorMsg, + serverError: error, }); // as long as the previous overrides are different to the current overrides, @@ -216,6 +213,16 @@ export class FileDataVisualizerView extends Component { this.hideBottomBar(); }; + closeExplanationFlyout = () => { + this.setState({ isExplanationFlyoutVisible: false }); + this.showBottomBar(); + }; + + showExplanationFlyout = () => { + this.setState({ isExplanationFlyoutVisible: true }); + this.hideBottomBar(); + }; + showBottomBar = () => { this.setState({ bottomBarVisible: true }); }; @@ -252,14 +259,16 @@ export class FileDataVisualizerView extends Component { loading, loaded, results, + explanation, fileContents, fileName, fileSize, fileTooLarge, fileCouldNotBeRead, - serverErrorMessage, + serverError, mode, isEditFlyoutVisible, + isExplanationFlyoutVisible, bottomBarVisible, hasPermissionToImport, } = this.state; @@ -277,11 +286,13 @@ export class FileDataVisualizerView extends Component { {loading && } - {fileTooLarge && } + {fileTooLarge && ( + + )} {fileCouldNotBeRead && loading === false && ( - + )} @@ -289,9 +300,12 @@ export class FileDataVisualizerView extends Component { {loaded && ( this.showEditFlyout()} + showExplanationFlyout={() => this.showExplanationFlyout()} + disableButtons={isEditFlyoutVisible || isExplanationFlyoutVisible} /> )} + {isExplanationFlyoutVisible && ( + + )} + {bottomBarVisible && loaded && ( = ({ fileSize, maxFileSize }) => { const fileSizeFormatted = numeral(fileSize).format(FILE_SIZE_DISPLAY_FORMAT); const maxFileSizeFormatted = numeral(maxFileSize).format(FILE_SIZE_DISPLAY_FORMAT); @@ -67,9 +72,15 @@ export function FileTooLarge({ fileSize, maxFileSize }) { {errorText} ); +}; + +interface FileCouldNotBeReadProps { + error: ErrorResponse; + loaded: boolean; } -export function FileCouldNotBeRead({ error, loaded }) { +export const FileCouldNotBeRead: FC = ({ error, loaded }) => { + const message = error?.body?.message || ''; return ( - {error !== undefined &&

{error}

} + {message} + {loaded && ( -

+ <> + -

+ )}
); -} +}; + +export const Explanation: FC<{ error: ErrorResponse }> = ({ error }) => { + if (!error?.body?.attributes?.body?.error?.suppressed?.length) { + return null; + } + const reason: string = error.body.attributes.body.error.suppressed[0].reason; + return ( + <> + + {reason.split('\n').map((m, i) => ( +
{m}
+ ))} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/errors.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/errors.tsx similarity index 85% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/errors.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/errors.tsx index 6629c0109feaf..f723ad1a752bf 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/errors.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/errors.tsx @@ -5,13 +5,24 @@ */ import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; import { EuiCallOut, EuiAccordion } from '@elastic/eui'; -import { IMPORT_STATUS } from '../import_progress'; +import { IMPORT_STATUS, Statuses } from '../import_progress'; -export function ImportErrors({ errors, statuses }) { +interface ImportError { + msg: string; + more?: string; +} + +interface Props { + errors: any[]; + statuses: Statuses; +} + +export const ImportErrors: FC = ({ errors, statuses }) => { return ( {errors.map((e, i) => ( @@ -19,9 +30,9 @@ export function ImportErrors({ errors, statuses }) { ))} ); -} +}; -function title(statuses) { +function title(statuses: Statuses) { switch (IMPORT_STATUS.FAILED) { case statuses.readStatus: return ( @@ -82,7 +93,7 @@ function title(statuses) { } } -function ImportError(error, key) { +function ImportError(error: any, key: number) { const errorObj = toString(error); return ( @@ -106,7 +117,7 @@ function ImportError(error, key) { ); } -function toString(error) { +function toString(error: any): ImportError { if (typeof error === 'string') { return { msg: error }; } @@ -118,7 +129,7 @@ function toString(error) { if (typeof error.error === 'object') { if (error.error.msg !== undefined) { // this will catch a bulk ingest failure - const errorObj = { msg: error.error.msg }; + const errorObj: ImportError = { msg: error.error.msg }; if (error.error.body !== undefined) { errorObj.more = error.error.response; } @@ -139,11 +150,8 @@ function toString(error) { } return { - msg: ( - - ), + msg: i18n.translate('xpack.ml.fileDatavisualizer.importErrors.unknownErrorMessage', { + defaultMessage: 'Unknown error', + }), }; } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_errors/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/import_progress.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/import_progress.tsx similarity index 92% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/import_progress.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/import_progress.tsx index 272ec2979ad2f..533fec4ac50c8 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/import_progress.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/import_progress.tsx @@ -6,17 +6,31 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiStepsHorizontal, EuiProgress, EuiSpacer } from '@elastic/eui'; -export const IMPORT_STATUS = { - INCOMPLETE: 'incomplete', - COMPLETE: 'complete', - FAILED: 'danger', -}; +export enum IMPORT_STATUS { + INCOMPLETE = 'incomplete', + COMPLETE = 'complete', + FAILED = 'danger', +} + +export interface Statuses { + reading: boolean; + readStatus: IMPORT_STATUS; + parseJSONStatus: IMPORT_STATUS; + indexCreatedStatus: IMPORT_STATUS; + ingestPipelineCreatedStatus: IMPORT_STATUS; + indexPatternCreatedStatus: IMPORT_STATUS; + uploadProgress: number; + uploadStatus: IMPORT_STATUS; + createIndexPattern: boolean; + createPipeline: boolean; + permissionCheckStatus: IMPORT_STATUS; +} -export function ImportProgress({ statuses }) { +export const ImportProgress: FC<{ statuses: Statuses }> = ({ statuses }) => { const { reading, readStatus, @@ -271,9 +285,9 @@ export function ImportProgress({ statuses }) { )} ); -} +}; -function UploadFunctionProgress({ progress }) { +const UploadFunctionProgress: FC<{ progress: number }> = ({ progress }) => { return (

@@ -290,4 +304,4 @@ function UploadFunctionProgress({ progress }) { )} ); -} +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/index.ts new file mode 100644 index 0000000000000..9b0c6ca2264cb --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_progress/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 { ImportProgress, IMPORT_STATUS, Statuses } from './import_progress'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/advanced.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/advanced.tsx similarity index 85% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/advanced.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/advanced.tsx index 14cbe67662ed6..a79a7d36f3294 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/advanced.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/advanced.tsx @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiFieldText, @@ -20,7 +20,25 @@ import { import { MLJobEditor, ML_EDITOR_MODE } from '../../../../jobs/jobs_list/components/ml_job_editor'; const EDITOR_HEIGHT = '300px'; -export function AdvancedSettings({ +interface Props { + index: string; + indexPattern: string; + initialized: boolean; + onIndexChange(): void; + createIndexPattern: boolean; + onCreateIndexPatternChange(): void; + onIndexPatternChange(): void; + indexSettingsString: string; + mappingsString: string; + pipelineString: string; + onIndexSettingsStringChange(): void; + onMappingsStringChange(): void; + onPipelineStringChange(): void; + indexNameError: string; + indexPatternNameError: string; +} + +export const AdvancedSettings: FC = ({ index, indexPattern, initialized, @@ -36,7 +54,7 @@ export function AdvancedSettings({ onPipelineStringChange, indexNameError, indexPatternNameError, -}) { +}) => { return ( } - disabled={createIndexPattern === false || initialized === true} isInvalid={indexPatternNameError !== ''} error={[indexPatternNameError]} > @@ -133,9 +150,15 @@ export function AdvancedSettings({ ); +}; + +interface JsonEditorProps { + initialized: boolean; + data: string; + onChange(): void; } -function IndexSettings({ initialized, data, onChange }) { +const IndexSettings: FC = ({ initialized, data, onChange }) => { return ( } - disabled={initialized === true} fullWidth > ); -} +}; -function Mappings({ initialized, data, onChange }) { +const Mappings: FC = ({ initialized, data, onChange }) => { return ( } - disabled={initialized === true} fullWidth > ); -} +}; -function IngestPipeline({ initialized, data, onChange }) { +const IngestPipeline: FC = ({ initialized, data, onChange }) => { return ( } - disabled={initialized === true} fullWidth > ); -} +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/import_settings.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/import_settings.tsx similarity index 82% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/import_settings.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/import_settings.tsx index ba637c472333d..02cf647caaf16 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/import_settings.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/import_settings.tsx @@ -5,14 +5,32 @@ */ import { i18n } from '@kbn/i18n'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiTabbedContent, EuiSpacer } from '@elastic/eui'; import { SimpleSettings } from './simple'; import { AdvancedSettings } from './advanced'; -export const ImportSettings = ({ +interface Props { + index: string; + indexPattern: string; + initialized: boolean; + onIndexChange(): void; + createIndexPattern: boolean; + onCreateIndexPatternChange(): void; + onIndexPatternChange(): void; + indexSettingsString: string; + mappingsString: string; + pipelineString: string; + onIndexSettingsStringChange(): void; + onMappingsStringChange(): void; + onPipelineStringChange(): void; + indexNameError: string; + indexPatternNameError: string; +} + +export const ImportSettings: FC = ({ index, indexPattern, initialized, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/simple.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/simple.tsx similarity index 88% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/simple.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/simple.tsx index 271b9493aa1f3..1e716824729e3 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/simple.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_settings/simple.tsx @@ -6,11 +6,20 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiFieldText, EuiFormRow, EuiCheckbox, EuiSpacer } from '@elastic/eui'; -export const SimpleSettings = ({ +interface Props { + index: string; + initialized: boolean; + onIndexChange(): void; + createIndexPattern: boolean; + onCreateIndexPatternChange(): void; + indexNameError: string; +} + +export const SimpleSettings: FC = ({ index, initialized, onIndexChange, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.tsx similarity index 86% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.tsx index 0e67807a39fd9..6ee17d401bd70 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/import_summary.tsx @@ -5,11 +5,29 @@ */ import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { FC } from 'react'; import { EuiSpacer, EuiDescriptionList, EuiCallOut, EuiAccordion } from '@elastic/eui'; -export function ImportSummary({ +interface Props { + index: string; + indexPattern: string; + ingestPipelineId: string; + docCount: number; + importFailures: DocFailure[]; + createIndexPattern: boolean; + createPipeline: boolean; +} + +interface DocFailure { + item: number; + reason: string; + doc: { + message: string; + }; +} + +export const ImportSummary: FC = ({ index, indexPattern, ingestPipelineId, @@ -17,7 +35,7 @@ export function ImportSummary({ importFailures, createIndexPattern, createPipeline, -}) { +}) => { const items = createDisplayItems( index, indexPattern, @@ -75,9 +93,13 @@ export function ImportSummary({ )} ); +}; + +interface FailuresProps { + failedDocs: DocFailure[]; } -function Failures({ failedDocs }) { +const Failures: FC = ({ failedDocs }) => { return ( ); -} +}; function createDisplayItems( - index, - indexPattern, - ingestPipelineId, - docCount, - importFailures, - createIndexPattern, - createPipeline + index: string, + indexPattern: string, + ingestPipelineId: string, + docCount: number, + importFailures: DocFailure[], + createIndexPattern: boolean, + createPipeline: boolean ) { const items = [ { diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_summary/index.ts 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 0a58153e374df..4c9579bfd4b46 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 @@ -623,7 +623,6 @@ async function createKibanaIndexPattern( id, }; } catch (error) { - console.error(error); return { success: false, error, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.ts similarity index 73% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.ts index 27899a58beed2..c97f1c147c454 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.ts @@ -4,30 +4,53 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ml } from '../../../../../services/ml_api_service'; import { chunk } from 'lodash'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; +import { ml } from '../../../../../services/ml_api_service'; +import { + Doc, + ImportFailure, + ImportResponse, + Mappings, + Settings, + IngestPipeline, +} from '../../../../../../../common/types/file_datavisualizer'; const CHUNK_SIZE = 5000; const MAX_CHUNK_CHAR_COUNT = 1000000; const IMPORT_RETRIES = 5; +export interface ImportConfig { + settings: Settings; + mappings: Mappings; + pipeline: IngestPipeline; +} + +export interface ImportResults { + success: boolean; + failures?: any[]; + docCount?: number; + error?: any; +} + export class Importer { - constructor({ settings, mappings, pipeline }) { - this.settings = settings; - this.mappings = mappings; - this.pipeline = pipeline; - - this.data = []; - this.docArray = []; - this.docSizeArray = []; + private _settings: Settings; + private _mappings: Mappings; + private _pipeline: IngestPipeline; + + protected _docArray: Doc[] = []; + + constructor({ settings, mappings, pipeline }: ImportConfig) { + this._settings = settings; + this._mappings = mappings; + this._pipeline = pipeline; } - async initializeImport(index) { - const settings = this.settings; - const mappings = this.mappings; - const pipeline = this.pipeline; + async initializeImport(index: string) { + const settings = this._settings; + const mappings = this._mappings; + const pipeline = this._pipeline; updatePipelineTimezone(pipeline); // if no pipeline has been supplied, @@ -52,7 +75,12 @@ export class Importer { return createIndexResp; } - async import(id, index, pipelineId, setImportProgress) { + async import( + id: string, + index: string, + pipelineId: string, + setImportProgress: (progress: number) => void + ): Promise { if (!id || !index) { return { success: false, @@ -65,14 +93,14 @@ export class Importer { }; } - const chunks = createDocumentChunks(this.docArray); + const chunks = createDocumentChunks(this._docArray); const ingestPipeline = { id: pipelineId, }; let success = true; - const failures = []; + const failures: ImportFailure[] = []; let error; for (let i = 0; i < chunks.length; i++) { @@ -86,10 +114,13 @@ export class Importer { }; let retries = IMPORT_RETRIES; - let resp = { + let resp: ImportResponse = { success: false, failures: [], docCount: 0, + id: '', + index: '', + pipelineId: '', }; while (resp.success === false && retries > 0) { @@ -97,12 +128,14 @@ export class Importer { resp = await ml.fileDatavisualizer.import(aggs); if (retries < IMPORT_RETRIES) { + // eslint-disable-next-line no-console console.log(`Retrying import ${IMPORT_RETRIES - retries}`); } retries--; } catch (err) { - resp = { success: false, error: err }; + resp.success = false; + resp.error = err; retries = 0; } } @@ -110,6 +143,7 @@ export class Importer { if (resp.success) { setImportProgress(((i + 1) / chunks.length) * 100); } else { + // eslint-disable-next-line no-console console.error(resp); success = false; error = resp.error; @@ -120,10 +154,10 @@ export class Importer { populateFailures(resp, failures, i); } - const result = { + const result: ImportResults = { success, failures, - docCount: this.docArray.length, + docCount: this._docArray.length, }; if (success) { @@ -136,7 +170,7 @@ export class Importer { } } -function populateFailures(error, failures, chunkCount) { +function populateFailures(error: ImportResponse, failures: ImportFailure[], chunkCount: number) { if (error.failures && error.failures.length) { // update the item value to include the chunk count // e.g. item 3 in chunk 2 is actually item 20003 @@ -155,10 +189,10 @@ function populateFailures(error, failures, chunkCount) { // But it's not sending every single field that Filebeat would add, so the ingest pipeline // cannot look for a event.timezone variable in each input record. // Therefore we need to replace {{ event.timezone }} with the actual browser timezone -function updatePipelineTimezone(ingestPipeline) { +function updatePipelineTimezone(ingestPipeline: IngestPipeline) { if (ingestPipeline !== undefined && ingestPipeline.processors && ingestPipeline.processors) { const dateProcessor = ingestPipeline.processors.find( - p => p.date !== undefined && p.date.timezone === '{{ event.timezone }}' + (p: any) => p.date !== undefined && p.date.timezone === '{{ event.timezone }}' ); if (dateProcessor) { @@ -167,8 +201,8 @@ function updatePipelineTimezone(ingestPipeline) { } } -function createDocumentChunks(docArray) { - const chunks = []; +function createDocumentChunks(docArray: Doc[]) { + const chunks: Doc[][] = []; // chop docArray into 5000 doc chunks const tempChunks = chunk(docArray, CHUNK_SIZE); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer_factory.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer_factory.ts similarity index 76% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer_factory.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer_factory.ts index 381e8ef604452..a656b19220368 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer_factory.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer_factory.ts @@ -6,8 +6,14 @@ import { MessageImporter } from './message_importer'; import { NdjsonImporter } from './ndjson_importer'; +import { ImportConfig } from './importer'; +import { FindFileStructureResponse } from '../../../../../../../common/types/file_datavisualizer'; -export function importerFactory(format, results, settings) { +export function importerFactory( + format: string, + results: FindFileStructureResponse, + settings: ImportConfig +) { switch (format) { // delimited and semi-structured text are both handled by splitting the // file into messages, then sending these to ES for further processing diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.ts similarity index 76% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.ts index c2d3ac69f0963..7ccc5a8d673f4 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.ts @@ -4,17 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Importer } from './importer'; +import { Importer, ImportConfig } from './importer'; +import { + Doc, + FindFileStructureResponse, +} from '../../../../../../../common/types/file_datavisualizer'; export class MessageImporter extends Importer { - constructor(results, settings) { + private _excludeLinesRegex: RegExp | null; + private _multilineStartRegex: RegExp | null; + + constructor(results: FindFileStructureResponse, settings: ImportConfig) { super(settings); - this.excludeLinesRegex = + this._excludeLinesRegex = results.exclude_lines_pattern === undefined ? null : new RegExp(results.exclude_lines_pattern); - this.multilineStartRegex = + this._multilineStartRegex = results.multiline_start_pattern === undefined ? null : new RegExp(results.multiline_start_pattern); @@ -26,9 +33,9 @@ export class MessageImporter extends Importer { // multiline_start_pattern regex // if it does, it is a legitimate end of line and can be pushed into the list, // if not, it must be a newline char inside a field value, so keep looking. - read(text) { + read(text: string) { try { - const data = []; + const data: Doc[] = []; let message = ''; let line = ''; @@ -57,14 +64,12 @@ export class MessageImporter extends Importer { data.shift(); } - this.data = data; - this.docArray = this.data; + this._docArray = data; return { success: true, }; } catch (error) { - console.error(error); return { success: false, error, @@ -72,9 +77,9 @@ export class MessageImporter extends Importer { } } - processLine(data, message, line) { - if (this.excludeLinesRegex === null || line.match(this.excludeLinesRegex) === null) { - if (this.multilineStartRegex === null || line.match(this.multilineStartRegex) !== null) { + processLine(data: Doc[], message: string, line: string) { + if (this._excludeLinesRegex === null || line.match(this._excludeLinesRegex) === null) { + if (this._multilineStartRegex === null || line.match(this._multilineStartRegex) !== null) { this.addMessage(data, message); message = ''; } else if (data.length === 0) { @@ -90,7 +95,7 @@ export class MessageImporter extends Importer { return message; } - addMessage(data, message) { + addMessage(data: Doc[], message: string) { // if the message ended \r\n (Windows line endings) // then omit the \r as well as the \n for consistency message = message.replace(/\r$/, ''); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.ts similarity index 71% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.ts index 887bf1a41200a..7f5f37abc5246 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/ndjson_importer.ts @@ -4,18 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Importer } from './importer'; +import { Importer, ImportConfig } from './importer'; +import { FindFileStructureResponse } from '../../../../../../../common/types/file_datavisualizer'; export class NdjsonImporter extends Importer { - constructor(results, settings) { + constructor(results: FindFileStructureResponse, settings: ImportConfig) { super(settings); } - read(json) { + read(json: string) { try { const splitJson = json.split(/}\s*\n/); - const ndjson = []; + const ndjson: any[] = []; for (let i = 0; i < splitJson.length; i++) { if (splitJson[i] !== '') { // note the extra } at the end of the line, adding back @@ -24,7 +25,7 @@ export class NdjsonImporter extends Importer { } } - this.docArray = ndjson; + this._docArray = ndjson; return { success: true, diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/index.ts similarity index 100% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/index.ts diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.tsx similarity index 58% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.tsx index 96116e5cefa01..f9de03c119d28 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/results_view/results_view.tsx @@ -7,10 +7,10 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; - +import React, { FC } from 'react'; import { EuiButton, + EuiButtonEmpty, EuiPage, EuiPageBody, EuiPageContentHeader, @@ -18,13 +18,33 @@ import { EuiTabbedContent, EuiSpacer, EuiTitle, + EuiFlexGroup, + EuiFlexItem, } from '@elastic/eui'; +import { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer'; import { FileContents } from '../file_contents'; import { AnalysisSummary } from '../analysis_summary'; +// @ts-ignore import { FieldsStats } from '../fields_stats'; -export const ResultsView = ({ data, fileName, results, showEditFlyout }) => { +interface Props { + data: string; + fileName: string; + results: FindFileStructureResponse; + showEditFlyout(): void; + showExplanationFlyout(): void; + disableButtons: boolean; +} + +export const ResultsView: FC = ({ + data, + fileName, + results, + showEditFlyout, + showExplanationFlyout, + disableButtons, +}) => { const tabs = [ { id: 'file-stats', @@ -60,12 +80,24 @@ export const ResultsView = ({ data, fileName, results, showEditFlyout }) => { - showEditFlyout()}> - - + + + showEditFlyout()} disabled={disableButtons}> + + + + + showExplanationFlyout()} disabled={disableButtons}> + + + + diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts similarity index 90% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts index 6f670359586b7..0f0036a7c4616 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/index.ts @@ -10,4 +10,6 @@ export { processResults, readFile, reduceData, + getMaxBytes, + getMaxBytesFormatted, } from './utils'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/overrides.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/overrides.js deleted file mode 100644 index f8a90c87b9dc8..0000000000000 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/overrides.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. - */ - -export const DEFAULT_LINES_TO_SAMPLE = 1000; - -export const overrideDefaults = { - timestampFormat: undefined, - timestampField: undefined, - format: undefined, - delimiter: undefined, - quote: undefined, - hasHeaderRow: undefined, - charset: undefined, - columnNames: undefined, - shouldTrimFields: undefined, - grokPattern: undefined, - linesToSample: undefined, -}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.js b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts similarity index 72% rename from x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.js rename to x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts index 39cd25ba87d8c..0d2016b71ed83 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.js +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts @@ -4,11 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ -import { overrideDefaults, DEFAULT_LINES_TO_SAMPLE } from './overrides'; import { isEqual } from 'lodash'; +import numeral from '@elastic/numeral'; import { ml } from '../../../../services/ml_api_service'; - -export function readFile(file) { +import { AnalysisResult, InputOverrides } from '../../../../../../common/types/file_datavisualizer'; +import { + ABSOLUTE_MAX_BYTES, + FILE_SIZE_DISPLAY_FORMAT, +} from '../../../../../../common/constants/file_datavisualizer'; +import { getMlConfig } from '../../../../util/dependency_cache'; + +const DEFAULT_LINES_TO_SAMPLE = 1000; + +const overrideDefaults = { + timestampFormat: undefined, + timestampField: undefined, + format: undefined, + delimiter: undefined, + quote: undefined, + hasHeaderRow: undefined, + charset: undefined, + columnNames: undefined, + shouldTrimFields: undefined, + grokPattern: undefined, + linesToSample: undefined, +}; + +export function readFile(file: File) { return new Promise((resolve, reject) => { if (file && file.size) { const reader = new FileReader(); @@ -23,14 +45,14 @@ export function readFile(file) { resolve({ data }); } }; - })(file); + })(); } else { reject(); } }); } -export function reduceData(data, mb) { +export function reduceData(data: string, mb: number) { // assuming ascii characters in the file where 1 char is 1 byte // TODO - change this when other non UTF-8 formats are // supported for the read data @@ -38,8 +60,17 @@ export function reduceData(data, mb) { return data.length >= size ? data.slice(0, size) : data; } -export function createUrlOverrides(overrides, originalSettings) { - const formattedOverrides = {}; +export function getMaxBytes() { + const maxBytes = getMlConfig().file_data_visualizer.max_file_size_bytes; + return maxBytes < ABSOLUTE_MAX_BYTES ? maxBytes : ABSOLUTE_MAX_BYTES; +} + +export function getMaxBytesFormatted() { + return numeral(getMaxBytes()).format(FILE_SIZE_DISPLAY_FORMAT); +} + +export function createUrlOverrides(overrides: InputOverrides, originalSettings: InputOverrides) { + const formattedOverrides: InputOverrides = {}; for (const o in overrideDefaults) { if (overrideDefaults.hasOwnProperty(o)) { let value = overrides[o]; @@ -93,15 +124,15 @@ export function createUrlOverrides(overrides, originalSettings) { return formattedOverrides; } -export function processResults(results) { +export function processResults({ results, overrides }: AnalysisResult) { const timestampFormat = results.java_timestamp_formats !== undefined && results.java_timestamp_formats.length ? results.java_timestamp_formats[0] : undefined; const linesToSample = - results.overrides !== undefined && results.overrides.lines_to_sample !== undefined - ? results.overrides.lines_to_sample + overrides !== undefined && overrides.lines_to_sample !== undefined + ? overrides.lines_to_sample : DEFAULT_LINES_TO_SAMPLE; return { @@ -125,8 +156,8 @@ export function processResults(results) { * @param {string} indexName * @returns {Promise} */ -export async function hasImportPermission(indexName) { - const priv = { +export async function hasImportPermission(indexName: string) { + const priv: { cluster: string[]; index?: any } = { cluster: ['cluster:monitor/nodes/info', 'cluster:admin/ingest/pipeline/put'], }; diff --git a/x-pack/plugins/ml/public/application/management/index.ts b/x-pack/plugins/ml/public/application/management/index.ts index 385140771e08f..a6fe9e1d11953 100644 --- a/x-pack/plugins/ml/public/application/management/index.ts +++ b/x-pack/plugins/ml/public/application/management/index.ts @@ -16,8 +16,6 @@ import { take } from 'rxjs/operators'; import { CoreSetup } from 'kibana/public'; import { MlStartDependencies, MlSetupDependencies } from '../../plugin'; -import { LICENSE_CHECK_STATE } from '../../../../licensing/public'; - import { PLUGIN_ID, PLUGIN_ICON } from '../../../common/constants/app'; import { MINIMUM_FULL_LICENSE } from '../../../common/license'; @@ -27,7 +25,7 @@ export function initManagementSection( ) { const licensing = pluginsSetup.licensing.license$.pipe(take(1)); licensing.subscribe(license => { - if (license.check(PLUGIN_ID, MINIMUM_FULL_LICENSE).state === LICENSE_CHECK_STATE.Valid) { + if (license.check(PLUGIN_ID, MINIMUM_FULL_LICENSE).state === 'valid') { const management = pluginsSetup.management; const mlSection = management.sections.register({ id: PLUGIN_ID, diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/datavisualizer.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/datavisualizer.ts index 9b492530d303d..20332546d9cde 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/datavisualizer.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/datavisualizer.ts @@ -7,6 +7,7 @@ import { http } from '../http_service'; import { basePath } from './index'; +import { ImportResponse } from '../../../../common/types/file_datavisualizer'; export const fileDatavisualizer = { analyzeFile(file: string, params: Record = {}) { @@ -27,7 +28,7 @@ export const fileDatavisualizer = { mappings, ingestPipeline, }: { - id: string; + id: string | undefined; index: string; data: any; settings: any; @@ -43,7 +44,7 @@ export const fileDatavisualizer = { ingestPipeline, }); - return http({ + return http({ path: `${basePath()}/file_data_visualizer/import`, method: 'POST', query, diff --git a/x-pack/plugins/ml/public/application/util/dependency_cache.ts b/x-pack/plugins/ml/public/application/util/dependency_cache.ts index d5605d3bca65f..934a0a5e9ae3a 100644 --- a/x-pack/plugins/ml/public/application/util/dependency_cache.ts +++ b/x-pack/plugins/ml/public/application/util/dependency_cache.ts @@ -23,6 +23,7 @@ import { } from 'kibana/public'; import { SharePluginStart } from 'src/plugins/share/public'; import { SecurityPluginSetup } from '../../../../security/public'; +import { MlConfigType } from '../../../common/types/ml_config'; export interface DependencyCache { timefilter: DataPublicPluginSetup['query']['timefilter'] | null; @@ -42,6 +43,7 @@ export interface DependencyCache { security: SecurityPluginSetup | null; i18n: I18nStart | null; urlGenerators: SharePluginStart['urlGenerators'] | null; + mlConfig: MlConfigType | null; } const cache: DependencyCache = { @@ -62,6 +64,7 @@ const cache: DependencyCache = { security: null, i18n: null, urlGenerators: null, + mlConfig: null, }; export function setDependencyCache(deps: Partial) { @@ -82,6 +85,7 @@ export function setDependencyCache(deps: Partial) { cache.security = deps.security || null; cache.i18n = deps.i18n || null; cache.urlGenerators = deps.urlGenerators || null; + cache.mlConfig = deps.mlConfig || null; } export function getTimefilter() { @@ -202,6 +206,13 @@ export function getGetUrlGenerator() { return cache.urlGenerators.getUrlGenerator; } +export function getMlConfig() { + if (cache.mlConfig === null) { + throw new Error("mlConfig hasn't been initialized"); + } + return cache.mlConfig; +} + export function clearCache() { console.log('clearing dependency cache'); // eslint-disable-line no-console Object.keys(cache).forEach(k => { diff --git a/x-pack/plugins/ml/public/index.ts b/x-pack/plugins/ml/public/index.ts index 8070f94a1264d..4697496270edf 100755 --- a/x-pack/plugins/ml/public/index.ts +++ b/x-pack/plugins/ml/public/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializer } from 'kibana/public'; +import { PluginInitializer, PluginInitializerContext } from 'kibana/public'; import './index.scss'; import { MlPlugin, @@ -19,6 +19,6 @@ export const plugin: PluginInitializer< MlPluginStart, MlSetupDependencies, MlStartDependencies -> = () => new MlPlugin(); +> = (context: PluginInitializerContext) => new MlPlugin(context); export { MlPluginSetup, MlPluginStart }; diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index 62b60f81b672f..b51be4d248683 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -5,18 +5,27 @@ */ import { i18n } from '@kbn/i18n'; -import { Plugin, CoreStart, CoreSetup, AppMountParameters } from 'kibana/public'; +import { + Plugin, + CoreStart, + CoreSetup, + AppMountParameters, + PluginInitializerContext, +} from 'kibana/public'; import { ManagementSetup } from 'src/plugins/management/public'; import { SharePluginStart } from 'src/plugins/share/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { DataPublicPluginStart } from 'src/plugins/data/public'; +import { HomePublicPluginSetup } from 'src/plugins/home/public'; import { SecurityPluginSetup } from '../../security/public'; import { LicensingPluginSetup } from '../../licensing/public'; import { initManagementSection } from './application/management'; import { LicenseManagementUIPluginSetup } from '../../license_management/public'; import { setDependencyCache } from './application/util/dependency_cache'; import { PLUGIN_ID, PLUGIN_ICON } from '../common/constants/app'; +import { registerFeature } from './register_feature'; +import { MlConfigType } from '../common/types/ml_config'; export interface MlStartDependencies { data: DataPublicPluginStart; @@ -28,10 +37,14 @@ export interface MlSetupDependencies { management: ManagementSetup; usageCollection: UsageCollectionSetup; licenseManagement?: LicenseManagementUIPluginSetup; + home: HomePublicPluginSetup; } export class MlPlugin implements Plugin { + constructor(private readonly initializerContext: PluginInitializerContext) {} + setup(core: CoreSetup, pluginsSetup: MlSetupDependencies) { + const mlConfig = this.initializerContext.config.get(); core.application.register({ id: PLUGIN_ID, title: i18n.translate('xpack.ml.plugin.title', { @@ -53,6 +66,8 @@ export class MlPlugin implements Plugin { management: pluginsSetup.management, usageCollection: pluginsSetup.usageCollection, licenseManagement: pluginsSetup.licenseManagement, + home: pluginsSetup.home, + mlConfig, }, { element: params.element, @@ -64,6 +79,8 @@ export class MlPlugin implements Plugin { }, }); + registerFeature(pluginsSetup.home); + initManagementSection(pluginsSetup, core); return {}; } diff --git a/x-pack/plugins/ml/public/register_feature.ts b/x-pack/plugins/ml/public/register_feature.ts new file mode 100644 index 0000000000000..ca60de612c3d5 --- /dev/null +++ b/x-pack/plugins/ml/public/register_feature.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { + HomePublicPluginSetup, + FeatureCatalogueCategory, +} from '../../../../src/plugins/home/public'; +import { PLUGIN_ID } from '../common/constants/app'; + +export const registerFeature = (home: HomePublicPluginSetup) => { + // register ML for the kibana home screen. + // so the file data visualizer appears to allow people to import data + home.environment.update({ ml: true }); + + // register ML so it appears on the Kibana home page + home.featureCatalogue.register({ + id: PLUGIN_ID, + title: i18n.translate('xpack.ml.machineLearningTitle', { + defaultMessage: 'Machine Learning', + }), + description: i18n.translate('xpack.ml.machineLearningDescription', { + defaultMessage: + 'Automatically model the normal behavior of your time series data to detect anomalies.', + }), + icon: 'machineLearningApp', + path: '/app/ml', + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + }); +}; diff --git a/x-pack/plugins/ml/server/client/elasticsearch_ml.ts b/x-pack/plugins/ml/server/client/elasticsearch_ml.ts index caedaed92e5b1..d5c7882a30d20 100644 --- a/x-pack/plugins/ml/server/client/elasticsearch_ml.ts +++ b/x-pack/plugins/ml/server/client/elasticsearch_ml.ts @@ -740,7 +740,7 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any) urls: [ { fmt: - '/_ml/find_file_structure?&charset=<%=charset%>&format=<%=format%>&has_header_row=<%=has_header_row%>&column_names=<%=column_names%>&delimiter=<%=delimiter%>"e=<%=quote%>&should_trim_fields=<%=should_trim_fields%>&grok_pattern=<%=grok_pattern%>×tamp_field=<%=timestamp_field%>×tamp_format=<%=timestamp_format%>&lines_to_sample=<%=lines_to_sample%>', + '/_ml/find_file_structure?&explain=true&charset=<%=charset%>&format=<%=format%>&has_header_row=<%=has_header_row%>&column_names=<%=column_names%>&delimiter=<%=delimiter%>"e=<%=quote%>&should_trim_fields=<%=should_trim_fields%>&grok_pattern=<%=grok_pattern%>×tamp_field=<%=timestamp_field%>×tamp_format=<%=timestamp_format%>&lines_to_sample=<%=lines_to_sample%>', req: { charset: { type: 'string', @@ -778,7 +778,7 @@ export const elasticsearchJsPlugin = (Client: any, config: any, components: any) }, }, { - fmt: '/_ml/find_file_structure', + fmt: '/_ml/find_file_structure?&explain=true', }, ], needBody: true, diff --git a/x-pack/plugins/ml/server/client/error_wrapper.ts b/x-pack/plugins/ml/server/client/error_wrapper.ts index 7f69173295482..de53e4d4345a9 100644 --- a/x-pack/plugins/ml/server/client/error_wrapper.ts +++ b/x-pack/plugins/ml/server/client/error_wrapper.ts @@ -9,9 +9,13 @@ import { ResponseError, CustomHttpResponseOptions } from 'kibana/server'; export function wrapError(error: any): CustomHttpResponseOptions { const boom = isBoom(error) ? error : boomify(error, { statusCode: error.status }); + const statusCode = boom.output.statusCode; return { - body: boom, + body: { + message: boom, + ...(statusCode !== 500 && error.body ? { attributes: { body: error.body } } : {}), + }, headers: boom.output.headers, - statusCode: boom.output.statusCode, + statusCode, }; } diff --git a/x-pack/plugins/ml/server/config.ts b/x-pack/plugins/ml/server/config.ts new file mode 100644 index 0000000000000..7cef6f17bbefb --- /dev/null +++ b/x-pack/plugins/ml/server/config.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginConfigDescriptor } from 'kibana/server'; +import { MlConfigType, configSchema } from '../common/types/ml_config'; + +export const config: PluginConfigDescriptor = { + exposeToBrowser: { + file_data_visualizer: true, + }, + schema: configSchema, +}; diff --git a/x-pack/plugins/ml/server/index.ts b/x-pack/plugins/ml/server/index.ts index 175c20bf49c94..6e638d647a387 100644 --- a/x-pack/plugins/ml/server/index.ts +++ b/x-pack/plugins/ml/server/index.ts @@ -9,3 +9,5 @@ import { MlServerPlugin } from './plugin'; export { MlPluginSetup, MlPluginStart } from './plugin'; export const plugin = (ctx: PluginInitializerContext) => new MlServerPlugin(ctx); + +export { config } from './config'; diff --git a/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts index 9af755c6918fb..d53378b886a99 100644 --- a/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/file_data_visualizer/file_data_visualizer.ts @@ -4,40 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import Boom from 'boom'; import { APICaller } from 'kibana/server'; -import { FindFileStructureResponse } from '../../../common/types/file_datavisualizer'; +import { + AnalysisResult, + FormattedOverrides, + InputOverrides, +} from '../../../common/types/file_datavisualizer'; export type InputData = any[]; -export interface InputOverrides { - [key: string]: string; -} - -export type FormattedOverrides = InputOverrides & { - column_names: string[]; - has_header_row: boolean; - should_trim_fields: boolean; -}; - -export interface AnalysisResult { - results: FindFileStructureResponse; - overrides?: FormattedOverrides; -} - export function fileDataVisualizerProvider(callAsCurrentUser: APICaller) { async function analyzeFile(data: any, overrides: any): Promise { - let results = []; - - try { - results = await callAsCurrentUser('ml.fileStructure', { - body: data, - ...overrides, - }); - } catch (error) { - const err = error.message !== undefined ? error.message : error; - throw Boom.badRequest(err); - } + const results = await callAsCurrentUser('ml.fileStructure', { + body: data, + ...overrides, + }); const { hasOverrides, reducedOverrides } = formatOverrides(overrides); diff --git a/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts index ab8c702cbb12a..9d7009955124f 100644 --- a/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts +++ b/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts @@ -6,39 +6,24 @@ import { APICaller } from 'kibana/server'; import { INDEX_META_DATA_CREATED_BY } from '../../../common/constants/file_datavisualizer'; +import { + ImportResponse, + ImportFailure, + Settings, + Mappings, + IngestPipelineWrapper, +} from '../../../common/types/file_datavisualizer'; import { InputData } from './file_data_visualizer'; -export interface Settings { - pipeline?: string; - index: string; - body: any[]; - [key: string]: any; -} - -export interface Mappings { - [key: string]: any; -} - -export interface InjectPipeline { - id: string; - pipeline: any; -} - -interface Failure { - item: number; - reason: string; - doc: any; -} - export function importDataProvider(callAsCurrentUser: APICaller) { async function importData( id: string, index: string, settings: Settings, mappings: Mappings, - ingestPipeline: InjectPipeline, + ingestPipeline: IngestPipelineWrapper, data: InputData - ) { + ): Promise { let createdIndex; let createdPipelineId; const docCount = data.length; @@ -66,7 +51,7 @@ export function importDataProvider(callAsCurrentUser: APICaller) { createdPipelineId = pipelineId; } - let failures: Failure[] = []; + let failures: ImportFailure[] = []; if (data.length) { const resp = await indexData(index, createdPipelineId, data); if (resp.success === false) { @@ -144,7 +129,7 @@ export function importDataProvider(callAsCurrentUser: APICaller) { }; } } catch (error) { - let failures: Failure[] = []; + let failures: ImportFailure[] = []; let ingestError = false; if (error.errors !== undefined && Array.isArray(error.items)) { // an expected error where some or all of the bulk request @@ -169,7 +154,7 @@ export function importDataProvider(callAsCurrentUser: APICaller) { return await callAsCurrentUser('ingest.putPipeline', { id, body: pipeline }); } - function getFailures(items: any[], data: InputData): Failure[] { + function getFailures(items: any[], data: InputData): ImportFailure[] { const failures = []; for (let i = 0; i < items.length; i++) { const item = items[i]; diff --git a/x-pack/plugins/ml/server/models/file_data_visualizer/index.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/index.ts index 94529dc111696..f8a27fdcd7e1a 100644 --- a/x-pack/plugins/ml/server/models/file_data_visualizer/index.ts +++ b/x-pack/plugins/ml/server/models/file_data_visualizer/index.ts @@ -4,11 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { - fileDataVisualizerProvider, - InputOverrides, - InputData, - AnalysisResult, -} from './file_data_visualizer'; +export { fileDataVisualizerProvider, InputData } from './file_data_visualizer'; -export { importDataProvider, Settings, InjectPipeline, Mappings } from './import_data'; +export { importDataProvider } from './import_data'; diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index 7d3ef116e67ab..c7add12be142c 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -79,19 +79,36 @@ export class MlServerPlugin implements Plugin ({ + executeActions: jest.fn(), + getUiMessage: jest.fn(), +})); + +jest.mock('../lib/alerts/get_prepared_alert', () => ({ + getPreparedAlert: jest.fn(() => { + return { + emailAddress: 'foo@foo.com', + }; + }), +})); + +interface MockServices { + callCluster: jest.Mock; + alertInstanceFactory: jest.Mock; + savedObjectsClient: jest.Mock; +} + +describe('getClusterState', () => { + const services: MockServices | AlertServices = { + callCluster: jest.fn(), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), + }; + + const params: AlertCommonParams = { + dateFormat: 'YYYY', + timezone: 'UTC', + }; + + const emailAddress = 'foo@foo.com'; + const clusterUuid = 'kdksdfj434'; + const clusterName = 'monitoring_test'; + const cluster = { clusterUuid, clusterName }; + + async function setupAlert( + previousState: AlertClusterStateState, + newState: AlertClusterStateState + ): Promise { + const logger: Logger = { + warn: jest.fn(), + log: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), + info: jest.fn(), + get: jest.fn(), + }; + const getLogger = (): Logger => logger; + const ccrEnabled = false; + (getPreparedAlert as jest.Mock).mockImplementation(() => ({ + emailAddress, + data: [ + { + state: newState, + clusterUuid, + }, + ], + clusters: [cluster], + })); + + const alert = getClusterState(null as any, null as any, getLogger, ccrEnabled); + const state: AlertCommonState = { + [clusterUuid]: { + state: previousState, + ui: { + isFiring: false, + severity: 0, + message: null, + resolvedMS: 0, + lastCheckedMS: 0, + triggeredMS: 0, + }, + } as AlertClusterStatePerClusterState, + }; + + return (await alert.executor({ services, params, state } as any)) as AlertCommonState; + } + + afterEach(() => { + (executeActions as jest.Mock).mockClear(); + }); + + it('should configure the alert properly', () => { + const alert = getClusterState(null as any, null as any, jest.fn(), false); + expect(alert.id).toBe(ALERT_TYPE_CLUSTER_STATE); + expect(alert.actionGroups).toEqual([{ id: 'default', name: 'Default' }]); + }); + + it('should alert if green -> yellow', async () => { + const result = await setupAlert(AlertClusterStateState.Green, AlertClusterStateState.Yellow); + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + AlertClusterStateState.Yellow, + emailAddress + ); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Yellow); + expect(clusterResult.ui.isFiring).toBe(true); + expect(clusterResult.ui.resolvedMS).toBe(0); + }); + + it('should alert if yellow -> green', async () => { + const result = await setupAlert(AlertClusterStateState.Yellow, AlertClusterStateState.Green); + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + AlertClusterStateState.Green, + emailAddress, + true + ); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Green); + expect(clusterResult.ui.resolvedMS).toBeGreaterThan(0); + }); + + it('should alert if green -> red', async () => { + const result = await setupAlert(AlertClusterStateState.Green, AlertClusterStateState.Red); + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + AlertClusterStateState.Red, + emailAddress + ); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Red); + expect(clusterResult.ui.isFiring).toBe(true); + expect(clusterResult.ui.resolvedMS).toBe(0); + }); + + it('should alert if red -> green', async () => { + const result = await setupAlert(AlertClusterStateState.Red, AlertClusterStateState.Green); + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + AlertClusterStateState.Green, + emailAddress, + true + ); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Green); + expect(clusterResult.ui.resolvedMS).toBeGreaterThan(0); + }); + + it('should not alert if red -> yellow', async () => { + const result = await setupAlert(AlertClusterStateState.Red, AlertClusterStateState.Yellow); + expect(executeActions).not.toHaveBeenCalled(); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Red); + expect(clusterResult.ui.resolvedMS).toBe(0); + }); + + it('should not alert if yellow -> red', async () => { + const result = await setupAlert(AlertClusterStateState.Yellow, AlertClusterStateState.Red); + expect(executeActions).not.toHaveBeenCalled(); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Yellow); + expect(clusterResult.ui.resolvedMS).toBe(0); + }); + + it('should not alert if green -> green', async () => { + const result = await setupAlert(AlertClusterStateState.Green, AlertClusterStateState.Green); + expect(executeActions).not.toHaveBeenCalled(); + const clusterResult = result[clusterUuid] as AlertClusterStatePerClusterState; + expect(clusterResult.state).toBe(AlertClusterStateState.Green); + expect(clusterResult.ui.resolvedMS).toBe(0); + }); +}); diff --git a/x-pack/plugins/monitoring/server/alerts/cluster_state.ts b/x-pack/plugins/monitoring/server/alerts/cluster_state.ts new file mode 100644 index 0000000000000..9a5805b8af7ce --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/cluster_state.ts @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment-timezone'; +import { i18n } from '@kbn/i18n'; +import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'src/core/server'; +import { ALERT_TYPE_CLUSTER_STATE } from '../../common/constants'; +import { AlertType } from '../../../alerting/server'; +import { executeActions, getUiMessage } from '../lib/alerts/cluster_state.lib'; +import { + AlertCommonExecutorOptions, + AlertCommonState, + AlertClusterStatePerClusterState, + AlertCommonCluster, +} from './types'; +import { AlertClusterStateState } from './enums'; +import { getPreparedAlert } from '../lib/alerts/get_prepared_alert'; +import { fetchClusterState } from '../lib/alerts/fetch_cluster_state'; + +export const getClusterState = ( + getUiSettingsService: () => Promise, + monitoringCluster: ICustomClusterClient, + getLogger: (...scopes: string[]) => Logger, + ccsEnabled: boolean +): AlertType => { + const logger = getLogger(ALERT_TYPE_CLUSTER_STATE); + return { + id: ALERT_TYPE_CLUSTER_STATE, + name: 'Monitoring Alert - Cluster Status', + actionGroups: [ + { + id: 'default', + name: i18n.translate('xpack.monitoring.alerts.clusterState.actionGroups.default', { + defaultMessage: 'Default', + }), + }, + ], + defaultActionGroupId: 'default', + async executor({ + services, + params, + state, + }: AlertCommonExecutorOptions): Promise { + logger.debug( + `Firing alert with params: ${JSON.stringify(params)} and state: ${JSON.stringify(state)}` + ); + + const preparedAlert = await getPreparedAlert( + ALERT_TYPE_CLUSTER_STATE, + getUiSettingsService, + monitoringCluster, + logger, + ccsEnabled, + services, + fetchClusterState + ); + + if (!preparedAlert) { + return state; + } + + const { emailAddress, data: states, clusters } = preparedAlert; + + const result: AlertCommonState = { ...state }; + const defaultAlertState: AlertClusterStatePerClusterState = { + state: AlertClusterStateState.Green, + ui: { + isFiring: false, + message: null, + severity: 0, + resolvedMS: 0, + triggeredMS: 0, + lastCheckedMS: 0, + }, + }; + + for (const clusterState of states) { + const alertState: AlertClusterStatePerClusterState = + (state[clusterState.clusterUuid] as AlertClusterStatePerClusterState) || + defaultAlertState; + const cluster = clusters.find( + (c: AlertCommonCluster) => c.clusterUuid === clusterState.clusterUuid + ); + if (!cluster) { + logger.warn(`Unable to find cluster for clusterUuid='${clusterState.clusterUuid}'`); + continue; + } + const isNonGreen = clusterState.state !== AlertClusterStateState.Green; + const severity = clusterState.state === AlertClusterStateState.Red ? 2100 : 1100; + + const ui = alertState.ui; + let triggered = ui.triggeredMS; + let resolved = ui.resolvedMS; + let message = ui.message || {}; + let lastState = alertState.state; + const instance = services.alertInstanceFactory(ALERT_TYPE_CLUSTER_STATE); + + if (isNonGreen) { + if (lastState === AlertClusterStateState.Green) { + logger.debug(`Cluster state changed from green to ${clusterState.state}`); + executeActions(instance, cluster, clusterState.state, emailAddress); + lastState = clusterState.state; + triggered = moment().valueOf(); + } + message = getUiMessage(clusterState.state); + resolved = 0; + } else if (!isNonGreen && lastState !== AlertClusterStateState.Green) { + logger.debug(`Cluster state changed from ${lastState} to green`); + executeActions(instance, cluster, clusterState.state, emailAddress, true); + lastState = clusterState.state; + message = getUiMessage(clusterState.state, true); + resolved = moment().valueOf(); + } + + result[clusterState.clusterUuid] = { + state: lastState, + ui: { + message, + isFiring: isNonGreen, + severity, + resolvedMS: resolved, + triggeredMS: triggered, + lastCheckedMS: moment().valueOf(), + }, + } as AlertClusterStatePerClusterState; + } + + return result; + }, + }; +}; diff --git a/x-pack/plugins/monitoring/server/alerts/enums.ts b/x-pack/plugins/monitoring/server/alerts/enums.ts new file mode 100644 index 0000000000000..ccff588743af1 --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/enums.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export enum AlertClusterStateState { + Green = 'green', + Red = 'red', + Yellow = 'yellow', +} + +export enum AlertCommonPerClusterMessageTokenType { + Time = 'time', + Link = 'link', +} diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts index 0773af6e7f070..92047e300bc1f 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration.test.ts @@ -6,42 +6,31 @@ import moment from 'moment-timezone'; import { getLicenseExpiration } from './license_expiration'; -import { - ALERT_TYPE_LICENSE_EXPIRATION, - MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS, -} from '../../common/constants'; +import { ALERT_TYPE_LICENSE_EXPIRATION } from '../../common/constants'; import { Logger } from 'src/core/server'; -import { AlertServices, AlertInstance } from '../../../alerting/server'; +import { AlertServices } from '../../../alerting/server'; import { savedObjectsClientMock } from 'src/core/server/mocks'; import { - AlertState, - AlertClusterState, - AlertParams, - LicenseExpirationAlertExecutorOptions, + AlertCommonParams, + AlertCommonState, + AlertLicensePerClusterState, + AlertLicense, } from './types'; -import { SavedObject, SavedObjectAttributes } from 'src/core/server'; -import { SavedObjectsClientContract } from 'src/core/server'; - -function fillLicense(license: any, clusterUuid?: string) { - return { - hits: { - hits: [ - { - _source: { - license, - cluster_uuid: clusterUuid, - }, - }, - ], - }, - }; -} - -const clusterUuid = 'a4545jhjb'; -const params: AlertParams = { - dateFormat: 'YYYY', - timezone: 'UTC', -}; +import { executeActions } from '../lib/alerts/license_expiration.lib'; +import { PreparedAlert, getPreparedAlert } from '../lib/alerts/get_prepared_alert'; + +jest.mock('../lib/alerts/license_expiration.lib', () => ({ + executeActions: jest.fn(), + getUiMessage: jest.fn(), +})); + +jest.mock('../lib/alerts/get_prepared_alert', () => ({ + getPreparedAlert: jest.fn(() => { + return { + emailAddress: 'foo@foo.com', + }; + }), +})); interface MockServices { callCluster: jest.Mock; @@ -49,428 +38,169 @@ interface MockServices { savedObjectsClient: jest.Mock; } -const alertExecutorOptions: LicenseExpirationAlertExecutorOptions = { - alertId: '', - startedAt: new Date(), - services: { - callCluster: (path: string, opts: any) => new Promise(resolve => resolve()), - alertInstanceFactory: (id: string) => new AlertInstance(), - savedObjectsClient: {} as jest.Mocked, - }, - params: {}, - state: {}, - spaceId: '', - name: '', - tags: [], - previousStartedAt: null, - createdBy: null, - updatedBy: null, -}; - describe('getLicenseExpiration', () => { - const emailAddress = 'foo@foo.com'; - const getUiSettingsService: any = () => ({ - asScopedToClient: (): any => ({ - get: () => new Promise(resolve => resolve(emailAddress)), - }), - }); - const monitoringCluster: any = null; - const logger: Logger = { - warn: jest.fn(), - log: jest.fn(), - debug: jest.fn(), - trace: jest.fn(), - error: jest.fn(), - fatal: jest.fn(), - info: jest.fn(), - get: jest.fn(), + const services: MockServices | AlertServices = { + callCluster: jest.fn(), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), }; - const getLogger = (): Logger => logger; - const ccrEnabled = false; - afterEach(() => { - (logger.warn as jest.Mock).mockClear(); - }); - - it('should have the right id and actionGroups', () => { - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); - expect(alert.id).toBe(ALERT_TYPE_LICENSE_EXPIRATION); - expect(alert.actionGroups).toEqual([{ id: 'default', name: 'Default' }]); - }); + const params: AlertCommonParams = { + dateFormat: 'YYYY', + timezone: 'UTC', + }; - it('should return the state if no license is provided', async () => { - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); + const emailAddress = 'foo@foo.com'; + const clusterUuid = 'kdksdfj434'; + const clusterName = 'monitoring_test'; + const dateFormat = 'YYYY-MM-DD'; + const cluster = { clusterUuid, clusterName }; + const defaultUiState = { + isFiring: false, + severity: 0, + message: null, + resolvedMS: 0, + lastCheckedMS: 0, + triggeredMS: 0, + }; - const services: MockServices | AlertServices = { - callCluster: jest.fn(), - alertInstanceFactory: jest.fn(), - savedObjectsClient: savedObjectsClientMock.create(), + async function setupAlert( + license: AlertLicense | null, + expiredCheckDateMS: number, + preparedAlertResponse: PreparedAlert | null | undefined = undefined + ): Promise { + const logger: Logger = { + warn: jest.fn(), + log: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), + info: jest.fn(), + get: jest.fn(), }; - const state = { foo: 1 }; - - const result = await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - }); - - expect(result).toEqual(state); - }); + const getLogger = (): Logger => logger; + const ccrEnabled = false; + (getPreparedAlert as jest.Mock).mockImplementation(() => { + if (preparedAlertResponse !== undefined) { + return preparedAlertResponse; + } - it('should log a warning if no email is provided', async () => { - const customGetUiSettingsService: any = () => ({ - asScopedToClient: () => ({ - get: () => null, - }), + return { + emailAddress, + data: [license], + clusters: [cluster], + dateFormat, + }; }); - const alert = getLicenseExpiration( - customGetUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); - const services = { - callCluster: jest.fn( - (method: string, { filterPath }): Promise => { - return new Promise(resolve => { - if (filterPath.includes('hits.hits._source.license.*')) { - resolve( - fillLicense({ - status: 'good', - type: 'basic', - expiry_date_in_millis: moment() - .add(7, 'days') - .valueOf(), - }) - ); - } - resolve({}); - }); - } - ), - alertInstanceFactory: jest.fn(), - savedObjectsClient: savedObjectsClientMock.create(), + const alert = getLicenseExpiration(null as any, null as any, getLogger, ccrEnabled); + const state: AlertCommonState = { + [clusterUuid]: { + expiredCheckDateMS, + ui: { ...defaultUiState }, + } as AlertLicensePerClusterState, }; - const state = {}; + return (await alert.executor({ services, params, state } as any)) as AlertCommonState; + } - await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - }); - - expect((logger.warn as jest.Mock).mock.calls.length).toBe(1); - expect(logger.warn).toHaveBeenCalledWith( - `Unable to send email for ${ALERT_TYPE_LICENSE_EXPIRATION} because there is no email configured.` - ); + afterEach(() => { + (executeActions as jest.Mock).mockClear(); + (getPreparedAlert as jest.Mock).mockClear(); }); - it('should fire actions if going to expire', async () => { - const scheduleActions = jest.fn(); - const alertInstanceFactory = jest.fn( - (id: string): AlertInstance => { - const instance = new AlertInstance(); - instance.scheduleActions = scheduleActions; - return instance; - } - ); + it('should have the right id and actionGroups', () => { + const alert = getLicenseExpiration(null as any, null as any, jest.fn(), false); + expect(alert.id).toBe(ALERT_TYPE_LICENSE_EXPIRATION); + expect(alert.actionGroups).toEqual([{ id: 'default', name: 'Default' }]); + }); - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); + it('should return the state if no license is provided', async () => { + const result = await setupAlert(null, 0, null); + expect(result[clusterUuid].ui).toEqual(defaultUiState); + }); - const savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get.mockReturnValue( - new Promise(resolve => { - const savedObject: SavedObject = { - id: '', - type: '', - references: [], - attributes: { - [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, - }, - }; - resolve(savedObject); - }) - ); - const services = { - callCluster: jest.fn( - (method: string, { filterPath }): Promise => { - return new Promise(resolve => { - if (filterPath.includes('hits.hits._source.license.*')) { - resolve( - fillLicense( - { - status: 'active', - type: 'gold', - expiry_date_in_millis: moment() - .add(7, 'days') - .valueOf(), - }, - clusterUuid - ) - ); - } - resolve({}); - }); - } - ), - alertInstanceFactory, - savedObjectsClient, + it('should fire actions if going to expire', async () => { + const expiryDateMS = moment() + .add(7, 'days') + .valueOf(); + const license = { + status: 'active', + type: 'gold', + expiryDateMS, + clusterUuid, }; - - const state = {}; - - const result: AlertState = (await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - })) as AlertState; - - const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; - + const result = await setupAlert(license, 0); + const newState = result[clusterUuid] as AlertLicensePerClusterState; expect(newState.expiredCheckDateMS > 0).toBe(true); - expect(scheduleActions.mock.calls.length).toBe(1); - expect(scheduleActions.mock.calls[0][1].subject).toBe( - 'NEW X-Pack Monitoring: License Expiration' + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + moment.utc(expiryDateMS), + dateFormat, + emailAddress ); - expect(scheduleActions.mock.calls[0][1].to).toBe(emailAddress); }); it('should fire actions if the user fixed their license', async () => { - const scheduleActions = jest.fn(); - const alertInstanceFactory = jest.fn( - (id: string): AlertInstance => { - const instance = new AlertInstance(); - instance.scheduleActions = scheduleActions; - return instance; - } - ); - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); - - const savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get.mockReturnValue( - new Promise(resolve => { - const savedObject: SavedObject = { - id: '', - type: '', - references: [], - attributes: { - [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, - }, - }; - resolve(savedObject); - }) - ); - const services = { - callCluster: jest.fn( - (method: string, { filterPath }): Promise => { - return new Promise(resolve => { - if (filterPath.includes('hits.hits._source.license.*')) { - resolve( - fillLicense( - { - status: 'active', - type: 'gold', - expiry_date_in_millis: moment() - .add(120, 'days') - .valueOf(), - }, - clusterUuid - ) - ); - } - resolve({}); - }); - } - ), - alertInstanceFactory, - savedObjectsClient, - }; - - const state: AlertState = { - [clusterUuid]: { - expiredCheckDateMS: moment() - .subtract(1, 'day') - .valueOf(), - ui: { isFiring: true, severity: 0, message: null, resolvedMS: 0, expirationTime: 0 }, - }, + const expiryDateMS = moment() + .add(365, 'days') + .valueOf(); + const license = { + status: 'active', + type: 'gold', + expiryDateMS, + clusterUuid, }; - - const result: AlertState = (await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - })) as AlertState; - - const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; + const result = await setupAlert(license, 100); + const newState = result[clusterUuid] as AlertLicensePerClusterState; expect(newState.expiredCheckDateMS).toBe(0); - expect(scheduleActions.mock.calls.length).toBe(1); - expect(scheduleActions.mock.calls[0][1].subject).toBe( - 'RESOLVED X-Pack Monitoring: License Expiration' + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + moment.utc(expiryDateMS), + dateFormat, + emailAddress, + true ); - expect(scheduleActions.mock.calls[0][1].to).toBe(emailAddress); }); it('should not fire actions for trial license that expire in more than 14 days', async () => { - const scheduleActions = jest.fn(); - const alertInstanceFactory = jest.fn( - (id: string): AlertInstance => { - const instance = new AlertInstance(); - instance.scheduleActions = scheduleActions; - return instance; - } - ); - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); - - const savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get.mockReturnValue( - new Promise(resolve => { - const savedObject: SavedObject = { - id: '', - type: '', - references: [], - attributes: { - [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, - }, - }; - resolve(savedObject); - }) - ); - const services = { - callCluster: jest.fn( - (method: string, { filterPath }): Promise => { - return new Promise(resolve => { - if (filterPath.includes('hits.hits._source.license.*')) { - resolve( - fillLicense( - { - status: 'active', - type: 'trial', - expiry_date_in_millis: moment() - .add(15, 'days') - .valueOf(), - }, - clusterUuid - ) - ); - } - resolve({}); - }); - } - ), - alertInstanceFactory, - savedObjectsClient, + const expiryDateMS = moment() + .add(20, 'days') + .valueOf(); + const license = { + status: 'active', + type: 'trial', + expiryDateMS, + clusterUuid, }; - - const state = {}; - const result: AlertState = (await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - })) as AlertState; - - const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; - expect(newState.expiredCheckDateMS).toBe(undefined); - expect(scheduleActions).not.toHaveBeenCalled(); + const result = await setupAlert(license, 0); + const newState = result[clusterUuid] as AlertLicensePerClusterState; + expect(newState.expiredCheckDateMS).toBe(0); + expect(executeActions).not.toHaveBeenCalled(); }); it('should fire actions for trial license that in 14 days or less', async () => { - const scheduleActions = jest.fn(); - const alertInstanceFactory = jest.fn( - (id: string): AlertInstance => { - const instance = new AlertInstance(); - instance.scheduleActions = scheduleActions; - return instance; - } - ); - const alert = getLicenseExpiration( - getUiSettingsService, - monitoringCluster, - getLogger, - ccrEnabled - ); - - const savedObjectsClient = savedObjectsClientMock.create(); - savedObjectsClient.get.mockReturnValue( - new Promise(resolve => { - const savedObject: SavedObject = { - id: '', - type: '', - references: [], - attributes: { - [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, - }, - }; - resolve(savedObject); - }) - ); - const services = { - callCluster: jest.fn( - (method: string, { filterPath }): Promise => { - return new Promise(resolve => { - if (filterPath.includes('hits.hits._source.license.*')) { - resolve( - fillLicense( - { - status: 'active', - type: 'trial', - expiry_date_in_millis: moment() - .add(13, 'days') - .valueOf(), - }, - clusterUuid - ) - ); - } - resolve({}); - }); - } - ), - alertInstanceFactory, - savedObjectsClient, + const expiryDateMS = moment() + .add(7, 'days') + .valueOf(); + const license = { + status: 'active', + type: 'trial', + expiryDateMS, + clusterUuid, }; - - const state = {}; - const result: AlertState = (await alert.executor({ - ...alertExecutorOptions, - services, - params, - state, - })) as AlertState; - - const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; + const result = await setupAlert(license, 0); + const newState = result[clusterUuid] as AlertLicensePerClusterState; expect(newState.expiredCheckDateMS > 0).toBe(true); - expect(scheduleActions.mock.calls.length).toBe(1); + expect(executeActions).toHaveBeenCalledWith( + undefined, + cluster, + moment.utc(expiryDateMS), + dateFormat, + emailAddress + ); }); }); diff --git a/x-pack/plugins/monitoring/server/alerts/license_expiration.ts b/x-pack/plugins/monitoring/server/alerts/license_expiration.ts index 93397ff3641ae..2e5356150086b 100644 --- a/x-pack/plugins/monitoring/server/alerts/license_expiration.ts +++ b/x-pack/plugins/monitoring/server/alerts/license_expiration.ts @@ -5,24 +5,20 @@ */ import moment from 'moment-timezone'; -import { get } from 'lodash'; import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'src/core/server'; import { i18n } from '@kbn/i18n'; -import { ALERT_TYPE_LICENSE_EXPIRATION, INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants'; +import { ALERT_TYPE_LICENSE_EXPIRATION } from '../../common/constants'; import { AlertType } from '../../../../plugins/alerting/server'; import { fetchLicenses } from '../lib/alerts/fetch_licenses'; -import { fetchDefaultEmailAddress } from '../lib/alerts/fetch_default_email_address'; -import { fetchClusters } from '../lib/alerts/fetch_clusters'; -import { fetchAvailableCcs } from '../lib/alerts/fetch_available_ccs'; import { - AlertLicense, - AlertState, - AlertClusterState, - AlertClusterUiState, - LicenseExpirationAlertExecutorOptions, + AlertCommonState, + AlertLicensePerClusterState, + AlertCommonExecutorOptions, + AlertCommonCluster, + AlertLicensePerClusterUiState, } from './types'; -import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; import { executeActions, getUiMessage } from '../lib/alerts/license_expiration.lib'; +import { getPreparedAlert } from '../lib/alerts/get_prepared_alert'; const EXPIRES_DAYS = [60, 30, 14, 7]; @@ -32,14 +28,6 @@ export const getLicenseExpiration = ( getLogger: (...scopes: string[]) => Logger, ccsEnabled: boolean ): AlertType => { - async function getCallCluster(services: any): Promise { - if (!monitoringCluster) { - return services.callCluster; - } - - return monitoringCluster.callAsInternalUser; - } - const logger = getLogger(ALERT_TYPE_LICENSE_EXPIRATION); return { id: ALERT_TYPE_LICENSE_EXPIRATION, @@ -53,54 +41,50 @@ export const getLicenseExpiration = ( }, ], defaultActionGroupId: 'default', - async executor({ - services, - params, - state, - }: LicenseExpirationAlertExecutorOptions): Promise { + async executor({ services, params, state }: AlertCommonExecutorOptions): Promise { logger.debug( `Firing alert with params: ${JSON.stringify(params)} and state: ${JSON.stringify(state)}` ); - const callCluster = await getCallCluster(services); - - // Support CCS use cases by querying to find available remote clusters - // and then adding those to the index pattern we are searching against - let esIndexPattern = INDEX_PATTERN_ELASTICSEARCH; - if (ccsEnabled) { - const availableCcs = await fetchAvailableCcs(callCluster); - if (availableCcs.length > 0) { - esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); - } - } - - const clusters = await fetchClusters(callCluster, esIndexPattern); + const preparedAlert = await getPreparedAlert( + ALERT_TYPE_LICENSE_EXPIRATION, + getUiSettingsService, + monitoringCluster, + logger, + ccsEnabled, + services, + fetchLicenses + ); - // Fetch licensing information from cluster_stats documents - const licenses: AlertLicense[] = await fetchLicenses(callCluster, clusters, esIndexPattern); - if (licenses.length === 0) { - logger.warn(`No license found for ${ALERT_TYPE_LICENSE_EXPIRATION}.`); + if (!preparedAlert) { return state; } - const uiSettings = (await getUiSettingsService()).asScopedToClient( - services.savedObjectsClient - ); - const dateFormat: string = await uiSettings.get('dateFormat'); - const timezone: string = await uiSettings.get('dateFormat:tz'); - const emailAddress = await fetchDefaultEmailAddress(uiSettings); - if (!emailAddress) { - // TODO: we can do more here - logger.warn( - `Unable to send email for ${ALERT_TYPE_LICENSE_EXPIRATION} because there is no email configured.` - ); - return; - } + const { emailAddress, data: licenses, clusters, dateFormat } = preparedAlert; - const result: AlertState = { ...state }; + const result: AlertCommonState = { ...state }; + const defaultAlertState: AlertLicensePerClusterState = { + expiredCheckDateMS: 0, + ui: { + isFiring: false, + message: null, + severity: 0, + resolvedMS: 0, + lastCheckedMS: 0, + triggeredMS: 0, + }, + }; for (const license of licenses) { - const licenseState: AlertClusterState = state[license.clusterUuid] || {}; + const alertState: AlertLicensePerClusterState = + (state[license.clusterUuid] as AlertLicensePerClusterState) || defaultAlertState; + const cluster = clusters.find( + (c: AlertCommonCluster) => c.clusterUuid === license.clusterUuid + ); + if (!cluster) { + logger.warn(`Unable to find cluster for clusterUuid='${license.clusterUuid}'`); + continue; + } const $expiry = moment.utc(license.expiryDateMS); let isExpired = false; let severity = 0; @@ -123,31 +107,26 @@ export const getLicenseExpiration = ( } } - const ui: AlertClusterUiState = get(licenseState, 'ui', { - isFiring: false, - message: null, - severity: 0, - resolvedMS: 0, - expirationTime: 0, - }); + const ui = alertState.ui; + let triggered = ui.triggeredMS; let resolved = ui.resolvedMS; let message = ui.message; - let expiredCheckDate = licenseState.expiredCheckDateMS; + let expiredCheckDate = alertState.expiredCheckDateMS; const instance = services.alertInstanceFactory(ALERT_TYPE_LICENSE_EXPIRATION); if (isExpired) { - if (!licenseState.expiredCheckDateMS) { + if (!alertState.expiredCheckDateMS) { logger.debug(`License will expire soon, sending email`); - executeActions(instance, license, $expiry, dateFormat, emailAddress); - expiredCheckDate = moment().valueOf(); + executeActions(instance, cluster, $expiry, dateFormat, emailAddress); + expiredCheckDate = triggered = moment().valueOf(); } - message = getUiMessage(license, timezone); + message = getUiMessage(); resolved = 0; - } else if (!isExpired && licenseState.expiredCheckDateMS) { + } else if (!isExpired && alertState.expiredCheckDateMS) { logger.debug(`License expiration has been resolved, sending email`); - executeActions(instance, license, $expiry, dateFormat, emailAddress, true); + executeActions(instance, cluster, $expiry, dateFormat, emailAddress, true); expiredCheckDate = 0; - message = getUiMessage(license, timezone, true); + message = getUiMessage(true); resolved = moment().valueOf(); } @@ -159,8 +138,10 @@ export const getLicenseExpiration = ( isFiring: expiredCheckDate > 0, severity, resolvedMS: resolved, - }, - }; + triggeredMS: triggered, + lastCheckedMS: moment().valueOf(), + } as AlertLicensePerClusterUiState, + } as AlertLicensePerClusterState; } return result; diff --git a/x-pack/plugins/monitoring/server/alerts/types.d.ts b/x-pack/plugins/monitoring/server/alerts/types.d.ts index ff47d6f2ad4dc..b689d008b51a7 100644 --- a/x-pack/plugins/monitoring/server/alerts/types.d.ts +++ b/x-pack/plugins/monitoring/server/alerts/types.d.ts @@ -5,41 +5,79 @@ */ import { Moment } from 'moment'; import { AlertExecutorOptions } from '../../../alerting/server'; +import { AlertClusterStateState, AlertCommonPerClusterMessageTokenType } from './enums'; export interface AlertLicense { status: string; type: string; expiryDateMS: number; clusterUuid: string; - clusterName: string; } -export interface AlertState { - [clusterUuid: string]: AlertClusterState; +export interface AlertClusterState { + state: AlertClusterStateState; + clusterUuid: string; +} + +export interface AlertCommonState { + [clusterUuid: string]: AlertCommonPerClusterState; } -export interface AlertClusterState { - expiredCheckDateMS: number | Moment; - ui: AlertClusterUiState; +export interface AlertCommonPerClusterState { + ui: AlertCommonPerClusterUiState; } -export interface AlertClusterUiState { +export interface AlertClusterStatePerClusterState extends AlertCommonPerClusterState { + state: AlertClusterStateState; +} + +export interface AlertLicensePerClusterState extends AlertCommonPerClusterState { + expiredCheckDateMS: number; +} + +export interface AlertCommonPerClusterUiState { isFiring: boolean; severity: number; - message: string | null; + message: AlertCommonPerClusterMessage | null; resolvedMS: number; + lastCheckedMS: number; + triggeredMS: number; +} + +export interface AlertCommonPerClusterMessage { + text: string; // Do this. #link this is a link #link + tokens?: AlertCommonPerClusterMessageToken[]; +} + +export interface AlertCommonPerClusterMessageToken { + startToken: string; + endToken?: string; + type: AlertCommonPerClusterMessageTokenType; +} + +export interface AlertCommonPerClusterMessageLinkToken extends AlertCommonPerClusterMessageToken { + url?: string; +} + +export interface AlertCommonPerClusterMessageTimeToken extends AlertCommonPerClusterMessageToken { + isRelative: boolean; + isAbsolute: boolean; +} + +export interface AlertLicensePerClusterUiState extends AlertCommonPerClusterUiState { expirationTime: number; } -export interface AlertCluster { +export interface AlertCommonCluster { clusterUuid: string; + clusterName: string; } -export interface LicenseExpirationAlertExecutorOptions extends AlertExecutorOptions { - state: AlertState; +export interface AlertCommonExecutorOptions extends AlertExecutorOptions { + state: AlertCommonState; } -export interface AlertParams { +export interface AlertCommonParams { dateFormat: string; timezone: string; } diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_kibana_usage_collector.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_kibana_usage_collector.ts deleted file mode 100644 index 2c40ac56e19ec..0000000000000 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_kibana_usage_collector.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { get, snakeCase } from 'lodash'; -import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; -import { KIBANA_USAGE_TYPE, KIBANA_STATS_TYPE_MONITORING } from '../../../common/constants'; - -const TYPES = [ - 'dashboard', - 'visualization', - 'search', - 'index-pattern', - 'graph-workspace', - 'timelion-sheet', -]; - -/** - * Fetches saved object counts by querying the .kibana index - */ -export function getKibanaUsageCollector(usageCollection: any, kibanaIndex: string) { - return usageCollection.makeUsageCollector({ - type: KIBANA_USAGE_TYPE, - isReady: () => true, - async fetch(callCluster: CallCluster) { - const savedObjectCountSearchParams = { - index: kibanaIndex, - ignoreUnavailable: true, - filterPath: 'aggregations.types.buckets', - body: { - size: 0, - query: { - terms: { type: TYPES }, - }, - aggs: { - types: { - terms: { field: 'type', size: TYPES.length }, - }, - }, - }, - }; - - const resp = await callCluster('search', savedObjectCountSearchParams); - const buckets: any = get(resp, 'aggregations.types.buckets', []); - - // get the doc_count from each bucket - const bucketCounts = buckets.reduce( - (acc: any, bucket: any) => ({ - ...acc, - [bucket.key]: bucket.doc_count, - }), - {} - ); - - return { - index: kibanaIndex, - ...TYPES.reduce( - (acc, type) => ({ - // combine the bucketCounts and 0s for types that don't have documents - ...acc, - [snakeCase(type)]: { - total: bucketCounts[type] || 0, - }, - }), - {} - ), - }; - }, - - /* - * Format the response data into a model for internal upload - * 1. Make this data part of the "kibana_stats" type - * 2. Organize the payload in the usage namespace of the data payload (usage.index, etc) - */ - formatForBulkUpload: (result: any) => { - return { - type: KIBANA_STATS_TYPE_MONITORING, - payload: { - usage: result, - }, - }; - }, - }); -} diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_ops_stats_collector.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_ops_stats_collector.ts deleted file mode 100644 index 85357f786ddc1..0000000000000 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/get_ops_stats_collector.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Observable } from 'rxjs'; -import { cloneDeep } from 'lodash'; -import moment from 'moment'; -import { OpsMetrics } from 'kibana/server'; -import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { KIBANA_STATS_TYPE_MONITORING } from '../../../common/constants'; - -interface MonitoringOpsMetrics extends OpsMetrics { - timestamp: string; -} - -/* - * Initialize a collector for Kibana Ops Stats - */ -export function getOpsStatsCollector( - usageCollection: UsageCollectionSetup, - metrics$: Observable -) { - let lastMetrics: MonitoringOpsMetrics | null = null; - metrics$.subscribe(_metrics => { - const metrics: any = cloneDeep(_metrics); - // Ensure we only include the same data that Metricbeat collection would get - delete metrics.process.pid; - metrics.response_times = { - average: metrics.response_times.avg_in_millis, - max: metrics.response_times.max_in_millis, - }; - delete metrics.requests.statusCodes; - lastMetrics = { - ...metrics, - timestamp: moment.utc().toISOString(), - }; - }); - - return usageCollection.makeStatsCollector({ - type: KIBANA_STATS_TYPE_MONITORING, - isReady: () => !!lastMetrics, - fetch: () => lastMetrics, - }); -} diff --git a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts index e41b1512f1b29..dcd35b0d323eb 100644 --- a/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts +++ b/x-pack/plugins/monitoring/server/kibana_monitoring/collectors/index.ts @@ -3,20 +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 { Observable } from 'rxjs'; -import { OpsMetrics } from 'kibana/server'; -import { getKibanaUsageCollector } from './get_kibana_usage_collector'; -import { getOpsStatsCollector } from './get_ops_stats_collector'; + +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { getSettingsCollector } from './get_settings_collector'; import { MonitoringConfig } from '../../config'; export function registerCollectors( - usageCollection: any, - config: MonitoringConfig, - opsMetrics$: Observable, - kibanaIndex: string + usageCollection: UsageCollectionSetup, + config: MonitoringConfig ) { - usageCollection.registerCollector(getOpsStatsCollector(usageCollection, opsMetrics$)); - usageCollection.registerCollector(getKibanaUsageCollector(usageCollection, kibanaIndex)); usageCollection.registerCollector(getSettingsCollector(usageCollection, config)); } diff --git a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.test.ts new file mode 100644 index 0000000000000..81e375734cc50 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.test.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { executeActions, getUiMessage } from './cluster_state.lib'; +import { AlertClusterStateState } from '../../alerts/enums'; +import { AlertCommonPerClusterMessageLinkToken } from '../../alerts/types'; + +describe('clusterState lib', () => { + describe('executeActions', () => { + const clusterName = 'clusterA'; + const instance: any = { scheduleActions: jest.fn() }; + const license: any = { clusterName }; + const status = AlertClusterStateState.Green; + const emailAddress = 'test@test.com'; + + beforeEach(() => { + instance.scheduleActions.mockClear(); + }); + + it('should schedule actions when firing', () => { + executeActions(instance, license, status, emailAddress, false); + expect(instance.scheduleActions).toHaveBeenCalledWith('default', { + subject: 'NEW X-Pack Monitoring: Cluster Status', + message: `Allocate missing replica shards for cluster '${clusterName}'`, + to: emailAddress, + }); + }); + + it('should have a different message for red state', () => { + executeActions(instance, license, AlertClusterStateState.Red, emailAddress, false); + expect(instance.scheduleActions).toHaveBeenCalledWith('default', { + subject: 'NEW X-Pack Monitoring: Cluster Status', + message: `Allocate missing primary and replica shards for cluster '${clusterName}'`, + to: emailAddress, + }); + }); + + it('should schedule actions when resolved', () => { + executeActions(instance, license, status, emailAddress, true); + expect(instance.scheduleActions).toHaveBeenCalledWith('default', { + subject: 'RESOLVED X-Pack Monitoring: Cluster Status', + message: `This cluster alert has been resolved: Allocate missing replica shards for cluster '${clusterName}'`, + to: emailAddress, + }); + }); + }); + + describe('getUiMessage', () => { + it('should return a message when firing', () => { + const message = getUiMessage(AlertClusterStateState.Red, false); + expect(message.text).toBe( + `Elasticsearch cluster status is red. #start_linkAllocate missing primary and replica shards#end_link` + ); + expect(message.tokens && message.tokens.length).toBe(1); + expect(message.tokens && message.tokens[0].startToken).toBe('#start_link'); + expect(message.tokens && message.tokens[0].endToken).toBe('#end_link'); + expect( + message.tokens && (message.tokens[0] as AlertCommonPerClusterMessageLinkToken).url + ).toBe('elasticsearch/indices'); + }); + + it('should return a message when resolved', () => { + const message = getUiMessage(AlertClusterStateState.Green, true); + expect(message.text).toBe(`Elasticsearch cluster status is green.`); + expect(message.tokens).not.toBeDefined(); + }); + }); +}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts new file mode 100644 index 0000000000000..ae66d603507ca --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/cluster_state.lib.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { AlertInstance } from '../../../../alerting/server'; +import { + AlertCommonCluster, + AlertCommonPerClusterMessage, + AlertCommonPerClusterMessageLinkToken, +} from '../../alerts/types'; +import { AlertClusterStateState, AlertCommonPerClusterMessageTokenType } from '../../alerts/enums'; + +const RESOLVED_SUBJECT = i18n.translate('xpack.monitoring.alerts.clusterStatus.resolvedSubject', { + defaultMessage: 'RESOLVED X-Pack Monitoring: Cluster Status', +}); + +const NEW_SUBJECT = i18n.translate('xpack.monitoring.alerts.clusterStatus.newSubject', { + defaultMessage: 'NEW X-Pack Monitoring: Cluster Status', +}); + +const RED_STATUS_MESSAGE = i18n.translate('xpack.monitoring.alerts.clusterStatus.redMessage', { + defaultMessage: 'Allocate missing primary and replica shards', +}); + +const YELLOW_STATUS_MESSAGE = i18n.translate( + 'xpack.monitoring.alerts.clusterStatus.yellowMessage', + { + defaultMessage: 'Allocate missing replica shards', + } +); + +export function executeActions( + instance: AlertInstance, + cluster: AlertCommonCluster, + status: AlertClusterStateState, + emailAddress: string, + resolved: boolean = false +) { + const message = + status === AlertClusterStateState.Red ? RED_STATUS_MESSAGE : YELLOW_STATUS_MESSAGE; + if (resolved) { + instance.scheduleActions('default', { + subject: RESOLVED_SUBJECT, + message: `This cluster alert has been resolved: ${message} for cluster '${cluster.clusterName}'`, + to: emailAddress, + }); + } else { + instance.scheduleActions('default', { + subject: NEW_SUBJECT, + message: `${message} for cluster '${cluster.clusterName}'`, + to: emailAddress, + }); + } +} + +export function getUiMessage( + status: AlertClusterStateState, + resolved: boolean = false +): AlertCommonPerClusterMessage { + if (resolved) { + return { + text: i18n.translate('xpack.monitoring.alerts.clusterStatus.ui.resolvedMessage', { + defaultMessage: `Elasticsearch cluster status is green.`, + }), + }; + } + const message = + status === AlertClusterStateState.Red ? RED_STATUS_MESSAGE : YELLOW_STATUS_MESSAGE; + return { + text: i18n.translate('xpack.monitoring.alerts.clusterStatus.ui.firingMessage', { + defaultMessage: `Elasticsearch cluster status is {status}. #start_link{message}#end_link`, + values: { + status, + message, + }, + }), + tokens: [ + { + startToken: '#start_link', + endToken: '#end_link', + type: AlertCommonPerClusterMessageTokenType.Link, + url: 'elasticsearch/indices', + } as AlertCommonPerClusterMessageLinkToken, + ], + }; +} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.test.ts new file mode 100644 index 0000000000000..642ae3c39a027 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fetchClusterState } from './fetch_cluster_state'; + +describe('fetchClusterState', () => { + it('should return the cluster state', async () => { + const status = 'green'; + const clusterUuid = 'sdfdsaj34434'; + const callCluster = jest.fn(() => ({ + hits: { + hits: [ + { + _source: { + cluster_state: { + status, + }, + cluster_uuid: clusterUuid, + }, + }, + ], + }, + })); + + const clusters = [{ clusterUuid, clusterName: 'foo' }]; + const index = '.monitoring-es-*'; + + const state = await fetchClusterState(callCluster, clusters, index); + expect(state).toEqual([ + { + state: status, + clusterUuid, + }, + ]); + }); +}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts new file mode 100644 index 0000000000000..66ea30d5f2e96 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_cluster_state.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { get } from 'lodash'; +import { AlertCommonCluster, AlertClusterState } from '../../alerts/types'; + +export async function fetchClusterState( + callCluster: any, + clusters: AlertCommonCluster[], + index: string +): Promise { + const params = { + index, + filterPath: ['hits.hits._source.cluster_state.status', 'hits.hits._source.cluster_uuid'], + body: { + size: 1, + sort: [{ timestamp: { order: 'desc' } }], + query: { + bool: { + filter: [ + { + terms: { + cluster_uuid: clusters.map(cluster => cluster.clusterUuid), + }, + }, + { + term: { + type: 'cluster_stats', + }, + }, + { + range: { + timestamp: { + gte: 'now-2m', + }, + }, + }, + ], + }, + }, + }, + }; + + const response = await callCluster('search', params); + return get(response, 'hits.hits', []).map((hit: any) => { + return { + state: get(hit, '_source.cluster_state.status'), + clusterUuid: get(hit, '_source.cluster_uuid'), + }; + }); +} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts index 78eb9773df15f..7a9b61f37707b 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts @@ -6,21 +6,51 @@ import { fetchClusters } from './fetch_clusters'; describe('fetchClusters', () => { + const clusterUuid = '1sdfds734'; + const clusterName = 'monitoring'; + it('return a list of clusters', async () => { const callCluster = jest.fn().mockImplementation(() => ({ - aggregations: { - clusters: { - buckets: [ - { - key: 'clusterA', + hits: { + hits: [ + { + _source: { + cluster_uuid: clusterUuid, + cluster_name: clusterName, + }, + }, + ], + }, + })); + const index = '.monitoring-es-*'; + const result = await fetchClusters(callCluster, index); + expect(result).toEqual([{ clusterUuid, clusterName }]); + }); + + it('return the metadata name if available', async () => { + const metadataName = 'custom-monitoring'; + const callCluster = jest.fn().mockImplementation(() => ({ + hits: { + hits: [ + { + _source: { + cluster_uuid: clusterUuid, + cluster_name: clusterName, + cluster_settings: { + cluster: { + metadata: { + display_name: metadataName, + }, + }, + }, }, - ], - }, + }, + ], }, })); const index = '.monitoring-es-*'; const result = await fetchClusters(callCluster, index); - expect(result).toEqual([{ clusterUuid: 'clusterA' }]); + expect(result).toEqual([{ clusterUuid, clusterName: metadataName }]); }); it('should limit the time period in the query', async () => { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts index 8ef7339618a2c..d1513ac16fb15 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_clusters.ts @@ -4,18 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ import { get } from 'lodash'; -import { AlertCluster } from '../../alerts/types'; +import { AlertCommonCluster } from '../../alerts/types'; -interface AggregationResult { - key: string; -} - -export async function fetchClusters(callCluster: any, index: string): Promise { +export async function fetchClusters( + callCluster: any, + index: string +): Promise { const params = { index, - filterPath: 'aggregations.clusters.buckets', + filterPath: [ + 'hits.hits._source.cluster_settings.cluster.metadata.display_name', + 'hits.hits._source.cluster_uuid', + 'hits.hits._source.cluster_name', + ], body: { - size: 0, + size: 1000, query: { bool: { filter: [ @@ -34,19 +37,21 @@ export async function fetchClusters(callCluster: any, index: string): Promise ({ - clusterUuid: bucket.key, - })); + return get(response, 'hits.hits', []).map((hit: any) => { + const clusterName: string = + get(hit, '_source.cluster_settings.cluster.metadata.display_name') || + get(hit, '_source.cluster_name') || + get(hit, '_source.cluster_uuid'); + return { + clusterUuid: get(hit, '_source.cluster_uuid'), + clusterName, + }; + }); } diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts index dd6c074e68b1f..9dcb4ffb82a5f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts @@ -6,28 +6,28 @@ import { fetchLicenses } from './fetch_licenses'; describe('fetchLicenses', () => { + const clusterName = 'MyCluster'; + const clusterUuid = 'clusterA'; + const license = { + status: 'active', + expiry_date_in_millis: 1579532493876, + type: 'basic', + }; + it('return a list of licenses', async () => { - const clusterName = 'MyCluster'; - const clusterUuid = 'clusterA'; - const license = { - status: 'active', - expiry_date_in_millis: 1579532493876, - type: 'basic', - }; const callCluster = jest.fn().mockImplementation(() => ({ hits: { hits: [ { _source: { license, - cluster_name: clusterName, cluster_uuid: clusterUuid, }, }, ], }, })); - const clusters = [{ clusterUuid }]; + const clusters = [{ clusterUuid, clusterName }]; const index = '.monitoring-es-*'; const result = await fetchLicenses(callCluster, clusters, index); expect(result).toEqual([ @@ -36,15 +36,13 @@ describe('fetchLicenses', () => { type: license.type, expiryDateMS: license.expiry_date_in_millis, clusterUuid, - clusterName, }, ]); }); it('should only search for the clusters provided', async () => { - const clusterUuid = 'clusterA'; const callCluster = jest.fn(); - const clusters = [{ clusterUuid }]; + const clusters = [{ clusterUuid, clusterName }]; const index = '.monitoring-es-*'; await fetchLicenses(callCluster, clusters, index); const params = callCluster.mock.calls[0][1]; @@ -52,54 +50,11 @@ describe('fetchLicenses', () => { }); it('should limit the time period in the query', async () => { - const clusterUuid = 'clusterA'; const callCluster = jest.fn(); - const clusters = [{ clusterUuid }]; + const clusters = [{ clusterUuid, clusterName }]; const index = '.monitoring-es-*'; await fetchLicenses(callCluster, clusters, index); const params = callCluster.mock.calls[0][1]; expect(params.body.query.bool.filter[2].range.timestamp.gte).toBe('now-2m'); }); - - it('should give priority to the metadata name', async () => { - const clusterName = 'MyCluster'; - const clusterUuid = 'clusterA'; - const license = { - status: 'active', - expiry_date_in_millis: 1579532493876, - type: 'basic', - }; - const callCluster = jest.fn().mockImplementation(() => ({ - hits: { - hits: [ - { - _source: { - license, - cluster_name: 'fakeName', - cluster_uuid: clusterUuid, - cluster_settings: { - cluster: { - metadata: { - display_name: clusterName, - }, - }, - }, - }, - }, - ], - }, - })); - const clusters = [{ clusterUuid }]; - const index = '.monitoring-es-*'; - const result = await fetchLicenses(callCluster, clusters, index); - expect(result).toEqual([ - { - status: license.status, - type: license.type, - expiryDateMS: license.expiry_date_in_millis, - clusterUuid, - clusterName, - }, - ]); - }); }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts index 31a68e8aa9c3e..5b05c907e796e 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_licenses.ts @@ -4,21 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ import { get } from 'lodash'; -import { AlertLicense, AlertCluster } from '../../alerts/types'; +import { AlertLicense, AlertCommonCluster } from '../../alerts/types'; export async function fetchLicenses( callCluster: any, - clusters: AlertCluster[], + clusters: AlertCommonCluster[], index: string ): Promise { const params = { index, - filterPath: [ - 'hits.hits._source.license.*', - 'hits.hits._source.cluster_settings.cluster.metadata.display_name', - 'hits.hits._source.cluster_uuid', - 'hits.hits._source.cluster_name', - ], + filterPath: ['hits.hits._source.license.*', 'hits.hits._source.cluster_uuid'], body: { size: 1, sort: [{ timestamp: { order: 'desc' } }], @@ -50,17 +45,12 @@ export async function fetchLicenses( const response = await callCluster('search', params); return get(response, 'hits.hits', []).map((hit: any) => { - const clusterName: string = - get(hit, '_source.cluster_settings.cluster.metadata.display_name') || - get(hit, '_source.cluster_name') || - get(hit, '_source.cluster_uuid'); const rawLicense: any = get(hit, '_source.license', {}); const license: AlertLicense = { status: rawLicense.status, type: rawLicense.type, expiryDateMS: rawLicense.expiry_date_in_millis, clusterUuid: get(hit, '_source.cluster_uuid'), - clusterName, }; return license; }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.ts new file mode 100644 index 0000000000000..a3bcb61afacd6 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.test.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 { fetchStatus } from './fetch_status'; +import { AlertCommonPerClusterState } from '../../alerts/types'; + +describe('fetchStatus', () => { + const alertType = 'monitoringTest'; + const log = { warn: jest.fn() }; + const start = 0; + const end = 0; + const id = 1; + const defaultUiState = { + isFiring: false, + severity: 0, + message: null, + resolvedMS: 0, + lastCheckedMS: 0, + triggeredMS: 0, + }; + const alertsClient = { + find: jest.fn(() => ({ + total: 1, + data: [ + { + id, + }, + ], + })), + getAlertState: jest.fn(() => ({ + alertTypeState: { + state: { + ui: defaultUiState, + } as AlertCommonPerClusterState, + }, + })), + }; + + afterEach(() => { + (alertsClient.find as jest.Mock).mockClear(); + (alertsClient.getAlertState as jest.Mock).mockClear(); + }); + + it('should fetch from the alerts client', async () => { + const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); + expect(status).toEqual([]); + }); + + it('should return alerts that are firing', async () => { + alertsClient.getAlertState = jest.fn(() => ({ + alertTypeState: { + state: { + ui: { + ...defaultUiState, + isFiring: true, + }, + } as AlertCommonPerClusterState, + }, + })); + + const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); + expect(status.length).toBe(1); + expect(status[0].type).toBe(alertType); + expect(status[0].isFiring).toBe(true); + }); + + it('should return alerts that have been resolved in the time period', async () => { + alertsClient.getAlertState = jest.fn(() => ({ + alertTypeState: { + state: { + ui: { + ...defaultUiState, + resolvedMS: 1500, + }, + } as AlertCommonPerClusterState, + }, + })); + + const customStart = 1000; + const customEnd = 2000; + + const status = await fetchStatus( + alertsClient as any, + [alertType], + customStart, + customEnd, + log as any + ); + expect(status.length).toBe(1); + expect(status[0].type).toBe(alertType); + expect(status[0].isFiring).toBe(false); + }); + + it('should pass in the right filter to the alerts client', async () => { + await fetchStatus(alertsClient as any, [alertType], start, end, log as any); + expect((alertsClient.find as jest.Mock).mock.calls[0][0].options.filter).toBe( + `alert.attributes.alertTypeId:${alertType}` + ); + }); + + it('should return nothing if no alert state is found', async () => { + alertsClient.getAlertState = jest.fn(() => ({ + alertTypeState: null, + })) as any; + + const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); + expect(status).toEqual([]); + }); + + it('should return nothing if no alerts are found', async () => { + alertsClient.find = jest.fn(() => ({ + total: 0, + data: [], + })) as any; + + const status = await fetchStatus(alertsClient as any, [alertType], start, end, log as any); + expect(status).toEqual([]); + }); +}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts index 9f7c1d5a994d2..bf6ee965d3b2f 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_status.ts @@ -4,81 +4,53 @@ * you may not use this file except in compliance with the Elastic License. */ import moment from 'moment'; -import { get } from 'lodash'; -import { AlertClusterState } from '../../alerts/types'; -import { ALERT_TYPES, LOGGING_TAG } from '../../../common/constants'; +import { Logger } from '../../../../../../src/core/server'; +import { AlertCommonPerClusterState } from '../../alerts/types'; +import { AlertsClient } from '../../../../alerting/server'; export async function fetchStatus( - callCluster: any, + alertsClient: AlertsClient, + alertTypes: string[], start: number, end: number, - clusterUuid: string, - server: any + log: Logger ): Promise { - // TODO: this shouldn't query task manager directly but rather - // use an api exposed by the alerting/actions plugin - // See https://github.com/elastic/kibana/issues/48442 const statuses = await Promise.all( - ALERT_TYPES.map( + alertTypes.map( type => new Promise(async (resolve, reject) => { - try { - const params = { - index: '.kibana_task_manager', - filterPath: ['hits.hits._source.task.state'], - body: { - size: 1, - sort: [{ updated_at: { order: 'desc' } }], - query: { - bool: { - filter: [ - { - term: { - 'task.taskType': `alerting:${type}`, - }, - }, - ], - }, - }, - }, - }; - - const response = await callCluster('search', params); - const state = get(response, 'hits.hits[0]._source.task.state', '{}'); - const clusterState: AlertClusterState = get( - JSON.parse(state), - `alertTypeState.${clusterUuid}`, - { - expiredCheckDateMS: 0, - ui: { - isFiring: false, - message: null, - severity: 0, - resolvedMS: 0, - expirationTime: 0, - }, - } - ); - const isInBetween = moment(clusterState.ui.resolvedMS).isBetween(start, end); - if (clusterState.ui.isFiring || isInBetween) { - return resolve({ - type, - ...clusterState.ui, - }); - } + // We need to get the id from the alertTypeId + const alerts = await alertsClient.find({ + options: { + filter: `alert.attributes.alertTypeId:${type}`, + }, + }); + if (alerts.total === 0) { return resolve(false); - } catch (err) { - const reason = get(err, 'body.error.type'); - if (reason === 'index_not_found_exception') { - server.log( - ['error', LOGGING_TAG], - `Unable to fetch alerts. Alerts depends on task manager, which has not been started yet.` - ); - } else { - server.log(['error', LOGGING_TAG], err.message); - } + } + + if (alerts.total !== 1) { + log.warn(`Found more than one alert for type ${type} which is unexpected.`); + } + + const id = alerts.data[0].id; + + // Now that we have the id, we can get the state + const states = await alertsClient.getAlertState({ id }); + if (!states || !states.alertTypeState) { + log.warn(`No alert states found for type ${type} which is unexpected.`); return resolve(false); } + + const state = Object.values(states.alertTypeState)[0] as AlertCommonPerClusterState; + const isInBetween = moment(state.ui.resolvedMS).isBetween(start, end); + if (state.ui.isFiring || isInBetween) { + return resolve({ + type, + ...state.ui, + }); + } + return resolve(false); }) ) ); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.test.ts new file mode 100644 index 0000000000000..1840a2026a753 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.test.ts @@ -0,0 +1,163 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getPreparedAlert } from './get_prepared_alert'; +import { fetchClusters } from './fetch_clusters'; +import { fetchDefaultEmailAddress } from './fetch_default_email_address'; + +jest.mock('./fetch_clusters', () => ({ + fetchClusters: jest.fn(), +})); + +jest.mock('./fetch_default_email_address', () => ({ + fetchDefaultEmailAddress: jest.fn(), +})); + +describe('getPreparedAlert', () => { + const uiSettings = { get: jest.fn() }; + const alertType = 'test'; + const getUiSettingsService = async () => ({ + asScopedToClient: () => uiSettings, + }); + const monitoringCluster = null; + const logger = { warn: jest.fn() }; + const ccsEnabled = false; + const services = { + callCluster: jest.fn(), + savedObjectsClient: null, + }; + const emailAddress = 'foo@foo.com'; + const data = [{ foo: 1 }]; + const dataFetcher = () => data; + const clusterName = 'MonitoringCluster'; + const clusterUuid = 'sdf34sdf'; + const clusters = [{ clusterName, clusterUuid }]; + + afterEach(() => { + (uiSettings.get as jest.Mock).mockClear(); + (services.callCluster as jest.Mock).mockClear(); + (fetchClusters as jest.Mock).mockClear(); + (fetchDefaultEmailAddress as jest.Mock).mockClear(); + }); + + beforeEach(() => { + (fetchClusters as jest.Mock).mockImplementation(() => clusters); + (fetchDefaultEmailAddress as jest.Mock).mockImplementation(() => emailAddress); + }); + + it('should return fields as expected', async () => { + (uiSettings.get as jest.Mock).mockImplementation(() => { + return emailAddress; + }); + + const alert = await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + ccsEnabled, + services as any, + dataFetcher as any + ); + + expect(alert && alert.emailAddress).toBe(emailAddress); + expect(alert && alert.data).toBe(data); + }); + + it('should add ccs if specified', async () => { + const ccsClusterName = 'remoteCluster'; + (services.callCluster as jest.Mock).mockImplementation(() => { + return { + [ccsClusterName]: { + connected: true, + }, + }; + }); + + await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + true, + services as any, + dataFetcher as any + ); + + expect((fetchClusters as jest.Mock).mock.calls[0][1].includes(ccsClusterName)).toBe(true); + }); + + it('should ignore ccs if no remote clusters are available', async () => { + const ccsClusterName = 'remoteCluster'; + (services.callCluster as jest.Mock).mockImplementation(() => { + return { + [ccsClusterName]: { + connected: false, + }, + }; + }); + + await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + true, + services as any, + dataFetcher as any + ); + + expect((fetchClusters as jest.Mock).mock.calls[0][1].includes(ccsClusterName)).toBe(false); + }); + + it('should pass in the clusters into the data fetcher', async () => { + const customDataFetcher = jest.fn(() => data); + + await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + true, + services as any, + customDataFetcher as any + ); + + expect((customDataFetcher as jest.Mock).mock.calls[0][1]).toBe(clusters); + }); + + it('should return nothing if the data fetcher returns nothing', async () => { + const customDataFetcher = jest.fn(() => []); + + const result = await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + true, + services as any, + customDataFetcher as any + ); + + expect(result).toBe(null); + }); + + it('should return nothing if there is no email address', async () => { + (fetchDefaultEmailAddress as jest.Mock).mockImplementation(() => null); + + const result = await getPreparedAlert( + alertType, + getUiSettingsService as any, + monitoringCluster as any, + logger as any, + true, + services as any, + dataFetcher as any + ); + + expect(result).toBe(null); + }); +}); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts new file mode 100644 index 0000000000000..83a9e26e4c589 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/get_prepared_alert.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Logger, ICustomClusterClient, UiSettingsServiceStart } from 'kibana/server'; +import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; +import { AlertServices } from '../../../../alerting/server'; +import { AlertCommonCluster } from '../../alerts/types'; +import { INDEX_PATTERN_ELASTICSEARCH } from '../../../common/constants'; +import { fetchAvailableCcs } from './fetch_available_ccs'; +import { getCcsIndexPattern } from './get_ccs_index_pattern'; +import { fetchClusters } from './fetch_clusters'; +import { fetchDefaultEmailAddress } from './fetch_default_email_address'; + +export interface PreparedAlert { + emailAddress: string; + clusters: AlertCommonCluster[]; + data: any[]; + timezone: string; + dateFormat: string; +} + +async function getCallCluster( + monitoringCluster: ICustomClusterClient, + services: Pick +): Promise { + if (!monitoringCluster) { + return services.callCluster; + } + + return monitoringCluster.callAsInternalUser; +} + +export async function getPreparedAlert( + alertType: string, + getUiSettingsService: () => Promise, + monitoringCluster: ICustomClusterClient, + logger: Logger, + ccsEnabled: boolean, + services: Pick, + dataFetcher: ( + callCluster: CallCluster, + clusters: AlertCommonCluster[], + esIndexPattern: string + ) => Promise +): Promise { + const callCluster = await getCallCluster(monitoringCluster, services); + + // Support CCS use cases by querying to find available remote clusters + // and then adding those to the index pattern we are searching against + let esIndexPattern = INDEX_PATTERN_ELASTICSEARCH; + if (ccsEnabled) { + const availableCcs = await fetchAvailableCcs(callCluster); + if (availableCcs.length > 0) { + esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); + } + } + + const clusters = await fetchClusters(callCluster, esIndexPattern); + + // Fetch the specific data + const data = await dataFetcher(callCluster, clusters, esIndexPattern); + if (data.length === 0) { + logger.warn(`No data found for ${alertType}.`); + return null; + } + + const uiSettings = (await getUiSettingsService()).asScopedToClient(services.savedObjectsClient); + const dateFormat: string = await uiSettings.get('dateFormat'); + const timezone: string = await uiSettings.get('dateFormat:tz'); + const emailAddress = await fetchDefaultEmailAddress(uiSettings); + if (!emailAddress) { + // TODO: we can do more here + logger.warn(`Unable to send email for ${alertType} because there is no email configured.`); + return null; + } + + return { + emailAddress, + data, + clusters, + dateFormat, + timezone, + }; +} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts index 1a2eb1e44be84..b99208bdde2c8 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts @@ -39,17 +39,26 @@ describe('licenseExpiration lib', () => { }); describe('getUiMessage', () => { - const timezone = 'Europe/London'; - const license: any = { expiryDateMS: moment.tz('2020-01-20 08:00:00', timezone).utc() }; - it('should return a message when firing', () => { - const message = getUiMessage(license, timezone, false); - expect(message).toBe(`This cluster's license is going to expire in #relative at #absolute.`); + const message = getUiMessage(false); + expect(message.text).toBe( + `This cluster's license is going to expire in #relative at #absolute. #start_linkPlease update your license.#end_link` + ); + // LOL How do I avoid this in TS???? + if (!message.tokens) { + return expect(false).toBe(true); + } + expect(message.tokens.length).toBe(3); + expect(message.tokens[0].startToken).toBe('#relative'); + expect(message.tokens[1].startToken).toBe('#absolute'); + expect(message.tokens[2].startToken).toBe('#start_link'); + expect(message.tokens[2].endToken).toBe('#end_link'); }); it('should return a message when resolved', () => { - const message = getUiMessage(license, timezone, true); - expect(message).toBe(`This cluster's license is active.`); + const message = getUiMessage(true); + expect(message.text).toBe(`This cluster's license is active.`); + expect(message.tokens).not.toBeDefined(); }); }); }); diff --git a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts index 41b68d69bbd25..cfe9f02b9bd6a 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts @@ -6,7 +6,13 @@ import { Moment } from 'moment-timezone'; import { i18n } from '@kbn/i18n'; import { AlertInstance } from '../../../../alerting/server'; -import { AlertLicense } from '../../alerts/types'; +import { + AlertCommonPerClusterMessageLinkToken, + AlertCommonPerClusterMessageTimeToken, + AlertCommonCluster, + AlertCommonPerClusterMessage, +} from '../../alerts/types'; +import { AlertCommonPerClusterMessageTokenType } from '../../alerts/enums'; const RESOLVED_SUBJECT = i18n.translate( 'xpack.monitoring.alerts.licenseExpiration.resolvedSubject', @@ -21,7 +27,7 @@ const NEW_SUBJECT = i18n.translate('xpack.monitoring.alerts.licenseExpiration.ne export function executeActions( instance: AlertInstance, - license: AlertLicense, + cluster: AlertCommonCluster, $expiry: Moment, dateFormat: string, emailAddress: string, @@ -31,14 +37,14 @@ export function executeActions( instance.scheduleActions('default', { subject: RESOLVED_SUBJECT, message: `This cluster alert has been resolved: Cluster '${ - license.clusterName + cluster.clusterName }' license was going to expire on ${$expiry.format(dateFormat)}.`, to: emailAddress, }); } else { instance.scheduleActions('default', { subject: NEW_SUBJECT, - message: `Cluster '${license.clusterName}' license is going to expire on ${$expiry.format( + message: `Cluster '${cluster.clusterName}' license is going to expire on ${$expiry.format( dateFormat )}. Please update your license.`, to: emailAddress, @@ -46,13 +52,37 @@ export function executeActions( } } -export function getUiMessage(license: AlertLicense, timezone: string, resolved: boolean = false) { +export function getUiMessage(resolved: boolean = false): AlertCommonPerClusterMessage { if (resolved) { - return i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.resolvedMessage', { - defaultMessage: `This cluster's license is active.`, - }); + return { + text: i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.resolvedMessage', { + defaultMessage: `This cluster's license is active.`, + }), + }; } - return i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.firingMessage', { - defaultMessage: `This cluster's license is going to expire in #relative at #absolute.`, - }); + return { + text: i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.firingMessage', { + defaultMessage: `This cluster's license is going to expire in #relative at #absolute. #start_linkPlease update your license.#end_link`, + }), + tokens: [ + { + startToken: '#relative', + type: AlertCommonPerClusterMessageTokenType.Time, + isRelative: true, + isAbsolute: false, + } as AlertCommonPerClusterMessageTimeToken, + { + startToken: '#absolute', + type: AlertCommonPerClusterMessageTokenType.Time, + isAbsolute: true, + isRelative: false, + } as AlertCommonPerClusterMessageTimeToken, + { + startToken: '#start_link', + endToken: '#end_link', + type: AlertCommonPerClusterMessageTokenType.Link, + url: 'license', + } as AlertCommonPerClusterMessageLinkToken, + ], + }; } diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js index c5091c36c3bbe..1bddede52207b 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js @@ -29,6 +29,7 @@ import { CODE_PATH_BEATS, CODE_PATH_APM, KIBANA_ALERTING_ENABLED, + ALERT_TYPES, } from '../../../common/constants'; import { getApmsForClusters } from '../apm/get_apms_for_clusters'; import { i18n } from '@kbn/i18n'; @@ -102,15 +103,8 @@ export async function getClustersFromRequest( if (isInCodePath(codePaths, [CODE_PATH_ALERTS])) { if (KIBANA_ALERTING_ENABLED) { - const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); - const callCluster = (...args) => callWithRequest(req, ...args); - cluster.alerts = await fetchStatus( - callCluster, - start, - end, - cluster.cluster_uuid, - req.server - ); + const alertsClient = req.getAlertsClient ? req.getAlertsClient() : null; + cluster.alerts = await fetchStatus(alertsClient, ALERT_TYPES, start, end, req.logger); } else { cluster.alerts = await alertsClusterSearch( req, diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 24d8bcaa4397c..0fa1a5bf144ac 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -47,6 +47,7 @@ import { PluginSetupContract as AlertingPluginSetupContract, } from '../../alerting/server'; import { getLicenseExpiration } from './alerts/license_expiration'; +import { getClusterState } from './alerts/cluster_state'; import { InfraPluginSetup } from '../../infra/server'; export interface LegacyAPI { @@ -154,6 +155,17 @@ export class Plugin { config.ui.ccs.enabled ) ); + plugins.alerting.registerType( + getClusterState( + async () => { + const coreStart = (await core.getStartServices())[0]; + return coreStart.uiSettings; + }, + cluster, + this.getLogger, + config.ui.ccs.enabled + ) + ); } // Initialize telemetry @@ -165,12 +177,7 @@ export class Plugin { // Register collector objects for stats to show up in the APIs if (plugins.usageCollection) { - registerCollectors( - plugins.usageCollection, - config, - core.metrics.getOpsMetrics$(), - get(legacyConfig, 'kibana.index') - ); + registerCollectors(plugins.usageCollection, config); } // If collection is enabled, create the bulk uploader @@ -269,18 +276,23 @@ export class Plugin { catalogue: ['monitoring'], privileges: null, reserved: { - privilege: { - app: ['monitoring', 'kibana'], - catalogue: ['monitoring'], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, description: i18n.translate('xpack.monitoring.feature.reserved.description', { defaultMessage: 'To grant users access, you should also assign the monitoring_user role.', }), + privileges: [ + { + id: 'monitoring', + privilege: { + app: ['monitoring', 'kibana'], + catalogue: ['monitoring'], + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + }, + ], }, }); } diff --git a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js index 56922bd8e87e2..d5a43d32f600a 100644 --- a/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js +++ b/x-pack/plugins/monitoring/server/routes/api/v1/alerts/alerts.js @@ -8,8 +8,12 @@ import { schema } from '@kbn/config-schema'; import { isFunction } from 'lodash'; import { ALERT_TYPE_LICENSE_EXPIRATION, + ALERT_TYPE_CLUSTER_STATE, MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS, + ALERT_TYPES, } from '../../../../../common/constants'; +import { handleError } from '../../../../lib/errors'; +import { fetchStatus } from '../../../../lib/alerts/fetch_status'; async function createAlerts(req, alertsClient, { selectedEmailActionId }) { const createdAlerts = []; @@ -17,7 +21,21 @@ async function createAlerts(req, alertsClient, { selectedEmailActionId }) { // Create alerts const ALERT_TYPES = { [ALERT_TYPE_LICENSE_EXPIRATION]: { - schedule: { interval: '10s' }, + schedule: { interval: '1m' }, + actions: [ + { + group: 'default', + id: selectedEmailActionId, + params: { + subject: '{{context.subject}}', + message: `{{context.message}}`, + to: ['{{context.to}}'], + }, + }, + ], + }, + [ALERT_TYPE_CLUSTER_STATE]: { + schedule: { interval: '1m' }, actions: [ { group: 'default', @@ -86,4 +104,37 @@ export function createKibanaAlertsRoute(server) { return { alerts, emailResponse }; }, }); + + server.route({ + method: 'POST', + path: '/api/monitoring/v1/alert_status', + config: { + validate: { + payload: schema.object({ + timeRange: schema.object({ + min: schema.string(), + max: schema.string(), + }), + }), + }, + }, + async handler(req, headers) { + const alertsClient = isFunction(req.getAlertsClient) ? req.getAlertsClient() : null; + if (!alertsClient) { + return headers.response().code(404); + } + + const start = req.payload.timeRange.min; + const end = req.payload.timeRange.max; + let alerts; + + try { + alerts = await fetchStatus(alertsClient, ALERT_TYPES, start, end, req.logger); + } catch (err) { + throw handleError(err, req); + } + + return { alerts }; + }, + }); } diff --git a/x-pack/plugins/painless_lab/public/plugin.tsx b/x-pack/plugins/painless_lab/public/plugin.tsx index b9ca7031cf670..e76128313545a 100644 --- a/x-pack/plugins/painless_lab/public/plugin.tsx +++ b/x-pack/plugins/painless_lab/public/plugin.tsx @@ -11,7 +11,6 @@ import { first } from 'rxjs/operators'; import { EuiBetaBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FeatureCatalogueCategory } from '../../../../src/plugins/home/public'; -import { LICENSE_CHECK_STATE } from '../../licensing/public'; import { PLUGIN } from '../common/constants'; @@ -82,7 +81,7 @@ export class PainlessLabUIPlugin implements Plugin { const { state, message } = license.check(pluginId, minimumLicenseType); - const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + const hasRequiredLicense = state === 'valid'; if (hasRequiredLicense) { this.licenseStatus = { isValid: true }; diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap index 4c109c557fdb0..1e6c2c4d289aa 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/__snapshots__/remote_cluster_form.test.js.snap @@ -350,7 +350,7 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u description={ @@ -367,7 +367,7 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u data-test-subj="remoteClusterFormConnectionModeToggle" label={ @@ -443,11 +443,11 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u className="euiTextColor euiTextColor--subdued" > - Use seed nodes by default, or switch to a single proxy address. + Use seed nodes by default, or switch to proxy mode. @@ -534,11 +534,11 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u onClick={[Function]} > - Use a single proxy address + Use proxy mode

@@ -687,23 +687,37 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u + + , + } + } /> } + isInvalid={false} label={ } @@ -711,28 +725,31 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u >
@@ -740,20 +757,19 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u
-
- + - +
- + + + , + } + } > - The number of socket connections to open per remote cluster. + A string sent in the server_name field of the TLS Server Name Indication extension if TLS is enabled. + + +
@@ -801,37 +845,23 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u
- - , - } - } + defaultMessage="The number of socket connections to open per remote cluster." + id="xpack.remoteClusters.remoteClusterForm.fieldSocketConnectionsHelpText" + values={Object {}} /> } - isInvalid={false} label={ } @@ -839,31 +869,28 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u >
@@ -871,19 +898,20 @@ exports[`RemoteClusterForm proxy mode renders correct connection settings when u
-
- + - +
- + - - , - } - } + defaultMessage="The number of socket connections to open per remote cluster." + id="xpack.remoteClusters.remoteClusterForm.fieldSocketConnectionsHelpText" + values={Object {}} > - A string sent in the server_name field of the TLS Server Name Indication extension if TLS is enabled. - - - + The number of socket connections to open per remote cluster.
@@ -1447,7 +1447,7 @@ Array [
- Use seed nodes by default, or switch to a single proxy address. + Use seed nodes by default, or switch to proxy mode.
- Use a single proxy address + Use proxy mode
diff --git a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.js b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.js index c98bd73bf83a0..a392cc9607784 100644 --- a/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.js +++ b/x-pack/plugins/remote_clusters/public/application/sections/components/remote_cluster_form/remote_cluster_form.js @@ -405,30 +405,6 @@ export class RemoteClusterForm extends Component { /> - - } - helpText={ - - } - fullWidth - > - - this.onFieldsChange({ proxySocketConnections: Number(e.target.value) || null }) - } - fullWidth - /> - + + + } + helpText={ + + } + fullWidth + > + + this.onFieldsChange({ proxySocketConnections: Number(e.target.value) || null }) + } + fullWidth + /> + ); } @@ -498,14 +499,14 @@ export class RemoteClusterForm extends Component { <> } checked={mode === PROXY_MODE} @@ -519,15 +520,38 @@ export class RemoteClusterForm extends Component { <> } - iconType="pin" - size="s" - /> + > + + + + ), + searchString: ( + + + + ), + }} + /> + ) : null} diff --git a/x-pack/plugins/remote_clusters/server/plugin.ts b/x-pack/plugins/remote_clusters/server/plugin.ts index 837d53c7dbc1b..b86f16228878a 100644 --- a/x-pack/plugins/remote_clusters/server/plugin.ts +++ b/x-pack/plugins/remote_clusters/server/plugin.ts @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'src/core/server'; import { Observable } from 'rxjs'; -import { LICENSE_CHECK_STATE } from '../../licensing/common/types'; import { PLUGIN } from '../common/constants'; import { Dependencies, LicenseStatus, RouteDependencies } from './types'; @@ -54,7 +53,7 @@ export class RemoteClustersServerPlugin implements Plugin licensing.license$.subscribe(license => { const { state, message } = license.check(PLUGIN.getI18nName(), PLUGIN.minimumLicenseType); - const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + const hasRequiredLicense = state === 'valid'; if (hasRequiredLicense) { this.licenseStatus = { valid: true }; } else { diff --git a/x-pack/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap b/x-pack/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap index 75bc1f9eea696..271d61c224210 100644 --- a/x-pack/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap +++ b/x-pack/plugins/reporting/public/components/__snapshots__/report_listing.test.tsx.snap @@ -2,525 +2,6 @@ exports[`ReportListing Report job listing with some items 1`] = ` Array [ - -
- - -
- -
- - - -
-
- - - - -
- - -
-
- - - -
- -
- - - -
- - -
-
- -
- -
- -
- - -
- -
- -
- - -
- - -
- -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
- - -
- -
-
- - -
-
-
- - Report - -
-
-
- - Created at - -
-
-
- - Status - -
-
-
- - Actions - -
-
-
- - Loading reports - -
-
-
-
-
- -
- , { throw error; } } + + // Since the contents of the table have changed, we must reset the pagination + // and re-fetch. Otherwise, the Nth page we are on could be empty of jobs. + this.setState(() => ({ page: 0 }), this.fetchJobs); }; return ( @@ -476,34 +480,32 @@ class ReportListingUi extends Component { onSelectionChange: this.onSelectionChange, }; - const search = { - toolsRight: this.renderDeleteButton(), - }; - return ( - + + + {this.state.selectedJobs.length > 0 ? this.renderDeleteButton() : null} + ); } } diff --git a/x-pack/plugins/reporting/public/lib/license_check.test.ts b/x-pack/plugins/reporting/public/lib/license_check.test.ts index 24e14969d2c81..22737509923d3 100644 --- a/x-pack/plugins/reporting/public/lib/license_check.test.ts +++ b/x-pack/plugins/reporting/public/lib/license_check.test.ts @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ import { checkLicense } from './license_check'; -import { LicenseCheck } from '../../../licensing/public'; describe('License check', () => { it('enables and shows links when licenses are good mkay', () => { - expect(checkLicense({ state: 'VALID' } as LicenseCheck)).toEqual({ + expect(checkLicense({ state: 'valid' })).toEqual({ enableLinks: true, showLinks: true, message: '', @@ -16,7 +15,7 @@ describe('License check', () => { }); it('disables and shows links when licenses are not valid', () => { - expect(checkLicense({ state: 'INVALID' } as LicenseCheck)).toEqual({ + expect(checkLicense({ state: 'invalid' })).toEqual({ enableLinks: false, showLinks: false, message: 'Your license does not support Reporting. Please upgrade your license.', @@ -24,7 +23,7 @@ describe('License check', () => { }); it('shows links, but disables them, on expired licenses', () => { - expect(checkLicense({ state: 'EXPIRED' } as LicenseCheck)).toEqual({ + expect(checkLicense({ state: 'expired' })).toEqual({ enableLinks: false, showLinks: true, message: 'You cannot use Reporting because your license has expired.', @@ -32,7 +31,7 @@ describe('License check', () => { }); it('shows links, but disables them, when license checks are unavailable', () => { - expect(checkLicense({ state: 'UNAVAILABLE' } as LicenseCheck)).toEqual({ + expect(checkLicense({ state: 'unavailable' })).toEqual({ enableLinks: false, showLinks: true, message: diff --git a/x-pack/plugins/reporting/public/lib/license_check.ts b/x-pack/plugins/reporting/public/lib/license_check.ts index ca803fb38ef2a..0c16ead0b116d 100644 --- a/x-pack/plugins/reporting/public/lib/license_check.ts +++ b/x-pack/plugins/reporting/public/lib/license_check.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import { LicenseCheckResults } from '../..'; -import { LICENSE_CHECK_STATE, LicenseCheck } from '../../../licensing/public'; +import { LicenseCheck } from '../../../licensing/public'; export const checkLicense = (checkResults: LicenseCheck): LicenseCheckResults => { switch (checkResults.state) { - case LICENSE_CHECK_STATE.Valid: { + case 'valid': { return { showLinks: true, enableLinks: true, @@ -16,7 +16,7 @@ export const checkLicense = (checkResults: LicenseCheck): LicenseCheckResults => }; } - case LICENSE_CHECK_STATE.Invalid: { + case 'invalid': { return { showLinks: false, enableLinks: false, @@ -24,7 +24,7 @@ export const checkLicense = (checkResults: LicenseCheck): LicenseCheckResults => }; } - case LICENSE_CHECK_STATE.Unavailable: { + case 'unavailable': { return { showLinks: true, enableLinks: false, @@ -33,7 +33,7 @@ export const checkLicense = (checkResults: LicenseCheck): LicenseCheckResults => }; } - case LICENSE_CHECK_STATE.Expired: { + case 'expired': { return { showLinks: true, enableLinks: false, diff --git a/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx b/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx index ece22905c64d9..27f040f3e9eec 100644 --- a/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx +++ b/x-pack/plugins/searchprofiler/public/application/editor/editor.tsx @@ -5,6 +5,8 @@ */ import React, { memo, useRef, useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiScreenReaderOnly } from '@elastic/eui'; import { Editor as AceEditor } from 'brace'; import { initializeEditor } from './init_editor'; @@ -31,6 +33,8 @@ const createEditorShim = (aceEditor: AceEditor) => { }; }; +const EDITOR_INPUT_ID = 'SearchProfilerTextArea'; + export const Editor = memo(({ licenseEnabled, initialValue, onEditorReady }: Props) => { const containerRef = useRef(null as any); const editorInstanceRef = useRef(null as any); @@ -43,10 +47,25 @@ export const Editor = memo(({ licenseEnabled, initialValue, onEditorReady }: Pro const divEl = containerRef.current; editorInstanceRef.current = initializeEditor({ el: divEl, licenseEnabled }); editorInstanceRef.current.setValue(initialValue, 1); + const textarea = divEl.querySelector('textarea'); + if (textarea) { + textarea.setAttribute('id', EDITOR_INPUT_ID); + } setTextArea(licenseEnabled ? containerRef.current!.querySelector('textarea') : null); onEditorReady(createEditorShim(editorInstanceRef.current)); }, [initialValue, onEditorReady, licenseEnabled]); - return
; + return ( + <> + + + +
+ + ); }); diff --git a/x-pack/plugins/searchprofiler/public/plugin.ts b/x-pack/plugins/searchprofiler/public/plugin.ts index 7d77b274ec49d..179886c0807d2 100644 --- a/x-pack/plugins/searchprofiler/public/plugin.ts +++ b/x-pack/plugins/searchprofiler/public/plugin.ts @@ -9,7 +9,6 @@ import { Plugin, CoreStart, CoreSetup, PluginInitializerContext } from 'kibana/p import { first } from 'rxjs/operators'; import { FeatureCatalogueCategory } from '../../../../src/plugins/home/public'; -import { LICENSE_CHECK_STATE } from '../../licensing/public'; import { PLUGIN } from '../common'; import { AppPublicPluginDependencies } from './types'; @@ -50,7 +49,7 @@ export class SearchProfilerUIPlugin implements Plugin { const { state, message } = license.check(PLUGIN.id, PLUGIN.minimumLicenseType); - const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + const hasRequiredLicense = state === 'valid'; if (hasRequiredLicense) { this.licenseStatus = { valid: true }; } else { diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx index 779a2302cfadf..698c0d37dbc64 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/api_keys_grid_page.tsx @@ -28,8 +28,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment-timezone'; import _ from 'lodash'; import { NotificationsStart } from 'src/core/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SectionLoading } from '../../../../../../../src/plugins/es_ui_shared/public/components/section_loading'; +import { SectionLoading } from '../../../../../../../src/plugins/es_ui_shared/public'; import { ApiKey, ApiKeyToInvalidate } from '../../../../common/model'; import { APIKeysAPIClient } from '../api_keys_api_client'; import { DocumentationLinksService } from '../documentation_links'; diff --git a/x-pack/plugins/security/server/authorization/index.ts b/x-pack/plugins/security/server/authorization/index.ts index f065c9cfd90ba..cf970a561b93f 100644 --- a/x-pack/plugins/security/server/authorization/index.ts +++ b/x-pack/plugins/security/server/authorization/index.ts @@ -29,6 +29,7 @@ import { initAppAuthorization } from './app_authorization'; import { initAPIAuthorization } from './api_authorization'; import { disableUICapabilitiesFactory } from './disable_ui_capabilities'; import { validateFeaturePrivileges } from './validate_feature_privileges'; +import { validateReservedPrivileges } from './validate_reserved_privileges'; import { registerPrivilegesWithCluster } from './register_privileges_with_cluster'; import { APPLICATION_PREFIX } from '../../common/constants'; import { SecurityLicense } from '../../common/licensing'; @@ -121,7 +122,9 @@ export function setupAuthorization({ }, registerPrivilegesWithCluster: async () => { - validateFeaturePrivileges(featuresService.getFeatures()); + const features = featuresService.getFeatures(); + validateFeaturePrivileges(features); + validateReservedPrivileges(features); await registerPrivilegesWithCluster(logger, privileges, applicationName, clusterClient); }, diff --git a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts index 3d25fc03f568b..b023c12d35b79 100644 --- a/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts +++ b/x-pack/plugins/security/server/authorization/privileges/privileges.test.ts @@ -409,13 +409,18 @@ describe('features', () => { }, privileges: null, reserved: { - privilege: { - savedObject: { - all: ['ignore-me-1', 'ignore-me-2'], - read: ['ignore-me-1', 'ignore-me-2'], + privileges: [ + { + id: 'reserved', + privilege: { + savedObject: { + all: ['ignore-me-1', 'ignore-me-2'], + read: ['ignore-me-1', 'ignore-me-2'], + }, + ui: ['ignore-me-1'], + }, }, - ui: ['ignore-me-1'], - }, + ], description: '', }, }), @@ -591,13 +596,18 @@ describe('reserved', () => { }, privileges: null, reserved: { - privilege: { - savedObject: { - all: [], - read: [], + privileges: [ + { + id: 'foo', + privilege: { + savedObject: { + all: [], + read: [], + }, + ui: [], + }, }, - ui: [], - }, + ], description: '', }, }), @@ -627,13 +637,18 @@ describe('reserved', () => { app: [], privileges: null, reserved: { - privilege: { - savedObject: { - all: ['savedObject-all-1', 'savedObject-all-2'], - read: ['savedObject-read-1', 'savedObject-read-2'], + privileges: [ + { + id: 'foo', + privilege: { + savedObject: { + all: ['savedObject-all-1', 'savedObject-all-2'], + read: ['savedObject-read-1', 'savedObject-read-2'], + }, + ui: ['ui-1', 'ui-2'], + }, }, - ui: ['ui-1', 'ui-2'], - }, + ], description: '', }, }), diff --git a/x-pack/plugins/security/server/authorization/privileges/privileges.ts b/x-pack/plugins/security/server/authorization/privileges/privileges.ts index b25aad30a3423..9a8935f80a174 100644 --- a/x-pack/plugins/security/server/authorization/privileges/privileges.ts +++ b/x-pack/plugins/security/server/authorization/privileges/privileges.ts @@ -110,10 +110,12 @@ export function privilegesFactory( }, reserved: features.reduce((acc: Record, feature: Feature) => { if (feature.reserved) { - acc[feature.id] = [ - actions.version, - ...featurePrivilegeBuilder.getActions(feature.reserved!.privilege, feature), - ]; + feature.reserved.privileges.forEach(reservedPrivilege => { + acc[reservedPrivilege.id] = [ + actions.version, + ...uniq(featurePrivilegeBuilder.getActions(reservedPrivilege.privilege, feature)), + ]; + }); } return acc; }, {}), diff --git a/x-pack/plugins/security/server/authorization/validate_feature_privileges.test.ts b/x-pack/plugins/security/server/authorization/validate_feature_privileges.test.ts index ac386d287cff1..cd2c7faa263c9 100644 --- a/x-pack/plugins/security/server/authorization/validate_feature_privileges.test.ts +++ b/x-pack/plugins/security/server/authorization/validate_feature_privileges.test.ts @@ -26,13 +26,18 @@ it('allows features with reserved privileges to be defined', () => { privileges: null, reserved: { description: 'foo', - privilege: { - savedObject: { - all: ['foo'], - read: ['bar'], + privileges: [ + { + id: 'reserved', + privilege: { + savedObject: { + all: ['foo'], + read: ['bar'], + }, + ui: [], + }, }, - ui: [], - }, + ], }, }); diff --git a/x-pack/plugins/security/server/authorization/validate_reserved_privileges.test.ts b/x-pack/plugins/security/server/authorization/validate_reserved_privileges.test.ts new file mode 100644 index 0000000000000..26af0dadfb288 --- /dev/null +++ b/x-pack/plugins/security/server/authorization/validate_reserved_privileges.test.ts @@ -0,0 +1,181 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 '../../../features/server'; +import { validateReservedPrivileges } from './validate_reserved_privileges'; + +it('allows features to be defined without privileges', () => { + const feature: Feature = new Feature({ + id: 'foo', + name: 'foo', + app: [], + privileges: null, + }); + + validateReservedPrivileges([feature]); +}); + +it('allows features with a single reserved privilege to be defined', () => { + const feature: Feature = new Feature({ + id: 'foo', + name: 'foo', + app: [], + privileges: null, + reserved: { + description: 'foo', + privileges: [ + { + id: 'reserved', + privilege: { + savedObject: { + all: ['foo'], + read: ['bar'], + }, + ui: [], + }, + }, + ], + }, + }); + + validateReservedPrivileges([feature]); +}); + +it('allows multiple features with reserved privileges to be defined', () => { + const feature1: Feature = new Feature({ + id: 'foo', + name: 'foo', + app: [], + privileges: null, + reserved: { + description: 'foo', + privileges: [ + { + id: 'reserved-1', + privilege: { + savedObject: { + all: ['foo'], + read: ['bar'], + }, + ui: [], + }, + }, + ], + }, + }); + + const feature2: Feature = new Feature({ + id: 'foo2', + name: 'foo', + app: [], + privileges: null, + reserved: { + description: 'foo', + privileges: [ + { + id: 'reserved-2', + privilege: { + savedObject: { + all: ['foo'], + read: ['bar'], + }, + ui: [], + }, + }, + ], + }, + }); + + validateReservedPrivileges([feature1, feature2]); +}); + +it('prevents a feature from specifying the same reserved privilege id', () => { + const feature1: Feature = new Feature({ + id: 'foo', + name: 'foo', + app: [], + privileges: null, + reserved: { + description: 'foo', + privileges: [ + { + id: 'reserved', + privilege: { + savedObject: { + all: ['foo'], + read: ['bar'], + }, + ui: [], + }, + }, + { + id: 'reserved', + privilege: { + savedObject: { + all: ['foo'], + read: ['bar'], + }, + ui: [], + }, + }, + ], + }, + }); + + expect(() => validateReservedPrivileges([feature1])).toThrowErrorMatchingInlineSnapshot( + `"Duplicate reserved privilege id detected: reserved. This is not allowed."` + ); +}); + +it('prevents features from sharing a reserved privilege id', () => { + const feature1: Feature = new Feature({ + id: 'foo', + name: 'foo', + app: [], + privileges: null, + reserved: { + description: 'foo', + privileges: [ + { + id: 'reserved', + privilege: { + savedObject: { + all: ['foo'], + read: ['bar'], + }, + ui: [], + }, + }, + ], + }, + }); + + const feature2: Feature = new Feature({ + id: 'foo2', + name: 'foo', + app: [], + privileges: null, + reserved: { + description: 'foo', + privileges: [ + { + id: 'reserved', + privilege: { + savedObject: { + all: ['foo'], + read: ['bar'], + }, + ui: [], + }, + }, + ], + }, + }); + + expect(() => validateReservedPrivileges([feature1, feature2])).toThrowErrorMatchingInlineSnapshot( + `"Duplicate reserved privilege id detected: reserved. This is not allowed."` + ); +}); diff --git a/x-pack/plugins/security/server/authorization/validate_reserved_privileges.ts b/x-pack/plugins/security/server/authorization/validate_reserved_privileges.ts new file mode 100644 index 0000000000000..0915308fc0f89 --- /dev/null +++ b/x-pack/plugins/security/server/authorization/validate_reserved_privileges.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 { Feature } from '../../../features/server'; + +export function validateReservedPrivileges(features: Feature[]) { + const seenPrivilegeIds = new Set(); + + for (const feature of features) { + (feature?.reserved?.privileges ?? []).forEach(({ id }) => { + if (seenPrivilegeIds.has(id)) { + throw new Error(`Duplicate reserved privilege id detected: ${id}. This is not allowed.`); + } + seenPrivilegeIds.add(id); + }); + } +} diff --git a/x-pack/plugins/security/server/routes/api_keys/get.test.ts b/x-pack/plugins/security/server/routes/api_keys/get.test.ts index 2b2283edea2e8..f77469552d980 100644 --- a/x-pack/plugins/security/server/routes/api_keys/get.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/get.test.ts @@ -5,7 +5,7 @@ */ import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; -import { LICENSE_CHECK_STATE, LicenseCheck } from '../../../../licensing/server'; +import { LicenseCheck } from '../../../../licensing/server'; import { defineGetApiKeysRoutes } from './get'; import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; @@ -22,12 +22,7 @@ interface TestOptions { describe('Get API keys', () => { const getApiKeysTest = ( description: string, - { - licenseCheckResult = { state: LICENSE_CHECK_STATE.Valid }, - apiResponse, - asserts, - isAdmin = true, - }: TestOptions + { licenseCheckResult = { state: 'valid' }, apiResponse, asserts, isAdmin = true }: TestOptions ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); @@ -71,7 +66,7 @@ describe('Get API keys', () => { describe('failure', () => { getApiKeysTest('returns result of license checker', { - licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, + licenseCheckResult: { state: 'invalid', message: 'test forbidden message' }, asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, }); diff --git a/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts b/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts index 4ea21bda5f743..2889cf78aff83 100644 --- a/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts @@ -7,7 +7,7 @@ import Boom from 'boom'; import { Type } from '@kbn/config-schema'; import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; -import { LICENSE_CHECK_STATE, LicenseCheck } from '../../../../licensing/server'; +import { LicenseCheck } from '../../../../licensing/server'; import { defineInvalidateApiKeysRoutes } from './invalidate'; import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; @@ -23,12 +23,7 @@ interface TestOptions { describe('Invalidate API keys', () => { const postInvalidateTest = ( description: string, - { - licenseCheckResult = { state: LICENSE_CHECK_STATE.Valid }, - apiResponses = [], - asserts, - payload, - }: TestOptions + { licenseCheckResult = { state: 'valid' }, apiResponses = [], asserts, payload }: TestOptions ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); @@ -116,7 +111,7 @@ describe('Invalidate API keys', () => { describe('failure', () => { postInvalidateTest('returns result of license checker', { - licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, + licenseCheckResult: { state: 'invalid', message: 'test forbidden message' }, payload: { apiKeys: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key' }], isAdmin: true }, asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, }); diff --git a/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts b/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts index 866e455063bdc..311d50e9eb169 100644 --- a/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts @@ -5,7 +5,7 @@ */ import Boom from 'boom'; -import { LICENSE_CHECK_STATE, LicenseCheck } from '../../../../licensing/server'; +import { LicenseCheck } from '../../../../licensing/server'; import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; @@ -21,11 +21,7 @@ interface TestOptions { describe('Check API keys privileges', () => { const getPrivilegesTest = ( description: string, - { - licenseCheckResult = { state: LICENSE_CHECK_STATE.Valid }, - apiResponses = [], - asserts, - }: TestOptions + { licenseCheckResult = { state: 'valid' }, apiResponses = [], asserts }: TestOptions ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); @@ -68,7 +64,7 @@ describe('Check API keys privileges', () => { describe('failure', () => { getPrivilegesTest('returns result of license checker', { - licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, + licenseCheckResult: { state: 'invalid', message: 'test forbidden message' }, asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, }); diff --git a/x-pack/plugins/security/server/routes/authentication/basic.test.ts b/x-pack/plugins/security/server/routes/authentication/basic.test.ts index 3c114978f26d2..5eed8e166c957 100644 --- a/x-pack/plugins/security/server/routes/authentication/basic.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/basic.test.ts @@ -12,7 +12,6 @@ import { RequestHandlerContext, RouteConfig, } from '../../../../../../src/core/server'; -import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; import { Authentication, AuthenticationResult } from '../../authentication'; import { defineBasicRoutes } from './basic'; @@ -33,7 +32,7 @@ describe('Basic authentication routes', () => { mockContext = ({ licensing: { - license: { check: jest.fn().mockReturnValue({ check: LICENSE_CHECK_STATE.Valid }) }, + license: { check: jest.fn().mockReturnValue({ check: 'valid' }) }, }, } as unknown) as RequestHandlerContext; diff --git a/x-pack/plugins/security/server/routes/authentication/common.test.ts b/x-pack/plugins/security/server/routes/authentication/common.test.ts index e2f9593bc09ee..156c03e90210b 100644 --- a/x-pack/plugins/security/server/routes/authentication/common.test.ts +++ b/x-pack/plugins/security/server/routes/authentication/common.test.ts @@ -12,7 +12,6 @@ import { RequestHandlerContext, RouteConfig, } from '../../../../../../src/core/server'; -import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; import { Authentication, AuthenticationResult, @@ -37,7 +36,7 @@ describe('Common authentication routes', () => { mockContext = ({ licensing: { - license: { check: jest.fn().mockReturnValue({ check: LICENSE_CHECK_STATE.Valid }) }, + license: { check: jest.fn().mockReturnValue({ check: 'valid' }) }, }, } as unknown) as RequestHandlerContext; diff --git a/x-pack/plugins/security/server/routes/authentication/index.ts b/x-pack/plugins/security/server/routes/authentication/index.ts index f3082b089faf5..7e9eb75bbf753 100644 --- a/x-pack/plugins/security/server/routes/authentication/index.ts +++ b/x-pack/plugins/security/server/routes/authentication/index.ts @@ -16,7 +16,6 @@ export function createCustomResourceResponse(body: string, contentType: string, body, headers: { 'content-type': contentType, - 'cache-control': 'private, no-cache, no-store', 'content-security-policy': cspHeader, }, statusCode: 200, diff --git a/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts b/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts index 6afbad8e83ebe..7301a3cf51974 100644 --- a/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/privileges/get.test.ts @@ -5,7 +5,7 @@ */ import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; -import { LicenseCheck, LICENSE_CHECK_STATE } from '../../../../../licensing/server'; +import { LicenseCheck } from '../../../../../licensing/server'; import { RawKibanaPrivileges } from '../../../../common/model'; import { defineGetPrivilegesRoutes } from './get'; @@ -46,11 +46,7 @@ interface TestOptions { describe('GET privileges', () => { const getPrivilegesTest = ( description: string, - { - licenseCheckResult = { state: LICENSE_CHECK_STATE.Valid }, - includeActions, - asserts, - }: TestOptions + { licenseCheckResult = { state: 'valid' }, includeActions, asserts }: TestOptions ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); @@ -82,7 +78,7 @@ describe('GET privileges', () => { describe('failure', () => { getPrivilegesTest('returns result of license checker', { - licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, + licenseCheckResult: { state: 'invalid', message: 'test forbidden message' }, asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, }); }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts index 22268245c3a44..ada6a1c8d2dc3 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts @@ -6,7 +6,7 @@ import Boom from 'boom'; import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; -import { LicenseCheck, LICENSE_CHECK_STATE } from '../../../../../licensing/server'; +import { LicenseCheck } from '../../../../../licensing/server'; import { defineDeleteRolesRoutes } from './delete'; import { @@ -25,12 +25,7 @@ interface TestOptions { describe('DELETE role', () => { const deleteRoleTest = ( description: string, - { - name, - licenseCheckResult = { state: LICENSE_CHECK_STATE.Valid }, - apiResponse, - asserts, - }: TestOptions + { name, licenseCheckResult = { state: 'valid' }, apiResponse, asserts }: TestOptions ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); @@ -75,7 +70,7 @@ describe('DELETE role', () => { describe('failure', () => { deleteRoleTest('returns result of license checker', { name: 'foo-role', - licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, + licenseCheckResult: { state: 'invalid', message: 'test forbidden message' }, asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts index bb9edbd17b2c8..49123fe9c74d7 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts @@ -5,7 +5,7 @@ */ import Boom from 'boom'; import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; -import { LicenseCheck, LICENSE_CHECK_STATE } from '../../../../../licensing/server'; +import { LicenseCheck } from '../../../../../licensing/server'; import { defineGetRolesRoutes } from './get'; import { @@ -27,12 +27,7 @@ interface TestOptions { describe('GET role', () => { const getRoleTest = ( description: string, - { - name, - licenseCheckResult = { state: LICENSE_CHECK_STATE.Valid }, - apiResponse, - asserts, - }: TestOptions + { name, licenseCheckResult = { state: 'valid' }, apiResponse, asserts }: TestOptions ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); @@ -76,7 +71,7 @@ describe('GET role', () => { describe('failure', () => { getRoleTest('returns result of license checker', { - licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, + licenseCheckResult: { state: 'invalid', message: 'test forbidden message' }, asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts index 96f065d6c765a..5dbe8682c5426 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts @@ -5,7 +5,7 @@ */ import Boom from 'boom'; import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; -import { LicenseCheck, LICENSE_CHECK_STATE } from '../../../../../licensing/server'; +import { LicenseCheck } from '../../../../../licensing/server'; import { defineGetAllRolesRoutes } from './get_all'; import { @@ -27,7 +27,7 @@ interface TestOptions { describe('GET all roles', () => { const getRolesTest = ( description: string, - { licenseCheckResult = { state: LICENSE_CHECK_STATE.Valid }, apiResponse, asserts }: TestOptions + { licenseCheckResult = { state: 'valid' }, apiResponse, asserts }: TestOptions ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); @@ -68,7 +68,7 @@ describe('GET all roles', () => { describe('failure', () => { getRolesTest('returns result of license checker', { - licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, + licenseCheckResult: { state: 'invalid', message: 'test forbidden message' }, asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts index 62b49f0c4e7f0..d7710bf669ce1 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts @@ -6,7 +6,7 @@ import { Type } from '@kbn/config-schema'; import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; -import { LicenseCheck, LICENSE_CHECK_STATE } from '../../../../../licensing/server'; +import { LicenseCheck } from '../../../../../licensing/server'; import { GLOBAL_RESOURCE } from '../../../../common/constants'; import { definePutRolesRoutes } from './put'; @@ -55,7 +55,7 @@ const putRoleTest = ( { name, payload, - licenseCheckResult = { state: LICENSE_CHECK_STATE.Valid }, + licenseCheckResult = { state: 'valid' }, apiResponses = [], asserts, }: TestOptions @@ -140,7 +140,7 @@ describe('PUT role', () => { describe('failure', () => { putRoleTest('returns result of license checker', { name: 'foo-role', - licenseCheckResult: { state: LICENSE_CHECK_STATE.Invalid, message: 'test forbidden message' }, + licenseCheckResult: { state: 'invalid', message: 'test forbidden message' }, asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, }); }); diff --git a/x-pack/plugins/security/server/routes/licensed_route_handler.ts b/x-pack/plugins/security/server/routes/licensed_route_handler.ts index bc5a6a1139215..b113b2ca59e3e 100644 --- a/x-pack/plugins/security/server/routes/licensed_route_handler.ts +++ b/x-pack/plugins/security/server/routes/licensed_route_handler.ts @@ -5,16 +5,12 @@ */ import { RequestHandler } from 'kibana/server'; -import { LICENSE_CHECK_STATE } from '../../../licensing/server'; export const createLicensedRouteHandler = (handler: RequestHandler) => { const licensedRouteHandler: RequestHandler = (context, request, responseToolkit) => { const { license } = context.licensing; const licenseCheck = license.check('security', 'basic'); - if ( - licenseCheck.state === LICENSE_CHECK_STATE.Unavailable || - licenseCheck.state === LICENSE_CHECK_STATE.Invalid - ) { + if (licenseCheck.state === 'unavailable' || licenseCheck.state === 'invalid') { return responseToolkit.forbidden({ body: { message: licenseCheck.message! } }); } diff --git a/x-pack/plugins/security/server/routes/role_mapping/delete.test.ts b/x-pack/plugins/security/server/routes/role_mapping/delete.test.ts index e8a8a7216330b..34961dbe27675 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/delete.test.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/delete.test.ts @@ -7,7 +7,6 @@ import { routeDefinitionParamsMock } from '../index.mock'; import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; -import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; import { defineRoleMappingDeleteRoutes } from './delete'; describe('DELETE role mappings', () => { @@ -33,7 +32,7 @@ describe('DELETE role mappings', () => { }); const mockContext = ({ licensing: { - license: { check: jest.fn().mockReturnValue({ state: LICENSE_CHECK_STATE.Valid }) }, + license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, }, } as unknown) as RequestHandlerContext; @@ -67,7 +66,7 @@ describe('DELETE role mappings', () => { licensing: { license: { check: jest.fn().mockReturnValue({ - state: LICENSE_CHECK_STATE.Invalid, + state: 'invalid', message: 'test forbidden message', }), }, diff --git a/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts b/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts index f2c48fd370434..4fde93d0ad859 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts @@ -11,7 +11,7 @@ import { RequestHandlerContext, IClusterClient, } from '../../../../../../src/core/server'; -import { LICENSE_CHECK_STATE, LicenseCheck } from '../../../../licensing/server'; +import { LicenseCheck } from '../../../../licensing/server'; import { defineRoleMappingFeatureCheckRoute } from './feature_check'; interface TestOptions { @@ -62,7 +62,7 @@ describe('GET role mappings feature check', () => { const getFeatureCheckTest = ( description: string, { - licenseCheckResult = { state: LICENSE_CHECK_STATE.Valid }, + licenseCheckResult = { state: 'valid' }, canManageRoleMappings = true, nodeSettingsResponse = {}, xpackUsageResponse = defaultXpackUsageResponse, diff --git a/x-pack/plugins/security/server/routes/role_mapping/get.test.ts b/x-pack/plugins/security/server/routes/role_mapping/get.test.ts index c60d5518097ba..e0df59ebe7a00 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/get.test.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/get.test.ts @@ -9,7 +9,6 @@ import { routeDefinitionParamsMock } from '../index.mock'; import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; import { defineRoleMappingGetRoutes } from './get'; import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; -import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; const mockRoleMappingResponse = { mapping1: { @@ -70,7 +69,7 @@ describe('GET role mappings', () => { }); const mockContext = ({ licensing: { - license: { check: jest.fn().mockReturnValue({ state: LICENSE_CHECK_STATE.Valid }) }, + license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, }, } as unknown) as RequestHandlerContext; @@ -158,7 +157,7 @@ describe('GET role mappings', () => { }); const mockContext = ({ licensing: { - license: { check: jest.fn().mockReturnValue({ state: LICENSE_CHECK_STATE.Valid }) }, + license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, }, } as unknown) as RequestHandlerContext; @@ -201,7 +200,7 @@ describe('GET role mappings', () => { licensing: { license: { check: jest.fn().mockReturnValue({ - state: LICENSE_CHECK_STATE.Invalid, + state: 'invalid', message: 'test forbidden message', }), }, @@ -238,7 +237,7 @@ describe('GET role mappings', () => { }); const mockContext = ({ licensing: { - license: { check: jest.fn().mockReturnValue({ state: LICENSE_CHECK_STATE.Valid }) }, + license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, }, } as unknown) as RequestHandlerContext; diff --git a/x-pack/plugins/security/server/routes/role_mapping/post.test.ts b/x-pack/plugins/security/server/routes/role_mapping/post.test.ts index 7d820d668a6da..ed3d1bbd0fca2 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/post.test.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/post.test.ts @@ -7,7 +7,6 @@ import { routeDefinitionParamsMock } from '../index.mock'; import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; -import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; import { defineRoleMappingPostRoutes } from './post'; describe('POST role mappings', () => { @@ -42,7 +41,7 @@ describe('POST role mappings', () => { }); const mockContext = ({ licensing: { - license: { check: jest.fn().mockReturnValue({ state: LICENSE_CHECK_STATE.Valid }) }, + license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, }, } as unknown) as RequestHandlerContext; @@ -86,7 +85,7 @@ describe('POST role mappings', () => { licensing: { license: { check: jest.fn().mockReturnValue({ - state: LICENSE_CHECK_STATE.Invalid, + state: 'invalid', message: 'test forbidden message', }), }, diff --git a/x-pack/plugins/security/server/routes/users/change_password.test.ts b/x-pack/plugins/security/server/routes/users/change_password.test.ts index bac40202ee6ef..fd05821f9d520 100644 --- a/x-pack/plugins/security/server/routes/users/change_password.test.ts +++ b/x-pack/plugins/security/server/routes/users/change_password.test.ts @@ -16,7 +16,6 @@ import { RouteConfig, ScopeableRequest, } from '../../../../../../src/core/server'; -import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; import { Authentication, AuthenticationResult } from '../../authentication'; import { defineChangeUserPasswordRoutes } from './change_password'; @@ -63,7 +62,7 @@ describe('Change password', () => { mockContext = ({ licensing: { - license: { check: jest.fn().mockReturnValue({ check: LICENSE_CHECK_STATE.Valid }) }, + license: { check: jest.fn().mockReturnValue({ check: 'valid' }) }, }, } as unknown) as RequestHandlerContext; diff --git a/x-pack/plugins/snapshot_restore/public/application/mount_management_section.ts b/x-pack/plugins/snapshot_restore/public/application/mount_management_section.ts new file mode 100644 index 0000000000000..9697e24a7147e --- /dev/null +++ b/x-pack/plugins/snapshot_restore/public/application/mount_management_section.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { CoreSetup } from 'src/core/public'; +import { ManagementAppMountParams } from 'src/plugins/management/public'; +import { i18n } from '@kbn/i18n'; + +import { ClientConfigType } from '../types'; +import { httpService } from './services/http'; +import { UiMetricService } from './services'; +import { breadcrumbService, docTitleService } from './services/navigation'; +import { documentationLinksService } from './services/documentation'; +import { AppDependencies } from './app_context'; +import { renderApp } from '.'; + +export async function mountManagementSection( + coreSetup: CoreSetup, + services: { + uiMetricService: UiMetricService; + }, + config: ClientConfigType, + params: ManagementAppMountParams +) { + const { element, setBreadcrumbs } = params; + const [core] = await coreSetup.getStartServices(); + const { + docLinks, + chrome: { docTitle }, + } = core; + + docTitleService.setup(docTitle.change); + breadcrumbService.setup(setBreadcrumbs); + documentationLinksService.setup(docLinks); + + const appDependencies: AppDependencies = { + core, + config, + services: { + httpService, + uiMetricService: services.uiMetricService, + i18n, + }, + }; + + return renderApp(element, appDependencies); +} diff --git a/x-pack/plugins/snapshot_restore/public/plugin.ts b/x-pack/plugins/snapshot_restore/public/plugin.ts index 30862c2adb35a..d966d0c32651c 100644 --- a/x-pack/plugins/snapshot_restore/public/plugin.ts +++ b/x-pack/plugins/snapshot_restore/public/plugin.ts @@ -9,11 +9,9 @@ import { CoreSetup, PluginInitializerContext } from 'src/core/public'; import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/public'; import { ManagementSetup } from '../../../../src/plugins/management/public'; import { PLUGIN } from '../common/constants'; -import { AppDependencies } from './application'; + import { ClientConfigType } from './types'; -import { breadcrumbService, docTitleService } from './application/services/navigation'; -import { documentationLinksService } from './application/services/documentation'; import { httpService, setUiMetricService } from './application/services/http'; import { textService } from './application/services/text'; import { UiMetricService } from './application/services'; @@ -34,7 +32,7 @@ export class SnapshotRestoreUIPlugin { public setup(coreSetup: CoreSetup, plugins: PluginsDependencies): void { const config = this.initializerContext.config.get(); - const { http, getStartServices } = coreSetup; + const { http } = coreSetup; const { management, usageCollection } = plugins; // Initialize services @@ -48,29 +46,12 @@ export class SnapshotRestoreUIPlugin { defaultMessage: 'Snapshot and Restore', }), order: 7, - mount: async ({ element, setBreadcrumbs }) => { - const [core] = await getStartServices(); - const { - docLinks, - chrome: { docTitle }, - } = core; - - docTitleService.setup(docTitle.change); - breadcrumbService.setup(setBreadcrumbs); - documentationLinksService.setup(docLinks); - - const appDependencies: AppDependencies = { - core, - config, - services: { - httpService, - uiMetricService: this.uiMetricService, - i18n, - }, + mount: async params => { + const { mountManagementSection } = await import('./application/mount_management_section'); + const services = { + uiMetricService: this.uiMetricService, }; - - const { renderApp } = await import('./application'); - return renderApp(element, appDependencies); + return await mountManagementSection(coreSetup, services, config, params); }, }); } diff --git a/x-pack/plugins/snapshot_restore/public/shared_imports.ts b/x-pack/plugins/snapshot_restore/public/shared_imports.ts index 0c5b82c1f0e43..7e7ef09d0c09d 100644 --- a/x-pack/plugins/snapshot_restore/public/shared_imports.ts +++ b/x-pack/plugins/snapshot_restore/public/shared_imports.ts @@ -10,9 +10,6 @@ export { UseRequestConfig, sendRequest, useRequest, -} from '../../../../src/plugins/es_ui_shared/public'; - -export { CronEditor, DAY, -} from '../../../../src/plugins/es_ui_shared/public/components/cron_editor'; +} from '../../../../src/plugins/es_ui_shared/public'; diff --git a/x-pack/plugins/snapshot_restore/server/services/license.ts b/x-pack/plugins/snapshot_restore/server/services/license.ts index 74696bb966e8a..31d3654c51e3e 100644 --- a/x-pack/plugins/snapshot_restore/server/services/license.ts +++ b/x-pack/plugins/snapshot_restore/server/services/license.ts @@ -13,7 +13,6 @@ import { import { LicensingPluginSetup } from '../../../licensing/server'; import { LicenseType } from '../../../licensing/common/types'; -import { LICENSE_CHECK_STATE } from '../../../licensing/common/types'; export interface LicenseStatus { isValid: boolean; @@ -38,7 +37,7 @@ export class License { ) { licensing.license$.subscribe(license => { const { state, message } = license.check(pluginId, minimumLicenseType); - const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + const hasRequiredLicense = state === 'valid'; if (hasRequiredLicense) { this.licenseStatus = { isValid: true }; diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx index 7809b511adda4..99b4e184c071a 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx @@ -6,7 +6,6 @@ import React from 'react'; import Boom from 'boom'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { mockManagementPlugin } from '../../../../../../src/legacy/core_plugins/management/public/np_ready/mocks'; import { CopySavedObjectsToSpaceFlyout } from './copy_to_space_flyout'; import { CopyToSpaceForm } from './copy_to_space_form'; import { EuiLoadingSpinner, EuiEmptyPrompt } from '@elastic/eui'; @@ -19,11 +18,6 @@ import { spacesManagerMock } from '../../spaces_manager/mocks'; import { SpacesManager } from '../../spaces_manager'; import { ToastsApi } from 'src/core/public'; -jest.mock('../../../../../../src/legacy/core_plugins/management/public/legacy', () => ({ - setup: mockManagementPlugin.createSetupContract(), - start: mockManagementPlugin.createStartContract(), -})); - interface SetupOpts { mockSpaces?: Space[]; returnBeforeSpacesLoad?: boolean; diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx index 4d92505c4aebb..fee41fc7e36d3 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx @@ -25,7 +25,7 @@ import { ToastsStart } from 'src/core/public'; import { ProcessedImportResponse, processImportResponse, -} from '../../../../../../src/legacy/core_plugins/management/public'; +} from '../../../../../../src/legacy/core_plugins/kibana/public'; import { SavedObjectsManagementRecord } from '../../../../../../src/plugins/saved_objects_management/public'; import { Space } from '../../../common/model/space'; import { SpacesManager } from '../../spaces_manager'; diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx index b22cec0af5ea8..4f6ff55dbfbb2 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx @@ -8,7 +8,7 @@ import React, { Fragment } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiStat, EuiHorizontalRule } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/kibana/public'; import { ImportRetry } from '../types'; interface Props { diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx index 96cbac4b48065..ea74fc92b95ea 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx @@ -13,7 +13,7 @@ import { EuiHorizontalRule, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/kibana/public'; import { SavedObjectsManagementRecord } from '../../../../../../src/plugins/saved_objects_management/public'; import { Space } from '../../../common/model/space'; import { CopyOptions, ImportRetry } from '../types'; diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts index fb2616619c644..65a0cabfeb716 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts @@ -5,7 +5,7 @@ */ import { summarizeCopyResult } from './summarize_copy_result'; -import { ProcessedImportResponse } from 'src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from 'src/legacy/core_plugins/kibana/public'; const createSavedObjectsManagementRecord = () => ({ type: 'dashboard', diff --git a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts index 96e642b0f45d8..44c9e9993bf10 100644 --- a/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts +++ b/x-pack/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ProcessedImportResponse } from 'src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from 'src/legacy/core_plugins/kibana/public'; import { SavedObjectsManagementRecord } from 'src/plugins/saved_objects_management/public'; export interface SummarizedSavedObjectResult { diff --git a/x-pack/plugins/spaces/server/routes/api/__fixtures__/route_contexts.ts b/x-pack/plugins/spaces/server/routes/api/__fixtures__/route_contexts.ts index 0bc1685345857..be8322138ec78 100644 --- a/x-pack/plugins/spaces/server/routes/api/__fixtures__/route_contexts.ts +++ b/x-pack/plugins/spaces/server/routes/api/__fixtures__/route_contexts.ts @@ -5,13 +5,12 @@ */ import { RequestHandlerContext } from 'src/core/server'; -import { LICENSE_CHECK_STATE } from '../../../../../licensing/server'; export const mockRouteContext = ({ licensing: { license: { check: jest.fn().mockReturnValue({ - state: LICENSE_CHECK_STATE.Valid, + state: 'valid', }), }, }, @@ -21,7 +20,7 @@ export const mockRouteContextWithInvalidLicense = ({ licensing: { license: { check: jest.fn().mockReturnValue({ - state: LICENSE_CHECK_STATE.Invalid, + state: 'invalid', message: 'License is invalid for spaces', }), }, diff --git a/x-pack/plugins/spaces/server/routes/lib/licensed_route_handler.ts b/x-pack/plugins/spaces/server/routes/lib/licensed_route_handler.ts index 13bed5ce58e2b..d56414a12b838 100644 --- a/x-pack/plugins/spaces/server/routes/lib/licensed_route_handler.ts +++ b/x-pack/plugins/spaces/server/routes/lib/licensed_route_handler.ts @@ -5,16 +5,12 @@ */ import { RequestHandler } from 'kibana/server'; -import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; export const createLicensedRouteHandler = (handler: RequestHandler) => { const licensedRouteHandler: RequestHandler = (context, request, responseToolkit) => { const { license } = context.licensing; const licenseCheck = license.check('spaces', 'basic'); - if ( - licenseCheck.state === LICENSE_CHECK_STATE.Unavailable || - licenseCheck.state === LICENSE_CHECK_STATE.Invalid - ) { + if (licenseCheck.state === 'unavailable' || licenseCheck.state === 'invalid') { return responseToolkit.forbidden({ body: { message: licenseCheck.message! } }); } diff --git a/x-pack/plugins/task_manager/server/README.md b/x-pack/plugins/task_manager/server/README.md index a4154f3ecf212..c3d45be5d8f22 100644 --- a/x-pack/plugins/task_manager/server/README.md +++ b/x-pack/plugins/task_manager/server/README.md @@ -456,6 +456,6 @@ The task manager's public API is create / delete / list. Updates aren't directly ``` - Integration tests: ``` - node scripts/functional_tests_server.js --config x-pack/test/plugin_api_integration/config.js - node scripts/functional_test_runner --config x-pack/test/plugin_api_integration/config.js + node scripts/functional_tests_server.js --config x-pack/test/plugin_api_integration/config.ts + node scripts/functional_test_runner --config x-pack/test/plugin_api_integration/config.ts ``` diff --git a/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx b/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx index c0c85f74418fc..c50df0366d698 100644 --- a/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx +++ b/x-pack/plugins/transform/public/app/components/pivot_preview/pivot_preview.tsx @@ -245,6 +245,10 @@ export const PivotPreview: FC = React.memo( return formatHumanReadableDateTimeSeconds(moment(cellValue).unix() * 1000); } + if (previewMappings.properties[columnId].type === ES_FIELD_TYPES.BOOLEAN) { + return cellValue ? 'true' : 'false'; + } + return cellValue; }; }, [pageData, pagination.pageIndex, pagination.pageSize, previewMappings.properties]); diff --git a/x-pack/plugins/transform/public/app/components/toast_notification_text.test.tsx b/x-pack/plugins/transform/public/app/components/toast_notification_text.test.tsx index e51119d67d567..40fcf789b30bb 100644 --- a/x-pack/plugins/transform/public/app/components/toast_notification_text.test.tsx +++ b/x-pack/plugins/transform/public/app/components/toast_notification_text.test.tsx @@ -7,6 +7,8 @@ import React from 'react'; import { render } from '@testing-library/react'; +import { CoreStart } from 'src/core/public'; + import { ToastNotificationText } from './toast_notification_text'; jest.mock('../../shared_imports'); @@ -15,6 +17,7 @@ jest.mock('../../app/app_dependencies'); describe('ToastNotificationText', () => { test('should render the text as plain text', () => { const props = { + overlays: {} as CoreStart['overlays'], text: 'a short text message', }; const { container } = render(); @@ -23,6 +26,7 @@ describe('ToastNotificationText', () => { test('should render the text within a modal', () => { const props = { + overlays: {} as CoreStart['overlays'], text: 'a text message that is longer than 140 characters. a text message that is longer than 140 characters. a text message that is longer than 140 characters. ', }; diff --git a/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx b/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx index 44927e61a42c4..1044081670523 100644 --- a/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx +++ b/x-pack/plugins/transform/public/app/components/toast_notification_text.tsx @@ -18,15 +18,20 @@ import { import { i18n } from '@kbn/i18n'; -import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; +import { CoreStart } from 'src/core/public'; -import { useAppDependencies } from '../app_dependencies'; +import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; const MAX_SIMPLE_MESSAGE_LENGTH = 140; -export const ToastNotificationText: FC<{ text: any }> = ({ text }) => { - const { overlays } = useAppDependencies(); +// Because of the use of `toMountPoint`, `useKibanaContext` doesn't work via `useAppDependencies`. +// That's why we need to pass in `overlays` as a prop cannot get it via context. +interface ToastNotificationTextProps { + overlays: CoreStart['overlays']; + text: any; +} +export const ToastNotificationText: FC = ({ overlays, text }) => { if (typeof text === 'string' && text.length <= MAX_SIMPLE_MESSAGE_LENGTH) { return text; } diff --git a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx index 6210c72ef9d05..23693cd7222c8 100644 --- a/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx +++ b/x-pack/plugins/transform/public/app/hooks/use_delete_transform.tsx @@ -11,13 +11,16 @@ import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public' import { TransformEndpointRequest, TransformEndpointResult } from '../../../common'; -import { useToastNotifications } from '../app_dependencies'; +import { getErrorMessage } from '../../shared_imports'; + +import { useAppDependencies, useToastNotifications } from '../app_dependencies'; import { TransformListRow, refreshTransformList$, REFRESH_TRANSFORM_LIST_STATE } from '../common'; import { ToastNotificationText } from '../components'; import { useApi } from './use_api'; export const useDeleteTransforms = () => { + const { overlays } = useAppDependencies(); const toastNotifications = useToastNotifications(); const api = useApi(); @@ -56,9 +59,7 @@ export const useDeleteTransforms = () => { title: i18n.translate('xpack.transform.transformList.deleteTransformGenericErrorMessage', { defaultMessage: 'An error occurred calling the API endpoint to delete transforms.', }), - text: toMountPoint( - - ), + text: toMountPoint(), }); } }; diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx index c56263b721032..bcdeb7ddb0d36 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/source_index_preview/source_index_preview.tsx @@ -105,6 +105,9 @@ export const SourceIndexPreview: React.FC = React.memo(({ indexPattern, q let schema; switch (field?.type) { + case KBN_FIELD_TYPES.BOOLEAN: + schema = 'boolean'; + break; case KBN_FIELD_TYPES.DATE: schema = 'datetime'; break; @@ -190,6 +193,10 @@ export const SourceIndexPreview: React.FC = React.memo(({ indexPattern, q return formatHumanReadableDateTimeSeconds(moment(cellValue).unix() * 1000); } + if (field?.type === KBN_FIELD_TYPES.BOOLEAN) { + return cellValue ? 'true' : 'false'; + } + return cellValue; }; }, [data, indexPattern.fields, pagination.pageIndex, pagination.pageSize]); 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 5dcaece28bdde..5e5aace4139f0 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 @@ -32,6 +32,8 @@ import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_reac import { PROGRESS_REFRESH_INTERVAL_MS } from '../../../../../../common/constants'; +import { getErrorMessage } from '../../../../../shared_imports'; + import { getTransformProgress, getDiscoverUrl } from '../../../../common'; import { useApi } from '../../../../hooks/use_api'; import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; @@ -116,7 +118,9 @@ export const StepCreateForm: FC = React.memo( defaultMessage: 'An error occurred creating the transform {transformId}:', values: { transformId }, }), - text: toMountPoint(), + text: toMountPoint( + + ), }); setCreated(false); setLoading(false); @@ -157,7 +161,9 @@ export const StepCreateForm: FC = React.memo( defaultMessage: 'An error occurred starting the transform {transformId}:', values: { transformId }, }), - text: toMountPoint(), + text: toMountPoint( + + ), }); setStarted(false); setLoading(false); @@ -223,7 +229,9 @@ export const StepCreateForm: FC = React.memo( 'An error occurred creating the Kibana index pattern {indexPatternName}:', values: { indexPatternName }, }), - text: toMountPoint(), + text: toMountPoint( + + ), }); setLoading(false); return false; @@ -260,7 +268,9 @@ export const StepCreateForm: FC = React.memo( title: i18n.translate('xpack.transform.stepCreateForm.progressErrorMessage', { defaultMessage: 'An error occurred getting the progress percentage:', }), - text: toMountPoint(), + text: toMountPoint( + + ), }); clearInterval(interval); } diff --git a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx index d47af47214851..9a39616fb0989 100644 --- a/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx +++ b/x-pack/plugins/transform/public/app/sections/create_transform/components/step_details/step_details_form.tsx @@ -16,6 +16,8 @@ import { toMountPoint } from '../../../../../../../../../src/plugins/kibana_reac import { TransformId } from '../../../../../../common'; import { isValidIndexName } from '../../../../../../common/utils/es_utils'; +import { getErrorMessage } from '../../../../../shared_imports'; + import { useAppDependencies, useToastNotifications } from '../../../../app_dependencies'; import { ToastNotificationText } from '../../../../components'; import { useDocumentationLinks } from '../../../../hooks/use_documentation_links'; @@ -116,7 +118,9 @@ export const StepDetailsForm: FC = React.memo( title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingTransformList', { defaultMessage: 'An error occurred getting the existing transform IDs:', }), - text: toMountPoint(), + text: toMountPoint( + + ), }); } @@ -127,7 +131,9 @@ export const StepDetailsForm: FC = React.memo( title: i18n.translate('xpack.transform.stepDetailsForm.errorGettingIndexNames', { defaultMessage: 'An error occurred getting the existing index names:', }), - text: toMountPoint(), + text: toMountPoint( + + ), }); } @@ -141,7 +147,9 @@ export const StepDetailsForm: FC = React.memo( defaultMessage: 'An error occurred getting the existing index pattern titles:', } ), - text: toMountPoint(), + text: toMountPoint( + + ), }); } })(); diff --git a/x-pack/plugins/transform/public/plugin.ts b/x-pack/plugins/transform/public/plugin.ts index cfe84a5ab693d..e6f19bcf1c2bb 100644 --- a/x-pack/plugins/transform/public/plugin.ts +++ b/x-pack/plugins/transform/public/plugin.ts @@ -8,15 +8,18 @@ import { i18n as kbnI18n } from '@kbn/i18n'; import { CoreSetup } from 'src/core/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { ManagementSetup } from 'src/plugins/management/public'; +import { HomePublicPluginSetup } from 'src/plugins/home/public'; +import { registerFeature } from './register_feature'; export interface PluginsDependencies { data: DataPublicPluginStart; management: ManagementSetup; + home: HomePublicPluginSetup; } export class TransformUiPlugin { public setup(coreSetup: CoreSetup, pluginsSetup: PluginsDependencies): void { - const { management } = pluginsSetup; + const { management, home } = pluginsSetup; // Register management section const esSection = management.sections.getSection('elasticsearch'); @@ -33,6 +36,7 @@ export class TransformUiPlugin { }, }); } + registerFeature(home); } public start() {} diff --git a/x-pack/plugins/transform/public/register_feature.ts b/x-pack/plugins/transform/public/register_feature.ts new file mode 100644 index 0000000000000..708dfcb70c67a --- /dev/null +++ b/x-pack/plugins/transform/public/register_feature.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { + HomePublicPluginSetup, + FeatureCatalogueCategory, +} from '../../../../src/plugins/home/public'; + +export const registerFeature = (home: HomePublicPluginSetup) => { + // register Transforms so it appears on the Kibana home page + home.featureCatalogue.register({ + id: 'transform', + title: i18n.translate('xpack.transform.transformsTitle', { + defaultMessage: 'Transforms', + }), + description: i18n.translate('xpack.transform.transformsDescription', { + defaultMessage: + 'Use transforms to pivot existing Elasticsearch indices into summarized or entity-centric indices.', + }), + icon: 'managementApp', // there is currently no Transforms icon, so using the general management app icon + path: '/app/kibana#/management/elasticsearch/transform', + showOnHomePage: true, + category: FeatureCatalogueCategory.ADMIN, + }); +}; diff --git a/x-pack/plugins/transform/public/shared_imports.ts b/x-pack/plugins/transform/public/shared_imports.ts index 938ec77344d8f..8eb42ad677c0f 100644 --- a/x-pack/plugins/transform/public/shared_imports.ts +++ b/x-pack/plugins/transform/public/shared_imports.ts @@ -15,3 +15,5 @@ export { UseRequestConfig, useRequest, } from '../../../../src/plugins/es_ui_shared/public/request/np_ready_request'; + +export { getErrorMessage } from '../../ml/common/util/errors'; diff --git a/x-pack/plugins/transform/server/services/license.ts b/x-pack/plugins/transform/server/services/license.ts index 93346160c6f44..18ed7db1e3f6d 100644 --- a/x-pack/plugins/transform/server/services/license.ts +++ b/x-pack/plugins/transform/server/services/license.ts @@ -12,7 +12,7 @@ import { RequestHandlerContext, } from 'kibana/server'; -import { LicensingPluginSetup, LicenseType, LICENSE_CHECK_STATE } from '../../../licensing/server'; +import { LicensingPluginSetup, LicenseType } from '../../../licensing/server'; export interface LicenseStatus { isValid: boolean; @@ -39,7 +39,7 @@ export class License { ) { licensing.license$.subscribe(license => { const { state, message } = license.check(pluginId, minimumLicenseType); - const hasRequiredLicense = state === LICENSE_CHECK_STATE.Valid; + const hasRequiredLicense = state === 'valid'; const securityFeature = license.getFeature('security'); const isSecurityEnabled = diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 2fcebb286639e..103eed24a6852 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -76,6 +76,60 @@ } }, "messages": { + "advancedSettings.advancedSettingsLabel": "高度な設定", + "advancedSettings.badge.readOnly.text": "読み取り専用", + "advancedSettings.badge.readOnly.tooltip": "高度な設定を保存できません", + "advancedSettings.callOutCautionDescription": "これらの設定は非常に上級ユーザー向けなのでご注意ください。ここでの変更は Kibana の重要な部分に不具合を生じさせる可能性があります。これらの設定は非公開、サポート対象外、または実験的な場合があります。フィールドにデフォルト値がある場合、そのフィールドを未入力のままにするとデフォルトに戻り、他の構成で許容されないことがあります。カスタム設定を削除すると、Kibana の構成から永久に削除されます。", + "advancedSettings.callOutCautionTitle": "注意:不具合につながる可能性があります", + "advancedSettings.categoryNames.dashboardLabel": "ダッシュボード", + "advancedSettings.categoryNames.discoverLabel": "発見", + "advancedSettings.categoryNames.generalLabel": "一般", + "advancedSettings.categoryNames.notificationsLabel": "通知", + "advancedSettings.categoryNames.reportingLabel": "レポート", + "advancedSettings.categoryNames.searchLabel": "検索", + "advancedSettings.categoryNames.siemLabel": "SIEM", + "advancedSettings.categoryNames.timelionLabel": "Timelion", + "advancedSettings.categoryNames.visualizationsLabel": "可視化", + "advancedSettings.categorySearchLabel": "カテゴリー", + "advancedSettings.field.changeImageLinkAriaLabel": "{ariaName} を変更", + "advancedSettings.field.changeImageLinkText": "画像を変更", + "advancedSettings.field.codeEditorSyntaxErrorMessage": "無効な JSON 構文", + "advancedSettings.field.customSettingAriaLabel": "カスタム設定", + "advancedSettings.field.customSettingTooltip": "カスタム設定", + "advancedSettings.field.defaultValueText": "デフォルト: {value}", + "advancedSettings.field.defaultValueTypeJsonText": "デフォルト: {value}", + "advancedSettings.field.deprecationClickAreaLabel": "クリックすると {settingName} のサポート終了に関するドキュメントが表示されます。", + "advancedSettings.field.helpText": "この設定は Kibana サーバーにより上書きされ、変更することはできません。", + "advancedSettings.field.imageChangeErrorMessage": "画像を保存できませんでした", + "advancedSettings.field.imageTooLargeErrorMessage": "画像が大きすぎます。最大サイズは {maxSizeDescription} です", + "advancedSettings.field.invalidIconLabel": "無効", + "advancedSettings.field.offLabel": "オフ", + "advancedSettings.field.onLabel": "オン", + "advancedSettings.field.resetToDefaultLinkAriaLabel": "{ariaName} をデフォルトにリセット", + "advancedSettings.field.resetToDefaultLinkText": "デフォルトにリセット", + "advancedSettings.field.settingIsUnsaved": "設定は現在保存されていません。", + "advancedSettings.field.unsavedIconLabel": "未保存", + "advancedSettings.form.cancelButtonLabel": "変更をキャンセル", + "advancedSettings.form.clearNoSearchResultText": "(検索結果を消去)", + "advancedSettings.form.clearSearchResultText": "(検索結果を消去)", + "advancedSettings.form.countOfSettingsChanged": "{unsavedCount} unsaved {unsavedCount, plural, one {setting} other {settings} }{hiddenCount, plural, =0 {} other {, # hidden} }", + "advancedSettings.form.noSearchResultText": "設定が見つかりませんでした {clearSearch}", + "advancedSettings.form.requiresPageReloadToastButtonLabel": "ページを再読み込み", + "advancedSettings.form.requiresPageReloadToastDescription": "設定を有効にするためにページの再読み込みが必要です。", + "advancedSettings.form.saveButtonLabel": "変更を保存", + "advancedSettings.form.saveButtonTooltipWithInvalidChanges": "保存前に無効な設定を修正してください。", + "advancedSettings.form.saveErrorMessage": "を保存できませんでした", + "advancedSettings.form.searchResultText": "検索用語により {settingsCount} 件の設定が非表示になっています {clearSearch}", + "advancedSettings.pageTitle": "設定", + "advancedSettings.searchBar.unableToParseQueryErrorMessage": "クエリをパースできません", + "advancedSettings.searchBarAriaLabel": "高度な設定を検索", + "advancedSettings.voiceAnnouncement.searchResultScreenReaderMessage": "{query} を検索しました。{sectionLenght, plural, one {# セクション} other {# セクション}}に{optionLenght, plural, one {# オプション} other {# オプション}}があります。", + "charts.colormaps.bluesText": "青", + "charts.colormaps.greensText": "緑", + "charts.colormaps.greenToRedText": "緑から赤", + "charts.colormaps.greysText": "グレー", + "charts.colormaps.redsText": "赤", + "charts.colormaps.yellowToRedText": "黄色から赤", "common.ui.aggResponse.allDocsTitle": "すべてのドキュメント", "common.ui.errorAutoCreateIndex.breadcrumbs.errorText": "エラー", "common.ui.errorAutoCreateIndex.errorDescription": "Elasticsearch クラスターの {autoCreateIndexActionConfig} 設定が原因で、Kibana が保存されたオブジェクトを格納するインデックスを自動的に作成できないようです。Kibana は、保存されたオブジェクトインデックスが適切なマッピング/スキーマを使用し Kibana から Elasticsearch へのポーリングの回数を減らすための最適な手段であるため、この Elasticsearch の機能を使用します。", @@ -195,6 +249,13 @@ "common.ui.fieldEditor.syntax.painlessLabel.painlessDetail": "Painless は非常に強力かつ使いやすい言語です。多くの {javaAPIs} にアクセスすることができます。{syntax} について読めば、すぐに習得することができます!", "common.ui.fieldEditor.syntax.painlessLabel.syntaxLink": "構文", "common.ui.fieldEditor.syntaxHeader": "構文", + "common.ui.fieldEditor.testScript.errorMessage": "スクリプト内にエラーがあります", + "common.ui.fieldEditor.testScript.fieldsLabel": "追加フィールド", + "common.ui.fieldEditor.testScript.fieldsPlaceholder": "選択してください…", + "common.ui.fieldEditor.testScript.instructions": "スクリプトを実行すると、最初の検索結果10件をプレビューできます。追加フィールドを選択して結果に含み、コンテクストをさらに加えたり、特定の文書上でフィルタにクエリを追加したりすることもできます。", + "common.ui.fieldEditor.testScript.resultsLabel": "最初の10件", + "common.ui.fieldEditor.testScript.resultsTitle": "結果を表示", + "common.ui.fieldEditor.testScript.submitButtonLabel": "スクリプトを実行", "common.ui.fieldEditor.truncate.lengthLabel": "フィールドの長さ", "common.ui.fieldEditor.typeLabel": "タイプ", "common.ui.fieldEditor.url.heightLabel": "高さ", @@ -245,6 +306,7 @@ "common.ui.flotCharts.tueLabel": "火", "common.ui.flotCharts.wedLabel": "水", "common.ui.scriptingLanguages.errorFetchingToastDescription": "Elasticsearch から利用可能なスクリプト言語の取得中にエラーが発生しました", + "common.ui.stackManagement.breadcrumb": "管理", "common.ui.stateManagement.unableToParseUrlErrorMessage": "URL をパースできません", "common.ui.stateManagement.unableToRestoreUrlErrorMessage": "URL を完全に復元できません。共有機能を使用していることを確認してください。", "common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage": "セッションがいっぱいで安全に削除できるアイテムが見つからないため、Kibana は履歴アイテムを保存できません。\n\nこれは大抵新規タブに移動することで解決されますが、より大きな問題が原因である可能性もあります。このメッセージが定期的に表示される場合は、{gitHubIssuesUrl} で問題を報告してください。", @@ -252,238 +314,12 @@ "common.ui.url.savedObjectIsMissingNotificationMessage": "保存されたオブジェクトがありません", "common.ui.vis.kibanaMap.leaflet.fitDataBoundsAriaLabel": "データバウンドを合わせる", "common.ui.vis.kibanaMap.zoomWarning": "ズームレベルが最大に達しました。完全にズームインするには、Elasticsearch と Kibana の {defaultDistribution} にアップグレードしてください。{ems} でより多くのズームレベルが利用できます。または、独自のマップサーバーを構成できます。詳細は { wms } または { configSettings} をご覧ください。", - "data.search.aggs.aggGroups.bucketsText": "バケット", - "data.search.aggs.aggGroups.metricsText": "メトリック", - "data.search.aggs.buckets.dateHistogramLabel": "{intervalDescription}ごとの {fieldName}", - "data.search.aggs.buckets.dateHistogramTitle": "日付ヒストグラム", - "data.search.aggs.buckets.dateRangeTitle": "日付範囲", - "data.search.aggs.buckets.filtersTitle": "フィルター", - "data.search.aggs.buckets.filterTitle": "フィルター", - "data.search.aggs.buckets.geohashGridTitle": "ジオハッシュ", - "data.search.aggs.buckets.geotileGridTitle": "ジオタイル", - "data.search.aggs.buckets.histogramTitle": "ヒストグラム", - "data.search.aggs.buckets.intervalOptions.autoDisplayName": "自動", - "data.search.aggs.buckets.intervalOptions.dailyDisplayName": "日ごと", - "data.search.aggs.buckets.intervalOptions.hourlyDisplayName": "1 時間ごと", - "data.search.aggs.buckets.intervalOptions.millisecondDisplayName": "ミリ秒", - "data.search.aggs.buckets.intervalOptions.minuteDisplayName": "分", - "data.search.aggs.buckets.intervalOptions.monthlyDisplayName": "月ごと", - "data.search.aggs.buckets.intervalOptions.secondDisplayName": "秒", - "data.search.aggs.buckets.intervalOptions.weeklyDisplayName": "週ごと", - "data.search.aggs.buckets.intervalOptions.yearlyDisplayName": "1 年ごと", - "data.search.aggs.buckets.ipRangeLabel": "{fieldName} IP 範囲", - "data.search.aggs.buckets.ipRangeTitle": "IPv4 範囲", - "data.search.aggs.aggTypes.rangesFormatMessage": "{gte} {from} と {lt} {to}", - "data.search.aggs.aggTypesLabel": "{fieldName} の範囲", - "data.search.aggs.buckets.rangeTitle": "範囲", - "data.search.aggs.buckets.significantTerms.excludeLabel": "除外", - "data.search.aggs.buckets.significantTerms.includeLabel": "含める", - "data.search.aggs.buckets.significantTermsLabel": "{fieldName} のトップ {size} の珍しいアイテム", - "data.search.aggs.buckets.significantTermsTitle": "重要な用語", - "data.search.aggs.buckets.terms.excludeLabel": "除外", - "data.search.aggs.buckets.terms.includeLabel": "含める", - "data.search.aggs.buckets.terms.missingBucketLabel": "欠測値", - "data.search.aggs.buckets.terms.orderAscendingTitle": "昇順", - "data.search.aggs.buckets.terms.orderDescendingTitle": "降順", - "data.search.aggs.buckets.terms.otherBucketDescription": "このリクエストは、データバケットの基準外のドキュメントの数をカウントします。", - "data.search.aggs.buckets.terms.otherBucketLabel": "その他", - "data.search.aggs.buckets.terms.otherBucketTitle": "他のバケット", - "data.search.aggs.buckets.termsTitle": "用語", - "data.search.aggs.histogram.missingMaxMinValuesWarning": "自動スケールヒストグラムバケットから最高値と最低値を取得できません。これによりビジュアライゼーションのパフォーマンスが低下する可能性があります。", - "data.search.aggs.metrics.averageBucketTitle": "平均バケット", - "data.search.aggs.metrics.averageLabel": "平均 {field}", - "data.search.aggs.metrics.averageTitle": "平均", - "data.search.aggs.metrics.countLabel": "カウント", - "data.search.aggs.metrics.countTitle": "カウント", - "data.search.aggs.metrics.cumulativeSumLabel": "累積合計", - "data.search.aggs.metrics.cumulativeSumTitle": "累積合計", - "data.search.aggs.metrics.derivativeLabel": "派生", - "data.search.aggs.metrics.derivativeTitle": "派生", - "data.search.aggs.metrics.geoBoundsLabel": "境界", - "data.search.aggs.metrics.geoBoundsTitle": "境界", - "data.search.aggs.metrics.geoCentroidLabel": "ジオセントロイド", - "data.search.aggs.metrics.geoCentroidTitle": "ジオセントロイド", - "data.search.aggs.metrics.maxBucketTitle": "最高バケット", - "data.search.aggs.metrics.maxLabel": "最高 {field}", - "data.search.aggs.metrics.maxTitle": "最高", - "data.search.aggs.metrics.medianLabel": "中央 {field}", - "data.search.aggs.metrics.medianTitle": "中央", - "data.search.aggs.metrics.metricAggregationsSubtypeTitle": "メトリック集約", - "data.search.aggs.metrics.minBucketTitle": "最低バケット", - "data.search.aggs.metrics.minLabel": "最低 {field}", - "data.search.aggs.metrics.minTitle": "最低", - "data.search.aggs.metrics.movingAvgLabel": "移動平均", - "data.search.aggs.metrics.movingAvgTitle": "移動平均", - "data.search.aggs.metrics.overallAverageLabel": "全体平均", - "data.search.aggs.metrics.overallMaxLabel": "全体最高", - "data.search.aggs.metrics.overallMinLabel": "全体最低", - "data.search.aggs.metrics.overallSumLabel": "全体合計", - "data.search.aggs.metrics.parentPipelineAggregationsSubtypeTitle": "親パイプライン集約", - "data.search.aggs.metrics.percentileRanks.valuePropsLabel": "「{label}」の {format} のパーセンタイル順位", - "data.search.aggs.metrics.percentileRanksLabel": "{field} のパーセンタイル順位", - "data.search.aggs.metrics.percentileRanksTitle": "パーセンタイル順位", - "data.search.aggs.metrics.percentiles.valuePropsLabel": "{label} の {percentile} パーセンタイル", - "data.search.aggs.metrics.percentilesLabel": "{field} のパーセンタイル", - "data.search.aggs.metrics.percentilesTitle": "パーセンタイル", - "data.search.aggs.metrics.serialDiffLabel": "差分の推移", - "data.search.aggs.metrics.serialDiffTitle": "差分の推移", - "data.search.aggs.metrics.siblingPipelineAggregationsSubtypeTitle": "シブリングパイプラインアグリゲーション", - "data.search.aggs.metrics.standardDeviation.keyDetailsLabel": "{fieldDisplayName} の標準偏差", - "data.search.aggs.metrics.standardDeviation.lowerKeyDetailsTitle": "下の{label}", - "data.search.aggs.metrics.standardDeviation.upperKeyDetailsTitle": "上の{label}", - "data.search.aggs.metrics.standardDeviationLabel": "{field} の標準偏差", - "data.search.aggs.metrics.standardDeviationTitle": "標準偏差", - "data.search.aggs.metrics.sumBucketTitle": "合計バケット", - "data.search.aggs.metrics.sumLabel": "{field} の合計", - "data.search.aggs.metrics.sumTitle": "合計", - "data.search.aggs.metrics.topHit.ascendingLabel": "昇順", - "data.search.aggs.metrics.topHit.averageLabel": "平均", - "data.search.aggs.metrics.topHit.concatenateLabel": "連結", - "data.search.aggs.metrics.topHit.descendingLabel": "降順", - "data.search.aggs.metrics.topHit.firstPrefixLabel": "最初", - "data.search.aggs.metrics.topHit.lastPrefixLabel": "最後", - "data.search.aggs.metrics.topHit.maxLabel": "最高", - "data.search.aggs.metrics.topHit.minLabel": "最低", - "data.search.aggs.metrics.topHit.sumLabel": "合計", - "data.search.aggs.metrics.topHitTitle": "トップヒット", - "data.search.aggs.metrics.uniqueCountLabel": "{field} のユニークカウント", - "data.search.aggs.metrics.uniqueCountTitle": "ユニークカウント", - "data.search.aggs.otherBucket.labelForMissingValuesLabel": "欠測値のラベル", - "data.search.aggs.otherBucket.labelForOtherBucketLabel": "他のバケットのラベル", - "data.search.aggs.paramTypes.field.invalidSavedFieldParameterErrorMessage": "保存された {fieldParameter} パラメーターが無効になりました。新しいフィールドを選択してください。", - "data.search.aggs.paramTypes.field.requiredFieldParameterErrorMessage": "{fieldParameter} は必須パラメーターです", - "data.search.aggs.string.customLabel": "カスタムラベル", - "data.search.aggs.percentageOfLabel": "{label} のパーセンテージ", - "data.filter.applyFilters.popupHeader": "適用するフィルターの選択", - "data.filter.applyFiltersPopup.cancelButtonLabel": "キャンセル", - "data.filter.applyFiltersPopup.saveButtonLabel": "適用", - "data.filter.filterBar.addFilterButtonLabel": "フィルターを追加します", - "data.filter.filterBar.deleteFilterButtonLabel": "削除", - "data.filter.filterBar.disabledFilterPrefix": "無効", - "data.filter.filterBar.disableFilterButtonLabel": "一時的に無効にする", - "data.filter.filterBar.editFilterButtonLabel": "フィルターを編集", - "data.filter.filterBar.enableFilterButtonLabel": "再度有効にする", - "data.filter.filterBar.excludeFilterButtonLabel": "結果を除外", - "data.filter.filterBar.filterItemBadgeAriaLabel": "フィルターアクション", - "data.filter.filterBar.filterItemBadgeIconAriaLabel": "削除", - "data.filter.filterBar.includeFilterButtonLabel": "結果を含める", - "data.filter.filterBar.indexPatternSelectPlaceholder": "インデックスパターンの選択", - "data.filter.filterBar.moreFilterActionsMessage": "フィルター:{innerText}。他のフィルターアクションを使用するには選択してください。", - "data.filter.filterBar.negatedFilterPrefix": "NOT ", - "data.filter.filterBar.pinFilterButtonLabel": "すべてのアプリにピン付け", - "data.filter.filterBar.pinnedFilterPrefix": "ピン付け済み", - "data.filter.filterBar.unpinFilterButtonLabel": "ピンを外す", - "data.filter.filterEditor.cancelButtonLabel": "キャンセル", - "data.filter.filterEditor.createCustomLabelInputLabel": "カスタムラベル", - "data.filter.filterEditor.createCustomLabelSwitchLabel": "カスタムラベルを作成しますか?", - "data.filter.filterEditor.dateFormatHelpLinkLabel": "対応データフォーマット", - "data.filter.filterEditor.doesNotExistOperatorOptionLabel": "存在しません", - "data.filter.filterEditor.editFilterPopupTitle": "フィルターを編集", - "data.filter.filterEditor.editFilterValuesButtonLabel": "フィルター値を編集", - "data.filter.filterEditor.editQueryDslButtonLabel": "クエリ DSL として編集", - "data.filter.filterEditor.existsOperatorOptionLabel": "存在する", - "data.filter.filterEditor.falseOptionLabel": "False", - "data.filter.filterEditor.fieldSelectLabel": "フィールド", - "data.filter.filterEditor.fieldSelectPlaceholder": "フィールドを選択", - "data.filter.filterEditor.indexPatternSelectLabel": "インデックスパターン", - "data.filter.filterEditor.isBetweenOperatorOptionLabel": "is between", - "data.filter.filterEditor.isNotBetweenOperatorOptionLabel": "is not between", - "data.filter.filterEditor.isNotOneOfOperatorOptionLabel": "is not one of", - "data.filter.filterEditor.isNotOperatorOptionLabel": "is not", - "data.filter.filterEditor.isOneOfOperatorOptionLabel": "is one of", - "data.filter.filterEditor.isOperatorOptionLabel": "が", - "data.filter.filterEditor.operatorSelectLabel": "演算子", - "data.filter.filterEditor.operatorSelectPlaceholderSelect": "選択してください", - "data.filter.filterEditor.operatorSelectPlaceholderWaiting": "待機中", - "data.filter.filterEditor.queryDslLabel": "Elasticsearch クエリ DSL", - "data.filter.filterEditor.rangeEndInputPlaceholder": "範囲の終了値", - "data.filter.filterEditor.rangeInputLabel": "範囲", - "data.filter.filterEditor.rangeStartInputPlaceholder": "範囲の開始値", - "data.filter.filterEditor.saveButtonLabel": "保存", - "data.filter.filterEditor.trueOptionLabel": "True", - "data.filter.filterEditor.valueInputLabel": "値", - "data.filter.filterEditor.valueInputPlaceholder": "値を入力", - "data.filter.filterEditor.valueSelectPlaceholder": "値を選択", - "data.filter.filterEditor.valuesSelectLabel": "値", - "data.filter.filterEditor.valuesSelectPlaceholder": "値を選択", - "data.filter.options.changeAllFiltersButtonLabel": "すべてのフィルターの変更", - "data.filter.options.deleteAllFiltersButtonLabel": "すべて削除", - "data.filter.options.disableAllFiltersButtonLabel": "すべて無効にする", - "data.filter.options.enableAllFiltersButtonLabel": "すべて有効にする", - "data.filter.options.invertDisabledFiltersButtonLabel": "有効・無効を反転", - "data.filter.options.invertNegatedFiltersButtonLabel": "含める・除外を反転", - "data.filter.options.pinAllFiltersButtonLabel": "すべてピン付け", - "data.filter.options.unpinAllFiltersButtonLabel": "すべてのピンを外す", - "data.filter.searchBar.changeAllFiltersTitle": "すべてのフィルターの変更", - "data.indexPatterns.unableWriteLabel": "インデックスパターンを書き込めません!このインデックスパターンへの最新の変更を取得するには、ページを更新してください。", - "data.indexPatterns.unknownFieldErrorMessage": "インデックスパターン「{title}」のフィールド「{name}」が不明なフィールドタイプを使用しています。", - "data.indexPatterns.unknownFieldHeader": "不明なフィールドタイプ {type}", - "data.parseEsInterval.invalidEsCalendarIntervalErrorMessage": "無効なカレンダー間隔:{interval}、1よりも大きな値が必要です", - "data.parseEsInterval.invalidEsIntervalFormatErrorMessage": "無効な間隔フォーマット:{interval}", - "data.query.queryBar.comboboxAriaLabel": "{pageType} ページの検索とフィルタリング", - "data.query.queryBar.kqlFullLanguageName": "Kibana クエリ言語", - "data.query.queryBar.kqlLanguageName": "KQL", - "data.query.queryBar.kqlOffLabel": "オフ", - "data.query.queryBar.kqlOnLabel": "オン", - "data.query.queryBar.luceneLanguageName": "Lucene", - "data.query.queryBar.luceneSyntaxWarningMessage": "Lucene クエリ構文を使用しているようですが、Kibana クエリ言語 (KQL) が選択されています。KQL ドキュメント {link} を確認してください。", - "data.query.queryBar.luceneSyntaxWarningOptOutText": "今後表示しない", - "data.query.queryBar.luceneSyntaxWarningTitle": "Lucene 構文警告", - "data.query.queryBar.searchInputAriaLabel": "{pageType} ページの検索とフィルタリングを行うには入力を開始してください", - "data.query.queryBar.searchInputPlaceholder": "検索", - "data.query.queryBar.syntaxOptionsDescription": "{docsLink} (KQL) は、シンプルなクエリ構文とスクリプトフィールドのサポートを提供します。また、KQL はベーシックライセンス以上をご利用の場合、自動入力も提供します。KQL をオフにすると、Kibana は Lucene を使用します。", - "data.query.queryBar.syntaxOptionsDescription.docsLinkText": "こちら", - "data.query.queryBar.syntaxOptionsTitle": "構文オプション", - "data.search.searchBar.savedQueryDescriptionLabelText": "説明", - "data.search.searchBar.savedQueryDescriptionText": "再度使用するクエリテキストとフィルターを保存します。", - "data.search.searchBar.savedQueryForm.titleConflictText": "タイトルが既に保存されているクエリに使用されています", - "data.search.searchBar.savedQueryForm.titleMissingText": "名前が必要です", - "data.search.searchBar.savedQueryForm.whitespaceErrorText": "タイトルの始めと終わりにはスペースを使用できません", - "data.search.searchBar.savedQueryFormCancelButtonText": "キャンセル", - "data.search.searchBar.savedQueryFormSaveButtonText": "保存", - "data.search.searchBar.savedQueryFormTitle": "クエリを保存", - "data.search.searchBar.savedQueryIncludeFiltersLabelText": "フィルターを含める", - "data.search.searchBar.savedQueryIncludeTimeFilterLabelText": "時間フィルターを含める", - "data.search.searchBar.savedQueryNameHelpText": "名前が必要です。タイトルの始めと終わりにはスペースを使用できません。名前は固有でなければなりません。", - "data.search.searchBar.savedQueryNameLabelText": "名前", - "data.search.searchBar.savedQueryNoSavedQueriesText": "保存されたクエリがありません。", - "data.search.searchBar.savedQueryPopoverButtonText": "保存されたクエリを表示", - "data.search.searchBar.savedQueryPopoverClearButtonAriaLabel": "現在保存されているクエリを消去", - "data.search.searchBar.savedQueryPopoverClearButtonText": "消去", - "data.search.searchBar.savedQueryPopoverConfirmDeletionCancelButtonText": "キャンセル", - "data.search.searchBar.savedQueryPopoverConfirmDeletionConfirmButtonText": "削除", - "data.search.searchBar.savedQueryPopoverConfirmDeletionTitle": "「{savedQueryName}」を削除しますか?", - "data.search.searchBar.savedQueryPopoverDeleteButtonAriaLabel": "保存されたクエリ {savedQueryName} を削除", - "data.search.searchBar.savedQueryPopoverSaveAsNewButtonAriaLabel": "新規保存クエリを保存", - "data.search.searchBar.savedQueryPopoverSaveAsNewButtonText": "新規保存", - "data.search.searchBar.savedQueryPopoverSaveButtonAriaLabel": "新規保存クエリを保存", - "data.search.searchBar.savedQueryPopoverSaveButtonText": "現在のクエリを保存", - "data.search.searchBar.savedQueryPopoverSaveChangesButtonAriaLabel": "{title} への変更を保存", - "data.search.searchBar.savedQueryPopoverSaveChangesButtonText": "変更を保存", - "data.search.searchBar.savedQueryPopoverSavedQueryListItemButtonAriaLabel": "保存クエリボタン {savedQueryName}", - "data.search.searchBar.savedQueryPopoverSavedQueryListItemDescriptionAriaLabel": "{savedQueryName} の説明", - "data.search.searchBar.savedQueryPopoverSavedQueryListItemSelectedButtonAriaLabel": "選択されたクエリボタン {savedQueryName} を保存しました。変更を破棄するには押してください。", - "data.search.searchBar.savedQueryPopoverTitleText": "保存されたクエリ", - "discover.fieldNameIcons.booleanAriaLabel": "ブールフィールド", - "discover.fieldNameIcons.conflictFieldAriaLabel": "矛盾フィールド", - "discover.fieldNameIcons.dateFieldAriaLabel": "日付フィールド", - "discover.fieldNameIcons.geoPointFieldAriaLabel": "地理ポイント", - "discover.fieldNameIcons.geoShapeFieldAriaLabel": "地理情報図形", - "discover.fieldNameIcons.ipAddressFieldAriaLabel": "IP アドレスフィールド", - "discover.fieldNameIcons.murmur3FieldAriaLabel": "Murmur3 フィールド", - "discover.fieldNameIcons.numberFieldAriaLabel": "数値フィールド", - "discover.fieldNameIcons.sourceFieldAriaLabel": "ソースフィールド", - "discover.fieldNameIcons.stringFieldAriaLabel": "文字列フィールド", - "discover.fieldNameIcons.unknownFieldAriaLabel": "不明なフィールド", - "charts.colormaps.bluesText": "青", - "charts.colormaps.greensText": "緑", - "charts.colormaps.greenToRedText": "緑から赤", - "charts.colormaps.greysText": "グレー", - "charts.colormaps.redsText": "赤", - "charts.colormaps.yellowToRedText": "黄色から赤", "console.autocomplete.addMethodMetaText": "メソド", "console.consoleDisplayName": "コンソール", "console.consoleMenu.copyAsCurlMessage": "リクエストが URL としてコピーされました", + "console.devToolsDescription": "cURL をスキップしこの JSON インスタンスを使って、データに直接アクセスします。", + "console.devToolsTitle": "コンソール", + "console.exampleOutputTextarea": "開発ツールコンソールエディターの例", "console.helpPage.keyboardCommands.autoIndentDescription": "現在のリクエストを自動インデントします", "console.helpPage.keyboardCommands.closeAutoCompleteMenuDescription": "自動入力メニューを閉じます", "console.helpPage.keyboardCommands.collapseAllScopesDescription": "現在のスコープを除きすべてのスコープを最小表示します。シフトを追加して拡張します。", @@ -505,11 +341,19 @@ "console.historyPage.noHistoryTextMessage": "履歴がありません", "console.historyPage.pageTitle": "履歴", "console.historyPage.requestListAriaLabel": "リクエストの送信履歴", + "console.inputTextarea": "開発ツールコンソール", + "console.loadingError.buttonLabel": "コンソールの再読み込み", + "console.loadingError.message": "最新データを取得するために再読み込みを試してください。", + "console.loadingError.title": "コンソールを読み込めません", + "console.outputTextarea": "開発ツールコンソール出力", + "console.pageHeading": "コンソール", + "console.requestInProgressBadgeText": "リクエストが進行中", "console.requestOptions.autoIndentButtonLabel": "自動インデント", "console.requestOptions.copyAsUrlButtonLabel": "cURL としてコピー", "console.requestOptions.openDocumentationButtonLabel": "ドキュメントを開く", "console.requestOptionsButtonAriaLabel": "リクエストオプション", - "console.sendRequestButtonTooltip": "クリックしてリクエストを送信します", + "console.requestTimeElapasedBadgeTooltipContent": "経過時間", + "console.sendRequestButtonTooltip": "クリックしてリクエストを送信", "console.settingsPage.autocompleteLabel": "自動入力", "console.settingsPage.cancelButtonLabel": "キャンセル", "console.settingsPage.fieldsLabelText": "フィールド", @@ -550,6 +394,7 @@ "core.euiBasicTable.selectThisRow": "この行を選択", "core.euiBasicTable.tableDescription": "以下は {itemCount} 件のアイテムの表です。", "core.euiBottomBar.screenReaderAnnouncement": "ドキュメントの最後にページレベルのコントロールと共に開く新しいメニューがあります。", + "core.euiBreadcrumbs.collapsedBadge.ariaLabel": "すべてのブレッドクラムを表示", "core.euiCardSelect.select": "選択してください", "core.euiCardSelect.selected": "利用不可", "core.euiCardSelect.unavailable": "選択済み", @@ -561,6 +406,20 @@ "core.euiCollapsedItemActions.allActions": "すべてのアクション", "core.euiColorPicker.screenReaderAnnouncement": "選択可能な色の範囲を表示するポップアップが開きました。選択可能な色を閲覧するには Tab を押し、Esc でこのポップアップを閉じます。", "core.euiColorPicker.swatchAriaLabel": "{swatch} を色として選択します", + "core.euiColorStops.screenReaderAnnouncement": "{label}: {readOnly} {disabled}色終了位置ピッカー。各終了には数値と対応するカラー値があります。上下矢印キーを使用して、個別の終了を選択します。Enterキーを押すと、新しい終了を作成します。", + "core.euiColorStopThumb.removeLabel": "この終了を削除", + "core.euiColorStopThumb.screenReaderAnnouncement": "カラー終了編集フォームのポップアップが開きました。Tabを押してフォームコントロールを閲覧するか、Escでこのポップアップを閉じます。", + "core.euiColumnSelector.hideAll": "すべて非表示", + "core.euiColumnSelector.selectAll": "すべて表示", + "core.euiColumnSorting.clearAll": "並び替えを消去", + "core.euiColumnSorting.emptySorting": "現在並び替えられているフィールドはありません", + "core.euiColumnSorting.pickFields": "並び替え基準でフィールドの選択", + "core.euiColumnSorting.sortFieldAriaLabel": "並べ替え基準", + "core.euiColumnSortingDraggable.activeSortLabel": "このデータグリッドを並び替え中", + "core.euiColumnSortingDraggable.defaultSortAsc": "A-Z", + "core.euiColumnSortingDraggable.defaultSortDesc": "Z-A", + "core.euiColumnSortingDraggable.removeSortLabel": "データグリッドの並び替えから削除", + "core.euiColumnSortingDraggable.toggleLegend": "フィールド向け並び替え方法を選択", "core.euiComboBoxOptionsList.allOptionsSelected": "利用可能なオプションをすべて選択しました", "core.euiComboBoxOptionsList.alreadyAdded": "{label} は既に追加されています", "core.euiComboBoxOptionsList.createCustomOption": "{searchValue} をカスタムオプションとして追加するには、{key} を押してください。", @@ -568,6 +427,19 @@ "core.euiComboBoxOptionsList.noAvailableOptions": "利用可能なオプションがありません", "core.euiComboBoxOptionsList.noMatchingOptions": "{searchValue} はどのオプションにも一致していません", "core.euiComboBoxPill.removeSelection": "グループの選択項目から {children} を削除してください", + "core.euiCommonlyUsedTimeRanges.legend": "頻繁に使用", + "core.euiDataGrid.screenReaderNotice": "セルにはインタラクティブコンテンツが含まれます。", + "core.euiDataGridCell.expandButtonTitle": "クリックするか enter を押すと、セルのコンテンツとインタラクトできます。", + "core.euiDataGridSchema.booleanSortTextAsc": "True-False", + "core.euiDataGridSchema.booleanSortTextDesc": "False-True", + "core.euiDataGridSchema.currencySortTextAsc": "低-高", + "core.euiDataGridSchema.currencySortTextDesc": "高-低", + "core.euiDataGridSchema.dateSortTextAsc": "新-旧", + "core.euiDataGridSchema.dateSortTextDesc": "旧-新", + "core.euiDataGridSchema.jsonSortTextAsc": "小-大", + "core.euiDataGridSchema.jsonSortTextDesc": "大-小", + "core.euiDataGridSchema.numberSortTextAsc": "低-高", + "core.euiDataGridSchema.numberSortTextDesc": "高-低", "core.euiFilterButton.filterBadge": "${count} ${filterCountLabel} 個のフィルター", "core.euiForm.addressFormErrors": "フォームのエラーを解決してください。", "core.euiFormControlLayoutClearButton.label": "インプットを消去", @@ -575,23 +447,45 @@ "core.euiHeaderLinks.appNavigation": "アプリのナビゲーション", "core.euiHeaderLinks.openNavigationMenu": "ナビゲーションメニューを開く", "core.euiHue.label": "HSV カラーモードの「true」値を選択", + "core.euiImage.closeImage": "全画面 {alt} 画像を閉じる", + "core.euiImage.openImage": "全画面 {alt} 画像を開く", + "core.euiLink.external.ariaLabel": "外部リンク", "core.euiModal.closeModal": "このモーダルウィンドウを閉じます", "core.euiPagination.jumpToLastPage": "最後のページ {pageCount} に移動します", "core.euiPagination.nextPage": "次のページ", "core.euiPagination.pageOfTotal": "{total} ページ中 {page} ページ目", "core.euiPagination.previousPage": "前のページ", - "core.euiPopover.screenReaderAnnouncement": "これはポップアップウィンドウです。このポップアップを閉じるには、Esc キーを押してください。", + "core.euiPopover.screenReaderAnnouncement": "これはダイアログです。ダイアログを閉じるには、 escape を押してください。", + "core.euiQuickSelect.applyButton": "適用", + "core.euiQuickSelect.fullDescription": "現在 {timeTense} {timeValue} {timeUnit}に設定されています。", + "core.euiQuickSelect.legendText": "時間範囲を素早く選択", + "core.euiQuickSelect.nextLabel": "次の時間ウインドウ", + "core.euiQuickSelect.previousLabel": "前の時間ウインドウ", + "core.euiQuickSelect.quickSelectTitle": "素早く選択", + "core.euiQuickSelect.tenseLabel": "時間テンス", + "core.euiQuickSelect.unitLabel": "時間単位", + "core.euiQuickSelect.valueLabel": "時間値", + "core.euiRefreshInterval.fullDescription": "現在 {optionValue} {optionText} に設定されています。", + "core.euiRefreshInterval.legend": "以下の感覚ごとに更新", + "core.euiRefreshInterval.start": "開始", + "core.euiRefreshInterval.stop": "停止", + "core.euiRelativeTab.fullDescription": "単位は変更可能です。現在 {unit} に設定されています。", + "core.euiRelativeTab.relativeDate": "{position} 日付", + "core.euiRelativeTab.roundingLabel": "{unit} に四捨五入する", + "core.euiRelativeTab.unitInputLabel": "相対的時間スパン", "core.euiSaturation.roleDescription": "HSV カラーモード彩度と値の選択", "core.euiSaturation.screenReaderAnnouncement": "矢印キーで四角いカラーグラデーションを操作します。キーを押すごとに移動する座標が 0 から 1 の範囲で HSV カラーモードの「彩度」と「値」の数字に使用されます。左で「彩度」の値を下げ、右で上げます。上で「値」の値を上げ、下で下げます。", "core.euiSelectable.loadingOptions": "オプションを読み込み中", "core.euiSelectable.noAvailableOptions": "利用可能なオプションがありません", "core.euiSelectable.noMatchingOptions": "{searchValue} はどのオプションにも一致していません", "core.euiStat.loadingText": "統計を読み込み中です", - "core.euiStepHorizontal.buttonTitle": "ステップ {step}: {title}{titleAppendix, select, completed { が完了} 無効 { が無効} other {}}", + "core.euiStep.ariaLabel": "{stepStatus}", + "core.euiStepHorizontal.buttonTitle": "ステップ {step}: {title}{titleAppendix}", "core.euiStepHorizontal.step": "手順", "core.euiStepNumber.hasErrors": "エラーがあります", "core.euiStepNumber.hasWarnings": "警告があります", "core.euiStepNumber.isComplete": "完了", + "core.euiStyleSelector.buttonText": "密度", "core.euiSuperDatePicker.showDatesButtonLabel": "日付を表示", "core.euiSuperSelect.screenReaderAnnouncement": "{optionsCount} 件のアイテムのフォームセレクターを使用中で、1 つのオプションを選択する必要があります。上下の矢印キーで移動するか、Esc キーで閉じます。", "core.euiSuperSelectControl.selectAnOption": "オプションの選択: {selectedValue} を選択済み", @@ -606,6 +500,8 @@ "core.euiToast.dismissToast": "トーストを閉じる", "core.euiToast.newNotification": "新しい通知が表示されます", "core.euiToast.notification": "通知", + "core.euiTreeView.ariaLabel": "{nodeLabel} {ariaLabel} のチャイルド", + "core.euiTreeView.listNavigationInstructions": "矢印キーを使ってこのリストを素早くナビゲートすることができます。", "core.fatalErrors.clearYourSessionButtonLabel": "セッションを消去", "core.fatalErrors.goBackButtonLabel": "戻る", "core.fatalErrors.somethingWentWrongTitle": "何か問題が発生", @@ -613,23 +509,384 @@ "core.notifications.errorToast.closeModal": "閉じる", "core.notifications.unableUpdateUISettingNotificationMessageTitle": "UI 設定を更新できません", "core.toasts.errorToast.seeFullError": "完全なエラーを表示", + "core.ui.analyzeNavList.label": "分析", "core.ui.chrome.headerGlobalNav.goHomePageIconAriaLabel": "ホームページに移動", "core.ui.chrome.headerGlobalNav.helpMenuAskElasticTitle": "Elasticに確認する", "core.ui.chrome.headerGlobalNav.helpMenuButtonAriaLabel": "ヘルプメニュー", + "core.ui.chrome.headerGlobalNav.helpMenuDocumentation": "ドキュメント", + "core.ui.chrome.headerGlobalNav.helpMenuGiveFeedbackOnApp": "{appName} についてのフィードバックを作成する", "core.ui.chrome.headerGlobalNav.helpMenuGiveFeedbackTitle": "フィードバックを作成する", "core.ui.chrome.headerGlobalNav.helpMenuKibanaDocumentationTitle": "Kibanaドキュメント", "core.ui.chrome.headerGlobalNav.helpMenuOpenGitHubIssueTitle": "GitHubで問題を開く", "core.ui.chrome.headerGlobalNav.helpMenuTitle": "ヘルプ", "core.ui.chrome.headerGlobalNav.helpMenuVersion": "v {version}", + "core.ui.chrome.headerGlobalNav.toggleSideNavAriaLabel": "サイドナビゲーションを切り替える", "core.ui.chrome.sideGlobalNav.viewRecentItemsFlyoutTitle": "最近のアイテム", "core.ui.chrome.sideGlobalNav.viewRecentItemsLabel": "最近閲覧", + "core.ui.legacyBrowserMessage": "この Kibana インストレーションは、現在ご使用のブラウザが満たしていない厳格なセキュリティ要件が有効になっています。", + "core.ui.legacyBrowserTitle": "ブラウザをアップグレードしてください", + "core.ui.managementNavList.label": "管理", + "core.ui.observabilityNavList.label": "オブザーバビリティ", "core.ui.overlays.banner.attentionTitle": "注意", "core.ui.overlays.banner.closeButtonLabel": "閉じる", + "core.ui.primaryNav.screenReaderLabel": "プライマリ", + "core.ui.primaryNavList.screenReaderLabel": "プライマリナビゲーションリンク", "core.ui.recentLinks.linkItem.screenReaderLabel": "{recentlyAccessedItemLinklabel}、タイプ: {pageType}", + "core.ui.recentLinks.screenReaderLabel": "最近閲覧したリンク、ナビゲーション", + "core.ui.securityNavList.label": "セキュリティ", + "core.ui.welcomeErrorMessage": "Elastic Kibana が正常に読み込まれませんでした。詳細はサーバーアウトプットを確認してください。", + "core.ui.welcomeMessage": "Elastic Kibana の読み込み中", "dashboard.actions.toggleExpandPanelMenuItem.expandedDisplayName": "最小化", "dashboard.actions.toggleExpandPanelMenuItem.notExpandedDisplayName": "全画面", + "dashboard.addPanel.noMatchingObjectsMessage": "一致するオブジェクトが見つかりませんでした。", + "dashboard.addPanel.savedObjectAddedToContainerSuccessMessageTitle": "{savedObjectName} が追加されました", "dashboard.dashboardGrid.toast.unableToLoadDashboardDangerMessage": "ダッシュボードが読み込めません。", "dashboard.factory.displayName": "ダッシュボード", + "dashboard.panel.removePanel.replacePanel": "パネルの交換", + "data.aggTypes.buckets.ranges.rangesFormatMessage": "{gte} {from} と {lt} {to}", + "data.common.kql.errors.endOfInputText": "インプットの終わり", + "data.common.kql.errors.fieldNameText": "フィールド名", + "data.common.kql.errors.literalText": "文字通り", + "data.common.kql.errors.syntaxError": "{expectedList} を期待しましたが {foundInput} が検出されました。", + "data.common.kql.errors.valueText": "値", + "data.common.kql.errors.whitespaceText": "空白類", + "data.fieldFormats.boolean.title": "ブール", + "data.fieldFormats.bytes.title": "バイト", + "data.fieldFormats.color.title": "色", + "data.fieldFormats.date_nanos.title": "日付ナノ", + "data.fieldFormats.date.title": "日付", + "data.fieldFormats.duration.inputFormats.days": "日", + "data.fieldFormats.duration.inputFormats.hours": "時間", + "data.fieldFormats.duration.inputFormats.microseconds": "マイクロ秒", + "data.fieldFormats.duration.inputFormats.milliseconds": "ミリ秒", + "data.fieldFormats.duration.inputFormats.minutes": "分", + "data.fieldFormats.duration.inputFormats.months": "か月", + "data.fieldFormats.duration.inputFormats.nanoseconds": "ナノ秒", + "data.fieldFormats.duration.inputFormats.picoseconds": "ピコ秒", + "data.fieldFormats.duration.inputFormats.seconds": "秒", + "data.fieldFormats.duration.inputFormats.weeks": "週間", + "data.fieldFormats.duration.inputFormats.years": "年", + "data.fieldFormats.duration.negativeLabel": "マイナス", + "data.fieldFormats.duration.outputFormats.asDays": "日", + "data.fieldFormats.duration.outputFormats.asHours": "時間", + "data.fieldFormats.duration.outputFormats.asMilliseconds": "ミリ秒", + "data.fieldFormats.duration.outputFormats.asMinutes": "分", + "data.fieldFormats.duration.outputFormats.asMonths": "か月", + "data.fieldFormats.duration.outputFormats.asSeconds": "秒", + "data.fieldFormats.duration.outputFormats.asWeeks": "週間", + "data.fieldFormats.duration.outputFormats.asYears": "年", + "data.fieldFormats.duration.outputFormats.humanize": "人間に読解可能", + "data.fieldFormats.duration.title": "期間", + "data.fieldFormats.ip.title": "IP アドレス", + "data.fieldFormats.number.title": "数字", + "data.fieldFormats.percent.title": "割合 (%)", + "data.fieldFormats.relative_date.title": "相対日付", + "data.fieldFormats.static_lookup.title": "静的ルックアップ", + "data.fieldFormats.string.title": "文字列", + "data.fieldFormats.string.transformOptions.base64": "Base64 デコード", + "data.fieldFormats.string.transformOptions.lower": "小文字", + "data.fieldFormats.string.transformOptions.none": "- なし -", + "data.fieldFormats.string.transformOptions.short": "短い点線", + "data.fieldFormats.string.transformOptions.title": "タイトルケース", + "data.fieldFormats.string.transformOptions.upper": "大文字", + "data.fieldFormats.string.transformOptions.url": "URL パラメーターデコード", + "data.fieldFormats.truncated_string.title": "切り詰めた文字列", + "data.fieldFormats.url.title": "Url", + "data.fieldFormats.url.types.audio": "音声", + "data.fieldFormats.url.types.img": "画像", + "data.fieldFormats.url.types.link": "リンク", + "data.filter.applyFilterActionTitle": "現在のビューにフィルターを適用", + "data.filter.applyFilters.popupHeader": "適用するフィルターの選択", + "data.filter.applyFiltersPopup.cancelButtonLabel": "キャンセル", + "data.filter.applyFiltersPopup.saveButtonLabel": "適用", + "data.filter.filterBar.addFilterButtonLabel": "フィルターを追加します", + "data.filter.filterBar.deleteFilterButtonLabel": "削除", + "data.filter.filterBar.disabledFilterPrefix": "無効", + "data.filter.filterBar.disableFilterButtonLabel": "一時的に無効にする", + "data.filter.filterBar.editFilterButtonLabel": "フィルターを編集", + "data.filter.filterBar.enableFilterButtonLabel": "再度有効にする", + "data.filter.filterBar.excludeFilterButtonLabel": "結果を除外", + "data.filter.filterBar.fieldNotFound": "インデックスパターン {indexPattern} にフィールド {key} がありません", + "data.filter.filterBar.filterItemBadgeAriaLabel": "フィルターアクション", + "data.filter.filterBar.filterItemBadgeIconAriaLabel": "削除", + "data.filter.filterBar.includeFilterButtonLabel": "結果を含める", + "data.filter.filterBar.indexPatternSelectPlaceholder": "インデックスパターンの選択", + "data.filter.filterBar.labelErrorMessage": "フィルターを表示できませんでした", + "data.filter.filterBar.labelErrorText": "エラー", + "data.filter.filterBar.moreFilterActionsMessage": "フィルター:{innerText}。他のフィルターアクションを使用するには選択してください。", + "data.filter.filterBar.negatedFilterPrefix": "NOT ", + "data.filter.filterBar.pinFilterButtonLabel": "すべてのアプリにピン付け", + "data.filter.filterBar.pinnedFilterPrefix": "ピン付け済み", + "data.filter.filterBar.unpinFilterButtonLabel": "ピンを外す", + "data.filter.filterEditor.cancelButtonLabel": "キャンセル", + "data.filter.filterEditor.createCustomLabelInputLabel": "カスタムラベル", + "data.filter.filterEditor.createCustomLabelSwitchLabel": "カスタムラベルを作成しますか?", + "data.filter.filterEditor.dateFormatHelpLinkLabel": "対応データフォーマット", + "data.filter.filterEditor.doesNotExistOperatorOptionLabel": "存在しません", + "data.filter.filterEditor.editFilterPopupTitle": "フィルターを編集", + "data.filter.filterEditor.editFilterValuesButtonLabel": "フィルター値を編集", + "data.filter.filterEditor.editQueryDslButtonLabel": "クエリ DSL として編集", + "data.filter.filterEditor.existsOperatorOptionLabel": "存在する", + "data.filter.filterEditor.falseOptionLabel": "False", + "data.filter.filterEditor.fieldSelectLabel": "フィールド", + "data.filter.filterEditor.fieldSelectPlaceholder": "フィールドを選択", + "data.filter.filterEditor.indexPatternSelectLabel": "インデックスパターン", + "data.filter.filterEditor.isBetweenOperatorOptionLabel": "is between", + "data.filter.filterEditor.isNotBetweenOperatorOptionLabel": "is not between", + "data.filter.filterEditor.isNotOneOfOperatorOptionLabel": "is not one of", + "data.filter.filterEditor.isNotOperatorOptionLabel": "is not", + "data.filter.filterEditor.isOneOfOperatorOptionLabel": "is one of", + "data.filter.filterEditor.isOperatorOptionLabel": "が", + "data.filter.filterEditor.operatorSelectLabel": "演算子", + "data.filter.filterEditor.operatorSelectPlaceholderSelect": "選択してください", + "data.filter.filterEditor.operatorSelectPlaceholderWaiting": "待機中", + "data.filter.filterEditor.queryDslLabel": "Elasticsearch クエリ DSL", + "data.filter.filterEditor.rangeEndInputPlaceholder": "範囲の終了値", + "data.filter.filterEditor.rangeInputLabel": "範囲", + "data.filter.filterEditor.rangeStartInputPlaceholder": "範囲の開始値", + "data.filter.filterEditor.saveButtonLabel": "保存", + "data.filter.filterEditor.trueOptionLabel": "True", + "data.filter.filterEditor.valueInputLabel": "値", + "data.filter.filterEditor.valueInputPlaceholder": "値を入力", + "data.filter.filterEditor.valueSelectPlaceholder": "値を選択", + "data.filter.filterEditor.valuesSelectLabel": "値", + "data.filter.filterEditor.valuesSelectPlaceholder": "値を選択", + "data.filter.options.changeAllFiltersButtonLabel": "すべてのフィルターの変更", + "data.filter.options.deleteAllFiltersButtonLabel": "すべて削除", + "data.filter.options.disableAllFiltersButtonLabel": "すべて無効にする", + "data.filter.options.enableAllFiltersButtonLabel": "すべて有効にする", + "data.filter.options.invertDisabledFiltersButtonLabel": "有効・無効を反転", + "data.filter.options.invertNegatedFiltersButtonLabel": "含める・除外を反転", + "data.filter.options.pinAllFiltersButtonLabel": "すべてピン付け", + "data.filter.options.unpinAllFiltersButtonLabel": "すべてのピンを外す", + "data.filter.searchBar.changeAllFiltersTitle": "すべてのフィルターの変更", + "data.functions.esaggs.help": "AggConfig 集約を実行します", + "data.functions.esaggs.inspector.dataRequest.description": "このリクエストは Elasticsearch にクエリし、ビジュアライゼーション用のデータを取得します。", + "data.functions.esaggs.inspector.dataRequest.title": "データ", + "data.indexPatterns.fetchFieldErrorTitle": "インデックスパターンのフィールド取得中にエラーが発生 {title} (ID: {id})", + "data.indexPatterns.unableWriteLabel": "インデックスパターンを書き込めません!このインデックスパターンへの最新の変更を取得するには、ページを更新してください。", + "data.indexPatterns.unknownFieldErrorMessage": "インデックスパターン「{title}」のフィールド「{name}」が不明なフィールドタイプを使用しています。", + "data.indexPatterns.unknownFieldHeader": "不明なフィールドタイプ {type}", + "data.parseEsInterval.invalidEsCalendarIntervalErrorMessage": "無効なカレンダー間隔:{interval}、1よりも大きな値が必要です", + "data.parseEsInterval.invalidEsIntervalFormatErrorMessage": "無効な間隔フォーマット:{interval}", + "data.query.queryBar.comboboxAriaLabel": "{pageType} ページの検索とフィルタリング", + "data.query.queryBar.kqlFullLanguageName": "Kibana クエリ言語", + "data.query.queryBar.kqlLanguageName": "KQL", + "data.query.queryBar.KQLNestedQuerySyntaxInfoDocLinkText": "ドキュメント", + "data.query.queryBar.KQLNestedQuerySyntaxInfoOptOutText": "今後表示しない", + "data.query.queryBar.KQLNestedQuerySyntaxInfoTitle": "KQL ネストされたクエリ構文", + "data.query.queryBar.kqlOffLabel": "オフ", + "data.query.queryBar.kqlOnLabel": "オン", + "data.query.queryBar.licenseOptions": "ライセンスオプションに進む", + "data.query.queryBar.longQueryMessage": "ライセンスをアップグレードすれば、リクエストの完了までに十分な時間を確保できます。", + "data.query.queryBar.luceneLanguageName": "Lucene", + "data.query.queryBar.luceneSyntaxWarningMessage": "Lucene クエリ構文を使用しているようですが、Kibana クエリ言語 (KQL) が選択されています。KQL ドキュメント {link} を確認してください。", + "data.query.queryBar.luceneSyntaxWarningOptOutText": "今後表示しない", + "data.query.queryBar.luceneSyntaxWarningTitle": "Lucene 構文警告", + "data.query.queryBar.searchInputAriaLabel": "{pageType} ページの検索とフィルタリングを行うには入力を開始してください", + "data.query.queryBar.searchInputPlaceholder": "検索", + "data.query.queryBar.syntaxOptionsDescription": "{docsLink} (KQL) は、シンプルなクエリ構文とスクリプトフィールドのサポートを提供します。また、KQL はベーシックライセンス以上をご利用の場合、自動入力も提供します。KQL をオフにすると、Kibana は Lucene を使用します。", + "data.query.queryBar.syntaxOptionsDescription.docsLinkText": "こちら", + "data.query.queryBar.syntaxOptionsTitle": "構文オプション", + "data.search.aggs.aggGroups.bucketsText": "バケット", + "data.search.aggs.aggGroups.metricsText": "メトリック", + "data.search.aggs.aggTypes.rangesFormatMessage": "{gte} {from} と {lt} {to}", + "data.search.aggs.aggTypesLabel": "{fieldName} の範囲", + "data.search.aggs.buckets.dateHistogramLabel": "{intervalDescription} ごとの {fieldName}", + "data.search.aggs.buckets.dateHistogramTitle": "日付ヒストグラム", + "data.search.aggs.buckets.dateRangeTitle": "日付範囲", + "data.search.aggs.buckets.filtersTitle": "フィルター", + "data.search.aggs.buckets.filterTitle": "フィルター", + "data.search.aggs.buckets.geohashGridTitle": "ジオハッシュ", + "data.search.aggs.buckets.geotileGridTitle": "ジオタイル", + "data.search.aggs.buckets.histogramTitle": "ヒストグラム", + "data.search.aggs.buckets.intervalOptions.autoDisplayName": "自動", + "data.search.aggs.buckets.intervalOptions.dailyDisplayName": "日ごと", + "data.search.aggs.buckets.intervalOptions.hourlyDisplayName": "1 時間ごと", + "data.search.aggs.buckets.intervalOptions.millisecondDisplayName": "ミリ秒", + "data.search.aggs.buckets.intervalOptions.minuteDisplayName": "分", + "data.search.aggs.buckets.intervalOptions.monthlyDisplayName": "月ごと", + "data.search.aggs.buckets.intervalOptions.secondDisplayName": "秒", + "data.search.aggs.buckets.intervalOptions.weeklyDisplayName": "週ごと", + "data.search.aggs.buckets.intervalOptions.yearlyDisplayName": "年ごと", + "data.search.aggs.buckets.ipRangeLabel": "{fieldName} IP 範囲", + "data.search.aggs.buckets.ipRangeTitle": "IPv4 範囲", + "data.search.aggs.buckets.rangeTitle": "範囲", + "data.search.aggs.buckets.significantTerms.excludeLabel": "除外", + "data.search.aggs.buckets.significantTerms.includeLabel": "含める", + "data.search.aggs.buckets.significantTermsLabel": "{fieldName} のトップ {size} の珍しいアイテム", + "data.search.aggs.buckets.significantTermsTitle": "重要な用語", + "data.search.aggs.buckets.terms.excludeLabel": "除外", + "data.search.aggs.buckets.terms.includeLabel": "含める", + "data.search.aggs.buckets.terms.missingBucketLabel": "欠測値", + "data.search.aggs.buckets.terms.orderAscendingTitle": "昇順", + "data.search.aggs.buckets.terms.orderDescendingTitle": "降順", + "data.search.aggs.buckets.terms.otherBucketDescription": "このリクエストは、データバケットの基準外のドキュメントの数をカウントします。", + "data.search.aggs.buckets.terms.otherBucketLabel": "その他", + "data.search.aggs.buckets.terms.otherBucketTitle": "他のバケット", + "data.search.aggs.buckets.termsTitle": "用語", + "data.search.aggs.histogram.missingMaxMinValuesWarning": "自動スケールヒストグラムバケットから最高値と最低値を取得できません。これによりビジュアライゼーションのパフォーマンスが低下する可能性があります。", + "data.search.aggs.metrics.averageBucketTitle": "平均バケット", + "data.search.aggs.metrics.averageLabel": "平均 {field}", + "data.search.aggs.metrics.averageTitle": "平均", + "data.search.aggs.metrics.countLabel": "カウント", + "data.search.aggs.metrics.countTitle": "カウント", + "data.search.aggs.metrics.cumulativeSumLabel": "累積合計", + "data.search.aggs.metrics.cumulativeSumTitle": "累積合計", + "data.search.aggs.metrics.derivativeLabel": "派生", + "data.search.aggs.metrics.derivativeTitle": "派生", + "data.search.aggs.metrics.geoBoundsLabel": "境界", + "data.search.aggs.metrics.geoBoundsTitle": "境界", + "data.search.aggs.metrics.geoCentroidLabel": "ジオセントロイド", + "data.search.aggs.metrics.geoCentroidTitle": "ジオセントロイド", + "data.search.aggs.metrics.maxBucketTitle": "最高バケット", + "data.search.aggs.metrics.maxLabel": "最高 {field}", + "data.search.aggs.metrics.maxTitle": "最高", + "data.search.aggs.metrics.medianLabel": "中央 {field}", + "data.search.aggs.metrics.medianTitle": "中央", + "data.search.aggs.metrics.metricAggregationsSubtypeTitle": "メトリック集約", + "data.search.aggs.metrics.minBucketTitle": "最低バケット", + "data.search.aggs.metrics.minLabel": "最低 {field}", + "data.search.aggs.metrics.minTitle": "最低", + "data.search.aggs.metrics.movingAvgLabel": "移動平均", + "data.search.aggs.metrics.movingAvgTitle": "移動平均", + "data.search.aggs.metrics.overallAverageLabel": "全体平均", + "data.search.aggs.metrics.overallMaxLabel": "全体最高", + "data.search.aggs.metrics.overallMinLabel": "全体最低", + "data.search.aggs.metrics.overallSumLabel": "全体合計", + "data.search.aggs.metrics.parentPipelineAggregationsSubtypeTitle": "親パイプライン集約", + "data.search.aggs.metrics.percentileRanks.valuePropsLabel": "「{label}」の {format} のパーセンタイル順位", + "data.search.aggs.metrics.percentileRanksLabel": "{field} のパーセンタイル順位", + "data.search.aggs.metrics.percentileRanksTitle": "パーセンタイル順位", + "data.search.aggs.metrics.percentiles.valuePropsLabel": "{label} の {percentile} パーセンタイル", + "data.search.aggs.metrics.percentilesLabel": "{field} のパーセンタイル", + "data.search.aggs.metrics.percentilesTitle": "パーセンタイル", + "data.search.aggs.metrics.serialDiffLabel": "差分の推移", + "data.search.aggs.metrics.serialDiffTitle": "差分の推移", + "data.search.aggs.metrics.siblingPipelineAggregationsSubtypeTitle": "シブリングパイプラインアグリゲーション", + "data.search.aggs.metrics.standardDeviation.keyDetailsLabel": "{fieldDisplayName} の標準偏差", + "data.search.aggs.metrics.standardDeviation.lowerKeyDetailsTitle": "下の{label}", + "data.search.aggs.metrics.standardDeviation.upperKeyDetailsTitle": "上の{label}", + "data.search.aggs.metrics.standardDeviationLabel": "{field} の標準偏差", + "data.search.aggs.metrics.standardDeviationTitle": "標準偏差", + "data.search.aggs.metrics.sumBucketTitle": "合計バケット", + "data.search.aggs.metrics.sumLabel": "{field} の合計", + "data.search.aggs.metrics.sumTitle": "合計", + "data.search.aggs.metrics.topHit.ascendingLabel": "昇順", + "data.search.aggs.metrics.topHit.averageLabel": "平均", + "data.search.aggs.metrics.topHit.concatenateLabel": "連結", + "data.search.aggs.metrics.topHit.descendingLabel": "降順", + "data.search.aggs.metrics.topHit.firstPrefixLabel": "最初", + "data.search.aggs.metrics.topHit.lastPrefixLabel": "最後", + "data.search.aggs.metrics.topHit.maxLabel": "最高", + "data.search.aggs.metrics.topHit.minLabel": "最低", + "data.search.aggs.metrics.topHit.sumLabel": "合計", + "data.search.aggs.metrics.topHitTitle": "トップヒット", + "data.search.aggs.metrics.uniqueCountLabel": "{field} のユニークカウント", + "data.search.aggs.metrics.uniqueCountTitle": "ユニークカウント", + "data.search.aggs.otherBucket.labelForMissingValuesLabel": "欠測値のラベル", + "data.search.aggs.otherBucket.labelForOtherBucketLabel": "他のバケットのラベル", + "data.search.aggs.paramTypes.field.invalidSavedFieldParameterErrorMessage": "保存した {fieldParameter} パラメーターが無効になりました。新しいフィールドを選択してください。", + "data.search.aggs.paramTypes.field.requiredFieldParameterErrorMessage": "{fieldParameter} は必須パラメーターです", + "data.search.aggs.percentageOfLabel": "{label} の割合", + "data.search.aggs.string.customLabel": "カスタムラベル", + "data.search.searchBar.savedQueryDescriptionLabelText": "説明", + "data.search.searchBar.savedQueryDescriptionText": "再度使用するクエリテキストとフィルターを保存します。", + "data.search.searchBar.savedQueryForm.titleConflictText": "タイトルが既に保存されているクエリに使用されています", + "data.search.searchBar.savedQueryForm.titleMissingText": "名前が必要です", + "data.search.searchBar.savedQueryForm.whitespaceErrorText": "タイトルの始めと終わりにはスペースを使用できません", + "data.search.searchBar.savedQueryFormCancelButtonText": "キャンセル", + "data.search.searchBar.savedQueryFormSaveButtonText": "保存", + "data.search.searchBar.savedQueryFormTitle": "クエリを保存", + "data.search.searchBar.savedQueryIncludeFiltersLabelText": "フィルターを含める", + "data.search.searchBar.savedQueryIncludeTimeFilterLabelText": "時間フィルターを含める", + "data.search.searchBar.savedQueryNameHelpText": "名前が必要です。タイトルの始めと終わりにはスペースを使用できません。名前は固有でなければなりません。", + "data.search.searchBar.savedQueryNameLabelText": "名前", + "data.search.searchBar.savedQueryNoSavedQueriesText": "保存されたクエリがありません。", + "data.search.searchBar.savedQueryPopoverButtonText": "保存されたクエリを表示", + "data.search.searchBar.savedQueryPopoverClearButtonAriaLabel": "現在保存されているクエリを消去", + "data.search.searchBar.savedQueryPopoverClearButtonText": "消去", + "data.search.searchBar.savedQueryPopoverConfirmDeletionCancelButtonText": "キャンセル", + "data.search.searchBar.savedQueryPopoverConfirmDeletionConfirmButtonText": "削除", + "data.search.searchBar.savedQueryPopoverConfirmDeletionTitle": "「{savedQueryName}」を削除しますか?", + "data.search.searchBar.savedQueryPopoverDeleteButtonAriaLabel": "保存されたクエリ {savedQueryName} を削除", + "data.search.searchBar.savedQueryPopoverSaveAsNewButtonAriaLabel": "新規保存クエリを保存", + "data.search.searchBar.savedQueryPopoverSaveAsNewButtonText": "新規保存", + "data.search.searchBar.savedQueryPopoverSaveButtonAriaLabel": "新規保存クエリを保存", + "data.search.searchBar.savedQueryPopoverSaveButtonText": "現在のクエリを保存", + "data.search.searchBar.savedQueryPopoverSaveChangesButtonAriaLabel": "{title} への変更を保存", + "data.search.searchBar.savedQueryPopoverSaveChangesButtonText": "変更を保存", + "data.search.searchBar.savedQueryPopoverSavedQueryListItemButtonAriaLabel": "保存クエリボタン {savedQueryName}", + "data.search.searchBar.savedQueryPopoverSavedQueryListItemDescriptionAriaLabel": "{savedQueryName} の説明", + "data.search.searchBar.savedQueryPopoverSavedQueryListItemSelectedButtonAriaLabel": "選択されたクエリボタン {savedQueryName} を保存しました。変更を破棄するには押してください。", + "data.search.searchBar.savedQueryPopoverTitleText": "保存されたクエリ", + "data.search.searchSource.fetch.requestTimedOutNotificationMessage": "リクエストがタイムアウトしたため、データが不完全な可能性があります", + "data.search.searchSource.fetch.shardsFailedModal.close": "閉じる", + "data.search.searchSource.fetch.shardsFailedModal.copyToClipboard": "応答をクリップボードにコピー", + "data.search.searchSource.fetch.shardsFailedModal.failureHeader": "{failureName}で{failureDetails}", + "data.search.searchSource.fetch.shardsFailedModal.showDetails": "詳細を表示", + "data.search.searchSource.fetch.shardsFailedModal.tabHeaderRequest": "リクエスト", + "data.search.searchSource.fetch.shardsFailedModal.tabHeaderResponse": "応答", + "data.search.searchSource.fetch.shardsFailedModal.tabHeaderShardFailures": "シャードエラー", + "data.search.searchSource.fetch.shardsFailedModal.tableColIndex": "インデックス", + "data.search.searchSource.fetch.shardsFailedModal.tableColNode": "ノード", + "data.search.searchSource.fetch.shardsFailedModal.tableColReason": "理由", + "data.search.searchSource.fetch.shardsFailedModal.tableColShard": "シャード", + "data.search.searchSource.fetch.shardsFailedModal.tableRowCollapse": "{rowDescription}を折りたたむ", + "data.search.searchSource.fetch.shardsFailedModal.tableRowExpand": "{rowDescription}を展開する", + "data.search.searchSource.fetch.shardsFailedNotificationDescription": "表示されているデータは不完全か誤りの可能性があります。", + "data.search.searchSource.fetch.shardsFailedNotificationMessage": "{shardsTotal} 件中 {shardsFailed} 件のシャードでエラーが発生しました", + "data.search.searchSource.hitsDescription": "クエリにより返されたドキュメントの数です。", + "data.search.searchSource.hitsLabel": "ヒット数", + "data.search.searchSource.hitsTotalDescription": "クエリに一致するドキュメントの数です。", + "data.search.searchSource.hitsTotalLabel": "ヒット数 (合計)", + "data.search.searchSource.indexPatternDescription": "Elasticsearch インデックスに接続したインデックスパターンです。", + "data.search.searchSource.indexPatternIdDescription": "{kibanaIndexPattern} インデックス内の ID です。", + "data.search.searchSource.indexPatternIdLabel": "インデックスパターン ID", + "data.search.searchSource.indexPatternLabel": "インデックスパターン", + "data.search.searchSource.noSearchStrategyRegisteredErrorMessageDescription": "検索リクエストの検索方法が見つかりませんでした", + "data.search.searchSource.noSearchStrategyRegisteredErrorMessageTitle": "検索方法が登録されていません", + "data.search.searchSource.queryTimeDescription": "クエリの処理の所要時間です。リクエストの送信やブラウザでのパースの時間は含まれません。", + "data.search.searchSource.queryTimeLabel": "クエリ時間", + "data.search.searchSource.queryTimeValue": "{queryTime}ms", + "data.search.searchSource.requestTimeDescription": "ブラウザから Elasticsearch にリクエストが送信され返されるまでの所要時間です。リクエストがキューで待機していた時間は含まれません。", + "data.search.searchSource.requestTimeLabel": "リクエスト時間", + "data.search.searchSource.requestTimeValue": "{requestTime}ms", + "data.search.unableToGetSavedQueryToastTitle": "保存したクエリ {savedQueryId} を読み込めません", + "devTools.badge.readOnly.text": "読み込み専用", + "devTools.badge.readOnly.tooltip": "を保存できませんでした", + "devTools.k7BreadcrumbsDevToolsLabel": "開発ツール", + "discover.docViews.json.codeEditorAriaLabel": "Elasticsearch ドキュメントの JSON ビューのみを読み込む", + "discover.docViews.json.jsonTitle": "JSON", + "discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel": "警告", + "discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip": "{underscoreSign} で始まるフィールド名はサポートされません", + "discover.docViews.table.filterForFieldPresentButtonAriaLabel": "フィールド表示のフィルター", + "discover.docViews.table.filterForFieldPresentButtonTooltip": "フィールド表示のフィルター", + "discover.docViews.table.filterForValueButtonAriaLabel": "値でフィルター", + "discover.docViews.table.filterForValueButtonTooltip": "値でフィルター", + "discover.docViews.table.filterOutValueButtonAriaLabel": "値を除外", + "discover.docViews.table.filterOutValueButtonTooltip": "値を除外", + "discover.docViews.table.noCachedMappingForThisFieldAriaLabel": "警告", + "discover.docViews.table.noCachedMappingForThisFieldTooltip": "このフィールドのキャッシュされたマッピングがありません。管理 > インデックスパターンページからフィールドリストを更新してください", + "discover.docViews.table.tableTitle": "表", + "discover.docViews.table.toggleColumnInTableButtonAriaLabel": "表の列を切り替える", + "discover.docViews.table.toggleColumnInTableButtonTooltip": "表の列を切り替える", + "discover.docViews.table.toggleFieldDetails": "フィールド詳細を切り替える", + "discover.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "メタフィールドの有無でフィルタリングできません", + "discover.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "スクリプトフィールドの有無でフィルタリングできません", + "discover.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "インデックスされていないフィールドは検索できません", + "discover.fieldNameIcons.booleanAriaLabel": "ブールフィールド", + "discover.fieldNameIcons.conflictFieldAriaLabel": "矛盾フィールド", + "discover.fieldNameIcons.dateFieldAriaLabel": "日付フィールド", + "discover.fieldNameIcons.geoPointFieldAriaLabel": "地理ポイントフィールド", + "discover.fieldNameIcons.geoShapeFieldAriaLabel": "地理情報シェイプフィールド", + "discover.fieldNameIcons.ipAddressFieldAriaLabel": "IP アドレスフィールド", + "discover.fieldNameIcons.murmur3FieldAriaLabel": "Murmur3 フィールド", + "discover.fieldNameIcons.nestedFieldAriaLabel": "入れ子フィールド", + "discover.fieldNameIcons.numberFieldAriaLabel": "数値フィールド", + "discover.fieldNameIcons.sourceFieldAriaLabel": "ソースフィールド", + "discover.fieldNameIcons.stringFieldAriaLabel": "文字列フィールド", + "discover.fieldNameIcons.unknownFieldAriaLabel": "不明なフィールド", "embeddableApi.actions.applyFilterActionTitle": "現在のビューにフィルターを適用", "embeddableApi.addPanel.createNewDefaultOption": "新規作成...", "embeddableApi.addPanel.displayName": "パネルの追加", @@ -646,6 +903,7 @@ "embeddableApi.customizeTitle.optionsMenuForm.panelTitleInputAriaLabel": "このインプットへの変更は直ちに適用されます。Enter を押して閉じます。", "embeddableApi.customizeTitle.optionsMenuForm.resetCustomDashboardButtonLabel": "タイトルをリセット", "embeddableApi.errors.embeddableFactoryNotFound": "{type} を読み込めません。Elasticsearch と Kibana のデフォルトのディストリビューションを適切なライセンスでアップグレードしてください。", + "embeddableApi.errors.factoryDoesNotExist": "{type} の埋め込み可能なファクトリーは存在しません。必要なプラグインが全てインストールおよび有効化済みであることを確かめてください。", "embeddableApi.errors.paneldoesNotExist": "パネルが見つかりません", "embeddableApi.panel.dashboardPanelAriaLabel": "ダッシュボードパネル", "embeddableApi.panel.editPanel.displayName": "{value} を編集", @@ -710,833 +968,33 @@ "esUi.forms.fieldValidation.indexNameStartsWithDotError": "インデックス名の始めにピリオド (.) は使用できません。", "esUi.forms.fieldValidation.indexPatternInvalidCharactersError": "インデックスパターンに無効な{characterListLength, plural, one {文字} other {文字}} { characterList } が含まれています。", "esUi.forms.fieldValidation.indexPatternSpacesError": "インデックスパターンにはスペースを使用できません。", - "inputControl.control.noIndexPatternTooltip": "index-pattern id が見つかりませんでした: {indexPatternId}.", - "inputControl.control.notInitializedTooltip": "コントロールが初期化されていません", - "inputControl.control.noValuesDisableTooltip": "「{indexPatternName}」インデックスパターンでいずれのドキュメントにも存在しない「{fieldName}」フィールドがフィルターの対象になっています。異なるフィールドを選択するか、このフィールドに値が入力されているドキュメントをインデックスしてください。", - "inputControl.editor.controlEditor.controlLabel": "コントロールラベル", - "inputControl.editor.controlEditor.moveControlDownAriaLabel": "コントロールを下に移動", - "inputControl.editor.controlEditor.moveControlUpAriaLabel": "コントロールを上に移動", - "inputControl.editor.controlEditor.removeControlAriaLabel": "コントロールを削除", - "inputControl.editor.controlsTab.addButtonLabel": "追加", - "inputControl.editor.controlsTab.select.addControlAriaLabel": "コントロールを追加", - "inputControl.editor.controlsTab.select.controlTypeAriaLabel": "コントロールタイプを選択してください", - "inputControl.editor.controlsTab.select.listDropDownOptionLabel": "オプションリスト", - "inputControl.editor.controlsTab.select.rangeDropDownOptionLabel": "範囲スライダー", - "inputControl.editor.fieldSelect.fieldLabel": "フィールド", - "inputControl.editor.fieldSelect.selectFieldPlaceholder": "フィールドを選択してください...", - "inputControl.editor.indexPatternSelect.patternLabel": "インデックスパターン", - "inputControl.editor.indexPatternSelect.patternPlaceholder": "インデックスパターンを選択してください", - "inputControl.editor.listControl.dynamicOptions.stringFieldDescription": "「文字列」フィールドでのみ利用可能", - "inputControl.editor.listControl.dynamicOptions.updateDescription": "ユーザーインプットに対する更新オプション", - "inputControl.editor.listControl.dynamicOptionsLabel": "ダイナミックオプション", - "inputControl.editor.listControl.multiselectDescription": "複数選択を許可", - "inputControl.editor.listControl.multiselectLabel": "複数選択", - "inputControl.editor.listControl.parentDescription": "オプションは親コントロールの値がベースになっています。親が設定されていない場合は無効です。", - "inputControl.editor.listControl.parentLabel": "親コントロール", - "inputControl.editor.listControl.sizeDescription": "オプション数", - "inputControl.editor.listControl.sizeLabel": "サイズ", - "inputControl.editor.optionsTab.pinFiltersLabel": "すべてのアプリケーションのフィルターをピン付け", - "inputControl.editor.optionsTab.updateFilterLabel": "変更するごとに Kibana フィルターを更新", - "inputControl.editor.optionsTab.useTimeFilterLabel": "時間フィルターを使用", - "inputControl.editor.rangeControl.decimalPlacesLabel": "小数部分の桁数", - "inputControl.editor.rangeControl.stepSizeLabel": "ステップサイズ", - "inputControl.function.help": "インプットコントロールビジュアライゼーション", - "inputControl.listControl.disableTooltip": "「{label}」が設定されるまで無効です。", - "inputControl.listControl.unableToFetchTooltip": "用語を取得できません、エラー: {errorMessage}", - "inputControl.rangeControl.unableToFetchTooltip": "範囲 (最低値と最高値) を取得できません、エラー: {errorMessage}", - "inputControl.register.controlsDescription": "ダッシュボードを簡単に操作できるように、インタラクティブなコントロールを作成します。", - "inputControl.register.controlsTitle": "コントロール", - "inputControl.register.tabs.controlsTitle": "コントロール", - "inputControl.register.tabs.optionsTitle": "オプション", - "inputControl.vis.inputControlVis.applyChangesButtonLabel": "変更を適用", - "inputControl.vis.inputControlVis.cancelChangesButtonLabel": "変更をキャンセル", - "inputControl.vis.inputControlVis.clearFormButtonLabel": "用語を消去", - "inputControl.vis.listControl.partialResultsWarningMessage": "リクエストに長くかかり過ぎているため、用語リストが不完全な可能性があります。完全な結果を得るには、kibana.yml の自動完了設定を調整してください。", - "inputControl.vis.listControl.selectPlaceholder": "選択してください…", - "inputControl.vis.listControl.selectTextPlaceholder": "選択してください…", - "inspector.closeButton": "インスペクターを閉じる", - "inspector.data.dataDescriptionTooltip": "ビジュアライゼーションの元のデータを表示", - "inspector.data.dataTitle": "データ", - "inspector.data.downloadCSVButtonLabel": "CSV をダウンロード", - "inspector.data.downloadCSVToggleButtonLabel": "CSV をダウンロード", - "inspector.data.filterForValueButtonAriaLabel": "値でフィルタリング", - "inspector.data.filterForValueButtonTooltip": "値でフィルタリング", - "inspector.data.filterOutValueButtonAriaLabel": "値を除外", - "inspector.data.filterOutValueButtonTooltip": "値を除外", - "inspector.data.formattedCSVButtonLabel": "フォーマット済み CSV", - "inspector.data.formattedCSVButtonTooltip": "データを表形式でダウンロード", - "inspector.data.gatheringDataLabel": "データを収集中", - "inspector.data.noDataAvailableDescription": "エレメントがデータを提供しませんでした。", - "inspector.data.noDataAvailableTitle": "利用可能なデータがありません", - "inspector.data.rawCSVButtonLabel": "CSV", - "inspector.data.rawCSVButtonTooltip": "日付をタイムスタンプとしてなど、提供されたデータをそのままダウンロードします", - "inspector.reqTimestampDescription": "リクエストの開始が記録された時刻です", - "inspector.reqTimestampKey": "リクエストのタイムスタンプ", - "inspector.requests.descriptionRowIconAriaLabel": "説明", - "inspector.requests.failedLabel": " (失敗)", - "inspector.requests.noRequestsLoggedDescription.elementHasNotLoggedAnyRequestsText": "エレメントが (まだ) リクエストを記録していません。", - "inspector.requests.noRequestsLoggedDescription.whatDoesItUsuallyMeanText": "これは通常、データを取得する必要がないか、エレメントがまだデータの取得を開始していないことを意味します。", - "inspector.requests.noRequestsLoggedTitle": "リクエストが記録されていません", - "inspector.requests.requestFailedTooltipTitle": "リクエストに失敗しました", - "inspector.requests.requestInProgressAriaLabel": "リクエスト進行中", - "inspector.requests.requestLabel": "リクエスト", - "inspector.requests.requestsDescriptionTooltip": "データを収集したリクエストを表示します", - "inspector.requests.requestsTitle": "リクエスト", - "inspector.requests.requestSucceededTooltipTitle": "リクエスト成功", - "inspector.requests.requestTabLabel": "リクエスト", - "inspector.requests.requestTimeLabel": "{requestTime}ms", - "inspector.requests.requestTooltipDescription": "リクエストの合計所要時間です。", - "inspector.requests.requestWasMadeDescription": "{requestsCount, plural, one {# リクエストが} other {# リクエストが} } 行われました{failedRequests}", - "inspector.requests.requestWasMadeDescription.requestHadFailureText": "、{failedCount} 件に失敗がありました", - "inspector.requests.responseTabLabel": "応答", - "inspector.requests.statisticsTabLabel": "統計", - "inspector.title": "インスペクター", - "inspector.view": "{viewName} を表示", - "kbn.advancedSettings.context.defaultSizeText": "コンテキストビューに表示される周りのエントリーの数", - "kbn.advancedSettings.context.defaultSizeTitle": "コンテキストサイズ", - "kbn.advancedSettings.context.sizeStepText": "コンテキストサイズを増減させる際の最低単位です", - "kbn.advancedSettings.context.sizeStepTitle": "コンテキストサイズのステップ", - "kbn.advancedSettings.context.tieBreakerFieldsText": "同じタイムスタンプ値のドキュメントを区別するためのコンマ区切りのフィールドのリストです。このリストから、現在のインデックスパターンに含まれ並べ替え可能な初めのフィールドが使用されます。", - "kbn.advancedSettings.context.tieBreakerFieldsTitle": "タイブレーカーフィールド", - "kbn.advancedSettings.courier.batchSearchesText": "無効の場合、ダッシュボードパネルは個々に読み込まれ、検索リクエストはユーザーが移動するか\n クエリを更新すると停止します。有効の場合、ダッシュボードパネルはすべてのデータが読み込まれると同時に読み込まれ、\n 検索は停止しません。", - "kbn.advancedSettings.courier.batchSearchesTitle": "同時検索のバッチ処理", - "kbn.advancedSettings.courier.customRequestPreference.requestPreferenceLinkText": "リクエスト設定", - "kbn.advancedSettings.courier.customRequestPreferenceText": "{setRequestReferenceSetting} が {customSettingValue} に設定されている時に使用される {requestPreferenceLink} です。", - "kbn.advancedSettings.courier.customRequestPreferenceTitle": "カスタムリクエスト設定", - "kbn.advancedSettings.courier.ignoreFilterText": "この構成は、似ていないインデックスにアクセスするビジュアライゼーションを含むダッシュボードのサポートを強化します。無効にすると、すべてのフィルターがすべてのビジュアライゼーションに適用されます。有効にすると、ビジュアライゼーションのインデックスにフィルター対象のフィールドが含まれていない場合、ビジュアライゼーションの際にフィルターが無視されます。", - "kbn.advancedSettings.courier.ignoreFilterTitle": "フィルターの無視", - "kbn.advancedSettings.courier.maxRequestsText": "Kibana から送信された _msearch requests リクエストに使用される {maxRequestsLink} 設定を管理します。この構成を無効にして Elasticsearch のデフォルトを使用するには、0 に設定します。", - "kbn.advancedSettings.courier.maxRequestsTitle": "最大同時シャードリクエスト", - "kbn.advancedSettings.courier.requestPreferenceCustom": "カスタム", - "kbn.advancedSettings.courier.requestPreferenceNone": "なし", - "kbn.advancedSettings.courier.requestPreferenceSessionId": "セッション ID", - "kbn.advancedSettings.courier.requestPreferenceText": "どのシャードが検索リクエストを扱うかを設定できます。
    \n
  • {sessionId}: 同じシャードのすべての検索リクエストを実行するため、オペレーションを制限します。\n これにはリクエスト間でシャードのキャッシュを共有できるというメリットがあります。
  • \n
  • {custom}: 独自の設定が可能になります。\n couriercustomRequestPreference で設定値をカスタマイズします。
  • \n
  • {none}: 設定されていないことを意味します。\n これにより、リクエストが全シャードコピー間に分散されるため、パフォーマンスが改善される可能性があります。\n ただし、シャードによって更新ステータスが異なる場合があるため、結果に矛盾が生じる可能性があります。
  • \n
", - "kbn.advancedSettings.courier.requestPreferenceTitle": "リクエスト設定", - "kbn.advancedSettings.csv.quoteValuesText": "csv エクスポートに値を引用するかどうかです", - "kbn.advancedSettings.csv.quoteValuesTitle": "CSV の値を引用", - "kbn.advancedSettings.csv.separatorText": "エクスポートされた値をこの文字列で区切ります", - "kbn.advancedSettings.csv.separatorTitle": "CSV セパレーター", - "kbn.advancedSettings.darkModeText": "Kibana UI のダークモードを有効にします。この設定を適用するにはページの更新が必要です。", - "kbn.advancedSettings.darkModeTitle": "ダークモード", - "kbn.advancedSettings.dateFormat.dayOfWeekText": "週の初めの曜日を設定します", - "kbn.advancedSettings.dateFormat.dayOfWeekTitle": "曜日", - "kbn.advancedSettings.dateFormat.optionsLinkText": "フォーマット", - "kbn.advancedSettings.dateFormat.scaled.intervalsLinkText": "ISO8601 間隔", - "kbn.advancedSettings.dateFormat.scaledText": "時間ベースのデータが順番にレンダリングされ、フォーマットされたタイムスタンプが測定値の間隔に適応すべき状況で使用されるフォーマットを定義する値です。キーは {intervalsLink}。", - "kbn.advancedSettings.dateFormat.scaledTitle": "スケーリングされたデータフォーマットです", - "kbn.advancedSettings.dateFormat.timezoneText": "使用されるタイムゾーンです。{defaultOption} ではご使用のブラウザにより検知されたタイムゾーンが使用されます。", - "kbn.advancedSettings.dateFormat.timezoneTitle": "データフォーマットのタイムゾーン", - "kbn.advancedSettings.dateFormatText": "きちんとフォーマットされたデータを表示する際、この {formatLink} を使用します", - "kbn.advancedSettings.dateFormatTitle": "データフォーマット", - "kbn.advancedSettings.dateNanosFormatText": "Elasticsearch の {dateNanosLink} データタイプに使用されます", - "kbn.advancedSettings.dateNanosFormatTitle": "ナノ秒フォーマットでの日付", - "kbn.advancedSettings.dateNanosLinkTitle": "date_nanos", - "kbn.advancedSettings.defaultColumnsText": "デフォルトでディスカバリタブに表示される列です", - "kbn.advancedSettings.defaultColumnsTitle": "デフォルトの列", - "kbn.advancedSettings.defaultIndexText": "インデックスが設定されていない時にアクセスするインデックスです", - "kbn.advancedSettings.defaultIndexTitle": "デフォルトのインデックス", - "kbn.advancedSettings.defaultRoute.defaultRouteText": "この設定は、Kibana 起動時のデフォルトのルートを設定します。この設定で、Kibana 起動時のランディングページを変更できます。ルートはスラッシュ (\"/\") で始まる必要があります。", - "kbn.advancedSettings.defaultRoute.defaultRouteTitle": "デフォルトのルート", - "kbn.advancedSettings.disableAnimationsText": "Kibana UI の不要なアニメーションをオフにします。変更を適用するにはページを更新してください。", - "kbn.advancedSettings.disableAnimationsTitle": "アニメーションを無効にする", - "kbn.advancedSettings.discover.aggsTermsSizeText": "「可視化」ボタンをクリックした際に、フィールドドロップダウンやディスカバリサイドバーに可視化される用語の数を設定します。", - "kbn.advancedSettings.discover.aggsTermsSizeTitle": "用語数", - "kbn.advancedSettings.discover.sampleSizeText": "表に表示する行数です", - "kbn.advancedSettings.discover.sampleSizeTitle": "行数", - "kbn.advancedSettings.discover.searchOnPageLoadText": "ディスカバリの最初の読み込み時に検索を実行するかを制御します。この設定は、保存された検索の読み込み時には影響しません。", - "kbn.advancedSettings.discover.searchOnPageLoadTitle": "ページの読み込み時の検索", - "kbn.advancedSettings.discover.sortDefaultOrderText": "ディスカバリアプリのインデックスパターンに基づく時刻のデフォルトの並べ替え方向をコントロールします。", - "kbn.advancedSettings.discover.sortDefaultOrderTitle": "デフォルトの並べ替え方向", - "kbn.advancedSettings.discover.sortOrderAsc": "昇順", - "kbn.advancedSettings.discover.sortOrderDesc": "降順", - "kbn.advancedSettings.docTableHideTimeColumnText": "ディスカバリと、ダッシュボードのすべての保存された検索で、「時刻」列を非表示にします。", - "kbn.advancedSettings.docTableHideTimeColumnTitle": "「時刻」列を非表示", - "kbn.advancedSettings.docTableHighlightText": "ディスカバリと保存された検索ダッシュボードの結果をハイライトします。ハイライトすることで、大きなドキュメントを扱う際にリクエストが遅くなります。", - "kbn.advancedSettings.docTableHighlightTitle": "結果をハイライト", - "kbn.advancedSettings.fieldsPopularLimitText": "最も頻繁に使用されるフィールドのトップ N を表示します", - "kbn.advancedSettings.fieldsPopularLimitTitle": "頻繁に使用されるフィールドの制限", - "kbn.advancedSettings.format.bytesFormat.numeralFormatLinkText": "数字フォーマット", - "kbn.advancedSettings.format.bytesFormatText": "「バイト」フォーマットのデフォルト {numeralFormatLink} です", - "kbn.advancedSettings.format.bytesFormatTitle": "バイトフォーマット", - "kbn.advancedSettings.format.currencyFormat.numeralFormatLinkText": "数字フォーマット", - "kbn.advancedSettings.format.currencyFormatText": "「通貨」フォーマットのデフォルト {numeralFormatLink} です", - "kbn.advancedSettings.format.currencyFormatTitle": "通貨フォーマット", - "kbn.advancedSettings.format.defaultTypeMapText": "各フィールドタイプにデフォルトで使用するフォーマット名のマップです。フィールドタイプが特に指定されていない場合は {defaultFormat} が使用されます", - "kbn.advancedSettings.format.defaultTypeMapTitle": "フィールドタイプフォーマット名", - "kbn.advancedSettings.format.formattingLocale.numeralLanguageLinkText": "数字言語", - "kbn.advancedSettings.format.formattingLocaleText": "{numeralLanguageLink} ロケール", - "kbn.advancedSettings.format.formattingLocaleTitle": "フォーマットロケール", - "kbn.advancedSettings.format.numberFormat.numeralFormatLinkText": "数字フォーマット", - "kbn.advancedSettings.format.numberFormatText": "「数字」フォーマットのデフォルト {numeralFormatLink} です", - "kbn.advancedSettings.format.numberFormatTitle": "数字フォーマット", - "kbn.advancedSettings.format.percentFormat.numeralFormatLinkText": "数字フォーマット", - "kbn.advancedSettings.format.percentFormatText": "「パーセント」フォーマットのデフォルト {numeralFormatLink} です", - "kbn.advancedSettings.format.percentFormatTitle": "パーセントフォーマット", - "kbn.advancedSettings.histogram.barTargetText": "日付ヒストグラムで「自動」間隔を使用する際、この数に近いバーの作成を試みます", - "kbn.advancedSettings.histogram.barTargetTitle": "目標バー数", - "kbn.advancedSettings.histogram.maxBarsText": "日付ヒストグラムに表示されるバーの数の上限です。必要に応じて値をスケーリングしてください", - "kbn.advancedSettings.histogram.maxBarsTitle": "最高バー数", - "kbn.advancedSettings.historyLimitText": "履歴があるフィールド (例: クエリインプット) に個の数の最近の値が表示されます", - "kbn.advancedSettings.historyLimitTitle": "履歴制限数", - "kbn.advancedSettings.indexPatternPlaceholderText": "「管理 > インデックスパターン > インデックスパターンを作成」で使用される「インデックスパターン名」フィールドのプレースホルダーです。", - "kbn.advancedSettings.indexPatternPlaceholderTitle": "インデックスパターンのプレースホルダー", - "kbn.advancedSettings.maxBucketsText": "1 つのデータソースが返せるバケットの最大数です", - "kbn.advancedSettings.maxBucketsTitle": "バケットの最大数", - "kbn.advancedSettings.maxCellHeightText": "表のセルが使用する高さの上限です。この切り捨てを無効にするには 0 に設定します", - "kbn.advancedSettings.maxCellHeightTitle": "表のセルの高さの上限", - "kbn.advancedSettings.metaFieldsText": "_source の外にあり、ドキュメントが表示される時に融合されるフィールドです", - "kbn.advancedSettings.metaFieldsTitle": "メタフィールド", - "kbn.advancedSettings.notifications.banner.markdownLinkText": "マークダウン対応", - "kbn.advancedSettings.notifications.bannerLifetimeText": "バナー通知が画面に表示されるミリ秒単位での時間です。{infinityValue} に設定するとカウントダウンが無効になります。", - "kbn.advancedSettings.notifications.bannerLifetimeTitle": "バナー通知時間", - "kbn.advancedSettings.notifications.bannerText": "すべてのユーザーへの一時的な通知を目的としたカスタムバナーです。{markdownLink}", - "kbn.advancedSettings.notifications.bannerTitle": "カスタムバナー通知", - "kbn.advancedSettings.notifications.errorLifetimeText": "エラー通知が画面に表示されるミリ秒単位での時間です。{infinityValue} に設定すると無効になります。", - "kbn.advancedSettings.notifications.errorLifetimeTitle": "エラー通知時間", - "kbn.advancedSettings.notifications.infoLifetimeText": "情報通知が画面に表示されるミリ秒単位での時間です。{infinityValue} に設定すると無効になります。", - "kbn.advancedSettings.notifications.infoLifetimeTitle": "情報通知時間", - "kbn.advancedSettings.notifications.warningLifetimeText": "警告通知が画面に表示されるミリ秒単位での時間です。{infinityValue} に設定すると無効になります。", - "kbn.advancedSettings.notifications.warningLifetimeTitle": "警告通知時間", - "kbn.advancedSettings.pinFiltersText": "フィルターがデフォルトでグローバル (ピン付けされた状態) になるかの設定です", - "kbn.advancedSettings.pinFiltersTitle": "フィルターをデフォルトでピン付けする", - "kbn.advancedSettings.query.allowWildcardsText": "設定すると、クエリ句の頭に * が使えるようになります。現在クエリバーで実験的クエリ機能が有効になっている場合にのみ適用されます。基本的な Lucene クエリでリーディングワイルドカードを無効にするには、{queryStringOptionsPattern} を使用します。", - "kbn.advancedSettings.query.allowWildcardsTitle": "クエリでリーディングワイルドカードを許可する", - "kbn.advancedSettings.query.queryStringOptions.optionsLinkText": "オプション", - "kbn.advancedSettings.query.queryStringOptionsText": "Lucene クエリ文字列パーサーの {optionsLink}「{queryLanguage}」が {luceneLanguage} に設定されている時にのみ使用されます。", - "kbn.advancedSettings.query.queryStringOptionsTitle": "クエリ文字列のオプション", - "kbn.advancedSettings.savedObjects.listingLimitText": "一覧ページ用に取得するオブジェクトの数です", - "kbn.advancedSettings.savedObjects.listingLimitTitle": "オブジェクト取得制限", - "kbn.advancedSettings.savedObjects.perPageText": "読み込みダイアログで表示されるページごとのオブジェクトの数です", - "kbn.advancedSettings.savedObjects.perPageTitle": "ページごとのオブジェクト数", - "kbn.advancedSettings.searchQueryLanguageKql": "KQL", - "kbn.advancedSettings.searchQueryLanguageLucene": "Lucene", - "kbn.advancedSettings.searchQueryLanguageText": "クエリバーで使用されるクエリ言語です。KQL は Kibana 用に特別に開発された新しい言語です。", - "kbn.advancedSettings.searchQueryLanguageTitle": "クエリ言語", - "kbn.advancedSettings.shortenFieldsText": "長いフィールドを短くします。例: foo.bar.baz の代わりに f.b.baz と表示", - "kbn.advancedSettings.shortenFieldsTitle": "フィールドの短縮", - "kbn.advancedSettings.sortOptions.optionsLinkText": "オプション", - "kbn.advancedSettings.sortOptionsText": "Elasticsearch の並べ替えパラメーターの {optionsLink}", - "kbn.advancedSettings.sortOptionsTitle": "並べ替えオプション", - "kbn.advancedSettings.storeUrlText": "URL は長くなりすぎてブラウザが対応できない場合があります。セッションストレージに URL の一部を保存することがで この問題に対処できるかテストしています。結果を教えてください!", - "kbn.advancedSettings.storeUrlTitle": "セッションストレージに URL を格納", - "kbn.advancedSettings.suggestFilterValuesText": "フィルターエディターがフィールドの値の候補を表示しないようにするには、このプロパティを false にしてください。", - "kbn.advancedSettings.suggestFilterValuesTitle": "フィルターエディターの候補値", - "kbn.advancedSettings.timepicker.last15Minutes": "過去 15 分間", - "kbn.advancedSettings.timepicker.last1Hour": "過去 1 時間", - "kbn.advancedSettings.timepicker.last1Year": "過去 1 年間", - "kbn.advancedSettings.timepicker.last24Hours": "過去 24 時間", - "kbn.advancedSettings.timepicker.last30Days": "過去 30 日間", - "kbn.advancedSettings.timepicker.last30Minutes": "過去 30 分間", - "kbn.advancedSettings.timepicker.last7Days": "過去 7 日間", - "kbn.advancedSettings.timepicker.last90Days": "過去 90 日間", - "kbn.advancedSettings.timepicker.quickRanges.acceptedFormatsLinkText": "対応フォーマット", - "kbn.advancedSettings.timepicker.quickRangesText": "タイムピッカーのクイックセクションに表示される範囲のリストです。それぞれのオブジェクトに「開始」、「終了」({acceptedFormatsLink} を参照)、「表示」(表示するタイトル) が含まれるオブジェクトの配列です。", - "kbn.advancedSettings.timepicker.quickRangesTitle": "タイムピッカーのクイック範囲", - "kbn.advancedSettings.timepicker.refreshIntervalDefaultsText": "時間フィルターのデフォルト更新間隔", - "kbn.advancedSettings.timepicker.refreshIntervalDefaultsTitle": "タイムピッカーの更新間隔", - "kbn.advancedSettings.timepicker.thisWeek": "今週", - "kbn.advancedSettings.timepicker.timeDefaultsText": "時間フィルターが選択されずに Kibana が起動した際に使用される時間フィルターです", - "kbn.advancedSettings.timepicker.timeDefaultsTitle": "デフォルトのタイムピッカー", - "kbn.advancedSettings.timepicker.today": "今日", - "kbn.advancedSettings.visualization.colorMappingText": "ビジュアライゼーション内の特定の色のマップ値です", - "kbn.advancedSettings.visualization.colorMappingTitle": "カラーマッピング", - "kbn.advancedSettings.visualization.dimmingOpacityText": "チャートの別のエレメントが選択された時に暗くなるチャート項目の透明度です。この数字が小さければ小さいほど、ハイライトされたエレメントが目立ちます。0 と 1 の間の数字で設定します。", - "kbn.advancedSettings.visualization.dimmingOpacityTitle": "減光透明度", - "kbn.advancedSettings.visualization.heatmap.maxBucketsText": "1 つのデータソースが返せるバケットの最大数です。値が大きいとブラウザのレンダリング速度が下がる可能性があります。", - "kbn.advancedSettings.visualization.heatmap.maxBucketsTitle": "ヒートマップの最大バケット数", - "kbn.advancedSettings.visualization.loadingDelayText": "クエリの際にビジュアライゼーションを暗くするまでの時間です", - "kbn.advancedSettings.visualization.loadingDelayTitle": "読み込み遅延", - "kbn.advancedSettings.visualization.showRegionMapWarningsText": "用語がマップの形に合わない場合に地域マップに警告を表示するかどうかです。", - "kbn.advancedSettings.visualization.showRegionMapWarningsTitle": "地域マップに警告を表示", - "kbn.advancedSettings.visualization.tileMap.maxPrecision.cellDimensionsLinkText": "ディメンションの説明", - "kbn.advancedSettings.visualization.tileMap.maxPrecisionText": "マップに表示されるジオハッシュの最高精度です。7 が高い、10 が非常に高い、12 が最高を意味します。{cellDimensionsLink}", - "kbn.advancedSettings.visualization.tileMap.maxPrecisionTitle": "タイルマップの最高精度", - "kbn.advancedSettings.visualization.tileMap.wmsDefaults.propertiesLinkText": "プロパティ", - "kbn.advancedSettings.visualization.tileMap.wmsDefaultsText": "座標マップの WMS マップサーバーサポートのデフォルトの {propertiesLink} です。", - "kbn.advancedSettings.visualization.tileMap.wmsDefaultsTitle": "デフォルトの WMS プロパティ", - "kbn.advancedSettings.visualizeEnableLabsText": "ユーザーが実験的なビジュアライゼーションを作成、表示、編集できるようになります。無効の場合、\n ユーザーは本番準備が整ったビジュアライゼーションのみを利用できます。", - "kbn.advancedSettings.visualizeEnableLabsTitle": "実験的なビジュアライゼーションを有効にする", - "kbn.context.breadcrumb": "{indexPatternTitle}#{docId} のコンテキスト", - "kbn.context.failedToLoadAnchorDocumentDescription": "別ののドキュメントの読み込みに失敗しました", - "kbn.context.failedToLoadAnchorDocumentErrorDescription": "別のドキュメントの読み込みに失敗しました。", - "kbn.context.loadButtonLabel": "読み込み", - "kbn.context.loadingDescription": "読み込み中…", - "kbn.context.newerDocumentsAriaLabel": "新しいドキュメントの数", - "kbn.context.newerDocumentsDescription": "新しいドキュメント", - "kbn.context.newerDocumentsWarning": "アンカーよりも新しいドキュメントは {docCount} 件しか見つかりませんでした。", - "kbn.context.newerDocumentsWarningZero": "アンカーよりも新しいドキュメントは見つかりませんでした。", - "kbn.context.noSearchableTiebreakerFieldDescription": "インデックスパターン {indexPatternId} で検索可能なタイブレーカーフィールドが見つかりませんでした。高度な設定 {tieBreakerFields} tを変更してこのインデックスパターンの有効なフィールドを含めてください。", - "kbn.context.olderDocumentsAriaLabel": "古いドキュメントの数", - "kbn.context.olderDocumentsDescription": "古いドキュメント", - "kbn.context.olderDocumentsWarning": "アンカーよりも古いドキュメントは {docCount} 件しか見つかりませんでした。", - "kbn.context.olderDocumentsWarningZero": "アンカーよりも古いドキュメントは見つかりませんでした。", - "kbn.context.reloadPageDescription.discoverLinkText": "ディスカバリ", - "kbn.context.reloadPageDescription.reloadOrVisitTextMessage": "再読み込みするか", - "kbn.context.reloadPageDescription.selectValidAnchorDocumentTextMessage": "にアクセスして有効な別のドキュメントを選択してください。", - "kbn.context.unableToLoadAnchorDocumentDescription": "別のドキュメントが読み込めません", - "kbn.context.unableToLoadDocumentDescription": "ドキュメントが読み込めません", - "kbn.dashboard.addVisualizationLinkAriaLabel": "ビジュアライゼーションを追加", - "kbn.dashboard.badge.readOnly.text": "読み込み専用", - "kbn.dashboard.badge.readOnly.tooltip": "ダッシュボードを保存できません", - "kbn.dashboard.changeViewModeConfirmModal.cancelButtonLabel": "編集を続行", - "kbn.dashboard.changeViewModeConfirmModal.confirmButtonLabel": "変更を破棄", - "kbn.dashboard.changeViewModeConfirmModal.discardChangesDescription": "変更を破棄すると、元に戻すことはできません。", - "kbn.dashboard.changeViewModeConfirmModal.discardChangesTitle": "ダッシュボードへの変更を破棄しますか?", - "kbn.dashboard.dashboardAppBreadcrumbsTitle": "ダッシュボード", - "kbn.dashboard.dashboardBreadcrumbsTitle": "ダッシュボード", - "kbn.dashboard.dashboardWasNotSavedDangerMessage": "ダッシュボード「{dashTitle}」は保存されませんでした。エラー: {errorMessage}", - "kbn.dashboard.dashboardWasSavedSuccessMessage": "ダッシュボード「{dashTitle}」が保存されました。", - "kbn.dashboard.featureCatalogue.dashboardDescription": "ビジュアライゼーションと保存された検索のコレクションの表示と共有を行います。", - "kbn.dashboard.featureCatalogue.dashboardTitle": "ダッシュボード", - "kbn.dashboard.fillDashboardTitle": "このダッシュボードは空です。コンテンツを追加しましょう!", - "kbn.dashboard.howToStartWorkingOnNewDashboardDescription1": "上のメニューバーの ", - "kbn.dashboard.howToStartWorkingOnNewDashboardDescription2": " ダッシュボードの作成を始めましょう。", - "kbn.dashboard.howToStartWorkingOnNewDashboardEditLinkText": "編集", - "kbn.dashboard.listing.createNewDashboard.combineDataViewFromKibanaAppDescription": "あらゆる Kibana アプリからダッシュボードでデータビューを組み合わせて、すべてを 1 か所に表示できます。", - "kbn.dashboard.listing.createNewDashboard.createButtonLabel": "新規ダッシュボードを作成", - "kbn.dashboard.listing.createNewDashboard.newToKibanaDescription": "Kibana は初心者ですか?{sampleDataInstallLink} してお試しください。", - "kbn.dashboard.listing.createNewDashboard.sampleDataInstallLinkText": "サンプルデータをインストール", - "kbn.dashboard.listing.createNewDashboard.title": "初めてのダッシュボードを作成してみましょう。", - "kbn.dashboard.listing.dashboardsTitle": "ダッシュボード", - "kbn.dashboard.listing.noItemsMessage": "ダッシュボードがないようです。", - "kbn.dashboard.listing.table.descriptionColumnName": "説明", - "kbn.dashboard.listing.table.entityName": "ダッシュボード", - "kbn.dashboard.listing.table.entityNamePlural": "ダッシュボード", - "kbn.dashboard.listing.table.titleColumnName": "タイトル", - "kbn.dashboard.panel.invalidData": "URLの無効なデータ", - "kbn.dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage": "「6.1.0」のダッシュボードの互換性のため、パネルデータを移行できませんでした。パネルには想定された列または行フィールドがありません", - "kbn.dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage": "「6.3.0」のダッシュボードの互換性のため、パネルデータを移行できませんでした。パネルに必要なフィールドがありません: {key}", - "kbn.dashboard.savedDashboard.newDashboardTitle": "新規ダッシュボード", - "kbn.dashboard.savedDashboardsTitle": "ダッシュボード", - "kbn.dashboard.stateManager.timeNotSavedWithDashboardErrorMessage": "このダッシュボードに時刻が保存されていないため、同期できません。", - "kbn.dashboard.strings.dashboardEditTitle": "{title} を編集中", - "kbn.dashboard.strings.dashboardUnsavedEditTitle": "{title} を編集中 (未保存)", - "kbn.dashboard.topNav.cloneModal.cancelButtonLabel": "キャンセル", - "kbn.dashboard.topNav.cloneModal.cloneDashboardModalHeaderTitle": "ダッシュボードのクローンを作成", - "kbn.dashboard.topNav.cloneModal.confirmButtonLabel": "クローンの確認", - "kbn.dashboard.topNav.cloneModal.confirmCloneDescription": "クローンの確認", - "kbn.dashboard.topNav.cloneModal.dashboardExistsDescription": "{confirmClone} をクリックして重複タイトルでダッシュボードのクローンを作成します。", - "kbn.dashboard.topNav.cloneModal.dashboardExistsTitle": "「{newDashboardName}」というタイトルのダッシュボードが既に存在します。", - "kbn.dashboard.topNav.cloneModal.enterNewNameForDashboardDescription": "ダッシュボードの新しい名前を入力してください。", - "kbn.dashboard.topNav.options.hideAllPanelTitlesSwitchLabel": "パネルタイトルを表示", - "kbn.dashboard.topNav.options.useMarginsBetweenPanelsSwitchLabel": "パネルの間に余白を使用", - "kbn.dashboard.topNav.saveModal.descriptionFormRowLabel": "説明", - "kbn.dashboard.topNav.saveModal.storeTimeWithDashboardFormRowHelpText": "有効化すると、ダッシュボードが読み込まれるごとに現在選択された時刻の時間フィルターが変更されます。", - "kbn.dashboard.topNav.saveModal.storeTimeWithDashboardFormRowLabel": "ダッシュボードに時刻を保存", - "kbn.dashboard.topNav.showCloneModal.dashboardCopyTitle": "{title} のコピー", - "kbn.dashboard.topNave.addButtonAriaLabel": "追加", - "kbn.dashboard.topNave.addConfigDescription": "ダッシュボードにパネルを追加します", - "kbn.dashboard.topNave.cancelButtonAriaLabel": "キャンセル", - "kbn.dashboard.topNave.cloneButtonAriaLabel": "クローンを作成", - "kbn.dashboard.topNave.cloneConfigDescription": "ダッシュボードのコピーを作成します", - "kbn.dashboard.topNave.editButtonAriaLabel": "編集", - "kbn.dashboard.topNave.editConfigDescription": "編集モードに切り替えます", - "kbn.dashboard.topNave.fullScreenButtonAriaLabel": "全画面", - "kbn.dashboard.topNave.fullScreenConfigDescription": "全画面モード", - "kbn.dashboard.topNave.optionsButtonAriaLabel": "オプション", - "kbn.dashboard.topNave.optionsConfigDescription": "オプション", - "kbn.dashboard.topNave.saveButtonAriaLabel": "保存", - "kbn.dashboard.topNave.saveConfigDescription": "ダッシュボードを保存します", - "kbn.dashboard.topNave.shareButtonAriaLabel": "共有", - "kbn.dashboard.topNave.shareConfigDescription": "ダッシュボードを共有します", - "kbn.dashboard.topNave.viewConfigDescription": "編集をキャンセルして表示限定モードに切り替えます", - "kbn.dashboard.urlWasRemovedInSixZeroWarningMessage": "URL「dashboard/create」は 6.0 で廃止されました。ブックマークを更新してください。", - "kbn.dashboard.visitVisualizeAppLinkText": "可視化アプリにアクセス", - "kbn.dashboardTitle": "ダッシュボード", - "kbn.devToolsTitle": "開発ツール", - "kbn.discover.backToTopLinkText": "最上部へ戻る。", - "kbn.discover.badge.readOnly.text": "読み込み専用", - "kbn.discover.badge.readOnly.tooltip": "検索を保存できません", - "kbn.discover.bucketIntervalTooltip": "この間隔は選択された時間範囲に表示される {bucketsDescription} が作成されるため、{bucketIntervalDescription} にスケーリングされています。", - "kbn.discover.bucketIntervalTooltip.tooLargeBucketsText": "大きすぎるバケット", - "kbn.discover.bucketIntervalTooltip.tooManyBucketsText": "バケットが多すぎます", - "kbn.discover.discoverBreadcrumbTitle": "ディスカバリ", - "kbn.discover.discoverDescription": "ドキュメントにクエリをかけたりフィルターを適用することで、データをインタラクティブに閲覧できます。", - "kbn.discover.discoverTitle": "ディスカバー", - "kbn.discover.documentsAriaLabel": "ドキュメント", - "kbn.discover.errorLoadingData": "データの読み込み中にエラーが発生", - "kbn.discover.fetchError.howToAddressErrorDescription": "このエラーは、{scriptedFields} タブにある {managementLink} の {fetchErrorScript} フィールドを編集することで解決できます。", - "kbn.discover.fetchError.managmentLinkText": "管理 > インデックスパターン", - "kbn.discover.fetchError.scriptedFieldsText": "「スクリプトフィールド」", - "kbn.discover.fieldChooser.detailViews.emptyStringText": "空の文字列", - "kbn.discover.fieldChooser.detailViews.recordsText": "記録", - "kbn.discover.fieldChooser.detailViews.topValuesInRecordsDescription": "次の記録のトップ 5 の値", - "kbn.discover.fieldChooser.detailViews.visualizeLinkText": "可視化", - "kbn.discover.fieldChooser.discoverField.scriptedFieldsTakeLongExecuteDescription": "スクリプトフィールドは実行に時間がかかる場合があります。", - "kbn.discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForGeoFieldsErrorMessage": "ジオフィールドは分析できません。", - "kbn.discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForObjectFieldsErrorMessage": "オブジェクトフィールドは分析できません。", - "kbn.discover.fieldChooser.fieldCalculator.fieldIsNotPresentInDocumentsErrorMessage": "このフィールドは Elasticsearch マッピングに表示されますが、ドキュメントテーブルの {hitsLength} 件のドキュメントには含まれません。可視化や検索は可能な場合があります。", - "kbn.discover.fieldChooser.fieldFilterFacetButtonLabel": "フィルタリングされたフィールド", - "kbn.discover.fieldChooser.filter.aggregatableLabel": "集約可能", - "kbn.discover.fieldChooser.filter.availableFieldsTitle": "利用可能なフィールド", - "kbn.discover.fieldChooser.filter.hideMissingFieldsLabel": "未入力のフィールドを非表示", - "kbn.discover.fieldChooser.filter.indexAndFieldsSectionAriaLabel": "インデックスとフィールド", - "kbn.discover.fieldChooser.filter.popularTitle": "人気", - "kbn.discover.fieldChooser.filter.searchableLabel": "検索可能", - "kbn.discover.fieldChooser.filter.selectedFieldsTitle": "スクリプトフィールド", - "kbn.discover.fieldChooser.filter.typeLabel": "タイプ", - "kbn.discover.fieldChooser.searchPlaceHolder": "検索フィールド", - "kbn.discover.fieldChooser.toggleFieldFilterButtonHideAriaLabel": "フィールド設定を非表示", - "kbn.discover.fieldChooser.toggleFieldFilterButtonShowAriaLabel": "フィールド設定を表示", - "kbn.discover.histogram.partialData.bucketTooltipText": "選択された時間範囲にはこのバケット全体は含まれていませんが、一部データが含まれている可能性があります。", - "kbn.discover.histogramOfFoundDocumentsAriaLabel": "発見されたドキュメントのヒストグラム", - "kbn.discover.hitsPluralTitle": "{hits, plural, one {ヒット} other {ヒット}}", - "kbn.discover.howToChangeTheTimeTooltip": "時刻を変更するには、ナビゲーションバーのカレンダーアイコンをクリックします", - "kbn.discover.howToSeeOtherMatchingDocumentsDescription": "これらは検索条件に一致した初めの {sampleSize} 件のドキュメントです。他の結果を表示するには検索条件を絞ってください。", - "kbn.discover.inspectorRequestDataTitle": "データ", - "kbn.discover.inspectorRequestDescription": "このリクエストは Elasticsearch にクエリをかけ、検索データを取得します。", - "kbn.discover.localMenu.inspectTitle": "検査", - "kbn.discover.localMenu.localMenu.newSearchTitle": "新規", - "kbn.discover.localMenu.newSearchDescription": "新規検索", - "kbn.discover.localMenu.openInspectorForSearchDescription": "検索用にインスペクターを開きます", - "kbn.discover.localMenu.openSavedSearchDescription": "保存された検索を開きます", - "kbn.discover.localMenu.openTitle": "開く", - "kbn.discover.localMenu.saveSaveSearchDescription": "ビジュアライゼーションとダッシュボードで使用できるようにディスカバリの検索を保存します", - "kbn.discover.localMenu.saveSearchDescription": "検索を保存します", - "kbn.discover.localMenu.saveTitle": "保存", - "kbn.discover.localMenu.shareSearchDescription": "検索を共有します", - "kbn.discover.localMenu.shareTitle": "共有", - "kbn.discover.noResults.addressShardFailuresTitle": "シャードエラーの解決", - "kbn.discover.noResults.expandYourTimeRangeTitle": "時間範囲を拡大", - "kbn.discover.noResults.indexFailureIndexText": "インデックス {failureIndex}", - "kbn.discover.noResults.indexFailureShardText": "{index}、シャード {failureShard}", - "kbn.discover.noResults.queryMayNotMatchTitle": "表示されているインデックスの 1 つまたは複数にデータフィールドが含まれています。クエリが現在の時間範囲のデータと一致しないか、現在選択された時間範囲にデータが全く存在しない可能性があります。データが存在する時間範囲に変えることができます。", - "kbn.discover.noResults.searchExamples.400to499StatusCodeExampleTitle": "400-499 のすべてのステータスコードを検索", - "kbn.discover.noResults.searchExamples.400to499StatusCodeWithPhpExtensionExampleTitle": "400-499 の php 拡張子のステータスコードを検索", - "kbn.discover.noResults.searchExamples.400to499StatusCodeWithPhpOrHtmlExtensionExampleTitle": "400-499 の php または html 拡張子のステータスコードを検索", - "kbn.discover.noResults.searchExamples.anyField200StatusCodeExampleTitle": "いずれかのフィールドに数字 200 が含まれているリクエストを検索", - "kbn.discover.noResults.searchExamples.howTosearchForWebServerLogsDescription": "画面上部の検索バーは、Elasticsearch の Lucene {queryStringSyntaxLink} サポートを利用します。新規フィールドにパースされたウェブサーバーログの検索方法の例は、次の通りです。", - "kbn.discover.noResults.searchExamples.noResultsMatchSearchCriteriaTitle": "検索条件と一致する結果がありません。", - "kbn.discover.noResults.searchExamples.queryStringSyntaxLinkText": "クエリ文字列の構文", - "kbn.discover.noResults.searchExamples.refineYourQueryTitle": "クエリの調整", - "kbn.discover.noResults.searchExamples.statusField200StatusCodeExampleTitle": "ステータスフィールドの 200 を検索", - "kbn.discover.noResults.shardFailuresDescription": "次のシャードエラーが発生しました:", - "kbn.discover.notifications.notSavedSearchTitle": "検索「{savedSearchTitle}」は保存されませんでした。", - "kbn.discover.notifications.savedSearchTitle": "検索「{savedSearchTitle}」が保存されました。", - "kbn.discover.painlessError.painlessScriptedFieldErrorMessage": "Painless スクリプトのフィールド「{script}」のエラー.", - "kbn.discover.reloadSavedSearchButton": "検索をリセット", - "kbn.discover.rootBreadcrumb": "ディスカバリ", - "kbn.discover.savedSearch.savedObjectName": "保存された検索", - "kbn.discover.scaledToDescription": "{bucketIntervalDescription} にスケーリング済み", - "kbn.discover.searchingTitle": "検索中", - "kbn.discover.showingDefaultIndexPatternWarningDescription": "デフォルトのインデックスパターン「{loadedIndexPatternTitle}」 ({loadedIndexPatternId}) を表示中", - "kbn.discover.showingSavedIndexPatternWarningDescription": "保存されたインデックスパターン「{ownIndexPatternTitle}」 ({ownIndexPatternId}) を表示中", - "kbn.discover.skipToBottomButtonLabel": "最下部に移動", - "kbn.discover.topNav.openSearchPanel.manageSearchesButtonLabel": "検索の管理", - "kbn.discover.topNav.openSearchPanel.noSearchesFoundDescription": "一致する検索が見つかりませんでした。", - "kbn.discover.topNav.openSearchPanel.openSearchTitle": "検索を開く", - "kbn.discover.uninitializedRefreshButtonText": "データを更新", - "kbn.discover.uninitializedText": "クエリを作成、フィルターを追加、または [更新] をクリックして、現在のクエリの結果を取得します。", - "kbn.discover.uninitializedTitle": "検索開始", - "kbn.discover.valueIsNotConfiguredIndexPatternIDWarningTitle": "{stateVal} は設定されたインデックスパターン ID ではありません", - "kbn.discoverTitle": "ディスカバリ", - "kbn.doc.couldNotFindDocumentsDescription": "その ID に一致するドキュメントがありません。", - "kbn.doc.failedToExecuteQueryDescription": "クエリの実行に失敗しました", - "kbn.doc.failedToLocateDocumentDescription": "ドキュメントが見つかりませんでした", - "kbn.doc.failedToLocateIndexPattern": "ID {indexPatternId} に一致するインデックスパターンがありません", - "kbn.doc.loadingDescription": "読み込み中…", - "kbn.doc.somethingWentWrongDescription": "{indexName} が欠けています。", - "kbn.doc.somethingWentWrongDescriptionAddon": "インデックスが存在することを確認してください。", - "kbn.docTable.limitedSearchResultLabel": "{resultCount} 件の結果に制限。検索結果の絞り込み。", - "kbn.docTable.noResultsTitle": "結果が見つかりませんでした", - "kbn.docTable.pagerControl.pagesCountLabel": "{startItem}–{endItem} of {totalItems}", - "kbn.docTable.tableHeader.moveColumnLeftButtonAriaLabel": "{columnName} 列を左に移動", - "kbn.docTable.tableHeader.moveColumnLeftButtonTooltip": "列を左に移動", - "kbn.docTable.tableHeader.moveColumnRightButtonAriaLabel": "{columnName} 列を右に移動", - "kbn.docTable.tableHeader.moveColumnRightButtonTooltip": "列を右に移動", - "kbn.docTable.tableHeader.removeColumnButtonAriaLabel": "{columnName} 列を削除", - "kbn.docTable.tableHeader.removeColumnButtonTooltip": "列の削除", - "kbn.docTable.tableHeader.sortByColumnAscendingAriaLabel": "{columnName} を昇順に並べ替える", - "kbn.docTable.tableHeader.sortByColumnDescendingAriaLabel": "{columnName} を降順に並べ替える", - "kbn.docTable.tableHeader.sortByColumnUnsortedAriaLabel": "{columnName} で並べ替えを止める", - "kbn.docTable.tableRow.detailHeading": "拡張ドキュメント", - "kbn.docTable.tableRow.filterForValueButtonAriaLabel": "値でフィルタリング", - "kbn.docTable.tableRow.filterForValueButtonTooltip": "値でフィルタリング", - "kbn.docTable.tableRow.filterOutValueButtonAriaLabel": "値を除外", - "kbn.docTable.tableRow.filterOutValueButtonTooltip": "値を除外", - "kbn.docTable.tableRow.toggleRowDetailsButtonAriaLabel": "行の詳細を切り替える", - "kbn.docTable.tableRow.viewSingleDocumentLinkText": "単一のドキュメントを表示", - "kbn.docTable.tableRow.viewSurroundingDocumentsLinkText": "周りのドキュメントを表示", - "kbn.embeddable.errorTitle": "データの取得中にエラーが発生", - "kbn.embeddable.inspectorRequestDataTitle": "データ", - "kbn.embeddable.inspectorRequestDescription": "このリクエストは Elasticsearch にクエリをかけ、検索データを取得します。", - "kbn.embeddable.search.displayName": "検索", - "kbn.management.createIndexPattern.betaLabel": "ベータ", - "kbn.management.createIndexPattern.emptyState.checkDataButton": "新規データを確認", - "kbn.management.createIndexPattern.emptyStateHeader": "Elasticsearch データが見つかりませんでした", - "kbn.management.createIndexPattern.emptyStateLabel.emptyStateDetail": "{needToIndex} {learnHowLink} または {getStartedLink}", - "kbn.management.createIndexPattern.emptyStateLabel.getStartedLink": "サンプルデータで始めましょう。", - "kbn.management.createIndexPattern.emptyStateLabel.learnHowLink": "方法を学習", - "kbn.management.createIndexPattern.emptyStateLabel.needToIndexLabel": "インデックスパターンを作成する前に、Elasticsearch へのデータのインデックスが必要です。", - "kbn.management.createIndexPattern.includeSystemIndicesToggleSwitchLabel": "システムインデックスを含める", - "kbn.management.createIndexPattern.loadClustersFailMsg": "リモートクラスターの読み込みに失敗", - "kbn.management.createIndexPattern.loadIndicesFailMsg": "インデックスの読み込みに失敗", - "kbn.management.createIndexPattern.loadingState.checkingLabel": "Elasticsearch データを確認中", - "kbn.management.createIndexPattern.step.indexPattern.allowLabel": "インデックスパターンでワイルドカードとして {asterisk} を使用できます。", - "kbn.management.createIndexPattern.step.indexPattern.disallowLabel": "スペースや {characterList} は使用できません。", - "kbn.management.createIndexPattern.step.indexPatternLabel": "インデックスパターン", - "kbn.management.createIndexPattern.step.indexPatternPlaceholder": "index-name-*", - "kbn.management.createIndexPattern.step.invalidCharactersErrorMessage": "{indexPatternName} にはスペースや {characterList} は使えません。", - "kbn.management.createIndexPattern.step.loadingHeader": "一致するインデックスを検索中…", - "kbn.management.createIndexPattern.step.loadingLabel": "お待ちください…", - "kbn.management.createIndexPattern.step.nextStepButton": "次のステップ", - "kbn.management.createIndexPattern.step.pagingLabel": "ページごとの行数: {perPage}", - "kbn.management.createIndexPattern.step.status.matchAnyLabel.matchAnyDetail": "インデックスパターンは下の{strongIndices} の いずれかに一致します。", - "kbn.management.createIndexPattern.step.status.noSystemIndicesLabel": "パターンに一致する Elasticsearch インデックスがありません。", - "kbn.management.createIndexPattern.step.status.noSystemIndicesWithPromptLabel": "パターンに一致する Elasticsearch インデックスがありません。一致するシステムインデックスを表示するには、右上のスイッチを切り替えます。", - "kbn.management.createIndexPattern.step.status.notMatchLabel.allIndicesLabel": "{indicesLength, plural, one {# インデックス} other {# インデックス}}", - "kbn.management.createIndexPattern.step.status.notMatchLabel.notMatchDetail": "入力されたインデックスパターンがどのインデックスにも一致しません。下の {indicesLength, plural, one {} other {}}{strongIndices} と一致させることができます。", - "kbn.management.createIndexPattern.step.status.partialMatchLabel.partialMatchDetail": "インデックスパターンがどのインデックスとも一致ませんが、似た {matchedIndicesLength, plural, one {} other {}}{strongIndices} があります。", - "kbn.management.createIndexPattern.step.status.partialMatchLabel.strongIndicesLabel": "{matchedIndicesLength, plural, one {# インデックス} other {# インデックス}}", - "kbn.management.createIndexPattern.step.status.successLabel.strongIndicesLabel": "{indicesLength, plural, one {# インデックス} other {# インデックス}}", - "kbn.management.createIndexPattern.step.status.successLabel.strongSuccessLabel": "成功!", - "kbn.management.createIndexPattern.step.status.successLabel.successDetail": "{strongSuccess} インデックスパターンが {strongIndices} と一致しています。", - "kbn.management.createIndexPattern.step.warningHeader": "既に {query} という名前のインデックスパターンがあります。", - "kbn.management.createIndexPattern.stepHeader": "ステップ 1/2:インデックスパターンの定義", - "kbn.management.createIndexPattern.stepTime.backButton": "戻る", - "kbn.management.createIndexPattern.stepTime.createPatternButton": "インデックスパターンを作成", - "kbn.management.createIndexPattern.stepTime.creatingLabel": "インデックスパターンを作成中…", - "kbn.management.createIndexPattern.stepTime.error": "エラー", - "kbn.management.createIndexPattern.stepTime.field.loadingDropDown": "読み込み中…", - "kbn.management.createIndexPattern.stepTime.field.noTimeFieldsLabel": "このインデックスパターンに一致するインデックスには時間フィールドがありません。", - "kbn.management.createIndexPattern.stepTime.fieldHeader": "時間フィルターのフィールド名", - "kbn.management.createIndexPattern.stepTime.fieldLabel": "時間フィルターはこのフィールドを使って時間でフィールドを絞ります。", - "kbn.management.createIndexPattern.stepTime.fieldWarningLabel": "時間フィールドを使わないこともできますが、その場合データを時間範囲で絞ることができません。", - "kbn.management.createIndexPattern.stepTime.noTimeFieldOptionLabel": "時間フィルターを使用しない", - "kbn.management.createIndexPattern.stepTime.noTimeFieldsLabel": "このインデックスパターンに一致するインデックスには時間フィールドがありません。", - "kbn.management.createIndexPattern.stepTime.options.hideButton": "高度なオプションを非表示", - "kbn.management.createIndexPattern.stepTime.options.patternHeader": "カスタムインデックスパターン ID", - "kbn.management.createIndexPattern.stepTime.options.patternLabel": "Kibana はそれぞれのインデックスパターンに固有の識別子を割り当てます。固有 ID を使用しない場合は、カスタム ID を入力してください。", - "kbn.management.createIndexPattern.stepTime.options.patternPlaceholder": "custom-index-pattern-id", - "kbn.management.createIndexPattern.stepTime.options.showButton": "高度なオプションを表示", - "kbn.management.createIndexPattern.stepTime.patterAlreadyExists": "カスタムインデックスパターン ID が既に存在します。", - "kbn.management.createIndexPattern.stepTime.refreshButton": "更新", - "kbn.management.createIndexPattern.stepTimeHeader": "ステップ 2/2:設定の変更", - "kbn.management.createIndexPattern.stepTimeLabel": "{indexPattern} を {indexPatternName} に定義しました。次に、作成前に他の設定を行うことができます。", - "kbn.management.createIndexPatternHeader": "{indexPatternName} の作成", - "kbn.management.createIndexPatternLabel": "Kibana は、可視化などを目的に Elasticsearch インデックスからデータを取得するために、インデックスパターンを使用します。", - "kbn.management.editIndexPattern.deleteButton": "削除", - "kbn.management.editIndexPattern.deleteFieldButton": "削除", - "kbn.management.editIndexPattern.deleteHeader": "インデックスパターンを削除しますか?", - "kbn.management.editIndexPattern.detailsAria": "インデックスパターンの詳細", - "kbn.management.editIndexPattern.editFieldButton": "編集", - "kbn.management.editIndexPattern.fields.allLangsDropDown": "すべての言語", - "kbn.management.editIndexPattern.fields.allTypesDropDown": "すべてのフィールドタイプ", - "kbn.management.editIndexPattern.fields.filterAria": "フィルター", - "kbn.management.editIndexPattern.fields.filterPlaceholder": "フィルター", - "kbn.management.editIndexPattern.fields.table.additionalInfoAriaLabel": "追加フィールド情報", - "kbn.management.editIndexPattern.fields.table.aggregatableDescription": "これらのフィールドはビジュアライゼーションの集約に使用できます", - "kbn.management.editIndexPattern.fields.table.aggregatableLabel": "集約可能", - "kbn.management.editIndexPattern.fields.table.editDescription": "編集", - "kbn.management.editIndexPattern.fields.table.editLabel": "編集", - "kbn.management.editIndexPattern.fields.table.excludedDescription": "取得の際に _source から除外されるフィールドです", - "kbn.management.editIndexPattern.fields.table.excludedLabel": "除外", - "kbn.management.editIndexPattern.fields.table.formatHeader": "フォーマット", - "kbn.management.editIndexPattern.fields.table.isAggregatableAria": "は集約可能です", - "kbn.management.editIndexPattern.fields.table.isExcludedAria": "は除外されています", - "kbn.management.editIndexPattern.fields.table.isSearchableAria": "は検索可能です", - "kbn.management.editIndexPattern.fields.table.multiTypeAria": "複数タイプのフィールド", - "kbn.management.editIndexPattern.fields.table.multiTypeTooltip": "フィールドのタイプがインデックスごとに変わります。多くの分析機能には使用できません。", - "kbn.management.editIndexPattern.fields.table.nameHeader": "名前", - "kbn.management.editIndexPattern.fields.table.primaryTimeAriaLabel": "プライマリ時間フィールド", - "kbn.management.editIndexPattern.fields.table.primaryTimeTooltip": "このフィールドはイベントの発生時刻を表します。", - "kbn.management.editIndexPattern.fields.table.searchableDescription": "これらのフィールドはフィルターバーで使用できます", - "kbn.management.editIndexPattern.fields.table.searchableHeader": "検索可能", - "kbn.management.editIndexPattern.fields.table.typeHeader": "タイプ", - "kbn.management.editIndexPattern.mappingConflictHeader": "マッピングの矛盾", - "kbn.management.editIndexPattern.mappingConflictLabel": "{conflictFieldsLength, plural, one {フィールドが} other {# フィールドが}}このパターンと一致するインデックスの間で異なるタイプ (文字列、整数など) に定義されています。これらの矛盾したフィールドは Kibana の一部で使用できますが、Kibana がタイプを把握しなければならない機能には使用できません。この問題を修正するにはデータのレンダリングが必要です。", - "kbn.management.editIndexPattern.notDateErrorMessage": "このフィールドは日付ではなく {fieldType} です。", - "kbn.management.editIndexPattern.refreshAria": "フィールドリストを再度読み込みます", - "kbn.management.editIndexPattern.refreshButton": "更新", - "kbn.management.editIndexPattern.refreshHeader": "フィールドリストを更新しますか?", - "kbn.management.editIndexPattern.refreshLabel": "この操作は各フィールドの使用頻度をリセットします。", - "kbn.management.editIndexPattern.refreshTooltip": "フィールドリストを更新", - "kbn.management.editIndexPattern.removeAria": "インデックスパターンを削除", - "kbn.management.editIndexPattern.removeTooltip": "インデックスパターンを削除います", - "kbn.management.editIndexPattern.scripted.addFieldButton": "スクリプトフィールドを追加", - "kbn.management.editIndexPattern.scripted.deleteField.cancelButton": "キャンセル", - "kbn.management.editIndexPattern.scripted.deleteField.deleteButton": "削除", - "kbn.management.editIndexPattern.scripted.deleteFieldLabel": "スクリプトフィールド「{fieldName}」を削除しますか?", - "kbn.management.editIndexPattern.scripted.deprecationLangHeader": "廃止された言語が使用されています", - "kbn.management.editIndexPattern.scripted.deprecationLangLabel.deprecationLangDetail": "次の廃止された言語が使用されています: {deprecatedLangsInUse}これらの言語は、Kibana と Elasticsearch の次のメジャーなバージョンでサポートされなくなります。問題を避けるため、スクリプトフィールドを {link} に変換してください。", - "kbn.management.editIndexPattern.scripted.deprecationLangLabel.painlessDescription": "パターン", - "kbn.management.editIndexPattern.scripted.newFieldPlaceholder": "新規スクリプトフィールド", - "kbn.management.editIndexPattern.scripted.noFieldLabel": "「{indexPatternTitle}」インデックスパターンには「{fieldName}」というスクリプトフィールドがありません", - "kbn.management.editIndexPattern.scripted.table.deleteDescription": "このフィールドを削除します", - "kbn.management.editIndexPattern.scripted.table.deleteHeader": "削除", - "kbn.management.editIndexPattern.scripted.table.editDescription": "このフィールドを編集します", - "kbn.management.editIndexPattern.scripted.table.editHeader": "編集", - "kbn.management.editIndexPattern.scripted.table.formatDescription": "フィールドに使用されているフォーマットです", - "kbn.management.editIndexPattern.scripted.table.formatHeader": "フォーマット", - "kbn.management.editIndexPattern.scripted.table.langDescription": "フィールドに使用されている言語です", - "kbn.management.editIndexPattern.scripted.table.langHeader": "言語", - "kbn.management.editIndexPattern.scripted.table.nameDescription": "フィールドの名前です", - "kbn.management.editIndexPattern.scripted.table.nameHeader": "名前", - "kbn.management.editIndexPattern.scripted.table.scriptDescription": "フィールドのスクリプトです", - "kbn.management.editIndexPattern.scripted.table.scriptHeader": "スクリプト", - "kbn.management.editIndexPattern.scripted.unknownModeErrorMessage": "不明なフィールド設定モード {mode}", - "kbn.management.editIndexPattern.scriptedHeader": "スクリプトフィールド", - "kbn.management.editIndexPattern.scriptedLabel": "ビジュアライゼーションにスクリプトフィールドを使用し、ドキュメントに表示させることができます。但し、スクリプトフィールドは検索できません。", - "kbn.management.editIndexPattern.setDefaultAria": "デフォルトのインデックスに設定", - "kbn.management.editIndexPattern.setDefaultTooltip": "デフォルトのインデックスに設定します", - "kbn.management.editIndexPattern.source.addButtonLabel": "追加", - "kbn.management.editIndexPattern.source.deleteFilter.cancelButtonLabel": "キャンセル", - "kbn.management.editIndexPattern.source.deleteFilter.deleteButtonLabel": "削除", - "kbn.management.editIndexPattern.source.deleteSourceFilterLabel": "ソースフィルター「{value}」を削除しますか?", - "kbn.management.editIndexPattern.source.noteLabel": "下の表で、マルチフィールドが一致として誤って表示されます。これらのフィルターは、オリジナルのソースドキュメントの\\フィールドのみに適用されるため、一致するマルチフィールドはフィルタリングされません。", - "kbn.management.editIndexPattern.source.table.cancelAria": "キャンセル", - "kbn.management.editIndexPattern.source.table.deleteAria": "削除", - "kbn.management.editIndexPattern.source.table.editAria": "編集", - "kbn.management.editIndexPattern.source.table.filterDescription": "フィルター名", - "kbn.management.editIndexPattern.source.table.filterHeader": "フィルター", - "kbn.management.editIndexPattern.source.table.matchesDescription": "フィールドに使用されている言語です", - "kbn.management.editIndexPattern.source.table.matchesHeader": "一致", - "kbn.management.editIndexPattern.source.table.notMatchedLabel": "ソースフィルターが既知のフィールドと一致しません。", - "kbn.management.editIndexPattern.source.table.saveAria": "保存", - "kbn.management.editIndexPattern.sourceHeader": "ソースフィルター", - "kbn.management.editIndexPattern.sourceLabel": "ソースフィルターは、ドキュメントソースの取得時に 1 つまたは複数のフィールドを除外するのに使用される場合もあります。これはディスカバリアプリでのドキュメントの表示中、またはダッシュボードアプリの保存された検索の結果を表示する表で起こります。それぞれの行は 1 つのドキュメントのソースで作成されており、ドキュメントに大きなフィールドや重要ではないフィールドが含まれている場合、このレベルでフィルターで除外すると良いかもしれません。", - "kbn.management.editIndexPattern.sourcePlaceholder": "ソースフィルター、ワイルドカード使用可 (例: 「user」と入力して「user」で始まるフィールドをフィルタリング)", - "kbn.management.editIndexPattern.tabs.fieldsHeader": "フィールド", - "kbn.management.editIndexPattern.tabs.scriptedHeader": "スクリプトフィールド", - "kbn.management.editIndexPattern.tabs.sourceHeader": "ソースフィルター", - "kbn.management.editIndexPattern.timeFilterHeader": "時間フィルターフィールド名: {timeFieldName}", - "kbn.management.editIndexPattern.timeFilterLabel.mappingAPILink": "マッピング API", - "kbn.management.editIndexPattern.timeFilterLabel.timeFilterDetail": "このページは {indexPatternTitle} インデックス内のすべてのフィールドと、Elasticsearch に記録された各フィールドのコアタイプを一覧表示します。フィールドタイプを変更するには Elasticsearch を使用します", - "kbn.management.editIndexPatternLiveRegionAriaLabel": "インデックスパターン", - "kbn.management.indexPattern.confirmOverwriteButton": "上書き", - "kbn.management.indexPattern.confirmOverwriteLabel": "「{title}」に上書きしてよろしいですか?", - "kbn.management.indexPattern.confirmOverwriteTitle": "{type} を上書きしますか?", - "kbn.management.indexPattern.sectionsHeader": "インデックスパターン", - "kbn.management.indexPattern.titleExistsLabel": "「{title}」というタイトルのインデックスパターンが既に存在します。", - "kbn.management.indexPatternList.createButton.betaLabel": "ベータ", - "kbn.management.indexPatternPrompt.exampleOne": "チャートを作成したりコンテンツを素早くクエリできるように log-west-001 という名前の単一のデータソースをインデックスします。", - "kbn.management.indexPatternPrompt.exampleOneTitle": "単一のデータソース", - "kbn.management.indexPatternPrompt.examplesTitle": "インデックスパターンの例", - "kbn.management.indexPatternPrompt.exampleThree": "比較目的に履歴の動向を集約できるよう、これらのログのアーカイブされた月々のロールアップメトリックスを指定通りに別々のインデックスパターンにグループ分けします。", - "kbn.management.indexPatternPrompt.exampleThreeTitle": "カスタムグルーピング", - "kbn.management.indexPatternPrompt.exampleTwo": "すべての西海岸のサーバーログに対してクエリを実行できるように、頭に「log-west」の付いたすべての受信データソースをグループ化します。", - "kbn.management.indexPatternPrompt.exampleTwoTitle": "複数データソース", - "kbn.management.indexPatternPrompt.subtitle": "インデックスパターンは、Kibana で共有フィールドにクエリを実行できるよう、種類の異なるデータソースをバケットにまとめることができます。", - "kbn.management.indexPatternPrompt.title": "インデックスパターンについて", - "kbn.management.indexPatterns.badge.readOnly.text": "読み込み専用", - "kbn.management.indexPatterns.badge.readOnly.tooltip": "インデックスパターンを保存できません", - "kbn.management.indexPatterns.createBreadcrumb": "インデックスパターンを作成", - "kbn.management.indexPatterns.createFieldBreadcrumb": "フィールドを作成", - "kbn.management.indexPatterns.listBreadcrumb": "インデックスパターン", - "kbn.management.indexPatternTable.createBtn": "インデックスパターンの作成", - "kbn.management.indexPatternTable.title": "インデックスパターン", - "kbn.management.landing.subhead": "インデックス、インデックスパターン、保存されたオブジェクト、Kibana の設定、その他を管理します。", - "kbn.management.landing.text": "すべてのツールの一覧は、左のメニューにあります。", - "kbn.management.objects.confirmModalOptions.deleteButtonLabel": "削除", - "kbn.management.objects.confirmModalOptions.modalDescription": "削除されたオブジェクトは復元できません", - "kbn.management.objects.confirmModalOptions.modalTitle": "{title} を削除しますか?", - "kbn.management.objects.deleteSavedObjectsConfirmModalDescription": "この操作は次の保存されたオブジェクトを削除します:", - "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.cancelButtonLabel": "キャンセル", - "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.deleteButtonLabel": "削除", - "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.deleteProcessButtonLabel": "削除中…", - "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.idColumnName": "ID", - "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.titleColumnName": "タイトル", - "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.typeColumnName": "タイプ", - "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModalTitle": "保存されたオブジェクトの削除", - "kbn.management.objects.objectsTable.export.dangerNotification": "エクスポートを生成できません", - "kbn.management.objects.objectsTable.export.successNotification": "ファイルはバックグラウンドでダウンロード中です", - "kbn.management.objects.objectsTable.export.successWithMissingRefsNotification": "ファイルはバックグラウンドでダウンロード中です。一部の関連オブジェクトが見つかりませんでした。足りないオブジェクトの一覧は、エクスポートされたファイルの最後の行をご覧ください。", - "kbn.management.objects.objectsTable.exportObjectsConfirmModal.cancelButtonLabel": "キャンセル", - "kbn.management.objects.objectsTable.exportObjectsConfirmModal.exportAllButtonLabel": "すべてエクスポート:", - "kbn.management.objects.objectsTable.exportObjectsConfirmModal.exportOptionsLabel": "オプション", - "kbn.management.objects.objectsTable.exportObjectsConfirmModal.includeReferencesDeepLabel": "関連オブジェクトを含める", - "kbn.management.objects.objectsTable.exportObjectsConfirmModalDescription": "エクスポートするタイプを選択してください", - "kbn.management.objects.objectsTable.exportObjectsConfirmModalTitle": "{filteredItemCount, plural, one{# オブジェクト} other {# オブジェクト}}をエクスポート", - "kbn.management.objects.objectsTable.flyout.confirmLegacyImport.resolvingConflictsLoadingMessage": "矛盾を解決中…", - "kbn.management.objects.objectsTable.flyout.confirmLegacyImport.retryingFailedObjectsLoadingMessage": "失敗したオブジェクトを再試行中…", - "kbn.management.objects.objectsTable.flyout.confirmLegacyImport.savedSearchAreLinkedProperlyLoadingMessage": "保存された検索が正しくリンクされていることを確認してください…", - "kbn.management.objects.objectsTable.flyout.confirmLegacyImport.savingConflictsLoadingMessage": "矛盾を保存中…", - "kbn.management.objects.objectsTable.flyout.confirmOverwriteBody": "{title} を上書きしてよろしいですか?", - "kbn.management.objects.objectsTable.flyout.confirmOverwriteCancelButtonText": "キャンセル", - "kbn.management.objects.objectsTable.flyout.confirmOverwriteOverwriteButtonText": "上書き", - "kbn.management.objects.objectsTable.flyout.confirmOverwriteTitle": "{type} を上書きしますか?", - "kbn.management.objects.objectsTable.flyout.errorCalloutTitle": "申し訳ございませんが、エラーが発生しました", - "kbn.management.objects.objectsTable.flyout.import.cancelButtonLabel": "キャンセル", - "kbn.management.objects.objectsTable.flyout.import.confirmButtonLabel": "インポート", - "kbn.management.objects.objectsTable.flyout.importFailedDescription": "{totalImportCount} 個中 {failedImportCount} 個のオブジェクトのインポートに失敗しました。インポート失敗", - "kbn.management.objects.objectsTable.flyout.importFailedMissingReference": "{type} [id={id}] は {refType} [id={refId}] を見つけられませんでした", - "kbn.management.objects.objectsTable.flyout.importFailedTitle": "インポート失敗", - "kbn.management.objects.objectsTable.flyout.importFailedUnsupportedType": "{type} [id={id}] サポートされていないタイプ", - "kbn.management.objects.objectsTable.flyout.importFileErrorMessage": "ファイルを処理できませんでした。", - "kbn.management.objects.objectsTable.flyout.importLegacyFileErrorMessage": "ファイルを処理できませんでした。", - "kbn.management.objects.objectsTable.flyout.importPromptText": "インポート", - "kbn.management.objects.objectsTable.flyout.importSavedObjectTitle": "保存されたオブジェクトのインポート", - "kbn.management.objects.objectsTable.flyout.importSuccessful.confirmAllChangesButtonLabel": "すべての変更を確定", - "kbn.management.objects.objectsTable.flyout.importSuccessful.confirmButtonLabel": "完了", - "kbn.management.objects.objectsTable.flyout.importSuccessfulCallout.noObjectsImportedTitle": "オブジェクトがインポートされませんでした", - "kbn.management.objects.objectsTable.flyout.importSuccessfulDescription": "{importCount} 個のオブジェクトがインポートされました。", - "kbn.management.objects.objectsTable.flyout.importSuccessfulTitle": "インポート成功", - "kbn.management.objects.objectsTable.flyout.indexPatternConflictsCalloutLinkText": "新規インデックスパターンを作成", - "kbn.management.objects.objectsTable.flyout.indexPatternConflictsDescription": "次の保存されたオブジェクトは、存在しないインデックスパターンを使用しています。別のデックスパターンを選択してください。必要に応じて {indexPatternLink} できます。", - "kbn.management.objects.objectsTable.flyout.indexPatternConflictsTitle": "インデックスパターンの矛盾", - "kbn.management.objects.objectsTable.flyout.invalidFormatOfImportedFileErrorMessage": "保存されたオブジェクトのファイル形式が無効なため、インポートできません。", - "kbn.management.objects.objectsTable.flyout.legacyFileUsedBody": "最新のレポートで NDJSON ファイルを作成すれば完了です。", - "kbn.management.objects.objectsTable.flyout.legacyFileUsedTitle": "JSON ファイルのサポートが終了します", - "kbn.management.objects.objectsTable.flyout.overwriteSavedObjectsLabel": "すべての保存されたオブジェクトを自動的に上書きしますか?", - "kbn.management.objects.objectsTable.flyout.renderConflicts.columnCountDescription": "影響されるオブジェクトの数です", - "kbn.management.objects.objectsTable.flyout.renderConflicts.columnCountName": "カウント", - "kbn.management.objects.objectsTable.flyout.renderConflicts.columnIdDescription": "インデックスパターンの ID です", - "kbn.management.objects.objectsTable.flyout.renderConflicts.columnIdName": "ID", - "kbn.management.objects.objectsTable.flyout.renderConflicts.columnNewIndexPatternName": "新規インデックスパターン", - "kbn.management.objects.objectsTable.flyout.renderConflicts.columnSampleOfAffectedObjectsDescription": "影響されるオブジェクトのサンプルです", - "kbn.management.objects.objectsTable.flyout.renderConflicts.columnSampleOfAffectedObjectsName": "影響されるオブジェクトのサンプル", - "kbn.management.objects.objectsTable.flyout.resolveImportErrorsFileErrorMessage": "ファイルを処理できませんでした。", - "kbn.management.objects.objectsTable.flyout.selectFileToImportFormRowLabel": "インポートするファイルを選択してください", - "kbn.management.objects.objectsTable.header.exportButtonLabel": "{filteredCount, plural, one{# オブジェクト} other {# オブジェクト}}をエクスポート", - "kbn.management.objects.objectsTable.header.importButtonLabel": "インポート", - "kbn.management.objects.objectsTable.header.refreshButtonLabel": "更新", - "kbn.management.objects.objectsTable.header.savedObjectsTitle": "保存されたオブジェクト", - "kbn.management.objects.objectsTable.howToDeleteSavedObjectsDescription": "ここから保存された検索などの保存されたオブジェクトを削除できます。保存されたオブジェクトの生データを編集することもできます。通常、オブジェクトは関連アプリケーションでのみ編集され、こn画面で編集するよりもそちらのほうが賢明です。", - "kbn.management.objects.objectsTable.relationships.columnActions.inspectActionDescription": "この保存されたオブジェクトを確認してください", - "kbn.management.objects.objectsTable.relationships.columnActions.inspectActionName": "検査", - "kbn.management.objects.objectsTable.relationships.columnActionsName": "アクション", - "kbn.management.objects.objectsTable.relationships.columnRelationship.childAsValue": "子", - "kbn.management.objects.objectsTable.relationships.columnRelationship.parentAsValue": "ペアレント", - "kbn.management.objects.objectsTable.relationships.columnRelationshipName": "直接関係", - "kbn.management.objects.objectsTable.relationships.columnTitleDescription": "保存されたオブジェクトのタイトルです", - "kbn.management.objects.objectsTable.relationships.columnTitleName": "タイトル", - "kbn.management.objects.objectsTable.relationships.columnTypeDescription": "保存されたオブジェクトのタイプです", - "kbn.management.objects.objectsTable.relationships.columnTypeName": "タイプ", - "kbn.management.objects.objectsTable.relationships.relationshipsTitle": "{title} に関連する保存されたオブジェクトはこちらです。この {type} を削除すると、親オブジェクトに影響がありますが、子オブジェクトには影響はありません。", - "kbn.management.objects.objectsTable.relationships.renderErrorMessage": "エラー", - "kbn.management.objects.objectsTable.relationships.search.filters.relationship.childAsValue.view": "子", - "kbn.management.objects.objectsTable.relationships.search.filters.relationship.name": "直接関係", - "kbn.management.objects.objectsTable.relationships.search.filters.relationship.parentAsValue.view": "親", - "kbn.management.objects.objectsTable.relationships.search.filters.type.name": "タイプ", - "kbn.management.objects.objectsTable.searchBar.unableToParseQueryErrorMessage": "クエリをパースできません", - "kbn.management.objects.objectsTable.table.columnActions.inspectActionDescription": "この保存されたオブジェクトを確認してください", - "kbn.management.objects.objectsTable.table.columnActions.inspectActionName": "検査", - "kbn.management.objects.objectsTable.table.columnActions.viewRelationshipsActionDescription": "この保存されたオブジェクトと他の保存されたオブジェクトとの関係性を表示します", - "kbn.management.objects.objectsTable.table.columnActions.viewRelationshipsActionName": "関係性", - "kbn.management.objects.objectsTable.table.columnActionsName": "アクション", - "kbn.management.objects.objectsTable.table.columnTitleDescription": "保存されたオブジェクトのタイトルです", - "kbn.management.objects.objectsTable.table.columnTitleName": "タイトル", - "kbn.management.objects.objectsTable.table.columnTypeDescription": "保存されたオブジェクトのタイプです", - "kbn.management.objects.objectsTable.table.columnTypeName": "タイプ", - "kbn.management.objects.objectsTable.table.deleteButtonLabel": "削除", - "kbn.management.objects.objectsTable.table.deleteButtonTitle": "保存されたオブジェクトを削除できません", - "kbn.management.objects.objectsTable.table.exportButtonLabel": "エクスポート", - "kbn.management.objects.objectsTable.table.exportPopoverButtonLabel": "エクスポート", - "kbn.management.objects.objectsTable.table.typeFilterName": "タイプ", - "kbn.management.objects.objectsTable.unableFindSavedObjectsNotificationMessage": "保存されたオブジェクトが見つかりません", - "kbn.management.objects.parsingFieldErrorMessage": "{fieldName} をインデックスパターン {indexName} 用にパース中にエラーが発生しました: {errorMessage}", - "kbn.management.objects.savedObjectsSectionLabel": "保存されたオブジェクト", - "kbn.management.objects.view.cancelButtonAriaLabel": "キャンセル", - "kbn.management.objects.view.cancelButtonLabel": "キャンセル", - "kbn.management.objects.view.deleteItemButtonLabel": "{title} を削除", - "kbn.management.objects.view.editItemTitle": "{title} の編集", - "kbn.management.objects.view.fieldDoesNotExistErrorMessage": "このオブジェクトに関連付けられたフィールドは、現在このインデックスパターンに存在しません。", - "kbn.management.objects.view.howToFixErrorDescription": "このエラーの原因がわかる場合は修正してください。わからない場合は上の削除ボタンをクリックしてください。", - "kbn.management.objects.view.howToModifyObjectDescription": " オブジェクトの編集は上級ユーザー向けです。オブジェクトのプロパティが検証されておらず、無効なオブジェクトはエラー、データ損失、またはそれ以上の問題の原因となります。コードを熟知した人に指示されていない限り、この設定は変更しない方が無難です。", - "kbn.management.objects.view.howToModifyObjectTitle": "十分ご注意ください!", - "kbn.management.objects.view.indexPatternDoesNotExistErrorMessage": "このオブジェクトに関連付けられたインデックスパターンは現在存在しません。", - "kbn.management.objects.view.saveButtonAriaLabel": "{ title } オブジェクトを保存", - "kbn.management.objects.view.saveButtonLabel": "{ title } オブジェクトを保存", - "kbn.management.objects.view.savedObjectProblemErrorMessage": "この保存されたオブジェクトに問題があります", - "kbn.management.objects.view.savedSearchDoesNotExistErrorMessage": "このオブジェクトに関連付けられた保存された検索は現在存在しません。", - "kbn.management.objects.view.viewItemButtonLabel": "{title} を表示", - "kbn.management.objects.view.viewItemTitle": "{title} を表示", - "kbn.management.savedObjects.editBreadcrumb": "{savedObjectType} を編集", - "kbn.management.savedObjects.indexBreadcrumb": "保存されたオブジェクト", - "kbn.managementTitle": "管理", - "kbn.topNavMenu.openInspectorButtonLabel": "検査", - "kbn.topNavMenu.refreshButtonLabel": "更新", - "kbn.topNavMenu.saveVisualizationButtonLabel": "保存", - "kbn.topNavMenu.shareVisualizationButtonLabel": "共有", - "kbn.visualize.badge.readOnly.text": "読み込み専用", - "kbn.visualize.badge.readOnly.tooltip": "ビジュアライゼーションを保存できません", - "kbn.visualize.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage": "indexPattern または savedSearchId が必要です", - "kbn.visualize.editor.createBreadcrumb": "作成", - "kbn.visualize.experimentalVisInfoText": "このビジュアライゼーションは実験的なものです。", - "kbn.visualize.linkedToSearch.unlinkSuccessNotificationText": "保存された検索「{searchTitle}」からリンクが解除されました", - "kbn.visualize.listing.betaTitle": "ベータ", - "kbn.visualize.listing.betaTooltip": "このビジュアライゼーションはベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません", - "kbn.visualize.listing.breadcrumb": "可視化", - "kbn.visualize.listing.createNew.createButtonLabel": "新規ビジュアライゼーションを追加", - "kbn.visualize.listing.createNew.description": "データに基づき異なるビジュアライゼーションを作成できます。", - "kbn.visualize.listing.createNew.title": "最初のビジュアライゼーションの作成", - "kbn.visualize.listing.experimentalTitle": "実験的", - "kbn.visualize.listing.experimentalTooltip": "このビジュアライゼーションは今後のリリースで変更または削除される可能性があり、SLA のサポート対象になりません。", - "kbn.visualize.listing.noItemsMessage": "ビジュアライゼーションがないようです。", - "kbn.visualize.listing.table.entityName": "ビジュアライゼーション", - "kbn.visualize.listing.table.entityNamePlural": "ビジュアライゼーション", - "kbn.visualize.listing.table.listTitle": "ビジュアライゼーション", - "kbn.visualize.listing.table.titleColumnName": "タイトル", - "kbn.visualize.listing.table.typeColumnName": "タイプ", - "kbn.visualize.pageHeading": "{chartName} {chartType} ビジュアライゼーション", - "kbn.visualize.saveDialog.saveAndAddToDashboardButtonLabel": "保存してダッシュボードに追加", - "kbn.visualize.topNavMenu.openInspectorButtonAriaLabel": "ビジュアライゼーションのインスペクターを開く", - "kbn.visualize.topNavMenu.openInspectorDisabledButtonTooltip": "このビジュアライゼーションはインスペクターをサポートしていません。", - "kbn.visualize.topNavMenu.refreshButtonAriaLabel": "更新", - "kbn.visualize.topNavMenu.saveVisualization.failureNotificationText": "「{visTitle}」の保存中にエラーが発生しました", - "kbn.visualize.topNavMenu.saveVisualization.successNotificationText": "「{visTitle}」が保存されました", - "kbn.visualize.topNavMenu.saveVisualizationButtonAriaLabel": "ビジュアライゼーションを保存", - "kbn.visualize.topNavMenu.saveVisualizationDisabledButtonTooltip": "保存する前に変更を適用または破棄", - "kbn.visualize.topNavMenu.shareVisualizationButtonAriaLabel": "ビジュアライゼーションを共有", - "kbn.visualize.visualizeDescription": "ビジュアライゼーションを作成して Elasticsearch インデックスに保存されたデータを集約します。", - "kbn.visualize.visualizeListingBreadcrumbsTitle": "可視化", - "kbn.visualize.visualizeListingDeleteErrorTitle": "ビジュアライゼーションの削除中にエラーが発生", - "kbn.visualize.wizard.step1Breadcrumb": "作成", - "kbn.visualize.wizard.step2Breadcrumb": "作成", - "kbn.visualizeTitle": "可視化", - "advancedSettings.badge.readOnly.text": "読み込み専用", - "advancedSettings.badge.readOnly.tooltip": "高度な設定を保存できません", - "advancedSettings.advancedSettingsLabel": "高度な設定", - "advancedSettings.callOutCautionDescription": "これらの設定は非常に上級ユーザー向けなのでご注意ください。ここでの変更は Kibana の重要な部分に不具合を生じさせる可能性があります。これらの設定はドキュメントに記載されていなかったり、サポートされていなかったり、実験的であったりします。フィールドにデフォルトの値が設定されている場合、そのフィールドを未入力のままにするとデフォルトに戻り、他の構成により利用できない可能性があります。カスタム設定を削除すると、Kibana の構成から永久に削除されます。", - "advancedSettings.callOutCautionTitle": "注意:不具合が起こる可能性があります", - "advancedSettings.categoryNames.dashboardLabel": "ダッシュボード", - "advancedSettings.categoryNames.discoverLabel": "ディスカバリ", - "advancedSettings.categoryNames.generalLabel": "一般", - "advancedSettings.categoryNames.notificationsLabel": "通知", - "advancedSettings.categoryNames.reportingLabel": "レポート", - "advancedSettings.categoryNames.searchLabel": "検索", - "advancedSettings.categoryNames.siemLabel": "SIEM", - "advancedSettings.categoryNames.timelionLabel": "Timelion", - "advancedSettings.categoryNames.visualizationsLabel": "ビジュアライゼーション", - "advancedSettings.categorySearchLabel": "カテゴリー", - "advancedSettings.field.changeImageLinkAriaLabel": "{ariaName} を変更", - "advancedSettings.field.changeImageLinkText": "画像を変更", - "advancedSettings.field.codeEditorSyntaxErrorMessage": "無効な JSON 構文", - "advancedSettings.field.customSettingAriaLabel": "カスタム設定", - "advancedSettings.field.customSettingTooltip": "カスタム設定", - "advancedSettings.field.defaultValueText": "デフォルト: {value}", - "advancedSettings.field.defaultValueTypeJsonText": "デフォルト: {value}", - "advancedSettings.field.helpText": "この設定は Kibana サーバーにより上書きされ、変更することはできません。", - "advancedSettings.field.imageChangeErrorMessage": "画像を保存できませんでした", - "advancedSettings.field.imageTooLargeErrorMessage": "画像が大きすぎます。最大サイズは {maxSizeDescription} です", - "advancedSettings.field.offLabel": "オフ", - "advancedSettings.field.onLabel": "オン", - "advancedSettings.field.resetToDefaultLinkAriaLabel": "{ariaName} をデフォルトにリセット", - "advancedSettings.field.resetToDefaultLinkText": "デフォルトにリセット", - "advancedSettings.form.clearNoSearchResultText": "(検索結果を消去)", - "advancedSettings.form.clearSearchResultText": "(検索結果を消去)", - "advancedSettings.form.searchResultText": "検索用語により {settingsCount} 件の設定が非表示になっています {clearSearch}", - "advancedSettings.pageTitle": "設定", - "advancedSettings.searchBar.unableToParseQueryErrorMessage": "クエリをパースできません", - "advancedSettings.searchBarAriaLabel": "高度な設定を検索", - "advancedSettings.voiceAnnouncement.searchResultScreenReaderMessage": "{query} を検索しました。{sectionLenght, plural, one {# セクション} other {# セクション}}に{optionLenght, plural, one {# オプション} other { # オプション}}があります。", - "management.indexPatternHeader": "インデックスパターン", - "management.indexPatternLabel": "Elasticsearch からのデータの取得に役立つインデックスパターンを管理します。", - "savedObjectsManagement.objects.savedObjectsDescription": "保存された検索、ビジュアライゼーション、ダッシュボードのインポート、エクスポート、管理を行います", - "savedObjectsManagement.objects.savedObjectsTitle": "保存されたオブジェクト", - "kibana_legacy.bigUrlWarningNotificationMessage": "{advancedSettingsLink}で{storeInSessionStorageParam}オプションを有効にするか、オンスクリーンビジュアルを簡素化してください。", - "kibana_legacy.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高度な設定", - "kibana_legacy.bigUrlWarningNotificationTitle": "URLが大きく、Kibanaの動作が停止する可能性があります", - "kibana_legacy.notify.fatalError.errorStatusMessage": "错误 {errStatus} {errStatusText}:{errMessage}", - "kibana_legacy.notify.fatalError.unavailableServerErrorMessage": "HTTP 请求无法连接。请检查 Kibana 服务器是否正在运行以及您的浏览器是否具有有效的连接,或请联系您的系统管理员。", - "kibana_legacy.notify.toaster.errorMessage": "错误:{errorMessage}\n {errorStack}", - "kibana_legacy.notify.toaster.errorStatusMessage": "错误 {errStatus} {errStatusText}:{errMessage}", - "kibana_legacy.notify.toaster.unavailableServerErrorMessage": "HTTP 请求无法连接。请检查 Kibana 服务器是否正在运行以及您的浏览器是否具有有效的连接,或请联系您的系统管理员。", - "kibana_legacy.paginate.controls.pageSizeLabel": "ページサイズ", - "kibana_legacy.paginate.controls.scrollTopButtonLabel": "最上部に移動", - "kibana_legacy.paginate.size.allDropDownOptionLabel": "すべて", + "esUi.validation.string.invalidJSONError": "無効なJSON", + "expressions.defaultErrorRenderer.errorTitle": "ビジュアライゼーションエラー", + "expressions.functions.font.args.alignHelpText": "水平テキスト配置", + "expressions.functions.font.args.colorHelpText": "文字の色です。", + "expressions.functions.font.args.familyHelpText": "利用可能な{css}ウェブフォント文字列です", + "expressions.functions.font.args.italicHelpText": "テキストを斜体にしますか?", + "expressions.functions.font.args.lHeightHelpText": "ピクセル単位の行の高さです。", + "expressions.functions.font.args.sizeHelpText": "ピクセル単位のフォントサイズです。", + "expressions.functions.font.args.underlineHelpText": "テキストに下線を引きますか?", + "expressions.functions.font.args.weightHelpText": "フォントの重量です。例えば、{list}、または {end}です。", + "expressions.functions.font.invalidFontWeightErrorMessage": "無効なフォント太さ:'{weight}'", + "expressions.functions.font.invalidTextAlignmentErrorMessage": "無効なテキストアラインメント:'{align}'", + "expressions.functions.fontHelpText": "フォントスタイルを作成します。", + "expressions.functions.kibana_context.filters.help": "Kibana ジェネリックフィルターを指定します", + "expressions.functions.kibana_context.help": "Kibana グローバルコンテキストを更新します", + "expressions.functions.kibana_context.q.help": "自由形式の Kibana テキストクエリを指定します", + "expressions.functions.kibana_context.savedSearchId.help": "クエリとフィルターに使用する保存検索ID を指定します。", + "expressions.functions.kibana_context.timeRange.help": "Kibana 時間範囲フィルターを指定します", + "expressions.functions.kibana.help": "Kibana グローバルコンテキストを取得します", + "expressions.functions.var.help": "Kibana グローバルコンテキストを更新", + "expressions.functions.var.name.help": "変数の名前を指定", + "expressions.functions.varset.help": "Kibana グローバルコンテキストを更新", + "expressions.functions.varset.name.help": "変数の名前を指定", + "expressions.functions.varset.val.help": "変数の値を指定指定がない場合、インプットコンテキストが使用されます", + "expressions.types.number.fromStringConversionErrorMessage": "\"{string}\" ストリンクを数字に変換できません", "home.addData.apm.addApmButtonLabel": "APM を追加", - "home.addData.apm.nameDescription": "APM は、集約内から自動的に詳細なパフォーマンスメトリックやエラーを集めます。", + "home.addData.apm.nameDescription": "APM は、集約内から自動的に詳細なパフォーマンスメトリックやエラーを収集します。", "home.addData.apm.nameTitle": "APM", "home.addData.logging.addLogDataButtonLabel": "ログデータを追加", "home.addData.logging.nameDescription": "頻繁に使用するデータソースからログを投入し、構成済みのダッシュボードで簡単に可視化できます。", @@ -1546,20 +1004,22 @@ "home.addData.metrics.nameTitle": "メトリック", "home.addData.sampleDataLink": "データセットと Kibana ダッシュボードを読み込む", "home.addData.sampleDataTitle": "サンプルデータの追加", - "home.addData.siem.addSiemEventsButtonLabel": "セキュリティイベントを追加", + "home.addData.siem.addSiemEventsButtonLabel": "イベントを追加", "home.addData.siem.nameDescription": "即利用可能なビジュアライゼーションで、セキュリティイベントをまとめてインタラクティブな調査を可能にします。", "home.addData.siem.nameTitle": "SIEM", + "home.addData.title.observability": "オブザーバビリティ", + "home.addData.title.security": "セキュリティ", "home.addData.uploadFileLink": "CSV、NDJSON、またはログファイルをインポート", "home.addData.uploadFileTitle": "ログファイルからデータをアップロード", "home.addData.yourDataLink": "Elasticsearch インデックスに接続", "home.addData.yourDataTitle": "Elasticsearch データの使用", - "home.breadcrumbs.addDataTitle": "データの投入", + "home.breadcrumbs.addDataTitle": "データの追加", "home.breadcrumbs.homeTitle": "ホーム", - "home.dataManagementDisableCollection": "収集を停止するには、", + "home.dataManagementDisableCollection": " 収集を停止するには、] ", "home.dataManagementDisableCollectionLink": "ここで使用状況データを無効にします。", "home.dataManagementDisclaimerPrivacy": "使用状況データがどのように製品とサービスの管理と改善につながるのかに関する詳細については ", "home.dataManagementDisclaimerPrivacyLink": "プライバシーポリシーをご覧ください。", - "home.dataManagementEnableCollection": " 収集を開始するには、", + "home.dataManagementEnableCollection": " 収集を開始するには、 ", "home.dataManagementEnableCollectionLink": "ここで使用状況データを有効にします。", "home.directories.manage.nameTitle": "Elastic Stack の管理", "home.directories.notFound.description": "お探しのものが見つかりませんでしたか?", @@ -1577,18 +1037,71 @@ "home.loadTutorials.requestFailedErrorMessage": "リクエスト失敗、ステータスコード: {status}", "home.loadTutorials.unableToLoadErrorMessage": "チュートリアルが読み込めません。", "home.recentlyAccessed.recentlyViewedTitle": "最近閲覧", + "home.sampleData.ecommerceSpec.averageSalesPerRegionTitle": "[e コマース] 地域ごとの平均売上", + "home.sampleData.ecommerceSpec.averageSalesPriceTitle": "[e コマース] 平均販売価格", + "home.sampleData.ecommerceSpec.averageSoldQuantityTitle": "[e コマース] 平均販売数", + "home.sampleData.ecommerceSpec.controlsTitle": "[e コマース] コントロール", + "home.sampleData.ecommerceSpec.markdownTitle": "[e コマース] マークダウン", + "home.sampleData.ecommerceSpec.ordersTitle": "[e コマース] 注文", + "home.sampleData.ecommerceSpec.promotionTrackingTitle": "[e コマース] プロモーショントラッキング", + "home.sampleData.ecommerceSpec.revenueDashboardDescription": "サンプルの e コマースの注文と収益を分析します", + "home.sampleData.ecommerceSpec.revenueDashboardTitle": "[e コマース] 収益ダッシュボード", + "home.sampleData.ecommerceSpec.salesByCategoryTitle": "[e コマース] カテゴリーごとの売上", + "home.sampleData.ecommerceSpec.salesByGenderTitle": "[e コマース] 性別ごとの売上", + "home.sampleData.ecommerceSpec.soldProductsPerDayTitle": "[e コマース] 1 日の販売製品", + "home.sampleData.ecommerceSpec.topSellingProductsTitle": "[e コマース] トップセラー製品", + "home.sampleData.ecommerceSpec.totalRevenueTitle": "[e コマース] 合計収益", + "home.sampleData.ecommerceSpecDescription": "e コマースの注文をトラッキングするサンプルデータ、ビジュアライゼーション、ダッシュボードです。", + "home.sampleData.ecommerceSpecTitle": "サンプル e コマース注文", + "home.sampleData.flightsSpec.airlineCarrierTitle": "[フライト] 航空会社", + "home.sampleData.flightsSpec.airportConnectionsTitle": "[フライト] 空港乗り継ぎ (空港にカーソルを合わせてください)", + "home.sampleData.flightsSpec.averageTicketPriceTitle": "[フライト] 平均運賃", + "home.sampleData.flightsSpec.controlsTitle": "[フライト] コントロール", + "home.sampleData.flightsSpec.delayBucketsTitle": "[フライト] 遅延バケット", + "home.sampleData.flightsSpec.delaysAndCancellationsTitle": "[フライト] 遅延・欠航", + "home.sampleData.flightsSpec.delayTypeTitle": "[フライト] 遅延タイプ", + "home.sampleData.flightsSpec.destinationWeatherTitle": "[フライト] 目的地の天候", + "home.sampleData.flightsSpec.flightCancellationsTitle": "[フライト] フライト欠航", + "home.sampleData.flightsSpec.flightCountAndAverageTicketPriceTitle": "[フライト] カウントと平均運賃", + "home.sampleData.flightsSpec.flightDelaysTitle": "[フライト] フライトの遅延", + "home.sampleData.flightsSpec.flightLogTitle": "[フライト] 飛行記録", + "home.sampleData.flightsSpec.globalFlightDashboardDescription": "ES-Air、Logstash Airways、Kibana Airlines、JetBeats のサンプル飛行データを分析します", + "home.sampleData.flightsSpec.globalFlightDashboardTitle": "[フライト] グローバルフライトダッシュボード", + "home.sampleData.flightsSpec.markdownInstructionsTitle": "[フライト] マークダウンの指示", + "home.sampleData.flightsSpec.originCountryTicketPricesTitle": "[フライト] 出発国の運賃", + "home.sampleData.flightsSpec.originCountryTitle": "[Flights] 出発国と到着国の比較", + "home.sampleData.flightsSpec.totalFlightCancellationsTitle": "[フライト] フライト欠航合計", + "home.sampleData.flightsSpec.totalFlightDelaysTitle": "[フライト] フライト遅延合計", + "home.sampleData.flightsSpec.totalFlightsTitle": "[フライト] フライト合計", + "home.sampleData.flightsSpecDescription": "飛行ルートを監視するサンプルデータ、ビジュアライゼーション、ダッシュボードです。", + "home.sampleData.flightsSpecTitle": "サンプル飛行データ", + "home.sampleData.logsSpec.fileTypeScatterPlotTitle": "[ログ] ファイルタイプ散布図", + "home.sampleData.logsSpec.goalsTitle": "[ログ] 目標", + "home.sampleData.logsSpec.heatmapTitle": "[ログ] ヒートマップ", + "home.sampleData.logsSpec.hostVisitsBytesTableTitle": "[ログ] ホスト、訪問数、バイト表", + "home.sampleData.logsSpec.inputControlsTitle": "[ログ] インプットコントロール", + "home.sampleData.logsSpec.markdownInstructionsTitle": "[ログ] マークダウンの指示", + "home.sampleData.logsSpec.responseCodesOverTimeTitle": "[ログ] 一定期間の応答コードと注釈", + "home.sampleData.logsSpec.sourceAndDestinationSankeyChartTitle": "[ログ] ソースと行先のサンキーダイアグラム", + "home.sampleData.logsSpec.uniqueVisitorsByCountryTitle": "[ログ] 国ごとのユニークビジター", + "home.sampleData.logsSpec.uniqueVisitorsTitle": "[ログ] ユニークビジターと平均バイトの比較", + "home.sampleData.logsSpec.visitorOSTitle": "[ログ] OS 別のビジター", + "home.sampleData.logsSpec.webTrafficDescription": "Elastic Web サイトのサンプル Webトラフィックログデータを分析します", + "home.sampleData.logsSpec.webTrafficTitle": "[ログ] Web トラフィック", + "home.sampleData.logsSpecDescription": "Web ログを監視するサンプルデータ、ビジュアライゼーション、ダッシュボードです。", + "home.sampleData.logsSpecTitle": "サンプル Web ログ", "home.sampleDataSet.installedLabel": "{name} がインストールされました", "home.sampleDataSet.unableToInstallErrorMessage": "サンプルデータセット「{name}」をインストールできません", "home.sampleDataSet.unableToLoadListErrorMessage": "サンプルデータセットのリストを読み込めません", "home.sampleDataSet.unableToUninstallErrorMessage": "サンプルデータセット「{name}」をアンインストールできません", "home.sampleDataSet.uninstalledLabel": "{name} がアンインストールされました", "home.sampleDataSetCard.addButtonAriaLabel": "{datasetName} を追加", - "home.sampleDataSetCard.addButtonLabel": "データの投入", + "home.sampleDataSetCard.addButtonLabel": "データの追加", "home.sampleDataSetCard.addingButtonAriaLabel": "{datasetName} を追加中", "home.sampleDataSetCard.addingButtonLabel": "追加中", "home.sampleDataSetCard.dashboardLinkLabel": "ダッシュボード", "home.sampleDataSetCard.default.addButtonAriaLabel": "{datasetName} を追加", - "home.sampleDataSetCard.default.addButtonLabel": "データの投入", + "home.sampleDataSetCard.default.addButtonLabel": "データの追加", "home.sampleDataSetCard.default.unableToVerifyErrorMessage": "データセットステータスを確認できません、エラー: {statusMsg}", "home.sampleDataSetCard.removeButtonAriaLabel": "{datasetName} を削除", "home.sampleDataSetCard.removeButtonLabel": "削除", @@ -1597,7 +1110,7 @@ "home.sampleDataSetCard.viewDataButtonAriaLabel": "{datasetName} を表示", "home.sampleDataSetCard.viewDataButtonLabel": "データを表示", "home.tryButtonLabel": "サンプルデータを試す", - "home.tutorial.addDataToKibanaTitle": "Kibana にデータを追加", + "home.tutorial.addDataToKibanaTitle": "データの追加", "home.tutorial.card.sampleDataDescription": "これらの「ワンクリック」データセットで Kibana の探索を始めましょう。", "home.tutorial.card.sampleDataTitle": "サンプルデータ", "home.tutorial.elasticCloudButtonLabel": "Elastic Cloud", @@ -1609,18 +1122,18 @@ "home.tutorial.instructionSet.successLabel": "成功", "home.tutorial.instructionSet.toggleAriaLabel": "コマンドパラメーターの可視性を調整します", "home.tutorial.introduction.betaLabel": "ベータ", - "home.tutorial.introduction.imageAltDescription": "プライマリダッシュボードのスクリーンショット", + "home.tutorial.introduction.imageAltDescription": "プライマリダッシュボードのスクリーンショット。", "home.tutorial.introduction.viewButtonLabel": "エクスポートされたフィールドを表示", "home.tutorial.noTutorialLabel": "チュートリアル {tutorialId} が見つかりません", "home.tutorial.savedObject.addedLabel": "{savedObjectsLength} 件の保存されたオブジェクトが追加されました", "home.tutorial.savedObject.confirmButtonLabel": "上書きを確定", "home.tutorial.savedObject.defaultButtonLabel": "Kibana オブジェクトを読み込む", "home.tutorial.savedObject.installLabel": "インデックスパターン、ビジュアライゼーション、事前定義済みのダッシュボードをインポートします。", - "home.tutorial.savedObject.installStatusLabel": "{savedObjectsLength} 件中 {overwriteErrorsLength} 件のオブジェクトが既に存在します。インポートして既存のオブジェクトを上書きするには、「上書きを確定」をクリックしてください。オブジェクトへの変更はすべて失われます。", + "home.tutorial.savedObject.installStatusLabel": "{savedObjectsLength} オブジェクトの {overwriteErrorsLength} が既に存在しますインポートして既存のオブジェクトを上書きするには、「上書きを確定」をクリックしてください。オブジェクトへの変更はすべて失われます。", "home.tutorial.savedObject.loadTitle": "Kibana オブジェクトを読み込む", "home.tutorial.savedObject.requestFailedErrorMessage": "リクエスト失敗、エラー: {message}", "home.tutorial.savedObject.unableToAddErrorMessage": "{savedObjectsLength} 件中 {errorsLength} 件の kibana オブジェクトが追加できません、エラー: {errorMessage}", - "home.tutorial.selfManagedButtonLabel": "セルフマネージド", + "home.tutorial.selfManagedButtonLabel": "自己管理", "home.tutorial.tabs.allTitle": "すべて", "home.tutorial.tabs.loggingTitle": "ログ", "home.tutorial.tabs.metricsTitle": "メトリック", @@ -1628,33 +1141,84 @@ "home.tutorial.tabs.siemTitle": "SIEM", "home.tutorial.unexpectedStatusCheckStateErrorDescription": "予期せぬステータス確認ステータス {statusCheckState}", "home.tutorial.unhandledInstructionTypeErrorDescription": "予期せぬ指示タイプ {visibleInstructions}", + "home.tutorials.activemqLogs.artifacts.dashboards.linkLabel": "ActiveMQ アプリケーションイベント", + "home.tutorials.activemqLogs.longDescription": "Filebeat で ActiveMQ ログを収集します。[詳細]({learnMoreLink})", + "home.tutorials.activemqLogs.nameTitle": "ActiveMQ ログ", + "home.tutorials.activemqLogs.shortDescription": "Filebeat で ActiveMQ ログを収集します。", + "home.tutorials.activemqMetrics.artifacts.application.label": "発見", + "home.tutorials.activemqMetrics.longDescription": "Metricbeat モジュール「activemq」は、ActiveMQ インスタンスから監視メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.activemqMetrics.nameTitle": "ActiveMQ メトリック", + "home.tutorials.activemqMetrics.shortDescription": "ActiveMQ インスタンスから監視メトリックを取得します。", + "home.tutorials.aerospikeMetrics.artifacts.application.label": "発見", + "home.tutorials.aerospikeMetrics.longDescription": "Metricbeat モジュール「aerospike」は、Aerospike から内部メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.aerospikeMetrics.nameTitle": "Aerospike メトリック", + "home.tutorials.aerospikeMetrics.shortDescription": "Aerospike サーバーから内部メトリックを取得します。", + "home.tutorials.apacheLogs.artifacts.dashboards.linkLabel": "Apache ログダッシュボード", + "home.tutorials.apacheLogs.longDescription": "apache Filebeat モジュールが、Apache 2 HTTP サーバーにより作成されたアクセスとエラーのログをパースします。[詳細]({learnMoreLink})。", + "home.tutorials.apacheLogs.nameTitle": "Apache ログ", + "home.tutorials.apacheLogs.shortDescription": "Apache HTTP サーバーが作成したアクセスとエラーのログを収集しパースします。", + "home.tutorials.apacheMetrics.artifacts.dashboards.linkLabel": "Apache メトリックダッシュボード", + "home.tutorials.apacheMetrics.longDescription": "Metricbeat モジュール「apache」は、Apache 2 HTTP サーバーから内部メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.apacheMetrics.nameTitle": "Apache メトリック", + "home.tutorials.apacheMetrics.shortDescription": "Apache 2 HTTP サーバーから内部メトリックを取得します。", + "home.tutorials.auditbeat.artifacts.dashboards.linkLabel": "SIEM アプリ", + "home.tutorials.auditbeat.longDescription": "Auditbeat を使用してホストから監査データを収集します。これらにはプロセス、ユーザー、ログイン、ソケット情報、ファイルアクセス、その他が含まれます。[詳細]({learnMoreLink})。", + "home.tutorials.auditbeat.nameTitle": "Auditbeat", + "home.tutorials.auditbeat.shortDescription": "ホストから監査データを収集します。", + "home.tutorials.awsLogs.artifacts.dashboards.linkLabel": "AWS S3 サーバーアクセスログダッシュボード", + "home.tutorials.awsLogs.longDescription": "SQS 通知設定されている S3 バケットに AWS ログをエクスポートすることで、AWS ログを収集します。[詳細]({learnMoreLink})。", + "home.tutorials.awsLogs.nameTitle": "AWS S3 ベースのログ", + "home.tutorials.awsLogs.shortDescription": "Filebeat で S3 バケットから AWS ログを収集します。", + "home.tutorials.awsMetrics.artifacts.dashboards.linkLabel": "AWS メトリックダッシュボード", + "home.tutorials.awsMetrics.longDescription": "Metricbeat モジュール「aws」は、AWS API と Cloudwatch から監視メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.awsMetrics.nameTitle": "AWS メトリック", + "home.tutorials.awsMetrics.shortDescription": "AWS API と Cloudwatch からの EC2 インスタンスの監視メトリックです。", + "home.tutorials.azureMetrics.artifacts.application.label": "発見", + "home.tutorials.azureMetrics.longDescription": "Metricbeat モジュール「azure」は、Azure から監視メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.azureMetrics.nameTitle": "Azure メトリック", + "home.tutorials.azureMetrics.shortDescription": "Azure 監視メトリックをフェッチします。", + "home.tutorials.cephMetrics.artifacts.application.label": "発見", + "home.tutorials.cephMetrics.longDescription": "Metricbeat モジュール「ceph」は、Ceph から内部メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.cephMetrics.nameTitle": "Ceph メトリック", + "home.tutorials.cephMetrics.shortDescription": "Ceph サーバーから内部メトリックを取得します。", + "home.tutorials.ciscoLogs.artifacts.dashboards.linkLabel": "SIEM アプリ", + "home.tutorials.ciscoLogs.longDescription": "これは Cisco ネットワークデバイスのログ用のモジュールです。現在、syslog 経由またはファイルから読み込まれた Cisco ASA ファイアウォールログの「asa」ファイルセットをサポートしています。[詳細]({learnMoreLink})。", + "home.tutorials.ciscoLogs.nameTitle": "Cisco", + "home.tutorials.ciscoLogs.shortDescription": "Cisco ASA ファイアウォールからのログを収集・解析します。", + "home.tutorials.cloudwatchLogs.longDescription": "Functionbeat を AWS Lambda 関数として実行するようデプロイし、Cloudwatch ログを収集します。[詳細({learnMoreLink})。", + "home.tutorials.cloudwatchLogs.nameTitle": "AWS Cloudwatch ログ", + "home.tutorials.cloudwatchLogs.shortDescription": "Functionbeat で Cloudwatch ログを収集します。", + "home.tutorials.cockroachdbMetrics.artifacts.dashboards.linkLabel": "CockroachDB メトリックダッシュボード", + "home.tutorials.cockroachdbMetrics.longDescription": "Metricbeat モジュール「cockroachdb」は、CockroachDB から監視メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.cockroachdbMetrics.nameTitle": "CockroachDB メトリック", + "home.tutorials.cockroachdbMetrics.shortDescription": "CockroachDB サーバーから監視メトリックを取得します。", "home.tutorials.common.auditbeat.cloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.auditbeat.premCloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.auditbeat.premInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.auditbeatCloudInstructions.config.debTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.auditbeatCloudInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.auditbeatCloudInstructions.config.debTitle": "構成の変更", + "home.tutorials.common.auditbeatCloudInstructions.config.debTitle": "構成を編集する", "home.tutorials.common.auditbeatCloudInstructions.config.osxTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.auditbeatCloudInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.auditbeatCloudInstructions.config.osxTitle": "構成の変更", + "home.tutorials.common.auditbeatCloudInstructions.config.osxTitle": "構成を編集する", "home.tutorials.common.auditbeatCloudInstructions.config.rpmTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.auditbeatCloudInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.auditbeatCloudInstructions.config.rpmTitle": "構成の変更", + "home.tutorials.common.auditbeatCloudInstructions.config.rpmTitle": "構成を編集する", "home.tutorials.common.auditbeatCloudInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.auditbeatCloudInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.auditbeatCloudInstructions.config.windowsTitle": "構成の変更", + "home.tutorials.common.auditbeatCloudInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.auditbeatInstructions.config.debTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.auditbeatInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.auditbeatInstructions.config.debTitle": "構成の変更", + "home.tutorials.common.auditbeatInstructions.config.debTitle": "構成を編集する", "home.tutorials.common.auditbeatInstructions.config.osxTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.auditbeatInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.auditbeatInstructions.config.osxTitle": "構成の変更", + "home.tutorials.common.auditbeatInstructions.config.osxTitle": "構成を編集する", "home.tutorials.common.auditbeatInstructions.config.rpmTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.auditbeatInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.auditbeatInstructions.config.rpmTitle": "構成の変更", + "home.tutorials.common.auditbeatInstructions.config.rpmTitle": "構成を編集する", "home.tutorials.common.auditbeatInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.auditbeatInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.auditbeatInstructions.config.windowsTitle": "構成の変更", + "home.tutorials.common.auditbeatInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.auditbeatInstructions.install.debTextPost": "32 ビットパッケージをお探しですか?[ダウンロードページ]({linkUrl}) をご覧ください。", "home.tutorials.common.auditbeatInstructions.install.debTextPre": "Auditbeat は初めてですか?[入門ガイド]({linkUrl}) をご覧ください。", "home.tutorials.common.auditbeatInstructions.install.debTitle": "Auditbeat のダウンロードとインストール", @@ -1684,16 +1248,16 @@ "home.tutorials.common.filebeat.premInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.filebeatCloudInstructions.config.debTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.filebeatCloudInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.filebeatCloudInstructions.config.debTitle": "構成の変更", + "home.tutorials.common.filebeatCloudInstructions.config.debTitle": "構成を編集する", "home.tutorials.common.filebeatCloudInstructions.config.osxTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.filebeatCloudInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.filebeatCloudInstructions.config.osxTitle": "構成の変更", + "home.tutorials.common.filebeatCloudInstructions.config.osxTitle": "構成を編集する", "home.tutorials.common.filebeatCloudInstructions.config.rpmTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.filebeatCloudInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.filebeatCloudInstructions.config.rpmTitle": "構成の変更", + "home.tutorials.common.filebeatCloudInstructions.config.rpmTitle": "構成を編集する", "home.tutorials.common.filebeatCloudInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.filebeatCloudInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.filebeatCloudInstructions.config.windowsTitle": "構成の変更", + "home.tutorials.common.filebeatCloudInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.filebeatEnableInstructions.debTextPost": "「/etc/filebeat/modules.d/{moduleName}.yml」ファイルで設定を変更します。", "home.tutorials.common.filebeatEnableInstructions.debTitle": "{moduleName} モジュールを有効にし構成します", "home.tutorials.common.filebeatEnableInstructions.osxTextPost": "「modules.d/{moduleName}.yml」」ファイルで設定を変更します。", @@ -1706,16 +1270,16 @@ "home.tutorials.common.filebeatEnableInstructions.windowsTitle": "{moduleName} モジュールを有効にし構成します", "home.tutorials.common.filebeatInstructions.config.debTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.filebeatInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.filebeatInstructions.config.debTitle": "構成の変更", + "home.tutorials.common.filebeatInstructions.config.debTitle": "構成を編集する", "home.tutorials.common.filebeatInstructions.config.osxTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.filebeatInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.filebeatInstructions.config.osxTitle": "構成の変更", + "home.tutorials.common.filebeatInstructions.config.osxTitle": "構成を編集する", "home.tutorials.common.filebeatInstructions.config.rpmTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.filebeatInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.filebeatInstructions.config.rpmTitle": "構成の変更", + "home.tutorials.common.filebeatInstructions.config.rpmTitle": "構成を編集する", "home.tutorials.common.filebeatInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.filebeatInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.filebeatInstructions.config.windowsTitle": "構成の変更", + "home.tutorials.common.filebeatInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.filebeatInstructions.install.debTextPost": "32 ビットパッケージをお探しですか?[ダウンロードページ]({linkUrl}) をご覧ください。", "home.tutorials.common.filebeatInstructions.install.debTextPre": "Filebeat は初めてですか?[入門ガイド]({linkUrl}) をご覧ください。", "home.tutorials.common.filebeatInstructions.install.debTitle": "Filebeat のダウンロードとインストール", @@ -1725,16 +1289,16 @@ "home.tutorials.common.filebeatInstructions.install.rpmTextPre": "Filebeat は初めてですか?[入門ガイド]({linkUrl}) をご覧ください。", "home.tutorials.common.filebeatInstructions.install.rpmTitle": "Filebeat のダウンロードとインストール", "home.tutorials.common.filebeatInstructions.install.windowsTextPost": "{filebeatPath} ファイルの {propertyName} を Elasticsearch のインストールに設定します。", - "home.tutorials.common.filebeatInstructions.install.windowsTextPre": "Filebeat は初めてですか?[入門ガイド]({guideLinkUrl}) をご覧ください。\n 1.[ダウンロード]({filebeatLinkUrl}) ページから Filebeat Windows zip ファイルをダウンロードします。\n 2.zip ファイルのコンテンツを {folderPath} に解凍します。\n 3.「{directoryName}」ディレクトリの名前を「Filebeat」に変更します。\n 4.管理者として PowerShell プロンプトを開きます (PowerShell アイコンを右クリックして「管理者として実行」を選択します)。Windows XP をご使用の場合、PowerShell のダウンロードとインストールが必要な場合があります。\n 5.PowerShell プロンプトで次のコマンドを実行し、Filebeat を Windows サービスとしてインストールします。", + "home.tutorials.common.filebeatInstructions.install.windowsTextPre": "Filebeat は初めてですか?[入門ガイド]({guideLinkUrl}) をご覧ください。\n 1.[ダウンロード]({filebeatLinkUrl}) ページから Auditbeat Windows zip ファイルをダウンロードします。\n 2.zip ファイルのコンテンツを {folderPath} に解凍します。\n 3.「{directoryName}」ディレクトリの名前を「Filebeat」に変更します。\n 4.管理者として PowerShell プロンプトを開きます (PowerShell アイコンを右クリックして「管理者として実行」を選択します)。Windows XP をご使用の場合、PowerShell のダウンロードとインストールが必要な場合があります。\n 5.PowerShell プロンプトで次のコマンドを実行し、Filebeat を Windows サービスとしてインストールします。", "home.tutorials.common.filebeatInstructions.install.windowsTitle": "Filebeat のダウンロードとインストール", "home.tutorials.common.filebeatInstructions.start.debTextPre": "「setup」コマンドで Kibana のダッシュボードを読み込みます。ダッシュボードが既にセットアップされている場合、このコマンドは省略します。", "home.tutorials.common.filebeatInstructions.start.debTitle": "Filebeat を起動します", "home.tutorials.common.filebeatInstructions.start.osxTextPre": "「setup」コマンドで Kibana のダッシュボードを読み込みます。ダッシュボードが既にセットアップされている場合、このコマンドは省略します。", - "home.tutorials.common.filebeatInstructions.start.osxTitle": "Filebeat を起動します", + "home.tutorials.common.filebeatInstructions.start.osxTitle": "Filebeat を起動", "home.tutorials.common.filebeatInstructions.start.rpmTextPre": "「setup」コマンドで Kibana のダッシュボードを読み込みます。ダッシュボードが既にセットアップされている場合、このコマンドは省略します。", - "home.tutorials.common.filebeatInstructions.start.rpmTitle": "Filebeat を起動します", + "home.tutorials.common.filebeatInstructions.start.rpmTitle": "Filebeat を起動", "home.tutorials.common.filebeatInstructions.start.windowsTextPre": "「setup」コマンドで Kibana のダッシュボードを読み込みます。ダッシュボードが既にセットアップされている場合、このコマンドは省略します。", - "home.tutorials.common.filebeatInstructions.start.windowsTitle": "Filebeat を起動します", + "home.tutorials.common.filebeatInstructions.start.windowsTitle": "Filebeat を起動", "home.tutorials.common.filebeatStatusCheck.buttonLabel": "データを確認してください", "home.tutorials.common.filebeatStatusCheck.errorText": "モジュールからまだデータを受け取っていません", "home.tutorials.common.filebeatStatusCheck.successText": "このモジュールからデータを受け取りました", @@ -1743,15 +1307,15 @@ "home.tutorials.common.functionbeat.cloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.functionbeat.premCloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.functionbeat.premInstructions.gettingStarted.title": "はじめに", - "home.tutorials.common.functionbeatAWSInstructions.textPost": "「」と「」がアカウント 認証情報で、「us-east-1」が希望の地域です。", + "home.tutorials.common.functionbeatAWSInstructions.textPost": "「」と「」がアカウント資格情報、「us-east-1」がご希望の地域です。", "home.tutorials.common.functionbeatAWSInstructions.textPre": "環境で AWS アカウント認証情報を設定します。", "home.tutorials.common.functionbeatAWSInstructions.title": "AWS 認証情報の設定", "home.tutorials.common.functionbeatCloudInstructions.config.osxTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.functionbeatCloudInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.functionbeatCloudInstructions.config.osxTitle": "構成の変更", + "home.tutorials.common.functionbeatCloudInstructions.config.osxTitle": "構成を編集する", "home.tutorials.common.functionbeatCloudInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.functionbeatCloudInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.functionbeatCloudInstructions.config.windowsTitle": "構成の変更", + "home.tutorials.common.functionbeatCloudInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.functionbeatEnableOnPremInstructions.defaultTextPost": "「」が投入するロググループの名前で、「」が Functionbeat デプロイのステージングに使用されるが有効な S3 バケット名です。", "home.tutorials.common.functionbeatEnableOnPremInstructions.defaultTitle": "Cloudwatch ロググループの構成", "home.tutorials.common.functionbeatEnableOnPremInstructionsOSXLinux.textPre": "「functionbeat.yml」ファイルで設定を変更します。", @@ -1761,7 +1325,7 @@ "home.tutorials.common.functionbeatInstructions.config.osxTitle": "Elastic クラスターの構成", "home.tutorials.common.functionbeatInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.functionbeatInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.functionbeatInstructions.config.windowsTitle": "構成の変更", + "home.tutorials.common.functionbeatInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.functionbeatInstructions.deploy.osxTextPre": "これにより Functionbeat が Lambda 関数としてインストールされます「setup」コマンドで Elasticsearch の構成を確認し、Kibana インデックスパターンを読み込みます。通常このコマンドを省いても大丈夫です。", "home.tutorials.common.functionbeatInstructions.deploy.osxTitle": "Functionbeat を AWS Lambda にデプロイ", "home.tutorials.common.functionbeatInstructions.deploy.windowsTextPre": "これにより Functionbeat が Lambda 関数としてインストールされます「setup」コマンドで Elasticsearch の構成を確認し、Kibana インデックスパターンを読み込みます。通常このコマンドを省いても大丈夫です。", @@ -1770,7 +1334,7 @@ "home.tutorials.common.functionbeatInstructions.install.linuxTitle": "Functionbeat のダウンロードとインストール", "home.tutorials.common.functionbeatInstructions.install.osxTextPre": "Functionbeat は初めてですか?[入門ガイド]({link}) をご覧ください。", "home.tutorials.common.functionbeatInstructions.install.osxTitle": "Functionbeat のダウンロードとインストール", - "home.tutorials.common.functionbeatInstructions.install.windowsTextPre": "Functionbeat は初めてですか?[入門ガイド]({functionbeatLink}) をご覧ください。\n 1.[ダウンロード]({elasticLink}) ページから Functionbeat Windows zip ファイルをダウンロードします。\n 2.zip ファイルのコンテンツを {folderPath} に解凍します。\n 3.{directoryName} ディレクトリの名前を「Functionbeat」に変更します。\n 4.管理者として PowerShell プロンプトを開きます (PowerShell アイコンを右クリックして「管理者として実行」を選択します)。Windows XP をご使用の場合、PowerShell のダウンロードとインストールが必要な場合があります。\n 5.PowerShell プロンプトから、Functionbeat ディレクトリに移動します:", + "home.tutorials.common.functionbeatInstructions.install.windowsTextPre": "Functionbeat は初めてですか?[入門ガイド]({functionbeatLink}) をご覧ください。\n 1.[ダウンロード]({elasticLink}) ページから Functionbeat Windows zip ファイルをダウンロードします。\n 2.zip ファイルのコンテンツを {folderPath} に解凍します。\n 3.「{directoryName} ディレクトリの名前を「Functionbeat」に変更します。\n 4.管理者として PowerShell プロンプトを開きます (PowerShell アイコンを右クリックして「管理者として実行」を選択します)。Windows XP をご使用の場合、PowerShell のダウンロードとインストールが必要な場合があります。\n 5.PowerShell プロンプトから、Functionbeat ディレクトリに移動します:", "home.tutorials.common.functionbeatInstructions.install.windowsTitle": "Functionbeat のダウンロードとインストール", "home.tutorials.common.functionbeatStatusCheck.buttonLabel": "データを確認してください", "home.tutorials.common.functionbeatStatusCheck.errorText": "Functionbeat からまだデータを受け取っていません", @@ -1782,40 +1346,40 @@ "home.tutorials.common.heartbeat.premInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.heartbeatCloudInstructions.config.debTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.heartbeatCloudInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.heartbeatCloudInstructions.config.debTitle": "構成の変更", + "home.tutorials.common.heartbeatCloudInstructions.config.debTitle": "構成を編集する", "home.tutorials.common.heartbeatCloudInstructions.config.osxTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.heartbeatCloudInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.heartbeatCloudInstructions.config.osxTitle": "構成の変更", + "home.tutorials.common.heartbeatCloudInstructions.config.osxTitle": "構成を編集する", "home.tutorials.common.heartbeatCloudInstructions.config.rpmTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.heartbeatCloudInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.heartbeatCloudInstructions.config.rpmTitle": "構成の変更", + "home.tutorials.common.heartbeatCloudInstructions.config.rpmTitle": "構成を編集する", "home.tutorials.common.heartbeatCloudInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.heartbeatCloudInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.heartbeatCloudInstructions.config.windowsTitle": "構成の変更", + "home.tutorials.common.heartbeatCloudInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.heartbeatEnableCloudInstructions.debTextPre": "「heartbeat.yml」ファイルの「heartbeat.monitors」設定を変更します。", - "home.tutorials.common.heartbeatEnableCloudInstructions.defaultTextPost": "Heartbeat のモニターの設定の詳細は、[Heartbeat 設定ドキュメント]({configureLink}) をご覧ください。", - "home.tutorials.common.heartbeatEnableCloudInstructions.defaultTitle": "設定の変更 - モニターの追加", + "home.tutorials.common.heartbeatEnableCloudInstructions.defaultTextPost": "Heartbeat の監視を構成する手順の詳細は、[Heartbeat 構成ドキュメント]({configureLink}) をご覧ください。", + "home.tutorials.common.heartbeatEnableCloudInstructions.defaultTitle": "構成を変更 - 監視を追加", "home.tutorials.common.heartbeatEnableCloudInstructions.osxTextPre": "「heartbeat.yml」ファイルの「heartbeat.monitors」設定を変更します。", "home.tutorials.common.heartbeatEnableCloudInstructions.rpmTextPre": "「heartbeat.yml」ファイルの「heartbeat.monitors」設定を変更します。", "home.tutorials.common.heartbeatEnableCloudInstructions.windowsTextPre": "「heartbeat.yml」ファイルの「heartbeat.monitors」設定を変更します。", "home.tutorials.common.heartbeatEnableOnPremInstructions.debTextPre": "「heartbeat.yml」ファイルの「heartbeat.monitors」設定を変更します。", - "home.tutorials.common.heartbeatEnableOnPremInstructions.defaultTextPost": "{hostTemplate} は監視対象の URL です。Heartbeat のモニターの設定の詳細は、[Heartbeat 設定ドキュメント]({configureLink}) をご覧ください。", - "home.tutorials.common.heartbeatEnableOnPremInstructions.defaultTitle": "設定の変更 - モニターの追加", + "home.tutorials.common.heartbeatEnableOnPremInstructions.defaultTextPost": "{hostTemplate} は監視対象の URL です。Heartbeat の監視を構成する手順の詳細は、[Heartbeat 構成ドキュメント]({configureLink}) をご覧ください。", + "home.tutorials.common.heartbeatEnableOnPremInstructions.defaultTitle": "構成を変更 - 監視を追加", "home.tutorials.common.heartbeatEnableOnPremInstructions.osxTextPre": "「heartbeat.yml」ファイルの「heartbeat.monitors」設定を変更します。", "home.tutorials.common.heartbeatEnableOnPremInstructions.rpmTextPre": "「heartbeat.yml」ファイルの「heartbeat.monitors」設定を変更します。", "home.tutorials.common.heartbeatEnableOnPremInstructions.windowsTextPre": "「heartbeat.yml」ファイルの「heartbeat.monitors」設定を変更します。", "home.tutorials.common.heartbeatInstructions.config.debTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.heartbeatInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.heartbeatInstructions.config.debTitle": "構成の変更", + "home.tutorials.common.heartbeatInstructions.config.debTitle": "構成を編集する", "home.tutorials.common.heartbeatInstructions.config.osxTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.heartbeatInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.heartbeatInstructions.config.osxTitle": "構成の変更", + "home.tutorials.common.heartbeatInstructions.config.osxTitle": "構成を編集する", "home.tutorials.common.heartbeatInstructions.config.rpmTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.heartbeatInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.heartbeatInstructions.config.rpmTitle": "構成の変更", + "home.tutorials.common.heartbeatInstructions.config.rpmTitle": "構成を編集する", "home.tutorials.common.heartbeatInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.heartbeatInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.heartbeatInstructions.config.windowsTitle": "構成の変更", + "home.tutorials.common.heartbeatInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.heartbeatInstructions.install.debTextPost": "32 ビットパッケージをお探しですか?[ダウンロードページ]({link}) をご覧ください。", "home.tutorials.common.heartbeatInstructions.install.debTextPre": "Heartbeat は初めてですか?[入門ガイド]({link}) をご覧ください。", "home.tutorials.common.heartbeatInstructions.install.debTitle": "Heartbeat のダウンロードとインストール", @@ -1823,7 +1387,7 @@ "home.tutorials.common.heartbeatInstructions.install.osxTitle": "Heartbeat のダウンロードとインストール", "home.tutorials.common.heartbeatInstructions.install.rpmTextPre": "Heartbeat は初めてですか?[入門ガイド]({link}) をご覧ください。", "home.tutorials.common.heartbeatInstructions.install.rpmTitle": "Heartbeat のダウンロードとインストール", - "home.tutorials.common.heartbeatInstructions.install.windowsTextPre": "Heartbeat は初めてですか?[入門ガイド]({heartbeatLink}) をご覧ください。\n 1.[ダウンロード]({elasticLink}) ページから Heartbeat Windows zip ファイルをダウンロードします。\n 2.zip ファイルのコンテンツを {folderPath} に解凍します。\n 3.「{directoryName}」ディレクトリの名前を「Heartbeat」に変更します。\n 4.管理者として PowerShell プロンプトを開きます (PowerShell アイコンを右クリックして「管理者として実行」を選択します)。Windows XP をご使用の場合、PowerShell のダウンロードとインストールが必要な場合があります。\n 5.PowerShell プロンプトで次のコマンドを実行し、Heartbeat を Windows サービスとしてインストールします。", + "home.tutorials.common.heartbeatInstructions.install.windowsTextPre": "Heartbeat は初めてですか?[入門ガイド]({heartbeatLink}) をご覧ください。\n 1.[ダウンロード]({elasticLink}) ページから Heartbeat Windows zip ファイルをダウンロードします。\n 2.zip ファイルのコンテンツを {folderPath} に解凍します。\n 3.「{directoryName} ディレクトリの名前を「Heartbeat」に変更します。\n 4.管理者として PowerShell プロンプトを開きます (PowerShell アイコンを右クリックして「管理者として実行」を選択します)。Windows XP をご使用の場合、PowerShell のダウンロードとインストールが必要な場合があります。\n 5.PowerShell プロンプトで次のコマンドを実行し、Heartbeat を Windows サービスとしてインストールします。", "home.tutorials.common.heartbeatInstructions.install.windowsTitle": "Heartbeat のダウンロードとインストール", "home.tutorials.common.heartbeatInstructions.start.debTextPre": "「setup」コマンドで Kibana のインデックスパターンを読み込みます。", "home.tutorials.common.heartbeatInstructions.start.debTitle": "Heartbeat を起動します", @@ -1851,16 +1415,16 @@ "home.tutorials.common.metricbeat.premInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.metricbeatCloudInstructions.config.debTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.metricbeatCloudInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.metricbeatCloudInstructions.config.debTitle": "構成の変更", + "home.tutorials.common.metricbeatCloudInstructions.config.debTitle": "構成を編集する", "home.tutorials.common.metricbeatCloudInstructions.config.osxTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.metricbeatCloudInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.metricbeatCloudInstructions.config.osxTitle": "構成の変更", + "home.tutorials.common.metricbeatCloudInstructions.config.osxTitle": "構成を編集する", "home.tutorials.common.metricbeatCloudInstructions.config.rpmTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.metricbeatCloudInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.metricbeatCloudInstructions.config.rpmTitle": "構成の変更", + "home.tutorials.common.metricbeatCloudInstructions.config.rpmTitle": "構成を編集する", "home.tutorials.common.metricbeatCloudInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.metricbeatCloudInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.metricbeatCloudInstructions.config.windowsTitle": "構成の変更", + "home.tutorials.common.metricbeatCloudInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.metricbeatEnableInstructions.debTextPost": "「/etc/metricbeat/modules.d/{moduleName}.yml」ファイルで設定を変更します。", "home.tutorials.common.metricbeatEnableInstructions.debTitle": "{moduleName} モジュールを有効にし構成します", "home.tutorials.common.metricbeatEnableInstructions.osxTextPost": "「modules.d/{moduleName}.yml」」ファイルで設定を変更します。", @@ -1873,16 +1437,16 @@ "home.tutorials.common.metricbeatEnableInstructions.windowsTitle": "{moduleName} モジュールを有効にし構成します", "home.tutorials.common.metricbeatInstructions.config.debTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.metricbeatInstructions.config.debTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.metricbeatInstructions.config.debTitle": "構成の変更", + "home.tutorials.common.metricbeatInstructions.config.debTitle": "構成を編集する", "home.tutorials.common.metricbeatInstructions.config.osxTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.metricbeatInstructions.config.osxTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.metricbeatInstructions.config.osxTitle": "構成の変更", + "home.tutorials.common.metricbeatInstructions.config.osxTitle": "構成を編集する", "home.tutorials.common.metricbeatInstructions.config.rpmTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.metricbeatInstructions.config.rpmTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.metricbeatInstructions.config.rpmTitle": "構成の変更", + "home.tutorials.common.metricbeatInstructions.config.rpmTitle": "構成を編集する", "home.tutorials.common.metricbeatInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.metricbeatInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.metricbeatInstructions.config.windowsTitle": "構成の変更", + "home.tutorials.common.metricbeatInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.metricbeatInstructions.install.debTextPost": "32 ビットパッケージをお探しですか?[ダウンロードページ]({link}) をご覧ください。", "home.tutorials.common.metricbeatInstructions.install.debTextPre": "Metricbeat は初めてですか?[入門ガイド]({link}) をご覧ください。", "home.tutorials.common.metricbeatInstructions.install.debTitle": "Metricbeat のダウンロードとインストール", @@ -1891,7 +1455,7 @@ "home.tutorials.common.metricbeatInstructions.install.rpmTextPre": "Metricbeat は初めてですか?[入門ガイド]({link}) をご覧ください。", "home.tutorials.common.metricbeatInstructions.install.rpmTitle": "Metricbeat のダウンロードとインストール", "home.tutorials.common.metricbeatInstructions.install.windowsTextPost": "{path} ファイルの「output.elasticsearch」を Elasticsearch のインストールに設定します。", - "home.tutorials.common.metricbeatInstructions.install.windowsTextPre": "Metricbeat は初めてですか?[入門ガイド]({metricbeatLink}) をご覧ください。\n 1.[ダウンロード]({elasticLink}) ページから Metricbeat Windows zip ファイルをダウンロードします。\n 2.zip ファイルのコンテンツを {folderPath} に解凍します。\n 3.「{directoryName}」ディレクトリの名前を「Metricbeat」に変更します。\n 4.管理者として PowerShell プロンプトを開きます (PowerShell アイコンを右クリックして「管理者として実行」を選択します)。Windows XP をご使用の場合、PowerShell のダウンロードとインストールが必要な場合があります。\n 5.PowerShell プロンプトで次のコマンドを実行し、Metricbeat を Windows サービスとしてインストールします。", + "home.tutorials.common.metricbeatInstructions.install.windowsTextPre": "Metricbeat は初めてですか?[入門ガイド]({metricbeatLink}) をご覧ください。\n 1.[ダウンロード]({elasticLink}) ページから Metricbeat Windows zip ファイルをダウンロードします。\n 2.zip ファイルのコンテンツを {folderPath} に解凍します。\n 3.「{directoryName} ディレクトリの名前を「Metricbeat」に変更します。\n 4.管理者として PowerShell プロンプトを開きます (PowerShell アイコンを右クリックして「管理者として実行」を選択します)。Windows XP をご使用の場合、PowerShell のダウンロードとインストールが必要な場合があります。\n 5.PowerShell プロンプトで次のコマンドを実行し、Metricbeat を Windows サービスとしてインストールします。", "home.tutorials.common.metricbeatInstructions.install.windowsTitle": "Metricbeat のダウンロードとインストール", "home.tutorials.common.metricbeatInstructions.start.debTextPre": "「setup」コマンドで Kibana のダッシュボードを読み込みます。ダッシュボードが既にセットアップされている場合、このコマンドは省略します。", "home.tutorials.common.metricbeatInstructions.start.debTitle": "Metricbeat を起動します", @@ -1906,21 +1470,21 @@ "home.tutorials.common.metricbeatStatusCheck.successText": "このモジュールからデータを受け取りました", "home.tutorials.common.metricbeatStatusCheck.text": "Metricbeat の「{moduleName}」モジュールからデータを受け取ったことを確認してください", "home.tutorials.common.metricbeatStatusCheck.title": "モジュールステータス", - "home.tutorials.common.premCloudInstructions.option1.textPre": "[Elastic Cloud] ({link}) にアクセスします。アカウントをお持ちでない場合は新規登録してください。14 日間の無料トライアルがご利用いただけます。\n\nElastic Cloud コンソールにログインします\n\nElastic Cloud コンソールで次の手順を実行し、クラスターを作成します。\n 1.「デプロイを作成」、「デプロイ名」の順にクリックします\n 2.必要に応じて他のデプロイオプションを変更します (デフォルトも使い始めるのに有効です)\n 3.「デプロイを作成」をクリックします\n 4.デプロイの作成が完了するまで待ちます\n 5.新規クラウド Kibana インスタンスにアクセスし、Kibana ホームの手順に従います。", + "home.tutorials.common.premCloudInstructions.option1.textPre": "[Elastic Cloud]({link}) にアクセスします。アカウントをお持ちでない場合は新規登録してください。14 日間の無料トライアルがご利用いただけます。\n\nElastic Cloud コンソールにログインします\n\nElastic Cloud コンソールで次の手順に従ってクラスターを作成します。\n 1.[デプロイを作成]を選択して[デプロイ名]を指定します\n 2.必要に応じて他のデプロイオプションを変更します (デフォルトも使い始めるのに有効です)\n 3.「デプロイを作成」をクリックします\n 4.デプロイの作成が完了するまで待ちます\n 5.新規クラウド Kibana インスタンスにアクセスし、Kibana ホームの手順に従います。", "home.tutorials.common.premCloudInstructions.option1.title": "オプション 1:Elastic Cloud でお試しください", - "home.tutorials.common.premCloudInstructions.option2.textPre": "この Kibana インスタンスをマネージド Elasticsearch インスタンスに対して実行している場合は、手動セットアップを行います。.\n\n「Elasticsearch」エンドポイントを {urlTemplate} として保存し、クラスターの「パスワード」を {passwordTemplate} として保存します。", + "home.tutorials.common.premCloudInstructions.option2.textPre": "この Kibana インスタンスをマネージド Elasticsearch インスタンスに対して実行している場合は、手動セットアップを行います。\n\n「Elasticsearch」エンドポイントを {urlTemplate} として保存し、クラスターの「パスワード」を {passwordTemplate} として保存します。", "home.tutorials.common.premCloudInstructions.option2.title": "オプション 2:Kibana を Cloud インスタンスに接続", "home.tutorials.common.winlogbeat.cloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.winlogbeat.premCloudInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.winlogbeat.premInstructions.gettingStarted.title": "はじめに", "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワードです。", "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTitle": "構成の変更", + "home.tutorials.common.winlogbeatCloudInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.winlogbeatInstructions.config.windowsTextPost": "{passwordTemplate} が「Elastic」ユーザーのパスワード、{esUrlTemplate} が Elasticsearch の URL、{kibanaUrlTemplate} が Kibana の URL です。", "home.tutorials.common.winlogbeatInstructions.config.windowsTextPre": "{path} を変更して Elastic Cloud への接続情報を設定します:", - "home.tutorials.common.winlogbeatInstructions.config.windowsTitle": "構成の変更", + "home.tutorials.common.winlogbeatInstructions.config.windowsTitle": "構成を編集する", "home.tutorials.common.winlogbeatInstructions.install.windowsTextPost": "{path} ファイルの「output.elasticsearch」を Elasticsearch のインストールに設定します。", - "home.tutorials.common.winlogbeatInstructions.install.windowsTextPre": "Winlogbeat は初めてですか?[入門ガイド]({winlogbeatLink}) をご覧ください。\n 1.[ダウンロード]({elasticLink}) ページから Winlogbeat Windows zip ファイルをダウンロードします。\n 2.zip ファイルのコンテンツを {folderPath} に解凍します。\n 3.{directoryName} ディレクトリの名前を「Winlogbeat」に変更します。\n 4.管理者として PowerShell プロンプトを開きます (PowerShell アイコンを右クリックして「管理者として実行」を選択します)。Windows XP をご使用の場合、PowerShell のダウンロードとインストールが必要な場合があります。\n 5.PowerShell プロンプトで次のコマンドを実行し、Winlogbeat を Windows サービスとしてインストールします。", + "home.tutorials.common.winlogbeatInstructions.install.windowsTextPre": "Winlogbeat は初めてですか?[入門ガイド]({winlogbeatLink}) をご覧ください。\n 1.[ダウンロード]({elasticLink}) ページから Winlogbeat Windows zip ファイルをダウンロードします。\n 2.zip ファイルのコンテンツを {folderPath} に解凍します。\n 3.「{directoryName} ディレクトリの名前を「Winlogbeat」に変更します。\n 4.管理者として PowerShell プロンプトを開きます (PowerShell アイコンを右クリックして「管理者として実行」を選択します)。Windows XP をご使用の場合、PowerShell のダウンロードとインストールが必要な場合があります。\n 5.PowerShell プロンプトで次のコマンドを実行し、Winlogbeat を Windows サービスとしてインストールします。", "home.tutorials.common.winlogbeatInstructions.install.windowsTitle": "Winlogbeat のダウンロードとインストール", "home.tutorials.common.winlogbeatInstructions.start.windowsTextPre": "「setup」コマンドで Kibana のダッシュボードを読み込みます。ダッシュボードが既にセットアップされている場合、このコマンドは省略します。", "home.tutorials.common.winlogbeatInstructions.start.windowsTitle": "Winlogbeat を起動", @@ -1929,139 +1493,123 @@ "home.tutorials.common.winlogbeatStatusCheck.successText": "データを受信しました", "home.tutorials.common.winlogbeatStatusCheck.text": "Winlogbeat からデータを受け取ったことを確認してください。", "home.tutorials.common.winlogbeatStatusCheck.title": "モジュールステータス", - "home.tutorials.aerospikeMetrics.artifacts.application.label": "ディスカバリ", - "home.tutorials.aerospikeMetrics.longDescription": "「aerospike」Metricbeat モジュールは、Aerospike から内部メトリックを取得します。 [詳細]({learnMoreLink})。", - "home.tutorials.aerospikeMetrics.nameTitle": "Aerospike メトリック", - "home.tutorials.aerospikeMetrics.shortDescription": "Aerospike サーバーから内部メトリックを取得します。", - "home.tutorials.apacheLogs.artifacts.dashboards.linkLabel": "Apache ログダッシュボード", - "home.tutorials.apacheLogs.longDescription": "apache Filebeat モジュールが、Apache 2 HTTP サーバーにより作成されたアクセスとエラーのログをパースします。[詳細]({learnMoreLink})。", - "home.tutorials.apacheLogs.nameTitle": "Apache ログ", - "home.tutorials.apacheLogs.shortDescription": "Apache HTTP サーバーが作成したアクセスとエラーのログを収集しパースします。", - "home.tutorials.apacheMetrics.artifacts.dashboards.linkLabel": "Apache メトリックダッシュボード", - "home.tutorials.apacheMetrics.longDescription": "「apache」Metricbeat モジュールは、Apache 2 HTTP サーバーから内部メトリックを取得します。 [詳細]({learnMoreLink})。", - "home.tutorials.apacheMetrics.nameTitle": "Apache メトリック", - "home.tutorials.apacheMetrics.shortDescription": "Apache 2 HTTP サーバーから内部メトリックを取得します。", - "home.tutorials.auditbeat.artifacts.dashboards.linkLabel": "SIEM アプリ", - "home.tutorials.auditbeat.longDescription": "Auditbeat を使用してホストから監査データを収集します。これらにはプロセス、ユーザー、ログイン、ソケット情報、ファイルアクセス、その他が含まれます。[詳細]({learnMoreLink})。", - "home.tutorials.auditbeat.nameTitle": "Auditbeat", - "home.tutorials.auditbeat.shortDescription": "ホストから監査データを収集します。", - "home.tutorials.awsMetrics.artifacts.dashboards.linkLabel": "AWS メトリックダッシュボード", - "home.tutorials.awsMetrics.longDescription": "「aws」Metricbeat モジュールが、AWS API と Cloudwatch から監視メトリックを取得します。[詳細]({learnMoreLink})。", - "home.tutorials.awsMetrics.nameTitle": "AWS メトリック", - "home.tutorials.awsMetrics.shortDescription": "AWS API と Cloudwatch からの EC2 インスタンスの監視メトリックです。", - "home.tutorials.cephMetrics.artifacts.application.label": "ディスカバー", - "home.tutorials.cephMetrics.longDescription": "「ceph」Metricbeat モジュールは、Ceph から内部メトリックを取得します。 [詳細]({learnMoreLink})。", - "home.tutorials.cephMetrics.nameTitle": "Ceph メトリック", - "home.tutorials.cephMetrics.shortDescription": "Ceph サーバーから内部メトリックを取得します。", - "home.tutorials.ciscoLogs.artifacts.dashboards.linkLabel": "SIEM アプリ", - "home.tutorials.ciscoLogs.longDescription": "これは Cisco ネットワークデバイスのログ用のモジュールです。現在、同期された、またはファイルから読み込まれた Cisco ASA ファイアウォールログの「asa」ファイルセットをサポートしています。[詳細]({learnMoreLink})。", - "home.tutorials.ciscoLogs.nameTitle": "Cisco", - "home.tutorials.ciscoLogs.shortDescription": "Cisco ASA ファイアウォールからのログを収集・解析します。", - "home.tutorials.cloudwatchLogs.longDescription": "Functionbeat を AWS Lambda 関数として実行するようデプロイし、Cloudwatch ログを収集します。 [詳細({learnMoreLink})。", - "home.tutorials.cloudwatchLogs.nameTitle": "Cloudwatch ログ", - "home.tutorials.cloudwatchLogs.shortDescription": "Functionbeat で Cloudwatch ログを収集します。", + "home.tutorials.consulMetrics.artifacts.dashboards.linkLabel": "Consul メトリックダッシュボード", + "home.tutorials.consulMetrics.longDescription": "Metricbeat モジュール「consul」は、Consul から監視メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.consulMetrics.nameTitle": "Consul メトリック", + "home.tutorials.consulMetrics.shortDescription": "CouchdB サーバーから監視メトリックを取得します。", "home.tutorials.corednsLogs.artifacts.dashboards.linkLabel": "CoreDNS ログダッシュボード", "home.tutorials.corednsLogs.longDescription": "「coredns」Filebeat モジュールは、[CoreDNS](https://coredns.io/manual/toc/) からログを収集します。[詳細]({learnMoreLink})。", "home.tutorials.corednsLogs.nameTitle": "CoreDNS ログ", "home.tutorials.corednsLogs.shortDescription": "Coredns により作成されたログを収集します。", - "home.tutorials.corednsMetrics.artifacts.application.label": "ディスカバー", - "home.tutorials.corednsMetrics.longDescription": "「coredns」Metricbeat モジュールが、CoreDNS から監視メトリックを取得します。[詳細({learnMoreLink})。", + "home.tutorials.corednsMetrics.artifacts.application.label": "発見", + "home.tutorials.corednsMetrics.longDescription": "Metricbeat モジュール「coredns」は、CoreDNS から監視メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.corednsMetrics.nameTitle": "CoreDNS メトリック", "home.tutorials.corednsMetrics.shortDescription": "CoreDNS サーバーから監視メトリックを取得します。", - "home.tutorials.couchbaseMetrics.artifacts.application.label": "ディスカバー", - "home.tutorials.couchbaseMetrics.longDescription": "「couchbase」Metricbeat モジュールは、Couchbase から内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.couchbaseMetrics.artifacts.application.label": "発見", + "home.tutorials.couchbaseMetrics.longDescription": "Metricbeat モジュール「couchbase」は、Couchbase から内部メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.couchbaseMetrics.nameTitle": "Couchbase メトリック", "home.tutorials.couchbaseMetrics.shortDescription": "Couchbase から内部メトリックを取得します。", "home.tutorials.couchdbMetrics.artifacts.dashboards.linkLabel": "CouchDB メトリックダッシュボード", - "home.tutorials.couchdbMetrics.longDescription": "「couchdb」Metricbeat モジュールが、CouchDB から監視メトリックを取得します。[詳細] ({learnMoreLink})。", + "home.tutorials.couchdbMetrics.longDescription": "Metricbeat モジュール「couchdb」は、CouchDB から監視メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.couchdbMetrics.nameTitle": "CouchDB メトリック", "home.tutorials.couchdbMetrics.shortDescription": "CouchdB サーバーから監視メトリックを取得します。", "home.tutorials.dockerMetrics.artifacts.dashboards.linkLabel": "Docker メトリックダッシュボード", - "home.tutorials.dockerMetrics.longDescription": "「docker」 Metricbeat モジュールは、Docker サーバーからメトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.dockerMetrics.longDescription": "Metricbeat モジュール「docker」 は、Docker サーバーからメトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.dockerMetrics.nameTitle": "Docker メトリック", "home.tutorials.dockerMetrics.shortDescription": "Docker コンテナーに関するメトリックを取得します。", - "home.tutorials.dropwizardMetrics.artifacts.application.label": "ディスカバリ", - "home.tutorials.dropwizardMetrics.longDescription": "「dropwizard」 Metricbeat モジュールは、Dropwizard Java アプリケーション から内部メトリックを取得します。[詳細[]({learnMoreLink})。", + "home.tutorials.dropwizardMetrics.artifacts.application.label": "発見", + "home.tutorials.dropwizardMetrics.longDescription": "Metricbeat モジュール「dropwizard」は、Dropwizard Java アプリケーション から内部メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.dropwizardMetrics.nameTitle": "Dropwizard メトリック", "home.tutorials.dropwizardMetrics.shortDescription": "Dropwizard Java アプリケーションから内部メトリックを取得します。", - "home.tutorials.elasticsearchLogs.artifacts.application.label": "ディスカバー", + "home.tutorials.elasticsearchLogs.artifacts.application.label": "発見", "home.tutorials.elasticsearchLogs.longDescription": "「elasticsearch」Filebeat モジュールが、Elasticsearch により作成されたログをパースします。[詳細({learnMoreLink})。", "home.tutorials.elasticsearchLogs.nameTitle": "Elasticsearch ログ", "home.tutorials.elasticsearchLogs.shortDescription": "Elasticsearch により作成されたログを収集しパースします。", - "home.tutorials.elasticsearchMetrics.artifacts.application.label": "ディスカバー", - "home.tutorials.elasticsearchMetrics.longDescription": "「elasticsearch」Metricbeat モジュールは、Elasticsearch から内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.elasticsearchMetrics.artifacts.application.label": "発見", + "home.tutorials.elasticsearchMetrics.longDescription": "Metricbeat モジュール「elasticsearch」は、Elasticsearch から内部メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.elasticsearchMetrics.nameTitle": "Elasticsearch メトリック", "home.tutorials.elasticsearchMetrics.shortDescription": "Elasticsearch から内部メトリックを取得します。", "home.tutorials.envoyproxyLogs.artifacts.dashboards.linkLabel": "SIEM アプリ", "home.tutorials.envoyproxyLogs.longDescription": "これは [Envoy proxy access log](https://www.envoyproxy.io/docs/envoy/v1.10.0/configuration/access_log) 用の Filebeatモジュールです。Kubernetes でのスタンドアロンのデプロイメントと Envoy プロキシデプロイメントの両方をサポートします。[詳細]({learnMoreLink})。", "home.tutorials.envoyproxyLogs.nameTitle": "Envoyproxy", "home.tutorials.envoyproxyLogs.shortDescription": "Envoy プロキシからのログを収集・解析します。", - "home.tutorials.etcdMetrics.artifacts.application.label": "ディスカバー", - "home.tutorials.etcdMetrics.longDescription": "「etcd」Metricbeat モジュールは、Etcd から内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.envoyproxyMetrics.longDescription": "Metricbeat モジュール「envoyproxy」は、Envoy Proxy から監視メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.envoyproxyMetrics.nameTitle": "Envoy Proxy メトリック", + "home.tutorials.envoyproxyMetrics.shortDescription": "Envoy Proxy サーバーから監視メトリックを取得します。", + "home.tutorials.etcdMetrics.artifacts.application.label": "発見", + "home.tutorials.etcdMetrics.longDescription": "Metricbeat モジュール「etcd」は、Etcd から内部メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.etcdMetrics.nameTitle": "Etcd メトリック", "home.tutorials.etcdMetrics.shortDescription": "Etcd サーバーから内部メトリックを取得します。", "home.tutorials.golangMetrics.artifacts.dashboards.linkLabel": "Golang メトリックダッシュボード", - "home.tutorials.golangMetrics.longDescription": "「{moduleName}」Metricbeat モジュールは、Golang アプリから内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.golangMetrics.longDescription": "Metricbeat モジュール「{moduleName}」は、Golang アプリから内部メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.golangMetrics.nameTitle": "Golang メトリック", "home.tutorials.golangMetrics.shortDescription": "Golang アプリから内部メトリックを取得します。", - "home.tutorials.haproxyMetrics.artifacts.application.label": "ディスカバリ", - "home.tutorials.haproxyMetrics.longDescription": "「haproxy」Metricbeat モジュールは、HAProxy アプリから内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.haproxyMetrics.artifacts.application.label": "発見", + "home.tutorials.haproxyMetrics.longDescription": "Metricbeat モジュール「haproxy」は、HAProxy アプリから内部メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.haproxyMetrics.nameTitle": "HAProxy メトリック", "home.tutorials.haproxyMetrics.shortDescription": "HAProxy サーバーから内部メトリックを取得します。", + "home.tutorials.ibmmqLogs.artifacts.dashboards.linkLabel": "IBM MQ イベント", + "home.tutorials.ibmmqLogs.longDescription": "Filebeat で IBM MQ ログを収集します。[詳細]({learnMoreLink})", + "home.tutorials.ibmmqLogs.nameTitle": "IBM MQ ログ", + "home.tutorials.ibmmqLogs.shortDescription": "Filebeat で IBM MQ ログを収集します。", + "home.tutorials.ibmmqMetrics.artifacts.application.label": "発見", + "home.tutorials.ibmmqMetrics.longDescription": "Metricbeat モジュール「ibmmq」は、IBM MQ インスタンスから監視メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.ibmmqMetrics.nameTitle": "IBM MQ メトリック", + "home.tutorials.ibmmqMetrics.shortDescription": "IBM MQ インスタンスから監視メトリックを取得します。", "home.tutorials.iisLogs.artifacts.dashboards.linkLabel": "IIS ログダッシュボード", "home.tutorials.iisLogs.longDescription": "「iis」Filebeat モジュールが、Nginx HTTP サーバーにより作成されたアクセスとエラーのログをパースします。[詳細]({learnMoreLink})。", "home.tutorials.iisLogs.nameTitle": "IIS ログ", "home.tutorials.iisLogs.shortDescription": "IIS HTTP サーバーにより作成されたアクセスとエラーのログを収集しパースします。", "home.tutorials.iptablesLogs.artifacts.dashboards.linkLabel": "SIEM アプリ", - "home.tutorials.iptablesLogs.longDescription": "これは iptables と ip6tables ログ用のモジュールです。同期でネットワーク上で受信したログや、ファイルからのログを解析します。また、ルールセット名、ルール番号、トラフィックに実行されたアクション (許可/拒否) を含む、Ubiquiti ファイアウォールにより追加された接頭辞も認識できます。[詳細]({learnMoreLink})。", + "home.tutorials.iptablesLogs.longDescription": "これは iptables と ip6tables ログ用のモジュールです。ネットワーク上で受信した syslog ログ経由や、ファイルからのログをパースします。また、ルールセット名、ルール番号、トラフィックに実行されたアクション (許可/拒否) を含む、Ubiquiti ファイアウォールにより追加された接頭辞も認識できます。[詳細]({learnMoreLink})。", "home.tutorials.iptablesLogs.nameTitle": "Iptables / Ubiquiti", "home.tutorials.iptablesLogs.shortDescription": "iptables と ip6tables ログ、または Ubiqiti からのログを収集・解析します。", "home.tutorials.kafkaLogs.artifacts.dashboards.linkLabel": "Kafka ログダッシュボード", "home.tutorials.kafkaLogs.longDescription": "「kafka」Filebeat モジュールは、Kafka が作成したログをパースします。[詳細]({learnMoreLink})。", "home.tutorials.kafkaLogs.nameTitle": "Kafka ログ", "home.tutorials.kafkaLogs.shortDescription": "Kafka が作成したログを収集しパースします。", - "home.tutorials.kafkaMetrics.artifacts.application.label": "ディスカバリ", - "home.tutorials.kafkaMetrics.longDescription": "「kafka」Metricbeat モジュールは、Kafka から内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.kafkaMetrics.artifacts.application.label": "発見", + "home.tutorials.kafkaMetrics.longDescription": "Metricbeat モジュール「kafka」は、Kafka から内部メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.kafkaMetrics.nameTitle": "Kafka メトリック", "home.tutorials.kafkaMetrics.shortDescription": "Kafka サーバーから内部メトリックを取得します。", - "home.tutorials.kibanaMetrics.artifacts.application.label": "ディスカバリ", - "home.tutorials.kibanaMetrics.longDescription": "「kibana」Metricbeat モジュールは、Kibana から内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.kibanaMetrics.artifacts.application.label": "発見", + "home.tutorials.kibanaMetrics.longDescription": "Metricbeat モジュール「kibana」は、Kibana から内部メトリックを取得します。 [詳細]({learnMoreLink})。", "home.tutorials.kibanaMetrics.nameTitle": "Kibana メトリック", "home.tutorials.kibanaMetrics.shortDescription": "Kibana から内部メトリックを取得します。", "home.tutorials.kubernetesMetrics.artifacts.dashboards.linkLabel": "Kubernetes メトリックダッシュボード", - "home.tutorials.kubernetesMetrics.longDescription": "「kubernetes」Metricbeat モジュールは、Kubernetes API からメトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.kubernetesMetrics.longDescription": "Metricbeat モジュール「kubernetes」は、Kubernetes API からメトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.kubernetesMetrics.nameTitle": "Kubernetes メトリック", "home.tutorials.kubernetesMetrics.shortDescription": "Kubernetes からメトリックを取得します。", "home.tutorials.logstashLogs.artifacts.dashboards.linkLabel": "Logstash ログダッシュボード", "home.tutorials.logstashLogs.longDescription": "「logstash」Filebeat モジュールが、Logstash 自体により作成されたデバッグとスローログをパースします。[詳細]({learnMoreLink})。", "home.tutorials.logstashLogs.nameTitle": "Logstash ログ", "home.tutorials.logstashLogs.shortDescription": "Logstash 自体により作成されたデバッグとスローログをパースします。", - "home.tutorials.logstashMetrics.artifacts.application.label": "ディスカバー", - "home.tutorials.logstashMetrics.longDescription": "「{moduleName}」Metricbeat モジュールは、Logstash サーバーから内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.logstashMetrics.artifacts.application.label": "発見", + "home.tutorials.logstashMetrics.longDescription": "Metricbeat モジュール「{moduleName}」は、Logstash サーバーから内部メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.logstashMetrics.nameTitle": "Logstash メトリック", "home.tutorials.logstashMetrics.shortDescription": "Logstash サーバーから内部メトリックを取得します。", - "home.tutorials.memcachedMetrics.artifacts.application.label": "ディスカバリ", - "home.tutorials.memcachedMetrics.longDescription": "「memcached」Metricbeat モジュールは、Memcached から内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.memcachedMetrics.artifacts.application.label": "発見", + "home.tutorials.memcachedMetrics.longDescription": "Metricbeat モジュール「memcached」は、Memcached から内部メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.memcachedMetrics.nameTitle": "Memcached メトリック", "home.tutorials.memcachedMetrics.shortDescription": "Memcached サーバーから内部メトリックを取得します。", "home.tutorials.mongodbMetrics.artifacts.dashboards.linkLabel": "MongoDB メトリックダッシュボード", - "home.tutorials.mongodbMetrics.longDescription": "「mongodb」Metricbeat モジュールは、MongoDB サーバーから内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.mongodbMetrics.longDescription": "Metricbeat モジュール「mongodb」は、MongoDB サーバーから内部メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.mongodbMetrics.nameTitle": "MongoDB メトリック", "home.tutorials.mongodbMetrics.shortDescription": "MongoDB から内部メトリックを取得します。", "home.tutorials.mssqlMetrics.artifacts.dashboards.linkLabel": "Microsoft SQL Server メトリックダッシュボード", - "home.tutorials.mssqlMetrics.longDescription": "「mssql」Metricbeat モジュールが、Microsoft SQL Server インスタンスからの監視、ログ、パフォーマンスメトリックを取得します。 詳細({learnMoreLink})。", - "home.tutorials.mssqlMetrics.nameTitle": "Microsoft SQL Server メトリック", + "home.tutorials.mssqlMetrics.longDescription": "Metricbeat モジュール「mssql」は、Microsoft SQL Server インスタンスからの監視、ログ、パフォーマンスメトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.mssqlMetrics.nameTitle": "Microsoft SQL Server Metrics", "home.tutorials.mssqlMetrics.shortDescription": "Microsoft SQL Server インスタンスから監視メトリックを取得します。", - "home.tutorials.muninMetrics.artifacts.application.label": "ディスカバー", - "home.tutorials.muninMetrics.longDescription": "「munin」Metricbeat モジュールは、Munin から内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.muninMetrics.artifacts.application.label": "発見", + "home.tutorials.muninMetrics.longDescription": "Metricbeat モジュール「munin」は、Munin から内部メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.muninMetrics.nameTitle": "Munin メトリック", "home.tutorials.muninMetrics.shortDescription": "Munin サーバーから内部メトリックを取得します。", "home.tutorials.mysqlLogs.artifacts.dashboards.linkLabel": "MySQL ログダッシュボード", - "home.tutorials.mysqlLogs.longDescription": "「mysql」Filebeat モジュールは、MySQL が作成したエラーとスローログをパースします。[詳細]({learnMoreLink}).", + "home.tutorials.mysqlLogs.longDescription": "「mysql」Filebeat モジュールは、MySQL が作成したエラーとスローログをパースします。[詳細]({learnMoreLink})。", "home.tutorials.mysqlLogs.nameTitle": "MySQL ログ", "home.tutorials.mysqlLogs.shortDescription": "MySQL が作成したエラーとスローログを収集しパースします。", "home.tutorials.mysqlMetrics.artifacts.dashboards.linkLabel": "MySQL メトリックダッシュボード", - "home.tutorials.mysqlMetrics.longDescription": "「mysql」Metricbeat モジュールは、MySQL サーバーから内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.mysqlMetrics.longDescription": "Metricbeat モジュール「mysql」は、MySQL サーバーから内部メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.mysqlMetrics.nameTitle": "MySQL メトリック", "home.tutorials.mysqlMetrics.shortDescription": "MySQL から内部メトリックを取得します。", "home.tutorials.natsLogs.artifacts.dashboards.linkLabel": "Nats ログダッシュボード", @@ -2069,33 +1617,33 @@ "home.tutorials.natsLogs.nameTitle": "Nats ログ", "home.tutorials.natsLogs.shortDescription": "Nats により作成されたログを収集しパースします。", "home.tutorials.natsMetrics.artifacts.dashboards.linkLabel": "Nats メトリックダッシュボード", - "home.tutorials.natsMetrics.longDescription": "「nats」Metricbeat モジュールが、Nats から監視メトリックを取得します。[詳細({learnMoreLink})。", + "home.tutorials.natsMetrics.longDescription": "Metricbeat モジュール「nats」は、Nats から監視メトリックを取得します。[詳細] {learnMoreLink})。", "home.tutorials.natsMetrics.nameTitle": "Nats メトリック", "home.tutorials.natsMetrics.shortDescription": "Nats サーバーから監視メトリックを取得します。", "home.tutorials.netflow.common.config.elasticCloud.osxTextPost": "{udpPort} は Logstash が Netflow データを受信する UDP ポートで、{password} は {elastic} ユーザーのパスワードです。", "home.tutorials.netflow.common.config.elasticCloud.osxTextPre": "{logstashConfigPath} を編集して構成パラメーターを設定します。", - "home.tutorials.netflow.common.config.elasticCloud.osxTitle": "構成の変更", + "home.tutorials.netflow.common.config.elasticCloud.osxTitle": "構成を編集する", "home.tutorials.netflow.common.config.elasticCloud.windowsTextPost": "{udpPort} は Logstash が Netflow データを受信する UDP ポートで、{password} は {elastic} ユーザーのパスワードです。", "home.tutorials.netflow.common.config.elasticCloud.windowsTextPre": "{logstashConfigPath} を編集して構成パラメーターを設定します。", - "home.tutorials.netflow.common.config.elasticCloud.windowsTitle": "構成の変更", + "home.tutorials.netflow.common.config.elasticCloud.windowsTitle": "構成を編集する", "home.tutorials.netflow.common.config.onPrem.osxTextPost": "{udpPort} は Logstash が Netflow データを受信する UDP ポートです。", "home.tutorials.netflow.common.config.onPrem.osxTextPre": "{logstashConfigPath} を編集して構成パラメーターを設定します。", - "home.tutorials.netflow.common.config.onPrem.osxTitle": "構成の変更", + "home.tutorials.netflow.common.config.onPrem.osxTitle": "構成を編集する", "home.tutorials.netflow.common.config.onPrem.windowsTextPost": "{udpPort} は Logstash が Netflow データを受信する UDP ポートです。", "home.tutorials.netflow.common.config.onPrem.windowsTextPre": "{logstashConfigPath} を編集して構成パラメーターを設定します。", - "home.tutorials.netflow.common.config.onPrem.windowsTitle": "構成の変更", + "home.tutorials.netflow.common.config.onPrem.windowsTitle": "構成を編集する", "home.tutorials.netflow.common.config.onPremElasticCloud.osxTextPost": "{udpPort} は Logstash が Netflow データを受信する UDP ポートで、{esUrl} は Elastic Cloud で実行中の Elasticsearch の URL で、{password} は {elastic} ユーザーのパスワードです。", "home.tutorials.netflow.common.config.onPremElasticCloud.osxTextPre": "{logstashConfigPath} を編集して構成パラメーターを設定します。", - "home.tutorials.netflow.common.config.onPremElasticCloud.osxTitle": "構成の変更", + "home.tutorials.netflow.common.config.onPremElasticCloud.osxTitle": "構成を編集する", "home.tutorials.netflow.common.config.onPremElasticCloud.windowsTextPost": "{udpPort} は Logstash が Netflow データを受信する UDP ポートで、{esUrl} は Elastic Cloud で実行中の Elasticsearch の URL で、{password} は {elastic} ユーザーのパスワードです。", "home.tutorials.netflow.common.config.onPremElasticCloud.windowsTextPre": "{logstashConfigPath} を編集して構成パラメーターを設定します。", - "home.tutorials.netflow.common.config.onPremElasticCloud.windowsTitle": "構成の変更", - "home.tutorials.netflow.common.setup.osxTextPost": "{setupOption} オプションは Elasticsearch で {netflowPrefix} インデックスパターンを作成し、Kibana ダッシュボードとビジュアライゼーションをインポートします。後の実行時には既存のダッシュボードに上書きしないよう、このオプションを省略します。", + "home.tutorials.netflow.common.config.onPremElasticCloud.windowsTitle": "構成を編集する", + "home.tutorials.netflow.common.setup.osxTextPost": "{setupOption} オプションは Elasticsearch で {netflowPrefix} インデックスパターンを作成し、Kibana のダッシュボードとビジュアライゼーションをインポートします。以降の実行時に既存のダッシュボードに上書きしないよう、このオプションを省略します。", "home.tutorials.netflow.common.setup.osxTextPre": "実行:", "home.tutorials.netflow.common.setup.osxTitle": "Netflow モジュールを実行", - "home.tutorials.netflow.common.setup.windowsTextPost": "{setupOption} オプションは Elasticsearch で {netflowPrefix} インデックスパターンを作成し、Kibana ダッシュボードとビジュアライゼーションをインポートします。後の実行時には既存のダッシュボードに上書きしないよう、このオプションを省略します。", + "home.tutorials.netflow.common.setup.windowsTextPost": "{setupOption} オプションは Elasticsearch で {netflowPrefix} インデックスパターンを作成し、Kibana のダッシュボードとビジュアライゼーションをインポートします。以降の実行時に既存のダッシュボードに上書きしないよう、このオプションを省略します。", "home.tutorials.netflow.common.setup.windowsTextPre": "実行:", - "home.tutorials.netflow.common.setup.windowsTitle": "Netflow モジュールの実行", + "home.tutorials.netflow.common.setup.windowsTitle": "Netflow モジュールを実行", "home.tutorials.netflow.elasticCloudInstructions.title": "はじめに", "home.tutorials.netflow.onPremElasticCloudInstructions.title": "はじめに", "home.tutorials.netflow.onPremInstructions.title": "はじめに", @@ -2106,278 +1654,886 @@ "home.tutorials.nginxLogs.nameTitle": "Nginx ログ", "home.tutorials.nginxLogs.shortDescription": "Nginx HTTP サーバーが作成したアクセスとエラーのログを収集しパースします。", "home.tutorials.nginxMetrics.artifacts.dashboards.linkLabel": "Nginx メトリックダッシュボード", - "home.tutorials.nginxMetrics.longDescription": "「nginx」Metricbeat モジュールは、Nginx サーバーから内部メトリックを取得します。このモジュールは {statusModuleLink} が作成したウェブページからサーバーステータスデータを取得します。Nginx で {statusModuleLink} が有効にする必要があります。[詳細]({learnMoreLink})。", + "home.tutorials.nginxMetrics.longDescription": "Metricbeat モジュール「nginx」は、Nginx サーバーから内部メトリックを取得します。このモジュールは {statusModuleLink} で生成したウェブページからサーバーステータスデータを収集しますが、これは Nginx で有効にする必要があります。[詳細]({learnMoreLink})。", "home.tutorials.nginxMetrics.nameTitle": "Nginx メトリック", "home.tutorials.nginxMetrics.shortDescription": "Nginx HTTP サーバーから内部メトリックを取得します。", + "home.tutorials.openmetricsMetrics.longDescription": "Metricbeat モジュール「openmetrics」は、OpenMetrics の形式でメトリックを提供するエンドポイントからメトリックをフェッチします。[詳細]({learnMoreLink})。", + "home.tutorials.openmetricsMetrics.nameTitle": "OpenMetrics メトリック", + "home.tutorials.openmetricsMetrics.shortDescription": "OpenMetrics 形式でメトリックを提供するエンドポイントからメトリックを取得します。", "home.tutorials.osqueryLogs.artifacts.dashboards.linkLabel": "Osquery ログダッシュボード", "home.tutorials.osqueryLogs.longDescription": "「osquery」Filebeat モジュールは、「osqueryd」が作成した JSON 結果ページを収集します。[詳細]({learnMoreLink})。", "home.tutorials.osqueryLogs.nameTitle": "Osquery ログ", "home.tutorials.osqueryLogs.shortDescription": "osqueryd により作成されたログを収集します。", - "home.tutorials.phpFpmMetrics.longDescription": "「php_fpm」Metricbeat モジュールは、PHP-FPM サーバーから内部メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.phpFpmMetrics.longDescription": "Metricbeat モジュール「php_fpm」は、PHP-FPM サーバーから内部メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.phpFpmMetrics.nameTitle": "PHP-FPM メトリック", "home.tutorials.phpFpmMetrics.shortDescription": "PHP-FPM から内部メトリックを取得します。", "home.tutorials.postgresqlLogs.artifacts.dashboards.linkLabel": "PostgreSQL ログダッシュボード", - "home.tutorials.postgresqlLogs.longDescription": "「postgresql」Filebeat モジュールが、PostgreSQL により作成されたエラーとスローログをパースします。[詳細]({learnMoreLink}).", + "home.tutorials.postgresqlLogs.longDescription": "「postgresql」Filebeat モジュールが、PostgreSQL により作成されたエラーとスローログをパースします。[詳細]({learnMoreLink})。", "home.tutorials.postgresqlLogs.nameTitle": "PostgreSQL ログ", "home.tutorials.postgresqlLogs.shortDescription": "PostgreSQL により作成されたエラーとスローログを収集しパースします。", - "home.tutorials.postgresqlMetrics.longDescription": "「postgresql」Metricbeat モジュールは、PostgreSQL サーバーから内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.postgresqlMetrics.longDescription": "Metricbeat モジュール「postgresql」は、PostgreSQL サーバーから内部メトリックを取得します。 [詳細]({learnMoreLink})。", "home.tutorials.postgresqlMetrics.nameTitle": "PostgreSQL メトリック", "home.tutorials.postgresqlMetrics.shortDescription": "PostgreSQL から内部メトリックを取得します。", - "home.tutorials.prometheusMetrics.artifacts.application.label": "ディスカバリ", - "home.tutorials.prometheusMetrics.longDescription": "「{moduleName}」Metricbeat モジュールは、Prometheus エンドポイントからメトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.prometheusMetrics.artifacts.application.label": "発見", + "home.tutorials.prometheusMetrics.longDescription": "Metricbeat モジュール「{moduleName}」は、Prometheus エンドポイントからメトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.prometheusMetrics.nameTitle": "Prometheus メトリック", "home.tutorials.prometheusMetrics.shortDescription": "Prometheus エクスポーターからメトリックを取得します。.", "home.tutorials.rabbitmqMetrics.artifacts.dashboards.linkLabel": "RabbitMQ メトリックダッシュボード", - "home.tutorials.rabbitmqMetrics.longDescription": "「rabbitmq」Metricbeat モジュールは、RabbitMQ サーバーから内部メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.rabbitmqMetrics.longDescription": "Metricbeat モジュール「rabbitmq」は、RabbitMQ サーバーから内部メトリックを取得します。[詳細]({learnMoreLink})。", "home.tutorials.rabbitmqMetrics.nameTitle": "RabbitMQ メトリック", "home.tutorials.rabbitmqMetrics.shortDescription": "RabbitMQ サーバーから内部メトリックを取得します。", + "home.tutorials.redisenterpriseMetrics.artifacts.application.label": "発見", + "home.tutorials.redisenterpriseMetrics.longDescription": "Metricbeat モジュール「redisenterprise」は Redis Enterprise Server 監視メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.redisenterpriseMetrics.nameTitle": "Redis Enterprise メトリック", + "home.tutorials.redisenterpriseMetrics.shortDescription": "Redis Enterprise Server から監視メトリックを取得します。", "home.tutorials.redisLogs.artifacts.dashboards.linkLabel": "Redis ログダッシュボード", - "home.tutorials.redisLogs.longDescription": "「redis」Filebeat モジュールは、Redis が作成したエラーとスローログをパースします。Redis がエラーログを作成するには、Redis 構成ファイルの「logfile」オプションが「redis-server.log」に設定されていることを確認してください。スローログは「SLOWLOG」コマンドで Redis から直接読み込まれます。Redis がスローログを記録するには、「slowlog-log-slower-than」オプションが設定されていることを確認してください。「slowlog」ファイルセットは実験的な機能のためご注意ください。[詳細]({learnMoreLink})。", - "home.tutorials.redisLogs.nameTitle": "Redis ログ", - "home.tutorials.redisLogs.shortDescription": "Redis が作成したエラーとスローログを収集しパースします。", - "home.tutorials.redisMetrics.artifacts.dashboards.linkLabel": "Redis メトリックダッシュボード", - "home.tutorials.redisMetrics.longDescription": "「redis」Metricbeat モジュールは、Redis サーバーから内部メトリックを取得します。 [詳細]({learnMoreLink})。", - "home.tutorials.redisMetrics.nameTitle": "Redis メトリック", - "home.tutorials.redisMetrics.shortDescription": "Redis から内部メトリックを取得します。", - "home.tutorials.suricataLogs.artifacts.dashboards.linkLabel": "Suricata ログダッシュボード", - "home.tutorials.suricataLogs.longDescription": "「suricata」Filebeat モジュールは、[Suricata Eve JSON アウトプット](https://suricata.readthedocs.io/en/latest/output/eve/eve-json-format.html) からログを収集します。[詳細]({learnMoreLink})。", - "home.tutorials.suricataLogs.nameTitle": "Suricata ログ", - "home.tutorials.suricataLogs.shortDescription": "Suricata IDS/IPS/NSM が作成したログを収集します。", - "home.tutorials.systemLogs.artifacts.dashboards.linkLabel": "システムログダッシュボード", - "home.tutorials.systemLogs.longDescription": "「system」Filebeat モジュールは、一般的な Unix/Linux ベースのディストリビューションのシステムログサービスが作成したログを収集しパースします。このモジュールは Windows では利用できません。[詳細]({learnMoreLink})。", - "home.tutorials.systemLogs.nameTitle": "システムログ", - "home.tutorials.systemLogs.shortDescription": "ローカル Syslog サーバーが作成したログを収集しパースします。", - "home.tutorials.systemMetrics.artifacts.dashboards.linkLabel": "システムメトリックダッシュボード", - "home.tutorials.systemMetrics.longDescription": "「system」Metricbeat モジュールは、ホストから CPU、メモリー、ネットワーク、ディスクの統計を収集します。システム全体の統計とプロセスやファイルシステムごとの統計を収集します。[詳細]({learnMoreLink})。", - "home.tutorials.systemMetrics.nameTitle": "システムメトリック", - "home.tutorials.systemMetrics.shortDescription": "ホストから CPU、メモリー、ネットワーク、ディスクの統計を収集します。", - "home.tutorials.traefikLogs.artifacts.dashboards.linkLabel": "Traefik ログダッシュボード", - "home.tutorials.traefikLogs.longDescription": "「traefik」Filebeat モジュールが、Traefik により作成されたアクセスログをパースします。[詳細]({learnMoreLink})。", - "home.tutorials.traefikLogs.nameTitle": "Traefik ログ", - "home.tutorials.traefikLogs.shortDescription": "Traefik Proxy により作成されたアクセスログを収集しパースします。", - "home.tutorials.uptimeMonitors.artifacts.dashboards.linkLabel": "アップタイムアプリ", - "home.tutorials.uptimeMonitors.longDescription": "アクティブなプロービングでサービスの稼働状況を監視します。 Heartbeat が URL のリストに基づき、シンプ:にたずねます:生きてる?と [詳細]({learnMoreLink})。", - "home.tutorials.uptimeMonitors.nameTitle": "アップタイムモニター", - "home.tutorials.uptimeMonitors.shortDescription": "サービスの稼働状況を監視します。", - "home.tutorials.uwsgiMetrics.artifacts.dashboards.linkLabel": "uWSGI メトリックダッシュボード", - "home.tutorials.uwsgiMetrics.longDescription": "「uwsgi」Metricbeat モジュールは、uWSGI サーバーから内部メトリックを取得します。 [詳細]({learnMoreLink})。", - "home.tutorials.uwsgiMetrics.nameTitle": "uWSGI メトリック", - "home.tutorials.uwsgiMetrics.shortDescription": "uWSGI サーバーから内部メトリックを取得します。", - "home.tutorials.vsphereMetrics.artifacts.application.label": "ディスカバリ", - "home.tutorials.vsphereMetrics.longDescription": "「vsphere」Metricbeat モジュールは、vSphere クラスターから内部メトリックを取得します。 [詳細]({learnMoreLink})。", - "home.tutorials.vsphereMetrics.nameTitle": "vSphere メトリック", - "home.tutorials.vsphereMetrics.shortDescription": "vSphere から内部メトリックを取得します。", - "home.tutorials.windowsEventLogs.artifacts.application.label": "SIEM アプリ", - "home.tutorials.windowsEventLogs.longDescription": "Winlogbeat を使用して Windows Event Log からのログを収集します。[詳細]({learnMoreLink})。", - "home.tutorials.windowsEventLogs.nameTitle": "Windows Event Log", - "home.tutorials.windowsEventLogs.shortDescription": "Windows Event Log からイベントを取得します。", - "home.tutorials.windowsMetrics.artifacts.application.label": "ディスカバー", - "home.tutorials.windowsMetrics.longDescription": "「windows」Metricbeat モジュールは、Windows から内部メトリックを取得します。 [詳細]({learnMoreLink})。", - "home.tutorials.windowsMetrics.nameTitle": "Windows メトリック", - "home.tutorials.windowsMetrics.shortDescription": "Windows から内部メトリックを取得します。", - "home.tutorials.zeekLogs.artifacts.dashboards.linkLabel": "Zeek ログダッシュボード", - "home.tutorials.zeekLogs.longDescription": "「zeek」Filebeat モジュールが、[Zeek](https://www.zeek.org//documentation/index.html) からログを収集します。[詳細]({learnMoreLink})。", - "home.tutorials.zeekLogs.nameTitle": "Zeek ログ", - "home.tutorials.zeekLogs.shortDescription": "Zeek/Bro により作成されたログを収集します。", - "home.tutorials.zookeeperMetrics.artifacts.application.label": "ディスカバー", - "home.tutorials.zookeeperMetrics.longDescription": "「{moduleName}」Metricbeat モジュールは、Zookeeper サーバーから内部メトリックを取得します。 [詳細]({learnMoreLink})。", - "home.tutorials.zookeeperMetrics.nameTitle": "Zookeeper メトリック", - "home.tutorials.zookeeperMetrics.shortDescription": "Zookeeper サーバーから内部メトリックを取得します。", - "home.welcomeDescription": "Elastic Stack への開かれた窓", - "home.welcomeHomePageHeader": "Kibana ホーム", - "home.welcomeTitle": "Kibana へようこそ", - "visTypeVislib.area.areaDescription": "折れ線グラフの下の数量を強調します。", - "visTypeVislib.area.areaTitle": "エリア", - "visTypeVislib.area.countText": "カウント", - "visTypeVislib.area.groupTitle": "系列を分割", - "visTypeVislib.area.metricsTitle": "Y 軸", - "visTypeVislib.area.radiusTitle": "点のサイズ", - "visTypeVislib.area.segmentTitle": "X 軸", - "visTypeVislib.area.splitTitle": "チャートを分割", - "visTypeVislib.area.tabs.metricsAxesTitle": "メトリックと軸", - "visTypeVislib.area.tabs.panelSettingsTitle": "パネル設定", - "visTypeVislib.axisModes.normalText": "標準", - "visTypeVislib.axisModes.percentageText": "パーセンテージ", - "visTypeVislib.axisModes.silhouetteText": "シルエット", - "visTypeVislib.axisModes.wiggleText": "振動", - "visTypeVislib.categoryAxis.rotate.angledText": "傾斜", - "visTypeVislib.categoryAxis.rotate.horizontalText": "横", - "visTypeVislib.categoryAxis.rotate.verticalText": "縦", - "visTypeVislib.chartModes.normalText": "標準", - "visTypeVislib.chartModes.stackedText": "スタック", - "visTypeVislib.chartTypes.areaText": "エリア", - "visTypeVislib.chartTypes.barText": "バー", - "visTypeVislib.chartTypes.lineText": "折れ線", - "visTypeVislib.controls.colorRanges.errorText": "各範囲は前の範囲よりも大きくなければなりません。", - "visTypeVislib.controls.colorSchema.colorSchemaLabel": "カラー図表", - "visTypeVislib.controls.colorSchema.howToChangeColorsDescription": "それぞれの色は凡例で変更できます。", - "visTypeVislib.controls.colorSchema.resetColorsButtonLabel": "色をリセット", - "visTypeVislib.controls.colorSchema.reverseColorSchemaLabel": "図表を反転", - "visTypeVislib.controls.gaugeOptions.alignmentLabel": "アラインメント", - "visTypeVislib.controls.gaugeOptions.autoExtendRangeLabel": "範囲を自動拡張", - "visTypeVislib.controls.gaugeOptions.displayWarningsLabel": "警告を表示", - "visTypeVislib.controls.gaugeOptions.extendRangeTooltip": "範囲をデータの最高値に広げます。", - "visTypeVislib.controls.gaugeOptions.gaugeTypeLabel": "ゲージタイプ", - "visTypeVislib.controls.gaugeOptions.labelsTitle": "ラベル", - "visTypeVislib.controls.gaugeOptions.percentageModeLabel": "パーセンテージモード", - "visTypeVislib.controls.gaugeOptions.rangesTitle": "範囲", - "visTypeVislib.controls.gaugeOptions.showLabelsLabel": "ラベルを表示", - "visTypeVislib.controls.gaugeOptions.showLegendLabel": "凡例を表示", - "visTypeVislib.controls.gaugeOptions.showScaleLabel": "縮尺を表示", - "visTypeVislib.controls.gaugeOptions.styleTitle": "スタイル", - "visTypeVislib.controls.gaugeOptions.subTextLabel": "サブラベル", - "visTypeVislib.controls.gaugeOptions.switchWarningsTooltip": "警告のオン・オフを切り替えます。オンにすると、すべてのラベルを表示できない際に警告が表示されます。", - "visTypeVislib.controls.heatmapOptions.colorLabel": "色", - "visTypeVislib.controls.heatmapOptions.colorScaleLabel": "カラースケール", - "visTypeVislib.controls.heatmapOptions.colorsNumberLabel": "色の数", - "visTypeVislib.controls.heatmapOptions.labelsTitle": "ラベル", - "visTypeVislib.controls.heatmapOptions.overwriteAutomaticColorLabel": "自動からーを上書きする", - "visTypeVislib.controls.heatmapOptions.percentageModeLabel": "パーセンテージモード", - "visTypeVislib.controls.heatmapOptions.rotateLabel": "回転", - "visTypeVislib.controls.heatmapOptions.scaleToDataBoundsLabel": "データバウンドに合わせる", - "visTypeVislib.controls.heatmapOptions.showLabelsTitle": "ラベルを表示", - "visTypeVislib.controls.heatmapOptions.useCustomRangesLabel": "カスタム範囲を使用", - "visTypeVislib.controls.pointSeries.categoryAxis.alignLabel": "配置", - "visTypeVislib.controls.pointSeries.categoryAxis.filterLabelsLabel": "フィルターラベル", - "visTypeVislib.controls.pointSeries.categoryAxis.labelsTitle": "ラベル", - "visTypeVislib.controls.pointSeries.categoryAxis.positionLabel": "配置", - "visTypeVislib.controls.pointSeries.categoryAxis.showLabel": "表示", - "visTypeVislib.controls.pointSeries.categoryAxis.showLabelsLabel": "ラベルを表示", - "visTypeVislib.controls.pointSeries.categoryAxis.xAxisTitle": "X 軸", - "visTypeVislib.controls.pointSeries.gridAxis.dontShowLabel": "非表示", - "visTypeVislib.controls.pointSeries.gridAxis.gridText": "グリッド", - "visTypeVislib.controls.pointSeries.gridAxis.xAxisLinesLabel": "X 軸線を表示", - "visTypeVislib.controls.pointSeries.gridAxis.yAxisLinesDisabledTooltip": "ヒストグラムに X 軸線は表示できません。", - "visTypeVislib.controls.pointSeries.gridAxis.yAxisLinesLabel": "Y 軸線を表示", - "visTypeVislib.controls.pointSeries.series.chartTypeLabel": "チャートタイプ", - "visTypeVislib.controls.pointSeries.series.lineModeLabel": "線のモード", - "visTypeVislib.controls.pointSeries.series.lineWidthLabel": "線の幅", - "visTypeVislib.controls.pointSeries.series.metricsTitle": "メトリック", - "visTypeVislib.controls.pointSeries.series.modeLabel": "モード", - "visTypeVislib.controls.pointSeries.series.newAxisLabel": "新規軸…", - "visTypeVislib.controls.pointSeries.series.showDotsLabel": "点を表示", - "visTypeVislib.controls.pointSeries.series.showLineLabel": "線を表示", - "visTypeVislib.controls.pointSeries.series.valueAxisLabel": "値軸", - "visTypeVislib.controls.pointSeries.seriesAccordionAriaLabel": "{agg} オプションを切り替える", - "visTypeVislib.controls.pointSeries.valueAxes.addButtonTooltip": "Y 軸を追加します", - "visTypeVislib.controls.pointSeries.valueAxes.customExtentsLabel": "カスタム範囲", - "visTypeVislib.controls.pointSeries.valueAxes.maxLabel": "最高", - "visTypeVislib.controls.pointSeries.valueAxes.minErrorMessage": "最低値は最高値よりも低く設定する必要があります", - "visTypeVislib.controls.pointSeries.valueAxes.minLabel": "最低", - "visTypeVislib.controls.pointSeries.valueAxes.minNeededScaleText": "ログスケールが選択されている場合、最低値は 0 よりも大きいものである必要があります", - "visTypeVislib.controls.pointSeries.valueAxes.modeLabel": "モード", - "visTypeVislib.controls.pointSeries.valueAxes.positionLabel": "配置", - "visTypeVislib.controls.pointSeries.valueAxes.removeButtonTooltip": "Y 軸を削除します", - "visTypeVislib.controls.pointSeries.valueAxes.scaleToDataBounds.boundsMargin": "境界マージン", - "visTypeVislib.controls.pointSeries.valueAxes.scaleToDataBounds.minNeededBoundsMargin": "境界マージンは 0 以上でなければなりません", - "visTypeVislib.controls.pointSeries.valueAxes.scaleToDataBoundsLabel": "データバウンドに合わせる", - "visTypeVislib.controls.pointSeries.valueAxes.scaleTypeLabel": "スケールタイプ", - "visTypeVislib.controls.pointSeries.valueAxes.setAxisExtentsLabel": "軸の範囲の設定", - "visTypeVislib.controls.pointSeries.valueAxes.showLabel": "表示", - "visTypeVislib.controls.pointSeries.valueAxes.titleLabel": "タイトル", - "visTypeVislib.controls.pointSeries.valueAxes.toggleCustomExtendsAriaLabel": "カスタム範囲を切り替える", - "visTypeVislib.controls.pointSeries.valueAxes.toggleOptionsAriaLabel": "{axisName} オプションを切り替える", - "visTypeVislib.controls.pointSeries.valueAxes.yAxisTitle": "Y 軸", - "visTypeVislib.controls.rangeErrorMessage": "値は {min} と {max} の間でなければなりません", - "visTypeVislib.controls.truncateLabel": "切り捨て", - "visTypeVislib.controls.vislibBasicOptions.legendPositionLabel": "凡例の配置", - "visTypeVislib.controls.vislibBasicOptions.showTooltipLabel": "ツールヒントを表示", - "visTypeVislib.editors.heatmap.basicSettingsTitle": "基本設定", - "visTypeVislib.editors.heatmap.heatmapSettingsTitle": "ヒートマップ設定", - "visTypeVislib.editors.heatmap.highlightLabel": "ハイライト範囲", - "visTypeVislib.vislib.heatmap.maxBucketsText": "定義された数列が多すぎます ({nr}).構成されている最高値は {max} です。", - "visTypeVislib.editors.heatmap.highlightLabelTooltip": "チャートのカーソルを当てた部分と凡例の対応するラベルをハイライトします。", - "visTypeVislib.editors.pie.donutLabel": "ドーナッツ", - "visTypeVislib.editors.pie.labelsSettingsTitle": "ラベル設定", - "visTypeVislib.editors.pie.pieSettingsTitle": "パイ設定", - "visTypeVislib.editors.pie.showLabelsLabel": "ラベルを表示", - "visTypeVislib.editors.pie.showTopLevelOnlyLabel": "トップレベルのみ表示", - "visTypeVislib.editors.pie.showValuesLabel": "値を表示", - "visTypeVislib.editors.pointSeries.currentTimeMarkerLabel": "現在時刻マーカー", - "visTypeVislib.editors.pointSeries.orderBucketsBySumLabel": "バケットを合計で並べ替え", - "visTypeVislib.editors.pointSeries.settingsTitle": "設定", - "visTypeVislib.editors.pointSeries.showLabels": "チャートに値を表示", - "visTypeVislib.editors.pointSeries.thresholdLine.colorLabel": "ラインカラー", - "visTypeVislib.editors.pointSeries.thresholdLine.showLabel": "しきい線を表示", - "visTypeVislib.editors.pointSeries.thresholdLine.styleLabel": "ラインスタイル", - "visTypeVislib.editors.pointSeries.thresholdLine.valueLabel": "しきい値", - "visTypeVislib.editors.pointSeries.thresholdLine.widthLabel": "線の幅", - "visTypeVislib.editors.pointSeries.thresholdLineSettingsTitle": "しきい線", - "visTypeVislib.functions.pie.help": "パイビジュアライゼーション", - "visTypeVislib.functions.vislib.help": "Vislib ビジュアライゼーション", - "visTypeVislib.gauge.alignmentAutomaticTitle": "自動", - "visTypeVislib.gauge.alignmentHorizontalTitle": "横", - "visTypeVislib.gauge.alignmentVerticalTitle": "縦", - "visTypeVislib.gauge.gaugeDescription": "ゲージはメトリックのステータスを示します。メトリックの値としきい値との関連性を示すのに使用します。", - "visTypeVislib.gauge.gaugeTitle": "ゲージ", - "visTypeVislib.gauge.gaugeTypes.arcText": "弧形", - "visTypeVislib.gauge.gaugeTypes.circleText": "円", - "visTypeVislib.gauge.groupTitle": "グループを分割", - "visTypeVislib.gauge.metricTitle": "メトリック", - "visTypeVislib.goal.goalDescription": "ゴールチャートは、最終目標にどれだけ近いかを示します。", - "visTypeVislib.goal.goalTitle": "ゴール", - "visTypeVislib.goal.groupTitle": "グループを分割", - "visTypeVislib.goal.metricTitle": "メトリック", - "visTypeVislib.heatmap.groupTitle": "Y 軸", - "visTypeVislib.heatmap.heatmapDescription": "マトリックス内のセルに影をつける。", - "visTypeVislib.heatmap.heatmapTitle": "ヒートマップ", - "visTypeVislib.heatmap.metricTitle": "値", - "visTypeVislib.heatmap.segmentTitle": "X 軸", - "visTypeVislib.heatmap.splitTitle": "チャートを分割", - "visTypeVislib.histogram.groupTitle": "系列を分割", - "visTypeVislib.histogram.histogramDescription": "連続変数を各軸に割り当てる。", - "visTypeVislib.histogram.histogramTitle": "縦棒", - "visTypeVislib.histogram.metricTitle": "Y 軸", - "visTypeVislib.histogram.radiusTitle": "点のサイズ", - "visTypeVislib.histogram.segmentTitle": "X 軸", - "visTypeVislib.histogram.splitTitle": "チャートを分割", - "visTypeVislib.horizontalBar.groupTitle": "系列を分割", - "visTypeVislib.horizontalBar.horizontalBarDescription": "連続変数を各軸に割り当てる。", - "visTypeVislib.horizontalBar.horizontalBarTitle": "横棒", - "visTypeVislib.horizontalBar.metricTitle": "Y 軸", - "visTypeVislib.horizontalBar.radiusTitle": "点のサイズ", - "visTypeVislib.horizontalBar.segmentTitle": "X 軸", - "visTypeVislib.horizontalBar.splitTitle": "チャートを分割", - "visTypeVislib.interpolationModes.smoothedText": "スムーズ", - "visTypeVislib.interpolationModes.steppedText": "ステップ", - "visTypeVislib.interpolationModes.straightText": "直線", - "visTypeVislib.legendPositions.bottomText": "一番下", - "visTypeVislib.legendPositions.leftText": "左", - "visTypeVislib.legendPositions.rightText": "右", - "visTypeVislib.legendPositions.topText": "一番上", - "visTypeVislib.line.groupTitle": "系列を分割", - "visTypeVislib.line.lineDescription": "トレンドを強調します。", - "visTypeVislib.line.lineTitle": "折れ線", - "visTypeVislib.line.metricTitle": "Y 軸", - "visTypeVislib.line.radiusTitle": "点のサイズ", - "visTypeVislib.line.segmentTitle": "X 軸", - "visTypeVislib.line.splitTitle": "チャートを分割", - "visTypeVislib.pie.metricTitle": "サイズのスライス", - "visTypeVislib.pie.pieDescription": "全体に対する内訳を表現する。", - "visTypeVislib.pie.pieTitle": "パイ", - "visTypeVislib.pie.segmentTitle": "スライスの分割", - "visTypeVislib.pie.splitTitle": "チャートを分割", - "visTypeVislib.scaleTypes.linearText": "直線", - "visTypeVislib.scaleTypes.logText": "ログ", - "visTypeVislib.scaleTypes.squareRootText": "平方根", - "visTypeVislib.thresholdLine.style.dashedText": "鎖線", - "visTypeVislib.thresholdLine.style.dotdashedText": "点線", - "visTypeVislib.thresholdLine.style.fullText": "完全", - "visTypeVislib.vislib.tooltip.fieldLabel": "フィールド", - "visTypeVislib.vislib.tooltip.valueLabel": "値", - "visTypeVislib.vislib.legend.filterForValueButtonAriaLabel": "値 {legendDataLabel} でフィルタリング", - "visTypeVislib.vislib.legend.filterOutValueButtonAriaLabel": "値 {legendDataLabel} を除外", - "visTypeVislib.vislib.legend.loadingLabel": "読み込み中…", - "visTypeVislib.vislib.legend.setColorScreenReaderDescription": "値 {legendDataLabel} の色を設定", - "visTypeVislib.vislib.legend.toggleLegendButtonAriaLabel": "凡例を切り替える", - "visTypeVislib.vislib.legend.toggleLegendButtonTitle": "凡例を切り替える", - "visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}、トグルオプション", + "home.tutorials.redisLogs.longDescription": "「redis」Filebeat モジュールは、Redis が作成したエラーとスローログをパースします。Redis がエラーログを作成するには、Redis 構成ファイルの「logfile」オプションが「redis-server.log」に設定されていることを確認してください。スローログは「SLOWLOG」コマンドで Redis から直接的に読み込まれます。Redis でスローログを記録するには、「slowlog-log-slower-than」オプションが設定されていることを確認してください。「slowlog」ファイルセットは実験的なものであることに注意してください。[詳細]({learnMoreLink})。", + "home.tutorials.redisLogs.nameTitle": "Redis ログ", + "home.tutorials.redisLogs.shortDescription": "Redis が作成したエラーとスローログを収集しパースします。", + "home.tutorials.redisMetrics.artifacts.dashboards.linkLabel": "Redis メトリックダッシュボード", + "home.tutorials.redisMetrics.longDescription": "Metricbeat モジュール「redis」は、Redis サーバーから内部メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.redisMetrics.nameTitle": "Redis メトリック", + "home.tutorials.redisMetrics.shortDescription": "Redis から内部メトリックを取得します。", + "home.tutorials.stanMetrics.artifacts.dashboards.linkLabel": "Stan メトリックダッシュボード", + "home.tutorials.stanMetrics.longDescription": "Metricbeat モジュール「stan」は、STAN から監視メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.stanMetrics.nameTitle": "STAN メトリック", + "home.tutorials.stanMetrics.shortDescription": "STAN サーバーから監視メトリックを取得します。", + "home.tutorials.statsdMetrics.longDescription": "Metricbeat モジュール「statsd」は、statsd から監視メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.statsdMetrics.nameTitle": "statsd メトリック", + "home.tutorials.statsdMetrics.shortDescription": "statsd から監視メトリックを取得します。", + "home.tutorials.suricataLogs.artifacts.dashboards.linkLabel": "Suricata ログダッシュボード", + "home.tutorials.suricataLogs.longDescription": "「suricata」Filebeat モジュールは、[Suricata Eve JSON アウトプット](https://suricata.readthedocs.io/en/latest/output/eve/eve-json-format.html) からログを収集します。[詳細]({learnMoreLink})。", + "home.tutorials.suricataLogs.nameTitle": "Suricata ログ", + "home.tutorials.suricataLogs.shortDescription": "Suricata IDS/IPS/NSM が作成したログを収集します。", + "home.tutorials.systemLogs.artifacts.dashboards.linkLabel": "システムログダッシュボード", + "home.tutorials.systemLogs.longDescription": "「system」Filebeat モジュールは、一般的な Unix/Linux ベースのディストリビューションのシステムログサービスが作成したログを収集しパースします。このモジュールは Windows では利用できません。[詳細]({learnMoreLink})。", + "home.tutorials.systemLogs.nameTitle": "システムログ", + "home.tutorials.systemLogs.shortDescription": "ローカル Syslog サーバーが作成したログを収集しパースします。", + "home.tutorials.systemMetrics.artifacts.dashboards.linkLabel": "システムメトリックダッシュボード", + "home.tutorials.systemMetrics.longDescription": "Metricbeat モジュール「system」は、ホストから CPU、メモリー、ネットワーク、ディスクの統計を収集します。システム全体の統計とプロセスやファイルシステムごとの統計を収集します。[詳細]({learnMoreLink})。", + "home.tutorials.systemMetrics.nameTitle": "システムメトリック", + "home.tutorials.systemMetrics.shortDescription": "ホストから CPU、メモリー、ネットワーク、ディスクの統計を収集します。", + "home.tutorials.traefikLogs.artifacts.dashboards.linkLabel": "Traefik ログダッシュボード", + "home.tutorials.traefikLogs.longDescription": "「traefik」Filebeat モジュールが、Traefik により作成されたアクセスログをパースします。[詳細]({learnMoreLink})。", + "home.tutorials.traefikLogs.nameTitle": "Traefik ログ", + "home.tutorials.traefikLogs.shortDescription": "Traefik Proxy により作成されたアクセスログを収集しパースします。", + "home.tutorials.traefikMetrics.longDescription": "Metricbeat モジュール「traefik」は、Traefik から監視メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.traefikMetrics.nameTitle": "Traefik メトリック", + "home.tutorials.traefikMetrics.shortDescription": "Traefik から監視メトリックを取得します。", + "home.tutorials.uptimeMonitors.artifacts.dashboards.linkLabel": "Uptime アプリ", + "home.tutorials.uptimeMonitors.longDescription": "アクティブなプロービングでサービスの稼働状況を監視します。 Heartbeat は URL のリストに基づいて質問します。稼働していますか? [詳細]({learnMoreLink})。", + "home.tutorials.uptimeMonitors.nameTitle": "稼働状況監視", + "home.tutorials.uptimeMonitors.shortDescription": "サービスの稼働状況を監視します。", + "home.tutorials.uwsgiMetrics.artifacts.dashboards.linkLabel": "uWSGI メトリックダッシュボード", + "home.tutorials.uwsgiMetrics.longDescription": "Metricbeat モジュール「uwsgi」は、uWSGI サーバーから内部メトリックを取得します。[詳細]({learnMoreLink})。", + "home.tutorials.uwsgiMetrics.nameTitle": "uWSGI メトリック", + "home.tutorials.uwsgiMetrics.shortDescription": "uWSGI サーバーから内部メトリックを取得します。", + "home.tutorials.vsphereMetrics.artifacts.application.label": "発見", + "home.tutorials.vsphereMetrics.longDescription": "「vsphere」Metricbeat モジュールは、vSphere クラスターから内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.vsphereMetrics.nameTitle": "vSphere メトリック", + "home.tutorials.vsphereMetrics.shortDescription": "vSphere から内部メトリックを取得します。", + "home.tutorials.windowsEventLogs.artifacts.application.label": "SIEM アプリ", + "home.tutorials.windowsEventLogs.longDescription": "Winlogbeat を使用して Windows イベントログからログを収集します。[詳細]({learnMoreLink})。", + "home.tutorials.windowsEventLogs.nameTitle": "Windows イベントログ", + "home.tutorials.windowsEventLogs.shortDescription": "Windows イベントログからイベントを取得します。", + "home.tutorials.windowsMetrics.artifacts.application.label": "発見", + "home.tutorials.windowsMetrics.longDescription": "「windows」Metricbeat モジュールは、Windows から内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.windowsMetrics.nameTitle": "Windows メトリック", + "home.tutorials.windowsMetrics.shortDescription": "Windows から内部メトリックを取得します。", + "home.tutorials.zeekLogs.artifacts.dashboards.linkLabel": "Zeek ログダッシュボード", + "home.tutorials.zeekLogs.longDescription": "「zeek」Filebeat モジュールが、[Zeek](https://www.zeek.org//documentation/index.html) からログを収集します。[詳細]({learnMoreLink})。", + "home.tutorials.zeekLogs.nameTitle": "Zeek ログ", + "home.tutorials.zeekLogs.shortDescription": "Zeek/Bro により作成されたログを収集します。", + "home.tutorials.zookeeperMetrics.artifacts.application.label": "発見", + "home.tutorials.zookeeperMetrics.longDescription": "「{moduleName}」Metricbeat モジュールは、Zookeeper サーバーから内部メトリックを取得します。 [詳細]({learnMoreLink})。", + "home.tutorials.zookeeperMetrics.nameTitle": "Zookeeper メトリック", + "home.tutorials.zookeeperMetrics.shortDescription": "Zookeeper サーバーから内部メトリックを取得します。", + "home.welcomeDescription": "Elastic Stack への入口", + "home.welcomeHomePageHeader": "Kibana ホーム", + "home.welcomeTitle": "Elastic Kibana へようこそ", + "inputControl.control.noIndexPatternTooltip": "index-pattern id が見つかりませんでした: {indexPatternId}.", + "inputControl.control.notInitializedTooltip": "コントロールが初期化されていません", + "inputControl.control.noValuesDisableTooltip": "「{indexPatternName}」インデックスパターンでいずれのドキュメントにも存在しない「{fieldName}」フィールドがフィルターの対象になっています。異なるフィールドを選択するか、このフィールドに値が入力されているドキュメントをインデックスしてください。", + "inputControl.editor.controlEditor.controlLabel": "コントロールラベル", + "inputControl.editor.controlEditor.moveControlDownAriaLabel": "コントロールを下に移動", + "inputControl.editor.controlEditor.moveControlUpAriaLabel": "コントロールを上に移動", + "inputControl.editor.controlEditor.removeControlAriaLabel": "コントロールを削除", + "inputControl.editor.controlsTab.addButtonLabel": "追加", + "inputControl.editor.controlsTab.select.addControlAriaLabel": "コントロールを追加", + "inputControl.editor.controlsTab.select.controlTypeAriaLabel": "コントロールタイプを選択してください", + "inputControl.editor.controlsTab.select.listDropDownOptionLabel": "オプションリスト", + "inputControl.editor.controlsTab.select.rangeDropDownOptionLabel": "範囲スライダー", + "inputControl.editor.fieldSelect.fieldLabel": "フィールド", + "inputControl.editor.fieldSelect.selectFieldPlaceholder": "フィールドを選択してください...", + "inputControl.editor.indexPatternSelect.patternLabel": "インデックスパターン", + "inputControl.editor.indexPatternSelect.patternPlaceholder": "インデックスパターンを選択してください", + "inputControl.editor.listControl.dynamicOptions.stringFieldDescription": "「文字列」フィールドでのみ利用可能", + "inputControl.editor.listControl.dynamicOptions.updateDescription": "ユーザーインプットに対する更新オプション", + "inputControl.editor.listControl.dynamicOptionsLabel": "ダイナミックオプション", + "inputControl.editor.listControl.multiselectDescription": "複数選択を許可", + "inputControl.editor.listControl.multiselectLabel": "複数選択", + "inputControl.editor.listControl.parentDescription": "オプションは親コントロールの値がベースになっています。親が設定されていない場合は無効です。", + "inputControl.editor.listControl.parentLabel": "親コントロール", + "inputControl.editor.listControl.sizeDescription": "オプション数", + "inputControl.editor.listControl.sizeLabel": "サイズ", + "inputControl.editor.optionsTab.pinFiltersLabel": "すべてのアプリケーションのフィルターをピン付け", + "inputControl.editor.optionsTab.updateFilterLabel": "変更するごとに Kibana フィルターを更新", + "inputControl.editor.optionsTab.useTimeFilterLabel": "時間フィルターを使用", + "inputControl.editor.rangeControl.decimalPlacesLabel": "小数部分の桁数", + "inputControl.editor.rangeControl.stepSizeLabel": "ステップサイズ", + "inputControl.function.help": "インプットコントロールビジュアライゼーション", + "inputControl.listControl.disableTooltip": "「{label}」が設定されるまで無効です。", + "inputControl.listControl.unableToFetchTooltip": "用語を取得できません、エラー: {errorMessage}", + "inputControl.rangeControl.unableToFetchTooltip": "範囲 (最低値と最高値) を取得できません、エラー: {errorMessage}", + "inputControl.register.controlsDescription": "ダッシュボードを簡単に操作できるように、インタラクティブなコントロールを作成します。", + "inputControl.register.controlsTitle": "コントロール", + "inputControl.register.tabs.controlsTitle": "コントロール", + "inputControl.register.tabs.optionsTitle": "オプション", + "inputControl.vis.inputControlVis.applyChangesButtonLabel": "変更を適用", + "inputControl.vis.inputControlVis.cancelChangesButtonLabel": "変更をキャンセル", + "inputControl.vis.inputControlVis.clearFormButtonLabel": "用語を消去", + "inputControl.vis.listControl.partialResultsWarningMessage": "リクエストに長くかかり過ぎているため、用語リストが不完全な可能性があります。完全な結果を得るには、kibana.yml の自動完了設定を調整してください。", + "inputControl.vis.listControl.selectPlaceholder": "選択してください…", + "inputControl.vis.listControl.selectTextPlaceholder": "選択してください…", + "inspector.closeButton": "インスペクターを閉じる", + "inspector.data.dataDescriptionTooltip": "ビジュアライゼーションの元のデータを表示", + "inspector.data.dataTitle": "データ", + "inspector.data.downloadCSVButtonLabel": "CSV をダウンロード", + "inspector.data.downloadCSVToggleButtonLabel": "CSV をダウンロード", + "inspector.data.downloadOptionsUnsavedFilename": "(未保存)", + "inspector.data.filterForValueButtonAriaLabel": "値でフィルタリング", + "inspector.data.filterForValueButtonTooltip": "値でフィルタリング", + "inspector.data.filterOutValueButtonAriaLabel": "値を除外", + "inspector.data.filterOutValueButtonTooltip": "値を除外", + "inspector.data.formattedCSVButtonLabel": "フォーマット済み CSV", + "inspector.data.formattedCSVButtonTooltip": "データを表形式でダウンロード", + "inspector.data.gatheringDataLabel": "データを収集中", + "inspector.data.noDataAvailableDescription": "エレメントがデータを提供しませんでした。", + "inspector.data.noDataAvailableTitle": "利用可能なデータがありません", + "inspector.data.rawCSVButtonLabel": "CSV", + "inspector.data.rawCSVButtonTooltip": "日付をタイムスタンプとしてなど、提供されたデータをそのままダウンロードします", + "inspector.reqTimestampDescription": "リクエストの開始が記録された時刻です", + "inspector.reqTimestampKey": "リクエストのタイムスタンプ", + "inspector.requests.descriptionRowIconAriaLabel": "説明", + "inspector.requests.failedLabel": " (失敗)", + "inspector.requests.noRequestsLoggedDescription.elementHasNotLoggedAnyRequestsText": "エレメントが (まだ) リクエストを記録していません。", + "inspector.requests.noRequestsLoggedDescription.whatDoesItUsuallyMeanText": "これは通常、データを取得する必要がないか、エレメントがまだデータの取得を開始していないことを意味します。", + "inspector.requests.noRequestsLoggedTitle": "リクエストが記録されていません", + "inspector.requests.requestFailedTooltipTitle": "リクエストに失敗しました", + "inspector.requests.requestInProgressAriaLabel": "リクエスト進行中", + "inspector.requests.requestLabel": "リクエスト", + "inspector.requests.requestsDescriptionTooltip": "データを収集したリクエストを表示します", + "inspector.requests.requestsTitle": "リクエスト", + "inspector.requests.requestSucceededTooltipTitle": "リクエスト成功", + "inspector.requests.requestTabLabel": "リクエスト", + "inspector.requests.requestTimeLabel": "{requestTime}ms", + "inspector.requests.requestTooltipDescription": "リクエストの合計所要時間です。", + "inspector.requests.requestWasMadeDescription": "{requestsCount, plural, one {# リクエストが} other {# リクエストが} } 行われました{failedRequests}", + "inspector.requests.requestWasMadeDescription.requestHadFailureText": "、{failedCount} 件に失敗がありました", + "inspector.requests.responseTabLabel": "応答", + "inspector.requests.statisticsTabLabel": "統計", + "inspector.title": "インスペクター", + "inspector.view": "{viewName} を表示", + "kbn.advancedSettings.context.defaultSizeText": "コンテキストビューに表示される周りのエントリーの数", + "kbn.advancedSettings.context.defaultSizeTitle": "コンテキストサイズ", + "kbn.advancedSettings.context.sizeStepText": "コンテキストサイズを増減させる際の最低単位です", + "kbn.advancedSettings.context.sizeStepTitle": "コンテキストサイズのステップ", + "kbn.advancedSettings.context.tieBreakerFieldsText": "同じタイムスタンプ値のドキュメントを区別するためのコンマ区切りのフィールドのリストです。このリストから、現在のインデックスパターンに含まれ並べ替え可能な初めのフィールドが使用されます。", + "kbn.advancedSettings.context.tieBreakerFieldsTitle": "タイブレーカーフィールド", + "kbn.advancedSettings.courier.batchSearchesText": "無効の場合、ダッシュボードパネルは個々に読み込まれ、検索リクエストはユーザーが移動するか\n クエリを更新すると停止します。有効の場合、ダッシュボードパネルはすべてのデータが読み込まれると同時に読み込まれ、\n 検索は停止しません。", + "kbn.advancedSettings.courier.batchSearchesTextDeprecation": "この設定はサポートが終了し、Kibana 8.0 では削除されます。", + "kbn.advancedSettings.courier.batchSearchesTitle": "同時検索のバッチ処理", + "kbn.advancedSettings.courier.customRequestPreference.requestPreferenceLinkText": "リクエスト設定", + "kbn.advancedSettings.courier.customRequestPreferenceText": "{setRequestReferenceSetting} が {customSettingValue} に設定されている時に使用される {requestPreferenceLink} です。", + "kbn.advancedSettings.courier.customRequestPreferenceTitle": "カスタムリクエスト設定", + "kbn.advancedSettings.courier.ignoreFilterText": "この構成は、似ていないインデックスにアクセスするビジュアライゼーションを含むダッシュボードのサポートを強化します。無効にすると、すべてのフィルターがすべてのビジュアライゼーションに適用されます。有効にすると、ビジュアライゼーションのインデックスにフィルター対象のフィールドが含まれていない場合、ビジュアライゼーションの際にフィルターが無視されます。", + "kbn.advancedSettings.courier.ignoreFilterTitle": "フィルターの無視", + "kbn.advancedSettings.courier.maxRequestsText": "Kibana から送信された _msearch requests リクエストに使用される {maxRequestsLink} 設定を管理します。この構成を無効にして Elasticsearch のデフォルトを使用するには、0 に設定します。", + "kbn.advancedSettings.courier.maxRequestsTitle": "最大同時シャードリクエスト", + "kbn.advancedSettings.courier.requestPreferenceCustom": "カスタム", + "kbn.advancedSettings.courier.requestPreferenceNone": "なし", + "kbn.advancedSettings.courier.requestPreferenceSessionId": "セッション ID", + "kbn.advancedSettings.courier.requestPreferenceText": "どのシャードが検索リクエストを扱うかを設定できます。
    \n
  • {sessionId}: 同じシャードのすべての検索リクエストを実行するため、オペレーションを制限します。\n これにはリクエスト間でシャードのキャッシュを共有できるというメリットがあります。
  • \n
  • {custom}: 独自の設定が可能になります。\n couriercustomRequestPreference で設定値をカスタマイズします。
  • \n
  • {none}: 設定されていないことを意味します。\n これにより、リクエストが全シャードコピー間に分散されるため、パフォーマンスが改善される可能性があります。\n ただし、シャードによって更新ステータスが異なる場合があるため、結果に矛盾が生じる可能性があります。
  • \n
", + "kbn.advancedSettings.courier.requestPreferenceTitle": "リクエスト設定", + "kbn.advancedSettings.csv.quoteValuesText": "csv エクスポートに値を引用するかどうかです", + "kbn.advancedSettings.csv.quoteValuesTitle": "CSV の値を引用", + "kbn.advancedSettings.csv.separatorText": "エクスポートされた値をこの文字列で区切ります", + "kbn.advancedSettings.csv.separatorTitle": "CSV セパレーター", + "kbn.advancedSettings.darkModeText": "Kibana UI のダークモードを有効にします。この設定を適用するにはページの更新が必要です。", + "kbn.advancedSettings.darkModeTitle": "ダークモード", + "kbn.advancedSettings.dateFormat.dayOfWeekText": "週の初めの曜日を設定します", + "kbn.advancedSettings.dateFormat.dayOfWeekTitle": "曜日", + "kbn.advancedSettings.dateFormat.optionsLinkText": "フォーマット", + "kbn.advancedSettings.dateFormat.scaled.intervalsLinkText": "ISO8601 間隔", + "kbn.advancedSettings.dateFormat.scaledText": "時間ベースのデータが順番にレンダリングされ、フォーマットされたタイムスタンプが測定値の間隔に適応すべき状況で使用されるフォーマットを定義する値です。キーは {intervalsLink}。", + "kbn.advancedSettings.dateFormat.scaledTitle": "スケーリングされたデータフォーマットです", + "kbn.advancedSettings.dateFormat.timezoneText": "使用されるタイムゾーンです。{defaultOption} ではご使用のブラウザにより検知されたタイムゾーンが使用されます。", + "kbn.advancedSettings.dateFormat.timezoneTitle": "データフォーマットのタイムゾーン", + "kbn.advancedSettings.dateFormatText": "きちんとフォーマットされたデータを表示する際、この {formatLink} を使用します", + "kbn.advancedSettings.dateFormatTitle": "データフォーマット", + "kbn.advancedSettings.dateNanosFormatText": "Elasticsearch の {dateNanosLink} データタイプに使用されます", + "kbn.advancedSettings.dateNanosFormatTitle": "ナノ秒フォーマットでの日付", + "kbn.advancedSettings.dateNanosLinkTitle": "date_nanos", + "kbn.advancedSettings.defaultColumnsText": "デフォルトでディスカバリタブに表示される列です", + "kbn.advancedSettings.defaultColumnsTitle": "デフォルトの列", + "kbn.advancedSettings.defaultIndexText": "インデックスが設定されていない時にアクセスするインデックスです", + "kbn.advancedSettings.defaultIndexTitle": "デフォルトのインデックス", + "kbn.advancedSettings.defaultRoute.defaultRouteIsRelativeValidationMessage": "相対 URL でなければなりません。", + "kbn.advancedSettings.defaultRoute.defaultRouteText": "この設定は、Kibana 起動時のデフォルトのルートを設定します。この設定で、Kibana 起動時のランディングページを変更できます。経路は相対 URL でなければなりません。", + "kbn.advancedSettings.defaultRoute.defaultRouteTitle": "デフォルトのルート", + "kbn.advancedSettings.disableAnimationsText": "Kibana UI の不要なアニメーションをオフにします。変更を適用するにはページを更新してください。", + "kbn.advancedSettings.disableAnimationsTitle": "アニメーションを無効にする", + "kbn.advancedSettings.discover.aggsTermsSizeText": "「可視化」ボタンをクリックした際に、フィールドドロップダウンやディスカバリサイドバーに可視化される用語の数を設定します。", + "kbn.advancedSettings.discover.aggsTermsSizeTitle": "用語数", + "kbn.advancedSettings.discover.sampleSizeText": "表に表示する行数です", + "kbn.advancedSettings.discover.sampleSizeTitle": "行数", + "kbn.advancedSettings.discover.searchOnPageLoadText": "ディスカバリの最初の読み込み時に検索を実行するかを制御します。この設定は、保存された検索の読み込み時には影響しません。", + "kbn.advancedSettings.discover.searchOnPageLoadTitle": "ページの読み込み時の検索", + "kbn.advancedSettings.discover.sortDefaultOrderText": "ディスカバリアプリのインデックスパターンに基づく時刻のデフォルトの並べ替え方向をコントロールします。", + "kbn.advancedSettings.discover.sortDefaultOrderTitle": "デフォルトの並べ替え方向", + "kbn.advancedSettings.discover.sortOrderAsc": "昇順", + "kbn.advancedSettings.discover.sortOrderDesc": "降順", + "kbn.advancedSettings.docTableHideTimeColumnText": "ディスカバリと、ダッシュボードのすべての保存された検索で、「時刻」列を非表示にします。", + "kbn.advancedSettings.docTableHideTimeColumnTitle": "「時刻」列を非表示", + "kbn.advancedSettings.docTableHighlightText": "ディスカバリと保存された検索ダッシュボードの結果をハイライトします。ハイライトすることで、大きなドキュメントを扱う際にリクエストが遅くなります。", + "kbn.advancedSettings.docTableHighlightTitle": "結果をハイライト", + "kbn.advancedSettings.fieldsPopularLimitText": "最も頻繁に使用されるフィールドのトップ N を表示します", + "kbn.advancedSettings.fieldsPopularLimitTitle": "頻繁に使用されるフィールドの制限", + "kbn.advancedSettings.format.bytesFormat.numeralFormatLinkText": "数字フォーマット", + "kbn.advancedSettings.format.bytesFormatText": "「バイト」フォーマットのデフォルト {numeralFormatLink} です", + "kbn.advancedSettings.format.bytesFormatTitle": "バイトフォーマット", + "kbn.advancedSettings.format.currencyFormat.numeralFormatLinkText": "数字フォーマット", + "kbn.advancedSettings.format.currencyFormatText": "「通貨」フォーマットのデフォルト {numeralFormatLink} です", + "kbn.advancedSettings.format.currencyFormatTitle": "通貨フォーマット", + "kbn.advancedSettings.format.defaultTypeMapText": "各フィールドタイプにデフォルトで使用するフォーマット名のマップです。フィールドタイプが特に指定されていない場合は {defaultFormat} が使用されます", + "kbn.advancedSettings.format.defaultTypeMapTitle": "フィールドタイプフォーマット名", + "kbn.advancedSettings.format.formattingLocale.numeralLanguageLinkText": "数字言語", + "kbn.advancedSettings.format.formattingLocaleText": "{numeralLanguageLink} ロケール", + "kbn.advancedSettings.format.formattingLocaleTitle": "フォーマットロケール", + "kbn.advancedSettings.format.numberFormat.numeralFormatLinkText": "数字フォーマット", + "kbn.advancedSettings.format.numberFormatText": "「数字」フォーマットのデフォルト {numeralFormatLink} です", + "kbn.advancedSettings.format.numberFormatTitle": "数字フォーマット", + "kbn.advancedSettings.format.percentFormat.numeralFormatLinkText": "数字フォーマット", + "kbn.advancedSettings.format.percentFormatText": "「パーセント」フォーマットのデフォルト {numeralFormatLink} です", + "kbn.advancedSettings.format.percentFormatTitle": "パーセントフォーマット", + "kbn.advancedSettings.histogram.barTargetText": "日付ヒストグラムで「自動」間隔を使用する際、この数に近いバーの作成を試みます", + "kbn.advancedSettings.histogram.barTargetTitle": "目標バー数", + "kbn.advancedSettings.histogram.maxBarsText": "日付ヒストグラムに表示されるバーの数の上限です。必要に応じて値をスケーリングしてください", + "kbn.advancedSettings.histogram.maxBarsTitle": "最高バー数", + "kbn.advancedSettings.historyLimitText": "履歴があるフィールド (例: クエリインプット) に個の数の最近の値が表示されます", + "kbn.advancedSettings.historyLimitTitle": "履歴制限数", + "kbn.advancedSettings.indexPatternPlaceholderText": "「管理 > インデックスパターン > インデックスパターンを作成」で使用される「インデックスパターン名」フィールドのプレースホルダーです。", + "kbn.advancedSettings.indexPatternPlaceholderTitle": "インデックスパターンのプレースホルダー", + "kbn.advancedSettings.maxBucketsText": "1 つのデータソースが返せるバケットの最大数です", + "kbn.advancedSettings.maxBucketsTitle": "バケットの最大数", + "kbn.advancedSettings.maxCellHeightText": "表のセルが使用する高さの上限です。この切り捨てを無効にするには 0 に設定します", + "kbn.advancedSettings.maxCellHeightTitle": "表のセルの高さの上限", + "kbn.advancedSettings.metaFieldsText": "_source の外にあり、ドキュメントが表示される時に融合されるフィールドです", + "kbn.advancedSettings.metaFieldsTitle": "メタフィールド", + "kbn.advancedSettings.notifications.banner.markdownLinkText": "マークダウン対応", + "kbn.advancedSettings.notifications.bannerLifetimeText": "バナー通知が画面に表示されるミリ秒単位での時間です。{infinityValue} に設定するとカウントダウンが無効になります。", + "kbn.advancedSettings.notifications.bannerLifetimeTitle": "バナー通知時間", + "kbn.advancedSettings.notifications.bannerText": "すべてのユーザーへの一時的な通知を目的としたカスタムバナーです。{markdownLink}", + "kbn.advancedSettings.notifications.bannerTitle": "カスタムバナー通知", + "kbn.advancedSettings.notifications.errorLifetimeText": "エラー通知が画面に表示されるミリ秒単位での時間です。{infinityValue} に設定すると無効になります。", + "kbn.advancedSettings.notifications.errorLifetimeTitle": "エラー通知時間", + "kbn.advancedSettings.notifications.infoLifetimeText": "情報通知が画面に表示されるミリ秒単位での時間です。{infinityValue} に設定すると無効になります。", + "kbn.advancedSettings.notifications.infoLifetimeTitle": "情報通知時間", + "kbn.advancedSettings.notifications.warningLifetimeText": "警告通知が画面に表示されるミリ秒単位での時間です。{infinityValue} に設定すると無効になります。", + "kbn.advancedSettings.notifications.warningLifetimeTitle": "警告通知時間", + "kbn.advancedSettings.pinFiltersText": "フィルターがデフォルトでグローバル (ピン付けされた状態) になるかの設定です", + "kbn.advancedSettings.pinFiltersTitle": "フィルターをデフォルトでピン付けする", + "kbn.advancedSettings.query.allowWildcardsText": "設定すると、クエリ句の頭に * が使えるようになります。現在クエリバーで実験的クエリ機能が有効になっている場合にのみ適用されます。基本的な Lucene クエリでリーディングワイルドカードを無効にするには、{queryStringOptionsPattern} を使用します。", + "kbn.advancedSettings.query.allowWildcardsTitle": "クエリでリーディングワイルドカードを許可する", + "kbn.advancedSettings.query.queryStringOptions.optionsLinkText": "オプション", + "kbn.advancedSettings.query.queryStringOptionsText": "Lucene クエリ文字列パーサーの {optionsLink}「{queryLanguage}」が {luceneLanguage} に設定されている時にのみ使用されます。", + "kbn.advancedSettings.query.queryStringOptionsTitle": "クエリ文字列のオプション", + "kbn.advancedSettings.savedObjects.listingLimitText": "一覧ページ用に取得するオブジェクトの数です", + "kbn.advancedSettings.savedObjects.listingLimitTitle": "オブジェクト取得制限", + "kbn.advancedSettings.savedObjects.perPageText": "読み込みダイアログで表示されるページごとのオブジェクトの数です", + "kbn.advancedSettings.savedObjects.perPageTitle": "ページごとのオブジェクト数", + "kbn.advancedSettings.searchQueryLanguageKql": "KQL", + "kbn.advancedSettings.searchQueryLanguageLucene": "Lucene", + "kbn.advancedSettings.searchQueryLanguageText": "クエリバーで使用されるクエリ言語です。KQL は Kibana 用に特別に開発された新しい言語です。", + "kbn.advancedSettings.searchQueryLanguageTitle": "クエリ言語", + "kbn.advancedSettings.shortenFieldsText": "長いフィールドを短くします。例: foo.bar.baz の代わりに f.b.baz と表示", + "kbn.advancedSettings.shortenFieldsTitle": "フィールドの短縮", + "kbn.advancedSettings.sortOptions.optionsLinkText": "オプション", + "kbn.advancedSettings.sortOptionsText": "Elasticsearch の並べ替えパラメーターの {optionsLink}", + "kbn.advancedSettings.sortOptionsTitle": "並べ替えオプション", + "kbn.advancedSettings.storeUrlText": "URL は長くなりすぎてブラウザが対応できない場合があります。セッションストレージに URL の一部を保存することがで この問題に対処できるかテストしています。結果を教えてください!", + "kbn.advancedSettings.storeUrlTitle": "セッションストレージに URL を格納", + "kbn.advancedSettings.suggestFilterValuesText": "フィルターエディターがフィールドの値の候補を表示しないようにするには、このプロパティを false にしてください。", + "kbn.advancedSettings.suggestFilterValuesTitle": "フィルターエディターの候補値", + "kbn.advancedSettings.timepicker.last15Minutes": "過去 15 分間", + "kbn.advancedSettings.timepicker.last1Hour": "過去 1 時間", + "kbn.advancedSettings.timepicker.last1Year": "過去 1 年間", + "kbn.advancedSettings.timepicker.last24Hours": "過去 24 時間", + "kbn.advancedSettings.timepicker.last30Days": "過去 30 日間", + "kbn.advancedSettings.timepicker.last30Minutes": "過去 30 分間", + "kbn.advancedSettings.timepicker.last7Days": "過去 7 日間", + "kbn.advancedSettings.timepicker.last90Days": "過去 90 日間", + "kbn.advancedSettings.timepicker.quickRanges.acceptedFormatsLinkText": "対応フォーマット", + "kbn.advancedSettings.timepicker.quickRangesText": "タイムピッカーのクイックセクションに表示される範囲のリストです。それぞれのオブジェクトに「開始」、「終了」({acceptedFormatsLink} を参照)、「表示」(表示するタイトル) が含まれるオブジェクトの配列です。", + "kbn.advancedSettings.timepicker.quickRangesTitle": "タイムピッカーのクイック範囲", + "kbn.advancedSettings.timepicker.refreshIntervalDefaultsText": "時間フィルターのデフォルト更新間隔", + "kbn.advancedSettings.timepicker.refreshIntervalDefaultsTitle": "タイムピッカーの更新間隔", + "kbn.advancedSettings.timepicker.thisWeek": "今週", + "kbn.advancedSettings.timepicker.timeDefaultsText": "時間フィルターが選択されずに Kibana が起動した際に使用される時間フィルターです", + "kbn.advancedSettings.timepicker.timeDefaultsTitle": "デフォルトのタイムピッカー", + "kbn.advancedSettings.timepicker.today": "今日", + "kbn.advancedSettings.visualization.colorMappingText": "ビジュアライゼーション内の特定の色のマップ値です", + "kbn.advancedSettings.visualization.colorMappingTitle": "カラーマッピング", + "kbn.advancedSettings.visualization.dimmingOpacityText": "チャートの別のエレメントが選択された時に暗くなるチャート項目の透明度です。この数字が小さければ小さいほど、ハイライトされたエレメントが目立ちます。0 と 1 の間の数字で設定します。", + "kbn.advancedSettings.visualization.dimmingOpacityTitle": "減光透明度", + "kbn.advancedSettings.visualization.heatmap.maxBucketsText": "1 つのデータソースが返せるバケットの最大数です。値が大きいとブラウザのレンダリング速度が下がる可能性があります。", + "kbn.advancedSettings.visualization.heatmap.maxBucketsTitle": "ヒートマップの最大バケット数", + "kbn.advancedSettings.visualization.loadingDelayText": "クエリの際にビジュアライゼーションを暗くするまでの時間です", + "kbn.advancedSettings.visualization.loadingDelayTitle": "読み込み遅延", + "kbn.advancedSettings.visualization.showRegionMapWarningsText": "用語がマップの形に合わない場合に地域マップに警告を表示するかどうかです。", + "kbn.advancedSettings.visualization.showRegionMapWarningsTitle": "地域マップに警告を表示", + "kbn.advancedSettings.visualization.tileMap.maxPrecision.cellDimensionsLinkText": "ディメンションの説明", + "kbn.advancedSettings.visualization.tileMap.maxPrecisionText": "マップに表示されるジオハッシュの最高精度です。7 が高い、10 が非常に高い、12 が最高を意味します。{cellDimensionsLink}", + "kbn.advancedSettings.visualization.tileMap.maxPrecisionTitle": "タイルマップの最高精度", + "kbn.advancedSettings.visualization.tileMap.wmsDefaults.propertiesLinkText": "プロパティ", + "kbn.advancedSettings.visualization.tileMap.wmsDefaultsText": "座標マップの WMS マップサーバーサポートのデフォルトの {propertiesLink} です。", + "kbn.advancedSettings.visualization.tileMap.wmsDefaultsTitle": "デフォルトの WMS プロパティ", + "kbn.advancedSettings.visualizeEnableLabsText": "ユーザーが実験的なビジュアライゼーションを作成、表示、編集できるようになります。無効の場合、\n ユーザーは本番準備が整ったビジュアライゼーションのみを利用できます。", + "kbn.advancedSettings.visualizeEnableLabsTitle": "実験的なビジュアライゼーションを有効にする", + "kbn.context.breadcrumb": "{indexPatternTitle}#{docId} のコンテキスト", + "kbn.context.failedToLoadAnchorDocumentDescription": "別ののドキュメントの読み込みに失敗しました", + "kbn.context.failedToLoadAnchorDocumentErrorDescription": "別のドキュメントの読み込みに失敗しました。", + "kbn.context.loadButtonLabel": "読み込み", + "kbn.context.loadingDescription": "読み込み中…", + "kbn.context.newerDocumentsAriaLabel": "新しいドキュメントの数", + "kbn.context.newerDocumentsDescription": "新しいドキュメント", + "kbn.context.newerDocumentsWarning": "アンカーよりも新しいドキュメントは {docCount} 件しか見つかりませんでした。", + "kbn.context.newerDocumentsWarningZero": "アンカーよりも新しいドキュメントは見つかりませんでした。", + "kbn.context.noSearchableTiebreakerFieldDescription": "インデックスパターン {indexPatternId} で検索可能なタイブレーカーフィールドが見つかりませんでした。高度な設定 {tieBreakerFields} tを変更してこのインデックスパターンの有効なフィールドを含めてください。", + "kbn.context.olderDocumentsAriaLabel": "古いドキュメントの数", + "kbn.context.olderDocumentsDescription": "古いドキュメント", + "kbn.context.olderDocumentsWarning": "アンカーよりも古いドキュメントは {docCount} 件しか見つかりませんでした。", + "kbn.context.olderDocumentsWarningZero": "アンカーよりも古いドキュメントは見つかりませんでした。", + "kbn.context.reloadPageDescription.discoverLinkText": "ディスカバリ", + "kbn.context.reloadPageDescription.reloadOrVisitTextMessage": "再読み込みするか", + "kbn.context.reloadPageDescription.selectValidAnchorDocumentTextMessage": "にアクセスして有効な別のドキュメントを選択してください。", + "kbn.context.unableToLoadAnchorDocumentDescription": "別のドキュメントが読み込めません", + "kbn.context.unableToLoadDocumentDescription": "ドキュメントが読み込めません", + "kbn.dashboard.listing.table.descriptionColumnName": "説明", + "kbn.dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage": "「6.1.0」のダッシュボードの互換性のため、パネルデータを移行できませんでした。パネルには想定された列または行フィールドがありません", + "kbn.dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage": "「6.3.0」のダッシュボードの互換性のため、パネルデータを移行できませんでした。パネルに必要なフィールドがありません: {key}", + "kbn.dashboard.savedDashboardsTitle": "ダッシュボード", + "kbn.dashboardTitle": "ダッシュボード", + "kbn.devToolsTitle": "開発ツール", + "kbn.discover.backToTopLinkText": "最上部へ戻る。", + "kbn.discover.badge.readOnly.text": "読み込み専用", + "kbn.discover.badge.readOnly.tooltip": "検索を保存できません", + "kbn.discover.bucketIntervalTooltip": "この間隔は選択された時間範囲に表示される {bucketsDescription} が作成されるため、{bucketIntervalDescription} にスケーリングされています。", + "kbn.discover.bucketIntervalTooltip.tooLargeBucketsText": "大きすぎるバケット", + "kbn.discover.bucketIntervalTooltip.tooManyBucketsText": "バケットが多すぎます", + "kbn.discover.discoverBreadcrumbTitle": "ディスカバリ", + "kbn.discover.discoverDescription": "ドキュメントにクエリをかけたりフィルターを適用することで、データをインタラクティブに閲覧できます。", + "kbn.discover.discoverTitle": "ディスカバー", + "kbn.discover.docTable.pager.toolbarPagerButtons.nextButtonAriaLabel": "表内の次ページ", + "kbn.discover.docTable.pager.toolbarPagerButtons.previousButtonAriaLabel": "表内の前ページ", + "kbn.discover.documentsAriaLabel": "ドキュメント", + "kbn.discover.errorLoadingData": "データの読み込み中にエラーが発生", + "kbn.discover.fetchError.howToAddressErrorDescription": "このエラーは、{scriptedFields} タブにある {managementLink} の {fetchErrorScript} フィールドを編集することで解決できます。", + "kbn.discover.fetchError.managmentLinkText": "管理 > インデックスパターン", + "kbn.discover.fetchError.scriptedFieldsText": "「スクリプトフィールド」", + "kbn.discover.fieldChooser.detailViews.emptyStringText": "空の文字列", + "kbn.discover.fieldChooser.detailViews.recordsText": "記録", + "kbn.discover.fieldChooser.detailViews.topValuesInRecordsDescription": "次の記録のトップ 5 の値", + "kbn.discover.fieldChooser.detailViews.visualizeLinkText": "可視化", + "kbn.discover.fieldChooser.discoverField.addButtonLabel": "追加", + "kbn.discover.fieldChooser.discoverField.removeButtonLabel": "削除", + "kbn.discover.fieldChooser.discoverField.scriptedFieldsTakeLongExecuteDescription": "スクリプトフィールドは実行に時間がかかる場合があります。", + "kbn.discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForGeoFieldsErrorMessage": "ジオフィールドは分析できません。", + "kbn.discover.fieldChooser.fieldCalculator.analysisIsNotAvailableForObjectFieldsErrorMessage": "オブジェクトフィールドは分析できません。", + "kbn.discover.fieldChooser.fieldCalculator.fieldIsNotPresentInDocumentsErrorMessage": "このフィールドは Elasticsearch マッピングに表示されますが、ドキュメントテーブルの {hitsLength} 件のドキュメントには含まれません。可視化や検索は可能な場合があります。", + "kbn.discover.fieldChooser.fieldFilterFacetButtonLabel": "フィルタリングされたフィールド", + "kbn.discover.fieldChooser.filter.aggregatableLabel": "集約可能", + "kbn.discover.fieldChooser.filter.availableFieldsTitle": "利用可能なフィールド", + "kbn.discover.fieldChooser.filter.fieldSelectorLabel": "{id} フィルターオプションの選択", + "kbn.discover.fieldChooser.filter.filterByTypeLabel": "タイプでフィルタリング", + "kbn.discover.fieldChooser.filter.hideMissingFieldsLabel": "未入力のフィールドを非表示", + "kbn.discover.fieldChooser.filter.indexAndFieldsSectionAriaLabel": "インデックスとフィールド", + "kbn.discover.fieldChooser.filter.popularTitle": "人気", + "kbn.discover.fieldChooser.filter.searchableLabel": "検索可能", + "kbn.discover.fieldChooser.filter.selectedFieldsTitle": "スクリプトフィールド", + "kbn.discover.fieldChooser.filter.typeLabel": "タイプ", + "kbn.discover.fieldChooser.indexPattern.changeIndexPatternTitle": "インデックスパターンを変更", + "kbn.discover.fieldChooser.searchPlaceHolder": "検索フィールド", + "kbn.discover.fieldChooser.toggleFieldFilterButtonHideAriaLabel": "フィールド設定を非表示", + "kbn.discover.fieldChooser.toggleFieldFilterButtonShowAriaLabel": "フィールド設定を表示", + "kbn.discover.helpMenu.appName": "ディスカバー", + "kbn.discover.histogram.partialData.bucketTooltipText": "選択された時間範囲にはこのバケット全体は含まれていませんが、一部データが含まれている可能性があります。", + "kbn.discover.histogramOfFoundDocumentsAriaLabel": "発見されたドキュメントのヒストグラム", + "kbn.discover.hitsPluralTitle": "{hits, plural, one {ヒット} other {ヒット}}", + "kbn.discover.howToChangeTheTimeTooltip": "時刻を変更するには、ナビゲーションバーのカレンダーアイコンをクリックします", + "kbn.discover.howToSeeOtherMatchingDocumentsDescription": "これらは検索条件に一致した初めの {sampleSize} 件のドキュメントです。他の結果を表示するには検索条件を絞ってください。 ", + "kbn.discover.inspectorRequestDataTitle": "データ", + "kbn.discover.inspectorRequestDescription": "このリクエストは Elasticsearch にクエリをかけ、検索データを取得します。", + "kbn.discover.localMenu.inspectTitle": "検査", + "kbn.discover.localMenu.localMenu.newSearchTitle": "新規", + "kbn.discover.localMenu.newSearchDescription": "新規検索", + "kbn.discover.localMenu.openInspectorForSearchDescription": "検索用にインスペクターを開きます", + "kbn.discover.localMenu.openSavedSearchDescription": "保存された検索を開きます", + "kbn.discover.localMenu.openTitle": "開く", + "kbn.discover.localMenu.saveSaveSearchDescription": "ビジュアライゼーションとダッシュボードで使用できるようにディスカバリの検索を保存します", + "kbn.discover.localMenu.saveSearchDescription": "検索を保存します", + "kbn.discover.localMenu.saveTitle": "保存", + "kbn.discover.localMenu.shareSearchDescription": "検索を共有します", + "kbn.discover.localMenu.shareTitle": "共有", + "kbn.discover.noResults.addressShardFailuresTitle": "シャードエラーの解決", + "kbn.discover.noResults.expandYourTimeRangeTitle": "時間範囲を拡大", + "kbn.discover.noResults.indexFailureIndexText": "インデックス {failureIndex}", + "kbn.discover.noResults.indexFailureShardText": "{index}、シャード {failureShard}", + "kbn.discover.noResults.queryMayNotMatchTitle": "表示されているインデックスの 1 つまたは複数にデータフィールドが含まれています。クエリが現在の時間範囲のデータと一致しないか、現在選択された時間範囲にデータが全く存在しない可能性があります。データが存在する時間範囲に変えることができます。", + "kbn.discover.noResults.searchExamples.400to499StatusCodeExampleTitle": "400-499 のすべてのステータスコードを検索", + "kbn.discover.noResults.searchExamples.400to499StatusCodeWithPhpExtensionExampleTitle": "400-499 の php 拡張子のステータスコードを検索", + "kbn.discover.noResults.searchExamples.400to499StatusCodeWithPhpOrHtmlExtensionExampleTitle": "400-499 の php または html 拡張子のステータスコードを検索", + "kbn.discover.noResults.searchExamples.anyField200StatusCodeExampleTitle": "いずれかのフィールドに数字 200 が含まれているリクエストを検索", + "kbn.discover.noResults.searchExamples.howTosearchForWebServerLogsDescription": "画面上部の検索バーは、Elasticsearch の Lucene {queryStringSyntaxLink} サポートを利用します。新規フィールドにパースされたウェブサーバーログの検索方法の例は、次の通りです。", + "kbn.discover.noResults.searchExamples.noResultsMatchSearchCriteriaTitle": "検索条件と一致する結果がありません。", + "kbn.discover.noResults.searchExamples.queryStringSyntaxLinkText": "クエリ文字列の構文", + "kbn.discover.noResults.searchExamples.refineYourQueryTitle": "クエリの調整", + "kbn.discover.noResults.searchExamples.statusField200StatusCodeExampleTitle": "ステータスフィールドの 200 を検索", + "kbn.discover.noResults.shardFailuresDescription": "次のシャードエラーが発生しました:", + "kbn.discover.notifications.notSavedSearchTitle": "検索「{savedSearchTitle}」は保存されませんでした。", + "kbn.discover.notifications.savedSearchTitle": "検索「{savedSearchTitle}」が保存されました。", + "kbn.discover.painlessError.painlessScriptedFieldErrorMessage": "Painless スクリプトのフィールド「{script}」のエラー.", + "kbn.discover.reloadSavedSearchButton": "検索をリセット", + "kbn.discover.rootBreadcrumb": "ディスカバリ", + "kbn.discover.savedSearch.savedObjectName": "保存された検索", + "kbn.discover.scaledToDescription": "{bucketIntervalDescription} にスケーリング済み", + "kbn.discover.searchingTitle": "検索中", + "kbn.discover.showingDefaultIndexPatternWarningDescription": "デフォルトのインデックスパターン「{loadedIndexPatternTitle}」 ({loadedIndexPatternId}) を表示中", + "kbn.discover.showingSavedIndexPatternWarningDescription": "保存されたインデックスパターン「{ownIndexPatternTitle}」 ({ownIndexPatternId}) を表示中", + "kbn.discover.skipToBottomButtonLabel": "最下部に移動", + "kbn.discover.topNav.openSearchPanel.manageSearchesButtonLabel": "検索の管理", + "kbn.discover.topNav.openSearchPanel.noSearchesFoundDescription": "一致する検索が見つかりませんでした。", + "kbn.discover.topNav.openSearchPanel.openSearchTitle": "検索を開く", + "kbn.discover.uninitializedRefreshButtonText": "データを更新", + "kbn.discover.uninitializedText": "クエリを作成、フィルターを追加、または [更新] をクリックして、現在のクエリの結果を取得します。", + "kbn.discover.uninitializedTitle": "検索開始", + "kbn.discover.valueIsNotConfiguredIndexPatternIDWarningTitle": "{stateVal} は設定されたインデックスパターン ID ではありません", + "kbn.discoverTitle": "ディスカバリ", + "kbn.doc.couldNotFindDocumentsDescription": "その ID に一致するドキュメントがありません。", + "kbn.doc.failedToExecuteQueryDescription": "クエリの実行に失敗しました", + "kbn.doc.failedToLocateDocumentDescription": "ドキュメントが見つかりませんでした", + "kbn.doc.failedToLocateIndexPattern": "ID {indexPatternId} に一致するインデックスパターンがありません", + "kbn.doc.loadingDescription": "読み込み中…", + "kbn.doc.somethingWentWrongDescription": "{indexName} が欠けています。", + "kbn.doc.somethingWentWrongDescriptionAddon": "インデックスが存在することを確認してください。", + "kbn.docTable.limitedSearchResultLabel": "{resultCount} 件の結果に制限。検索結果の絞り込み。", + "kbn.docTable.noResultsTitle": "結果が見つかりませんでした", + "kbn.docTable.pagerControl.pagesCountLabel": "{startItem}–{endItem} of {totalItems}", + "kbn.docTable.tableHeader.moveColumnLeftButtonAriaLabel": "{columnName} 列を左に移動", + "kbn.docTable.tableHeader.moveColumnLeftButtonTooltip": "列を左に移動", + "kbn.docTable.tableHeader.moveColumnRightButtonAriaLabel": "{columnName} 列を右に移動", + "kbn.docTable.tableHeader.moveColumnRightButtonTooltip": "列を右に移動", + "kbn.docTable.tableHeader.removeColumnButtonAriaLabel": "{columnName} 列を削除", + "kbn.docTable.tableHeader.removeColumnButtonTooltip": "列の削除", + "kbn.docTable.tableHeader.sortByColumnAscendingAriaLabel": "{columnName} を昇順に並べ替える", + "kbn.docTable.tableHeader.sortByColumnDescendingAriaLabel": "{columnName} を降順に並べ替える", + "kbn.docTable.tableHeader.sortByColumnUnsortedAriaLabel": "{columnName} で並べ替えを止める", + "kbn.docTable.tableRow.detailHeading": "拡張ドキュメント", + "kbn.docTable.tableRow.filterForValueButtonAriaLabel": "値でフィルタリング", + "kbn.docTable.tableRow.filterForValueButtonTooltip": "値でフィルタリング", + "kbn.docTable.tableRow.filterOutValueButtonAriaLabel": "値を除外", + "kbn.docTable.tableRow.filterOutValueButtonTooltip": "値を除外", + "kbn.docTable.tableRow.toggleRowDetailsButtonAriaLabel": "行の詳細を切り替える", + "kbn.docTable.tableRow.viewSingleDocumentLinkText": "単一のドキュメントを表示", + "kbn.docTable.tableRow.viewSurroundingDocumentsLinkText": "周りのドキュメントを表示", + "kbn.embeddable.errorTitle": "データの取得中にエラーが発生", + "kbn.embeddable.inspectorRequestDataTitle": "データ", + "kbn.embeddable.inspectorRequestDescription": "このリクエストは Elasticsearch にクエリをかけ、検索データを取得します。", + "kbn.embeddable.search.displayName": "検索", + "kbn.management.createIndexPattern.betaLabel": "ベータ", + "kbn.management.createIndexPattern.emptyState.checkDataButton": "新規データを確認", + "kbn.management.createIndexPattern.emptyStateHeader": "Elasticsearch データが見つかりませんでした", + "kbn.management.createIndexPattern.emptyStateLabel.emptyStateDetail": "{needToIndex} {learnHowLink} または {getStartedLink}", + "kbn.management.createIndexPattern.emptyStateLabel.getStartedLink": "サンプルデータで始めましょう。", + "kbn.management.createIndexPattern.emptyStateLabel.learnHowLink": "方法を学習", + "kbn.management.createIndexPattern.emptyStateLabel.needToIndexLabel": "インデックスパターンを作成する前に、Elasticsearch へのデータのインデックスが必要です。", + "kbn.management.createIndexPattern.includeSystemIndicesToggleSwitchLabel": "システムインデックスを含める", + "kbn.management.createIndexPattern.loadClustersFailMsg": "リモートクラスターの読み込みに失敗", + "kbn.management.createIndexPattern.loadIndicesFailMsg": "インデックスの読み込みに失敗", + "kbn.management.createIndexPattern.loadingState.checkingLabel": "Elasticsearch データを確認中", + "kbn.management.createIndexPattern.step.indexPattern.allowLabel": "インデックスパターンでワイルドカードとして {asterisk} を使用できます。", + "kbn.management.createIndexPattern.step.indexPattern.disallowLabel": "スペースや {characterList} は使用できません。", + "kbn.management.createIndexPattern.step.indexPatternLabel": "インデックスパターン", + "kbn.management.createIndexPattern.step.indexPatternPlaceholder": "index-name-*", + "kbn.management.createIndexPattern.step.invalidCharactersErrorMessage": "{indexPatternName} にはスペースや {characterList} は使えません。", + "kbn.management.createIndexPattern.step.loadingHeader": "一致するインデックスを検索中…", + "kbn.management.createIndexPattern.step.loadingLabel": "お待ちください…", + "kbn.management.createIndexPattern.step.nextStepButton": "次のステップ", + "kbn.management.createIndexPattern.step.pagingLabel": "ページごとの行数: {perPage}", + "kbn.management.createIndexPattern.step.status.matchAnyLabel.matchAnyDetail": "インデックスパターンは下の{strongIndices} の いずれかに一致します。", + "kbn.management.createIndexPattern.step.status.noSystemIndicesLabel": "パターンに一致する Elasticsearch インデックスがありません。", + "kbn.management.createIndexPattern.step.status.noSystemIndicesWithPromptLabel": "パターンに一致する Elasticsearch インデックスがありません。一致するシステムインデックスを表示するには、右上のスイッチを切り替えます。", + "kbn.management.createIndexPattern.step.status.notMatchLabel.allIndicesLabel": "{indicesLength, plural, one {# インデックス} other {# インデックス}}", + "kbn.management.createIndexPattern.step.status.notMatchLabel.notMatchDetail": "入力されたインデックスパターンがどのインデックスにも一致しません。下の {indicesLength, plural, one {} other {}}{strongIndices} と一致させることができます。", + "kbn.management.createIndexPattern.step.status.partialMatchLabel.partialMatchDetail": "インデックスパターンがどのインデックスとも一致ませんが、似た {matchedIndicesLength, plural, one {} other {}}{strongIndices} があります。", + "kbn.management.createIndexPattern.step.status.partialMatchLabel.strongIndicesLabel": "{matchedIndicesLength, plural, one {# インデックス} other {# インデックス}}", + "kbn.management.createIndexPattern.step.status.successLabel.strongIndicesLabel": "{indicesLength, plural, one {# インデックス} other {# インデックス}}", + "kbn.management.createIndexPattern.step.status.successLabel.strongSuccessLabel": "成功!", + "kbn.management.createIndexPattern.step.status.successLabel.successDetail": "{strongSuccess} インデックスパターンが {strongIndices} と一致しています。", + "kbn.management.createIndexPattern.step.warningHeader": "既に {query} という名前のインデックスパターンがあります。", + "kbn.management.createIndexPattern.stepHeader": "ステップ 1/2:インデックスパターンの定義", + "kbn.management.createIndexPattern.stepTime.backButton": "戻る", + "kbn.management.createIndexPattern.stepTime.createPatternButton": "インデックスパターンを作成", + "kbn.management.createIndexPattern.stepTime.creatingLabel": "インデックスパターンを作成中…", + "kbn.management.createIndexPattern.stepTime.error": "エラー", + "kbn.management.createIndexPattern.stepTime.field.loadingDropDown": "読み込み中…", + "kbn.management.createIndexPattern.stepTime.field.noTimeFieldsLabel": "このインデックスパターンに一致するインデックスには時間フィールドがありません。", + "kbn.management.createIndexPattern.stepTime.fieldHeader": "時間フィルターのフィールド名", + "kbn.management.createIndexPattern.stepTime.fieldLabel": "時間フィルターはこのフィールドを使って時間でフィールドを絞ります。", + "kbn.management.createIndexPattern.stepTime.fieldWarningLabel": "時間フィールドを使わないこともできますが、その場合データを時間範囲で絞ることができません。", + "kbn.management.createIndexPattern.stepTime.noTimeFieldOptionLabel": "時間フィルターを使用しない", + "kbn.management.createIndexPattern.stepTime.noTimeFieldsLabel": "このインデックスパターンに一致するインデックスには時間フィールドがありません。", + "kbn.management.createIndexPattern.stepTime.options.hideButton": "高度なオプションを非表示", + "kbn.management.createIndexPattern.stepTime.options.patternHeader": "カスタムインデックスパターン ID", + "kbn.management.createIndexPattern.stepTime.options.patternLabel": "Kibana はそれぞれのインデックスパターンに固有の識別子を割り当てます。固有 ID を使用しない場合は、カスタム ID を入力してください。", + "kbn.management.createIndexPattern.stepTime.options.patternPlaceholder": "custom-index-pattern-id", + "kbn.management.createIndexPattern.stepTime.options.showButton": "高度なオプションを表示", + "kbn.management.createIndexPattern.stepTime.patterAlreadyExists": "カスタムインデックスパターン ID が既に存在します。", + "kbn.management.createIndexPattern.stepTime.refreshButton": "更新", + "kbn.management.createIndexPattern.stepTimeHeader": "ステップ 2/2:設定の変更", + "kbn.management.createIndexPattern.stepTimeLabel": "{indexPattern} を {indexPatternName} に定義しました。次に、作成前に他の設定を行うことができます。", + "kbn.management.createIndexPatternHeader": "{indexPatternName} の作成", + "kbn.management.createIndexPatternLabel": "Kibana は、可視化などを目的に Elasticsearch インデックスからデータを取得するために、インデックスパターンを使用します。", + "kbn.management.editIndexPattern.deleteButton": "削除", + "kbn.management.editIndexPattern.deleteFieldButton": "削除", + "kbn.management.editIndexPattern.deleteHeader": "インデックスパターンを削除しますか?", + "kbn.management.editIndexPattern.detailsAria": "インデックスパターンの詳細", + "kbn.management.editIndexPattern.editFieldButton": "編集", + "kbn.management.editIndexPattern.fields.allLangsDropDown": "すべての言語", + "kbn.management.editIndexPattern.fields.allTypesDropDown": "すべてのフィールドタイプ", + "kbn.management.editIndexPattern.fields.filterAria": "フィルター", + "kbn.management.editIndexPattern.fields.filterPlaceholder": "フィルター", + "kbn.management.editIndexPattern.fields.table.additionalInfoAriaLabel": "追加フィールド情報", + "kbn.management.editIndexPattern.fields.table.aggregatableDescription": "これらのフィールドはビジュアライゼーションの集約に使用できます", + "kbn.management.editIndexPattern.fields.table.aggregatableLabel": "集約可能", + "kbn.management.editIndexPattern.fields.table.editDescription": "編集", + "kbn.management.editIndexPattern.fields.table.editLabel": "編集", + "kbn.management.editIndexPattern.fields.table.excludedDescription": "取得の際に _source から除外されるフィールドです", + "kbn.management.editIndexPattern.fields.table.excludedLabel": "除外", + "kbn.management.editIndexPattern.fields.table.formatHeader": "フォーマット", + "kbn.management.editIndexPattern.fields.table.isAggregatableAria": "は集約可能です", + "kbn.management.editIndexPattern.fields.table.isExcludedAria": "は除外されています", + "kbn.management.editIndexPattern.fields.table.isSearchableAria": "は検索可能です", + "kbn.management.editIndexPattern.fields.table.multiTypeAria": "複数タイプのフィールド", + "kbn.management.editIndexPattern.fields.table.multiTypeTooltip": "フィールドのタイプがインデックスごとに変わります。多くの分析機能には使用できません。", + "kbn.management.editIndexPattern.fields.table.nameHeader": "名前", + "kbn.management.editIndexPattern.fields.table.primaryTimeAriaLabel": "プライマリ時間フィールド", + "kbn.management.editIndexPattern.fields.table.primaryTimeTooltip": "このフィールドはイベントの発生時刻を表します。", + "kbn.management.editIndexPattern.fields.table.searchableDescription": "これらのフィールドはフィルターバーで使用できます", + "kbn.management.editIndexPattern.fields.table.searchableHeader": "検索可能", + "kbn.management.editIndexPattern.fields.table.typeHeader": "タイプ", + "kbn.management.editIndexPattern.mappingConflictHeader": "マッピングの矛盾", + "kbn.management.editIndexPattern.mappingConflictLabel": "{conflictFieldsLength, plural, one {フィールドが} other {# フィールドが}}このパターンと一致するインデックスの間で異なるタイプ (文字列、整数など) に定義されています。これらの矛盾したフィールドは Kibana の一部で使用できますが、Kibana がタイプを把握しなければならない機能には使用できません。この問題を修正するにはデータのレンダリングが必要です。", + "kbn.management.editIndexPattern.notDateErrorMessage": "このフィールドは日付ではなく {fieldType} です。", + "kbn.management.editIndexPattern.refreshAria": "フィールドリストを再度読み込みます", + "kbn.management.editIndexPattern.refreshButton": "更新", + "kbn.management.editIndexPattern.refreshHeader": "フィールドリストを更新しますか?", + "kbn.management.editIndexPattern.refreshLabel": "この操作は各フィールドの使用頻度をリセットします。", + "kbn.management.editIndexPattern.refreshTooltip": "フィールドリストを更新", + "kbn.management.editIndexPattern.removeAria": "インデックスパターンを削除", + "kbn.management.editIndexPattern.removeTooltip": "インデックスパターンを削除います", + "kbn.management.editIndexPattern.scripted.addFieldButton": "スクリプトフィールドを追加", + "kbn.management.editIndexPattern.scripted.deleteField.cancelButton": "キャンセル", + "kbn.management.editIndexPattern.scripted.deleteField.deleteButton": "削除", + "kbn.management.editIndexPattern.scripted.deleteFieldLabel": "スクリプトフィールド「{fieldName}」を削除しますか?", + "kbn.management.editIndexPattern.scripted.deprecationLangHeader": "廃止された言語が使用されています", + "kbn.management.editIndexPattern.scripted.deprecationLangLabel.deprecationLangDetail": "次の廃止された言語が使用されています: {deprecatedLangsInUse}これらの言語は、Kibana と Elasticsearch の次のメジャーなバージョンでサポートされなくなります。問題を避けるため、スクリプトフィールドを {link} に変換してください。", + "kbn.management.editIndexPattern.scripted.deprecationLangLabel.painlessDescription": "パターン", + "kbn.management.editIndexPattern.scripted.newFieldPlaceholder": "新規スクリプトフィールド", + "kbn.management.editIndexPattern.scripted.noFieldLabel": "「{indexPatternTitle}」インデックスパターンには「{fieldName}」というスクリプトフィールドがありません", + "kbn.management.editIndexPattern.scripted.table.deleteDescription": "このフィールドを削除します", + "kbn.management.editIndexPattern.scripted.table.deleteHeader": "削除", + "kbn.management.editIndexPattern.scripted.table.editDescription": "このフィールドを編集します", + "kbn.management.editIndexPattern.scripted.table.editHeader": "編集", + "kbn.management.editIndexPattern.scripted.table.formatDescription": "フィールドに使用されているフォーマットです", + "kbn.management.editIndexPattern.scripted.table.formatHeader": "フォーマット", + "kbn.management.editIndexPattern.scripted.table.langDescription": "フィールドに使用されている言語です", + "kbn.management.editIndexPattern.scripted.table.langHeader": "言語", + "kbn.management.editIndexPattern.scripted.table.nameDescription": "フィールドの名前です", + "kbn.management.editIndexPattern.scripted.table.nameHeader": "名前", + "kbn.management.editIndexPattern.scripted.table.scriptDescription": "フィールドのスクリプトです", + "kbn.management.editIndexPattern.scripted.table.scriptHeader": "スクリプト", + "kbn.management.editIndexPattern.scripted.unknownModeErrorMessage": "不明なフィールド設定モード {mode}", + "kbn.management.editIndexPattern.scriptedHeader": "スクリプトフィールド", + "kbn.management.editIndexPattern.scriptedLabel": "ビジュアライゼーションにスクリプトフィールドを使用し、ドキュメントに表示させることができます。但し、スクリプトフィールドは検索できません。", + "kbn.management.editIndexPattern.setDefaultAria": "デフォルトのインデックスに設定", + "kbn.management.editIndexPattern.setDefaultTooltip": "デフォルトのインデックスに設定します", + "kbn.management.editIndexPattern.source.addButtonLabel": "追加", + "kbn.management.editIndexPattern.source.deleteFilter.cancelButtonLabel": "キャンセル", + "kbn.management.editIndexPattern.source.deleteFilter.deleteButtonLabel": "削除", + "kbn.management.editIndexPattern.source.deleteSourceFilterLabel": "ソースフィルター「{value}」を削除しますか?", + "kbn.management.editIndexPattern.source.noteLabel": "下の表で、マルチフィールドが一致として誤って表示されます。これらのフィルターは、オリジナルのソースドキュメントの\\フィールドのみに適用されるため、一致するマルチフィールドはフィルタリングされません。", + "kbn.management.editIndexPattern.source.table.cancelAria": "キャンセル", + "kbn.management.editIndexPattern.source.table.deleteAria": "削除", + "kbn.management.editIndexPattern.source.table.editAria": "編集", + "kbn.management.editIndexPattern.source.table.filterDescription": "フィルター名", + "kbn.management.editIndexPattern.source.table.filterHeader": "フィルター", + "kbn.management.editIndexPattern.source.table.matchesDescription": "フィールドに使用されている言語です", + "kbn.management.editIndexPattern.source.table.matchesHeader": "一致", + "kbn.management.editIndexPattern.source.table.notMatchedLabel": "ソースフィルターが既知のフィールドと一致しません。", + "kbn.management.editIndexPattern.source.table.saveAria": "保存", + "kbn.management.editIndexPattern.sourceHeader": "ソースフィルター", + "kbn.management.editIndexPattern.sourceLabel": "ソースフィルターは、ドキュメントソースの取得時に 1 つまたは複数のフィールドを除外するのに使用される場合もあります。これはディスカバリアプリでのドキュメントの表示中、またはダッシュボードアプリの保存された検索の結果を表示する表で起こります。それぞれの行は 1 つのドキュメントのソースで作成されており、ドキュメントに大きなフィールドや重要ではないフィールドが含まれている場合、このレベルでフィルターで除外すると良いかもしれません。", + "kbn.management.editIndexPattern.sourcePlaceholder": "ソースフィルター、ワイルドカード使用可 (例: 「user」と入力して「user」で始まるフィールドをフィルタリング)", + "kbn.management.editIndexPattern.tabs.fieldsHeader": "フィールド", + "kbn.management.editIndexPattern.tabs.scriptedHeader": "スクリプトフィールド", + "kbn.management.editIndexPattern.tabs.sourceHeader": "ソースフィルター", + "kbn.management.editIndexPattern.timeFilterHeader": "時間フィルターフィールド名: {timeFieldName}", + "kbn.management.editIndexPattern.timeFilterLabel.mappingAPILink": "マッピング API", + "kbn.management.editIndexPattern.timeFilterLabel.timeFilterDetail": "このページは {indexPatternTitle} インデックス内のすべてのフィールドと、Elasticsearch に記録された各フィールドのコアタイプを一覧表示します。フィールドタイプを変更するには Elasticsearch を使用します", + "kbn.management.editIndexPatternLiveRegionAriaLabel": "インデックスパターン", + "kbn.management.indexPattern.confirmOverwriteButton": "上書き", + "kbn.management.indexPattern.confirmOverwriteLabel": "「{title}」に上書きしてよろしいですか?", + "kbn.management.indexPattern.confirmOverwriteTitle": "{type} を上書きしますか?", + "kbn.management.indexPattern.goToPatternButtonLabel": "既存のパターンに移動", + "kbn.management.indexPattern.sectionsHeader": "インデックスパターン", + "kbn.management.indexPattern.titleExistsLabel": "「{title}」というタイトルのインデックスパターンが既に存在します。", + "kbn.management.indexPatternList.createButton.betaLabel": "ベータ", + "kbn.management.indexPatternPrompt.exampleOne": "チャートを作成したりコンテンツを素早くクエリできるように log-west-001 という名前の単一のデータソースをインデックスします。", + "kbn.management.indexPatternPrompt.exampleOneTitle": "単一のデータソース", + "kbn.management.indexPatternPrompt.examplesTitle": "インデックスパターンの例", + "kbn.management.indexPatternPrompt.exampleThree": "比較目的に履歴の動向を集約できるよう、これらのログのアーカイブされた月々のロールアップメトリックスを指定通りに別々のインデックスパターンにグループ分けします。", + "kbn.management.indexPatternPrompt.exampleThreeTitle": "カスタムグルーピング", + "kbn.management.indexPatternPrompt.exampleTwo": "すべての西海岸のサーバーログに対してクエリを実行できるように、頭に「log-west」の付いたすべての受信データソースをグループ化します。", + "kbn.management.indexPatternPrompt.exampleTwoTitle": "複数データソース", + "kbn.management.indexPatternPrompt.subtitle": "インデックスパターンは、Kibana で共有フィールドにクエリを実行できるよう、種類の異なるデータソースをバケットにまとめることができます。", + "kbn.management.indexPatternPrompt.title": "インデックスパターンについて", + "kbn.management.indexPatterns.badge.readOnly.text": "読み込み専用", + "kbn.management.indexPatterns.badge.readOnly.tooltip": "インデックスパターンを保存できません", + "kbn.management.indexPatterns.createBreadcrumb": "インデックスパターンを作成", + "kbn.management.indexPatterns.createFieldBreadcrumb": "フィールドを作成", + "kbn.management.indexPatterns.listBreadcrumb": "インデックスパターン", + "kbn.management.indexPatternTable.createBtn": "インデックスパターンの作成", + "kbn.management.indexPatternTable.title": "インデックスパターン", + "kbn.management.landing.header": "Kibana {version} 管理", + "kbn.management.landing.subhead": "インデックス、インデックスパターン、保存されたオブジェクト、Kibana の設定、その他を管理します。", + "kbn.management.landing.text": "アプリの一覧は左側のメニューにあります。", + "kbn.management.objects.confirmModalOptions.deleteButtonLabel": "削除", + "kbn.management.objects.confirmModalOptions.modalDescription": "このアクションはオブジェクトを Kibana から永久に削除します。", + "kbn.management.objects.confirmModalOptions.modalTitle": "「{title}」を削除しますか?", + "kbn.management.objects.deleteSavedObjectsConfirmModalDescription": "この操作は次の保存されたオブジェクトを削除します:", + "kbn.management.objects.field.offLabel": "オフ", + "kbn.management.objects.field.onLabel": "オン", + "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.cancelButtonLabel": "キャンセル", + "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.deleteButtonLabel": "削除", + "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.deleteProcessButtonLabel": "削除中…", + "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.idColumnName": "ID", + "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.titleColumnName": "タイトル", + "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModal.typeColumnName": "タイプ", + "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModalTitle": "保存されたオブジェクトの削除", + "kbn.management.objects.objectsTable.export.dangerNotification": "エクスポートを生成できません", + "kbn.management.objects.objectsTable.export.successNotification": "ファイルはバックグラウンドでダウンロード中です", + "kbn.management.objects.objectsTable.export.successWithMissingRefsNotification": "ファイルはバックグラウンドでダウンロード中です。一部の関連オブジェクトが見つかりませんでした。足りないオブジェクトの一覧は、エクスポートされたファイルの最後の行をご覧ください。", + "kbn.management.objects.objectsTable.exportObjectsConfirmModal.cancelButtonLabel": "キャンセル", + "kbn.management.objects.objectsTable.exportObjectsConfirmModal.exportAllButtonLabel": "すべてエクスポート:", + "kbn.management.objects.objectsTable.exportObjectsConfirmModal.exportOptionsLabel": "オプション", + "kbn.management.objects.objectsTable.exportObjectsConfirmModal.includeReferencesDeepLabel": "関連オブジェクトを含める", + "kbn.management.objects.objectsTable.exportObjectsConfirmModalDescription": "エクスポートするタイプを選択してください", + "kbn.management.objects.objectsTable.exportObjectsConfirmModalTitle": "{filteredItemCount, plural, one{# オブジェクト} other {# オブジェクト}}をエクスポート", + "kbn.management.objects.objectsTable.flyout.confirmLegacyImport.resolvingConflictsLoadingMessage": "矛盾を解決中…", + "kbn.management.objects.objectsTable.flyout.confirmLegacyImport.retryingFailedObjectsLoadingMessage": "失敗したオブジェクトを再試行中…", + "kbn.management.objects.objectsTable.flyout.confirmLegacyImport.savedSearchAreLinkedProperlyLoadingMessage": "保存された検索が正しくリンクされていることを確認してください…", + "kbn.management.objects.objectsTable.flyout.confirmLegacyImport.savingConflictsLoadingMessage": "矛盾を保存中…", + "kbn.management.objects.objectsTable.flyout.confirmOverwriteBody": "{title} を上書きしてよろしいですか?", + "kbn.management.objects.objectsTable.flyout.confirmOverwriteCancelButtonText": "キャンセル", + "kbn.management.objects.objectsTable.flyout.confirmOverwriteOverwriteButtonText": "上書き", + "kbn.management.objects.objectsTable.flyout.confirmOverwriteTitle": "{type} を上書きしますか?", + "kbn.management.objects.objectsTable.flyout.errorCalloutTitle": "申し訳ございませんが、エラーが発生しました", + "kbn.management.objects.objectsTable.flyout.import.cancelButtonLabel": "キャンセル", + "kbn.management.objects.objectsTable.flyout.import.confirmButtonLabel": "インポート", + "kbn.management.objects.objectsTable.flyout.importFailedDescription": "{totalImportCount} 個中 {failedImportCount} 個のオブジェクトのインポートに失敗しました。インポート失敗", + "kbn.management.objects.objectsTable.flyout.importFailedMissingReference": "{type} [id={id}] は {refType} [id={refId}] を見つけられませんでした", + "kbn.management.objects.objectsTable.flyout.importFailedTitle": "インポート失敗", + "kbn.management.objects.objectsTable.flyout.importFailedUnsupportedType": "{type} [id={id}] サポートされていないタイプ", + "kbn.management.objects.objectsTable.flyout.importFileErrorMessage": "ファイルを処理できませんでした。", + "kbn.management.objects.objectsTable.flyout.importLegacyFileErrorMessage": "ファイルを処理できませんでした。", + "kbn.management.objects.objectsTable.flyout.importPromptText": "インポート", + "kbn.management.objects.objectsTable.flyout.importSavedObjectTitle": "保存されたオブジェクトのインポート", + "kbn.management.objects.objectsTable.flyout.importSuccessful.confirmAllChangesButtonLabel": "すべての変更を確定", + "kbn.management.objects.objectsTable.flyout.importSuccessful.confirmButtonLabel": "完了", + "kbn.management.objects.objectsTable.flyout.importSuccessfulCallout.noObjectsImportedTitle": "オブジェクトがインポートされませんでした", + "kbn.management.objects.objectsTable.flyout.importSuccessfulDescription": "{importCount} 個のオブジェクトがインポートされました。", + "kbn.management.objects.objectsTable.flyout.importSuccessfulTitle": "インポート成功", + "kbn.management.objects.objectsTable.flyout.indexPatternConflictsCalloutLinkText": "新規インデックスパターンを作成", + "kbn.management.objects.objectsTable.flyout.indexPatternConflictsDescription": "次の保存されたオブジェクトは、存在しないインデックスパターンを使用しています。別のデックスパターンを選択してください。必要に応じて {indexPatternLink} できます。", + "kbn.management.objects.objectsTable.flyout.indexPatternConflictsTitle": "インデックスパターンの矛盾", + "kbn.management.objects.objectsTable.flyout.invalidFormatOfImportedFileErrorMessage": "保存されたオブジェクトのファイル形式が無効なため、インポートできません。", + "kbn.management.objects.objectsTable.flyout.legacyFileUsedBody": "最新のレポートで NDJSON ファイルを作成すれば完了です。", + "kbn.management.objects.objectsTable.flyout.legacyFileUsedTitle": "JSON ファイルのサポートが終了します", + "kbn.management.objects.objectsTable.flyout.overwriteSavedObjectsLabel": "すべての保存されたオブジェクトを自動的に上書きしますか?", + "kbn.management.objects.objectsTable.flyout.renderConflicts.columnCountDescription": "影響されるオブジェクトの数です", + "kbn.management.objects.objectsTable.flyout.renderConflicts.columnCountName": "カウント", + "kbn.management.objects.objectsTable.flyout.renderConflicts.columnIdDescription": "インデックスパターンの ID です", + "kbn.management.objects.objectsTable.flyout.renderConflicts.columnIdName": "ID", + "kbn.management.objects.objectsTable.flyout.renderConflicts.columnNewIndexPatternName": "新規インデックスパターン", + "kbn.management.objects.objectsTable.flyout.renderConflicts.columnSampleOfAffectedObjectsDescription": "影響されるオブジェクトのサンプルです", + "kbn.management.objects.objectsTable.flyout.renderConflicts.columnSampleOfAffectedObjectsName": "影響されるオブジェクトのサンプル", + "kbn.management.objects.objectsTable.flyout.resolveImportErrorsFileErrorMessage": "ファイルを処理できませんでした。", + "kbn.management.objects.objectsTable.flyout.selectFileToImportFormRowLabel": "インポートするファイルを選択してください", + "kbn.management.objects.objectsTable.header.exportButtonLabel": "{filteredCount, plural, one{# オブジェクト} other {# オブジェクト}}をエクスポート", + "kbn.management.objects.objectsTable.header.importButtonLabel": "インポート", + "kbn.management.objects.objectsTable.header.refreshButtonLabel": "更新", + "kbn.management.objects.objectsTable.header.savedObjectsTitle": "保存されたオブジェクト", + "kbn.management.objects.objectsTable.howToDeleteSavedObjectsDescription": "ここから保存された検索などの保存されたオブジェクトを削除できます。保存されたオブジェクトの生データを編集することもできます。通常、オブジェクトは関連アプリケーションでのみ編集され、こn画面で編集するよりもそちらのほうが賢明です。", + "kbn.management.objects.objectsTable.relationships.columnActions.inspectActionDescription": "この保存されたオブジェクトを確認してください", + "kbn.management.objects.objectsTable.relationships.columnActions.inspectActionName": "検査", + "kbn.management.objects.objectsTable.relationships.columnActionsName": "アクション", + "kbn.management.objects.objectsTable.relationships.columnRelationship.childAsValue": "子", + "kbn.management.objects.objectsTable.relationships.columnRelationship.parentAsValue": "ペアレント", + "kbn.management.objects.objectsTable.relationships.columnRelationshipName": "直接関係", + "kbn.management.objects.objectsTable.relationships.columnTitleDescription": "保存されたオブジェクトのタイトルです", + "kbn.management.objects.objectsTable.relationships.columnTitleName": "タイトル", + "kbn.management.objects.objectsTable.relationships.columnTypeDescription": "保存されたオブジェクトのタイプです", + "kbn.management.objects.objectsTable.relationships.columnTypeName": "タイプ", + "kbn.management.objects.objectsTable.relationships.relationshipsTitle": "{title} に関連する保存されたオブジェクトはこちらです。この {type} を削除すると、親オブジェクトに影響がありますが、子オブジェクトには影響はありません。", + "kbn.management.objects.objectsTable.relationships.renderErrorMessage": "エラー", + "kbn.management.objects.objectsTable.relationships.search.filters.relationship.childAsValue.view": "子", + "kbn.management.objects.objectsTable.relationships.search.filters.relationship.name": "直接関係", + "kbn.management.objects.objectsTable.relationships.search.filters.relationship.parentAsValue.view": "親", + "kbn.management.objects.objectsTable.relationships.search.filters.type.name": "タイプ", + "kbn.management.objects.objectsTable.searchBar.unableToParseQueryErrorMessage": "クエリをパースできません", + "kbn.management.objects.objectsTable.table.columnActions.inspectActionDescription": "この保存されたオブジェクトを確認してください", + "kbn.management.objects.objectsTable.table.columnActions.inspectActionName": "検査", + "kbn.management.objects.objectsTable.table.columnActions.viewRelationshipsActionDescription": "この保存されたオブジェクトと他の保存されたオブジェクトとの関係性を表示します", + "kbn.management.objects.objectsTable.table.columnActions.viewRelationshipsActionName": "関係性", + "kbn.management.objects.objectsTable.table.columnActionsName": "アクション", + "kbn.management.objects.objectsTable.table.columnTitleDescription": "保存されたオブジェクトのタイトルです", + "kbn.management.objects.objectsTable.table.columnTitleName": "タイトル", + "kbn.management.objects.objectsTable.table.columnTypeDescription": "保存されたオブジェクトのタイプです", + "kbn.management.objects.objectsTable.table.columnTypeName": "タイプ", + "kbn.management.objects.objectsTable.table.deleteButtonLabel": "削除", + "kbn.management.objects.objectsTable.table.deleteButtonTitle": "保存されたオブジェクトを削除できません", + "kbn.management.objects.objectsTable.table.exportButtonLabel": "エクスポート", + "kbn.management.objects.objectsTable.table.exportPopoverButtonLabel": "エクスポート", + "kbn.management.objects.objectsTable.table.typeFilterName": "タイプ", + "kbn.management.objects.objectsTable.unableFindSavedObjectsNotificationMessage": "保存されたオブジェクトが見つかりません", + "kbn.management.objects.parsingFieldErrorMessage": "{fieldName} をインデックスパターン {indexName} 用にパース中にエラーが発生しました: {errorMessage}", + "kbn.management.objects.savedObjectsSectionLabel": "保存されたオブジェクト", + "kbn.management.objects.view.cancelButtonAriaLabel": "キャンセル", + "kbn.management.objects.view.cancelButtonLabel": "キャンセル", + "kbn.management.objects.view.deleteItemButtonLabel": "{title} を削除", + "kbn.management.objects.view.editItemTitle": "{title} の編集", + "kbn.management.objects.view.fieldDoesNotExistErrorMessage": "このオブジェクトに関連付けられたフィールドは、現在このインデックスパターンに存在しません。", + "kbn.management.objects.view.howToFixErrorDescription": "このエラーの原因がわかる場合は修正してください。わからない場合は上の削除ボタンをクリックしてください。", + "kbn.management.objects.view.howToModifyObjectDescription": "オブジェクトの編集は上級ユーザー向けです。オブジェクトのプロパティが検証されておらず、無効なオブジェクトはエラー、データ損失、またはそれ以上の問題の原因となります。コードを熟知した人に指示されていない限り、この設定は変更しない方が無難です。", + "kbn.management.objects.view.howToModifyObjectTitle": "十分ご注意ください!", + "kbn.management.objects.view.indexPatternDoesNotExistErrorMessage": "このオブジェクトに関連付けられたインデックスパターンは現在存在しません。", + "kbn.management.objects.view.saveButtonAriaLabel": "{ title } オブジェクトを保存", + "kbn.management.objects.view.saveButtonLabel": "{ title } オブジェクトを保存", + "kbn.management.objects.view.savedObjectProblemErrorMessage": "この保存されたオブジェクトに問題があります", + "kbn.management.objects.view.savedSearchDoesNotExistErrorMessage": "このオブジェクトに関連付けられた保存された検索は現在存在しません。", + "kbn.management.objects.view.viewItemButtonLabel": "{title} を表示", + "kbn.management.objects.view.viewItemTitle": "{title} を表示", + "kbn.management.savedObjects.editBreadcrumb": "{savedObjectType} を編集", + "kbn.management.savedObjects.indexBreadcrumb": "保存されたオブジェクト", + "kbn.managementTitle": "管理", + "kbn.topNavMenu.openInspectorButtonLabel": "検査", + "kbn.topNavMenu.refreshButtonLabel": "更新", + "kbn.topNavMenu.saveVisualizationButtonLabel": "保存", + "kbn.topNavMenu.shareVisualizationButtonLabel": "共有", + "kbn.visualize.badge.readOnly.text": "読み込み専用", + "kbn.visualize.badge.readOnly.tooltip": "ビジュアライゼーションを保存できません", + "kbn.visualize.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage": "indexPattern または savedSearchId が必要です", + "kbn.visualize.editor.createBreadcrumb": "作成", + "kbn.visualize.experimentalVisInfoText": "このビジュアライゼーションは実験的なものです。", + "kbn.visualize.helpMenu.appName": "可視化", + "kbn.visualize.linkedToSearch.unlinkSuccessNotificationText": "保存された検索「{searchTitle}」からリンクが解除されました", + "kbn.visualize.listing.betaTitle": "ベータ", + "kbn.visualize.listing.betaTooltip": "このビジュアライゼーションはベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません", + "kbn.visualize.listing.breadcrumb": "可視化", + "kbn.visualize.listing.createNew.createButtonLabel": "新規ビジュアライゼーションを追加", + "kbn.visualize.listing.createNew.description": "データに基づき異なるビジュアライゼーションを作成できます。", + "kbn.visualize.listing.createNew.title": "最初のビジュアライゼーションの作成", + "kbn.visualize.listing.experimentalTitle": "実験的", + "kbn.visualize.listing.experimentalTooltip": "このビジュアライゼーションは今後のリリースで変更または削除される可能性があり、SLA のサポート対象になりません。", + "kbn.visualize.listing.noItemsMessage": "ビジュアライゼーションがないようです。", + "kbn.visualize.listing.table.entityName": "ビジュアライゼーション", + "kbn.visualize.listing.table.entityNamePlural": "ビジュアライゼーション", + "kbn.visualize.listing.table.listTitle": "ビジュアライゼーション", + "kbn.visualize.listing.table.titleColumnName": "タイトル", + "kbn.visualize.listing.table.typeColumnName": "タイプ", + "kbn.visualize.pageHeading": "{chartName} {chartType} ビジュアライゼーション", + "kbn.visualize.saveDialog.saveAndAddToDashboardButtonLabel": "保存してダッシュボードに追加", + "kbn.visualize.topNavMenu.openInspectorButtonAriaLabel": "ビジュアライゼーションのインスペクターを開く", + "kbn.visualize.topNavMenu.openInspectorDisabledButtonTooltip": "このビジュアライゼーションはインスペクターをサポートしていません。", + "kbn.visualize.topNavMenu.refreshButtonAriaLabel": "更新", + "kbn.visualize.topNavMenu.saveVisualization.failureNotificationText": "「{visTitle}」の保存中にエラーが発生しました", + "kbn.visualize.topNavMenu.saveVisualization.successNotificationText": "「{visTitle}」が保存されました", + "kbn.visualize.topNavMenu.saveVisualizationButtonAriaLabel": "ビジュアライゼーションを保存", + "kbn.visualize.topNavMenu.saveVisualizationDisabledButtonTooltip": "保存する前に変更を適用または破棄", + "kbn.visualize.topNavMenu.shareVisualizationButtonAriaLabel": "ビジュアライゼーションを共有", + "kbn.visualize.visualizationTypeInvalidNotificationMessage": "無効なビジュアライゼーションタイプ", + "kbn.visualize.visualizeDescription": "ビジュアライゼーションを作成して Elasticsearch インデックスに保存されたデータを集約します。", + "kbn.visualize.visualizeListingBreadcrumbsTitle": "可視化", + "kbn.visualize.visualizeListingDeleteErrorTitle": "ビジュアライゼーションの削除中にエラーが発生", + "kbn.visualize.wizard.step1Breadcrumb": "作成", + "kbn.visualize.wizard.step2Breadcrumb": "作成", + "kbn.visualizeTitle": "可視化", + "kibana_legacy.bigUrlWarningNotificationMessage": "{advancedSettingsLink}で{storeInSessionStorageParam}オプションを有効にするか、オンスクリーンビジュアルを簡素化してください。", + "kibana_legacy.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高度な設定", + "kibana_legacy.bigUrlWarningNotificationTitle": "URLが大きく、Kibanaの動作が停止する可能性があります", + "kibana_legacy.notify.fatalError.errorStatusMessage": "エラー {errStatus} {errStatusText}: {errMessage}", + "kibana_legacy.notify.fatalError.unavailableServerErrorMessage": "HTTP リクエストで接続に失敗しました。Kibana サーバーが実行されていて、ご使用のブラウザの接続が正常に動作していることを確認するか、システム管理者にお問い合わせください。", + "kibana_legacy.notify.toaster.errorMessage": "エラー: {errorMessage}\n {errorStack}", + "kibana_legacy.notify.toaster.errorStatusMessage": "エラー {errStatus} {errStatusText}: {errMessage}", + "kibana_legacy.notify.toaster.unavailableServerErrorMessage": "HTTP リクエストで接続に失敗しました。Kibana サーバーが実行されていて、ご使用のブラウザの接続が正常に動作していることを確認するか、システム管理者にお問い合わせください。", + "kibana_legacy.paginate.controls.pageSizeLabel": "ページサイズ", + "kibana_legacy.paginate.controls.scrollTopButtonLabel": "最上部に移動", + "kibana_legacy.paginate.size.allDropDownOptionLabel": "すべて", + "kibana_utils.defaultFeedbackMessage": "フィードバックがありますか?{link} で問題を報告してください。", + "kibana_utils.history.savedObjectIsMissingNotificationMessage": "保存されたオブジェクトがありません", + "kibana_utils.indexPattern.bannerLabel": "Kibanaでデータの可視化と閲覧を行うには、Elasticsearchからデータを取得するためのインデックスパターンの作成が必要です。", + "kibana_utils.stateManagement.stateHash.unableToRestoreUrlErrorMessage": "URL を完全に復元できません。共有機能を使用していることを確認してください。", + "kibana_utils.stateManagement.stateHash.unableToStoreHistoryInSessionErrorMessage": "セッションがいっぱいで安全に削除できるアイテムが見つからないため、Kibana は履歴アイテムを保存できません。\n\nこれは大抵新規タブに移動することで解決されますが、より大きな問題が原因である可能性もあります。このメッセージが定期的に表示される場合は、{gitHubIssuesUrl} で問題を報告してください。", + "kibana-react.dualRangeControl.mustSetBothErrorMessage": "下と上の値の両方を設定する必要があります", + "kibana-react.dualRangeControl.outsideOfRangeErrorMessage": "値は {min} と {max} の間でなければなりません", + "kibana-react.dualRangeControl.upperValidErrorMessage": "上の値は下の値以上でなければなりません", + "kibana-react.exitFullScreenButton.exitFullScreenModeButtonAriaLabel": "全画面モードを終了", + "kibana-react.exitFullScreenButton.exitFullScreenModeButtonText": "全画面を終了", + "kibana-react.exitFullScreenButton.exitFullScreenModeButtonTitle": "Elastic Kibana", "kibana-react.exitFullScreenButton.fullScreenModeDescription": "ESC キーで全画面モードを終了します。", + "kibana-react.splitPanel.adjustPanelSizeAriaLabel": "左右のキーを押してパネルサイズを調整します", + "kibana-react.tableListView.listing.createNewItemButtonLabel": "Create {entityName}", + "kibana-react.tableListView.listing.deleteButtonMessage": "{itemCount} 件の {entityName} を削除", + "kibana-react.tableListView.listing.deleteConfirmModalDescription": "削除された {entityNamePlural} は復元できません。", + "kibana-react.tableListView.listing.deleteSelectedConfirmModal.title": "{itemCount} 件の {entityName} を削除", + "kibana-react.tableListView.listing.deleteSelectedItemsConfirmModal.cancelButtonLabel": "キャンセル", + "kibana-react.tableListView.listing.deleteSelectedItemsConfirmModal.confirmButtonLabel": "削除", + "kibana-react.tableListView.listing.deleteSelectedItemsConfirmModal.confirmButtonLabelDeleting": "削除中", + "kibana-react.tableListView.listing.listingLimitExceeded.advancedSettingsLinkText": "高度な設定", + "kibana-react.tableListView.listing.listingLimitExceededDescription": "{totalItems} 件の {entityNamePlural} がありますが、{listingLimitText} の設定により {listingLimitValue} 件までしか下の表に表示できません。この設定は {advancedSettingsLink} で変更できます。{advancedSettingsLink} の下でこの設定を変更できます。", + "kibana-react.tableListView.listing.listingLimitExceededTitle": "リスティング制限超過", + "kibana-react.tableListView.listing.noAvailableItemsMessage": "利用可能な {entityNamePlural} がありません。", + "kibana-react.tableListView.listing.noMatchedItemsMessage": "検索条件に一致する {entityNamePlural} がありません。", + "kibana-react.tableListView.listing.table.actionTitle": "アクション", + "kibana-react.tableListView.listing.table.editActionDescription": "編集", + "kibana-react.tableListView.listing.table.editActionName": "編集", + "kibana-react.tableListView.listing.unableToDeleteDangerMessage": "{entityName} を削除できません", + "management.breadcrumb": "管理", + "management.connectDataDisplayName": "データに接続", + "management.displayName": "管理", + "indexPatternManagement.editIndexPattern.createIndex.defaultButtonDescription": "すべてのデータに完全集約を実行", + "indexPatternManagement.editIndexPattern.createIndex.defaultButtonText": "標準インデックスパターン", + "indexPatternManagement.editIndexPattern.createIndex.defaultTypeName": "インデックスパターン", + "indexPatternManagement.editIndexPattern.list.defaultIndexPatternListName": "デフォルト", + "management.nav.label": "管理", + "management.nav.menu": "管理メニュー", + "management.stackManagement.managementDescription": "Elastic Stack の管理を行うセンターコンソールです。", "newsfeed.emptyPrompt.noNewsText": "Kibanaインスタンスがインターネットにアクセスできない場合、管理者にこの機能を無効にするように依頼してください。そうでない場合は、ニュースを取り込み続けます。", "newsfeed.emptyPrompt.noNewsTitle": "ニュースがない場合", "newsfeed.flyoutList.closeButtonLabel": "閉じる", "newsfeed.flyoutList.versionTextLabel": "{version}", - "newsfeed.flyoutList.whatsNewTitle": "新機能", + "newsfeed.flyoutList.whatsNewTitle": "Elastic の新機能", "newsfeed.loadingPrompt.gettingNewsText": "最新ニュースを取得しています...", "regionMap.choroplethLayer.downloadingVectorData404ErrorMessage": "{name} の取得時にサーバーから「404」が返されます。指定された場所にファイルが存在することを確認してください。", "regionMap.choroplethLayer.downloadingVectorDataErrorMessage": "{name} ファイルをダウンロードできません。サーバーの CORS 構成で、このホストの Kibana アプリケーションからのリクエストが許可されていることを確認してください。", @@ -2402,11 +2558,12 @@ "regionMap.visParams.vectorMapLabel": "ベクトルマップ", "regionMap.visualization.unableToShowMismatchesWarningText": "次の各用語がシェイプの結合フィールドのシェイプと一致することを確認してください: {mismatches}", "regionMap.visualization.unableToShowMismatchesWarningTitle": "{mismatchesLength} {oneMismatch, plural, one { 件の結果} other { 件の結果}}をマップに表示できません", + "savedObjects.confirmModal.cancelButtonLabel": "キャンセル", "savedObjects.confirmModal.overwriteButtonLabel": "上書き", "savedObjects.confirmModal.overwriteConfirmationMessage": "{title} を上書きしてよろしいですか?", "savedObjects.confirmModal.overwriteTitle": "{name} を上書きしますか?", "savedObjects.confirmModal.saveDuplicateButtonLabel": "{name} を保存", - "savedObjects.confirmModal.saveDuplicateConfirmationMessage": "「{title}」というタイトルの {name} が既に存在します。保存を続けますか?", + "savedObjects.confirmModal.saveDuplicateConfirmationMessage": "「{title}」というタイトルの {name} が既に存在します保存しますか?", "savedObjects.finder.filterButtonLabel": "タイプ", "savedObjects.finder.searchPlaceholder": "検索...", "savedObjects.finder.sortAsc": "昇順", @@ -2417,8 +2574,9 @@ "savedObjects.saveDuplicateRejectedDescription": "重複ファイルの保存確認が拒否されました", "savedObjects.saveModal.cancelButtonLabel": "キャンセル", "savedObjects.saveModal.descriptionLabel": "説明", + "savedObjects.saveModal.duplicateTitleDescription": "{confirmSaveLabel} をクリックすると {objectType} がこの重複したタイトルで保存されます。", "savedObjects.saveModal.duplicateTitleLabel": "「{title}」というタイトルの {objectType} が既に存在します", - "savedObjects.saveModal.saveAsNewLabel": "新規 {objectType} として保存", + "savedObjects.saveModal.saveAsNewLabel": "新しい {objectType} として保存", "savedObjects.saveModal.saveButtonLabel": "保存", "savedObjects.saveModal.saveTitle": "{objectType} を保存", "savedObjects.saveModal.titleLabel": "タイトル", @@ -2428,6 +2586,28 @@ "server.status.redTitle": "赤", "server.status.uninitializedTitle": "アンインストールしました", "server.status.yellowTitle": "黄色", + "share.contextMenu.embedCodeLabel": "埋め込みコード", + "share.contextMenu.embedCodePanelTitle": "埋め込みコード", + "share.contextMenu.permalinkPanelTitle": "パーマリンク", + "share.contextMenu.permalinksLabel": "パーマリンク", + "share.contextMenuTitle": "この {objectType} を共有", + "share.urlGenerators.error.createUrlFnProvided": "このジェネレーターは非推奨とマークされています。createUrl fn を付けないでください。", + "share.urlGenerators.error.migrateCalledNotDeprecated": "非推奨以外のジェネレーターで migrate を呼び出すことはできません。", + "share.urlGenerators.error.migrationFnGivenNotDeprecated": "移行機能を提供する場合、このジェネレーターに非推奨マークを付ける必要があります", + "share.urlGenerators.error.noCreateUrlFnProvided": "このジェネレーターには非推奨のマークがありません。createUrl fn を付けてください。", + "share.urlGenerators.error.noMigrationFnProvided": "アクセスリンクジェネレーターに非推奨マークが付いている場合、移行機能を提供する必要があります。", + "share.urlGenerators.errors.noGeneratorWithId": "{id} という ID のジェネレーターはありません", + "share.urlPanel.canNotShareAsSavedObjectHelpText": "{objectType} が保存されるまで保存されたオブジェクトを共有することはできません。", + "share.urlPanel.copyIframeCodeButtonLabel": "iFrame コードをコピー", + "share.urlPanel.copyLinkButtonLabel": "リンクをコピー", + "share.urlPanel.generateLinkAsLabel": "名前を付けてリンクを生成", + "share.urlPanel.savedObjectDescription": "この URL を共有することで、他のユーザーがこの {objectType} の最も最近保存されたバージョンを読み込めるようになります。", + "share.urlPanel.savedObjectLabel": "保存されたオブジェクト", + "share.urlPanel.shortUrlHelpText": "互換性が最も高くなるよう、短いスナップショット URL を共有することをお勧めします。Internet Explorer は URL の長さに制限があり、一部の wiki やマークアップパーサーは長い完全なスナップショット URL に対応していませんが、短い URL は正常に動作するはずです。", + "share.urlPanel.shortUrlLabel": "短い URL", + "share.urlPanel.snapshotDescription": "スナップショット URL には、{objectType} の現在の状態がエンコードされています。保存された {objectType} への編集内容はこの URL には反映されません。.", + "share.urlPanel.snapshotLabel": "スナップショット", + "share.urlPanel.unableCreateShortUrlErrorMessage": "短い URL を作成できません。エラー: {errorMessage}", "statusPage.loadStatus.serverIsDownErrorMessage": "サーバーステータスのリクエストに失敗しました。サーバーがダウンしている可能性があります。", "statusPage.loadStatus.serverStatusCodeErrorMessage": "サーバーステータスのリクエストに失敗しました。ステータスコード: {responseStatus}", "statusPage.metricsTiles.columns.heapTotalHeader": "ヒープ合計", @@ -2443,6 +2623,8 @@ "statusPage.statusApp.statusTitle": "プラグインステータス", "statusPage.statusTable.columns.idHeader": "ID", "statusPage.statusTable.columns.statusHeader": "ステータス", + "telemetry.callout.appliesSettingTitle": "この設定に加えた変更は {allOfKibanaText} に適用され、自動的に保存されます。", + "telemetry.callout.appliesSettingTitle.allOfKibanaText": "Kibana のすべて", "telemetry.callout.clusterStatisticsDescription": "これは収集される基本的なクラスター統計の例です。インデックス、シャード、ノードの数が含まれます。監視がオンになっているかどうかなどのハイレベルの使用統計も含まれます。", "telemetry.callout.clusterStatisticsTitle": "クラスター統計", "telemetry.callout.errorLoadingClusterStatisticsDescription": "クラスター統計の取得中に予期せぬエラーが発生しました。Elasticsearch、Kibana、またはネットワークのエラーが原因の可能性があります。Kibana を確認し、ページを再読み込みして再試行してください。", @@ -2453,13 +2635,19 @@ "telemetry.optInErrorToastTitle": "エラー", "telemetry.optInNoticeSeenErrorTitle": "エラー", "telemetry.optInNoticeSeenErrorToastText": "通知の消去中にエラーが発生しました", - "telemetry.readOurUsageDataPrivacyStatementLinkText": "使用データのプライバシーステートメントをお読みください", + "telemetry.optInSuccessOff": "使用状況データ収集がオフです。", + "telemetry.optInSuccessOn": "使用状況データ収集がオンです。", + "telemetry.provideUsageStatisticsAriaName": "使用統計を提供", + "telemetry.provideUsageStatisticsTitle": "使用統計を提供", + "telemetry.readOurUsageDataPrivacyStatementLinkText": "プライバシーポリシー", "telemetry.seeExampleOfWhatWeCollectLinkText": "収集されるデータの例を見る", "telemetry.telemetryBannerDescription": "Elastic Stackの改善にご協力ください使用状況データの収集は現在無効です。使用状況データの収集を有効にすると、製品とサービスを管理して改善することができます。詳細については、{privacyStatementLink}をご覧ください。", + "telemetry.telemetryConfigAndLinkDescription": "使用状況データの収集を有効にすると、製品とサービスを管理して改善することができます。詳細については、{privacyStatementLink}をご覧ください。", "telemetry.telemetryConfigDescription": "基本的な機能の利用状況に関する統計情報を提供して、Elastic Stack の改善にご協力ください。このデータは Elastic 社外と共有されません。", "telemetry.telemetryOptedInDisableUsage": "ここで使用状況データを無効にする", "telemetry.telemetryOptedInDismissMessage": "閉じる", "telemetry.telemetryOptedInNoticeDescription": "使用状況データがどのように製品とサービスの管理と改善につながるのかに関する詳細については、{privacyStatementLink}をご覧ください。収集を停止するには、{disableLink}。", + "telemetry.telemetryOptedInNoticeTitle": "Elastic Stack の改善にご協力ください", "telemetry.telemetryOptedInPrivacyStatement": "プライバシーポリシー", "telemetry.usageDataTitle": "使用データ", "telemetry.welcomeBanner.disableButtonLabel": "無効にする", @@ -2525,7 +2713,10 @@ "timelion.expressionSuggestions.arg.listTitle": "引数:", "timelion.expressionSuggestions.arg.nameTitle": "引数名", "timelion.expressionSuggestions.arg.typesTitle": "対応タイプ", + "timelion.expressionSuggestions.argument.description.acceptsText": "受け入れ", + "timelion.expressionSuggestions.func.description.chainableHelpText": "連鎖可能", "timelion.expressionSuggestions.func.description.chainableText": "{help} (連鎖可能)", + "timelion.expressionSuggestions.func.description.dataSourceHelpText": "データソース", "timelion.expressionSuggestions.func.description.dataSourceText": "{help} (データソース)", "timelion.fitFunctions.carry.downSampleErrorMessage": "ダウンサンプルには「carry」フィットメソドを使用せず、「scale」または「average」を使用してください", "timelion.fullscreen.exitAriaLabel": "全画面を終了", @@ -2619,7 +2810,7 @@ "timelion.help.functions.hideHelpText": "デフォルトで数列を非表示にします", "timelion.help.functions.holt.args.alphaHelpText": "\n 0 から 1 の平滑化加重です。\n アルファを上げると新しい数列がオリジナルにさらに近くなります。\n 下げると数列がスムーズになります", "timelion.help.functions.holt.args.betaHelpText": "\n 0 から 1 の傾向加重です。\n ベータを上げると線の上下の動きが長くなります。\n 下げると新しい傾向をより早く反映するようになります", - "timelion.help.functions.holt.args.gammaHelpText": "0 から 1 のシーズン加重です。データが波のようになっていますか?\n この数字を上げると、最近のシーズンの重要性が高まり、波形の動きを速くします。\n 下げると新しいシーズンの重要性が下がり、過去がより重要視されます。", + "timelion.help.functions.holt.args.gammaHelpText": "\n 0 から 1 のシーズン加重です。データが波のようになっていますか?\n この数字を上げると、最近のシーズンの重要性が高まり、波形の動きを速くします。\n 下げると新しいシーズンの重要性が下がり、過去がより重要視されます。\n ", "timelion.help.functions.holt.args.sampleHelpText": "\n シーズン数列の「予測」を開始する前にサンプリングするシーズンの数です。\n (gamma でのみ有効、デフォルト: all)", "timelion.help.functions.holt.args.seasonHelpText": "シーズンの長さです、例: パターンが毎週繰り返される場合は 1w。(gamma でのみ有効)", "timelion.help.functions.holtHelpText": "\n 数列の始めをサンプリングし、\n いくつかのオプションパラメーターを使用して何が起こるか予測します。基本的に、この機能は未来を予測するのではなく、\n 過去のデータに基づき現在何が起きているべきかを予測します。\n この情報は異常検知に役立ちます。null には予測値が入力されます。", @@ -2664,6 +2855,8 @@ "timelion.help.functions.points.args.symbolHelpText": "点のシンボルです。{validSymbols} の 1 つ", "timelion.help.functions.points.args.weightHelpText": "点の周りの太さです", "timelion.help.functions.pointsHelpText": "数列を点として表示します", + "timelion.help.functions.precision.args.precisionHelpText": "各値を切り捨てる桁数です", + "timelion.help.functions.precisionHelpText": "値の小数点以下を切り捨てる桁数です", "timelion.help.functions.props.args.globalHelpText": "各数列に対し、seriesList にプロップを設定します", "timelion.help.functions.propsHelpText": "数列に任意のプロパティを設定するため、自己責任で行ってください。例: {example}", "timelion.help.functions.quandl.args.codeHelpText": "プロットする Quandl コードです。これらは quandl.com に掲載されています。", @@ -2748,7 +2941,15 @@ "timelion.panels.noRenderFunctionErrorMessage": "パネルにはレンダリング関数が必要です", "timelion.panels.timechart.unknownIntervalErrorMessage": "不明な間隔", "timelion.requestHandlerErrorTitle": "Timelion リクエストエラー", - "timelion.savedObjects.howToSaveAsNewDescription": "Kibana の以前のバージョンでは、{savedObjectName} の名前を変更すると新しい名前でコピーが作成されました。今後この操作を行うには、「新規 {savedObjectName} として保存」を使用します。", + "timelion.savedObjectFinder.addNewItemButtonLabel": "新規{item}を追加", + "timelion.savedObjectFinder.manageItemsButtonLabel": "{items}の管理", + "timelion.savedObjectFinder.noMatchesFoundDescription": "一致する{items}が見つかりません。", + "timelion.savedObjectFinder.pageItemsFromHitCountDescription": "{hitCount} 件中 {pageFirstItem}-{pageLastItem} 件目", + "timelion.savedObjectFinder.sortByButtonLabeAscendingScreenReaderOnly": "昇順", + "timelion.savedObjectFinder.sortByButtonLabeDescendingScreenReaderOnly": "降順", + "timelion.savedObjectFinder.sortByButtonLabel": "名前", + "timelion.savedObjectFinder.sortByButtonLabelScreenReaderOnly": "並べ替え基準", + "timelion.savedObjects.howToSaveAsNewDescription": "以前のバージョンの Kibana では、{savedObjectName} の名前を変更すると新しい名前でコピーが作成されました。現在のバージョンで同じように保存するには、[新規 {savedObjectName} として保存]チェックボックスを使用します。", "timelion.savedObjects.saveAsNewLabel": "新規 {savedObjectName} として保存", "timelion.saveExpression.successNotificationText": "保存された式「{title}」", "timelion.saveSheet.successNotificationText": "保存されたシート「{title}」", @@ -2827,53 +3028,59 @@ "timelion.uiSettings.showTutorialDescription": "Timelion アプリの起動時にデフォルトでチュートリアルを表示しますか?", "timelion.uiSettings.showTutorialLabel": "チュートリアルを表示", "timelion.uiSettings.targetBucketsDescription": "自動間隔の使用時に目標となるバケット数です。", - "timelion.uiSettings.targetBucketsLabel": "目標バケット数", - "timelion.uiSettings.timeFieldDescription": "{esParam} の使用時にタイムスタンプを含むデフォルトのフィールドです", - "timelion.uiSettings.timeFieldLabel": "時間フィールド", - "timelion.vis.expressionLabel": "Timelion 式", - "timelion.vis.intervalLabel": "間隔", - "uiActions.actionPanel.title": "オプション", - "uiActions.errors.incompatibleAction": "操作に互換性がありません", - "visualizations.newVisWizard.betaDescription": "このビジュアライゼーションはベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません", - "visualizations.newVisWizard.betaTitle": "ベータ", - "visualizations.newVisWizard.chooseSourceTitle": "ソースの選択", - "visualizations.newVisWizard.experimentalDescription": "このビジュアライゼーションは実験的なものです。デザインと導入は安定したビジュアライゼーションよりも完成度が低く、変更される可能性があります。", - "visualizations.newVisWizard.experimentalTitle": "実験的", - "visualizations.newVisWizard.experimentalTooltip": "このビジュアライゼーションは今後のリリースで変更または削除される可能性があり、SLA のサポート対象になりません。", - "visualizations.newVisWizard.filterVisTypeAriaLabel": "ビジュアライゼーションのタイプでフィルタリング", - "visualizations.newVisWizard.helpText": "タイプを選択してビジュアライゼーションの作成を始めましょう。", - "visualizations.newVisWizard.helpTextAriaLabel": "タイプを選択してビジュアライゼーションの作成を始めましょう。ESC を押してこのモダルを閉じます。Tab キーを押して次に進みます。", - "visualizations.newVisWizard.newVisTypeTitle": "新規 {visTypeName}", - "visualizations.newVisWizard.resultsFound": "{resultCount} 個の{resultCount, plural, one {タイプ} other {タイプ} } が見つかりました", - "visualizations.newVisWizard.searchSelection.notFoundLabel": "一致するインデックスまたは保存された検索が見つかりませんでした。", - "visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "インデックスパターン", - "visualizations.newVisWizard.searchSelection.savedObjectType.search": "保存された検索", - "visualizations.newVisWizard.selectVisType": "ビジュアライゼーションのタイプを選択してください", - "visualizations.newVisWizard.title": "新規ビジュアライゼーション", - "visualizations.newVisWizard.visTypeAliasDescription": "Visualize外でKibanaアプリケーションを開きます。", - "visualizations.newVisWizard.visTypeAliasTitle": "Kibanaアプリケーション", + "timelion.uiSettings.targetBucketsLabel": "目標バケット数", + "timelion.uiSettings.timeFieldDescription": "{esParam} の使用時にタイムスタンプを含むデフォルトのフィールドです", + "timelion.uiSettings.timeFieldLabel": "時間フィールド", + "timelion.vis.expressionLabel": "Timelion 式", + "timelion.vis.interval.auto": "自動", + "timelion.vis.interval.day": "1日", + "timelion.vis.interval.hour": "1時間", + "timelion.vis.interval.minute": "1分", + "timelion.vis.interval.month": "1か月", + "timelion.vis.interval.second": "1秒", + "timelion.vis.interval.week": "1週間", + "timelion.vis.interval.year": "1年", + "timelion.vis.intervalLabel": "間隔", + "timelion.vis.invalidIntervalErrorMessage": "無効な間隔フォーマット。", + "timelion.vis.selectIntervalHelpText": "オプションを選択するかカスタム値を作成します。例30s、20m、24h、2d、1w、1M", + "timelion.vis.selectIntervalPlaceholder": "間隔を選択", + "uiActions.actionPanel.title": "オプション", + "uiActions.errors.incompatibleAction": "操作に互換性がありません", + "visDefaultEditor.advancedToggle.advancedLinkLabel": "高度な設定", + "visDefaultEditor.agg.disableAggButtonTooltip": "集約を無効にする", + "visDefaultEditor.agg.enableAggButtonTooltip": "集約を有効にする", + "visDefaultEditor.agg.errorsAriaLabel": "集約にエラーがあります", + "visDefaultEditor.agg.modifyPriorityButtonTooltip": "ドラッグして優先順位を変更します", + "visDefaultEditor.agg.removeDimensionButtonTooltip": "次元を削除", + "visDefaultEditor.agg.toggleEditorButtonAriaLabel": "{schema} エディターを切り替える", + "visDefaultEditor.aggAdd.addButtonLabel": "追加", + "visDefaultEditor.aggAdd.addGroupButtonLabel": "{groupNameLabel} を追加", + "visDefaultEditor.aggAdd.addSubGroupButtonLabel": "サブ {groupNameLabel} を追加", + "visDefaultEditor.aggAdd.bucketLabel": "バケット", + "visDefaultEditor.aggAdd.metricLabel": "メトリック", + "visDefaultEditor.aggParams.errors.aggWrongRunOrderErrorMessage": "「{schema}」集約は他のバケットの前に実行する必要があります!", "visDefaultEditor.aggSelect.aggregationLabel": "集約", - "visDefaultEditor.aggSelect.helpLinkLabel": "{aggTitle} のヘルプ", + "visDefaultEditor.aggSelect.helpLinkLabel": "{aggTitle}のヘルプ", "visDefaultEditor.aggSelect.noCompatibleAggsDescription": "インデックスパターン{indexPatternTitle}には集約可能なフィールドが含まれていません。", "visDefaultEditor.aggSelect.selectAggPlaceholder": "集約を選択してください", "visDefaultEditor.aggSelect.subAggregationLabel": "サブ集約", + "visDefaultEditor.buckets.mustHaveBucketErrorMessage": "「日付ヒストグラム」または「ヒストグラム」集約のバケットを追加します。", "visDefaultEditor.controls.aggNotValidLabel": "- 無効な集約 -", - "visDefaultEditor.controls.columnsLabel": "フィールド", - "visDefaultEditor.controls.dotSizeRatioHelpText": "最小の点から最大の点までの半径の比率を変更します。", - "visDefaultEditor.controls.dotSizeRatioLabel": "点サイズ率", - "visDefaultEditor.controls.rowsLabel": "行", - "visDefaultEditor.controls.splitByLegend": "行または列でチャートを分割します。", "visDefaultEditor.controls.aggregateWith.noAggsErrorTooltip": "選択されたフィールドには互換性のある集約がありません。", - "visDefaultEditor.controls.aggregateWithLabel": "アグリゲーション:", + "visDefaultEditor.controls.aggregateWithLabel": "集約:", "visDefaultEditor.controls.aggregateWithTooltip": "複数ヒットまたは複数値のフィールドを 1 つのメトリックにまとめる方法を選択します。", "visDefaultEditor.controls.changePrecisionLabel": "マップズームの精度を変更", + "visDefaultEditor.controls.columnsLabel": "列", "visDefaultEditor.controls.customMetricLabel": "カスタムメトリック", - "visDefaultEditor.controls.dateRanges.acceptedDateFormatsLinkText": "対応データフォーマット", + "visDefaultEditor.controls.dateRanges.acceptedDateFormatsLinkText": "許容可能な日付形式", "visDefaultEditor.controls.dateRanges.addRangeButtonLabel": "範囲を追加", "visDefaultEditor.controls.dateRanges.errorMessage": "各範囲は1つ以上の有効な日付にしてください。", - "visDefaultEditor.controls.dateRanges.fromColumnLabel": "From", + "visDefaultEditor.controls.dateRanges.fromColumnLabel": "開始:", "visDefaultEditor.controls.dateRanges.removeRangeButtonAriaLabel": "{from}から{to}の範囲を削除", - "visDefaultEditor.controls.dateRanges.toColumnLabel": "To", + "visDefaultEditor.controls.dateRanges.toColumnLabel": "終了:", + "visDefaultEditor.controls.definiteMetricLabel": "メトリック: {metric}", + "visDefaultEditor.controls.dotSizeRatioHelpText": "最小の点から最大の点までの半径の比率を変更します。", + "visDefaultEditor.controls.dotSizeRatioLabel": "点サイズ率", "visDefaultEditor.controls.dropPartialBucketsLabel": "不完全なバケットをドロップ", "visDefaultEditor.controls.dropPartialBucketsTooltip": "時間範囲外にわたるバケットを削除してヒストグラムが不完全なバケットで開始・終了しないようにします。", "visDefaultEditor.controls.extendedBounds.errorMessage": "最低値は最大値以下でなければなりません。", @@ -2883,7 +3090,7 @@ "visDefaultEditor.controls.extendedBoundsTooltip": "最低値と最高値は結果を絞るのではなく、結果セットのバウンドを拡張します", "visDefaultEditor.controls.field.fieldLabel": "フィールド", "visDefaultEditor.controls.field.noCompatibleFieldsDescription": "インデックスパターン` {indexPatternTitle} に次の互換性のあるフィールドタイプが 1 つも含まれていません: {fieldTypes}", - "visDefaultEditor.controls.field.selectFieldPlaceholder": "フィールドを選択", + "visDefaultEditor.controls.field.selectFieldPlaceholder": "フィールドの選択", "visDefaultEditor.controls.filters.addFilterButtonLabel": "フィルターを追加します", "visDefaultEditor.controls.filters.definiteFilterLabel": "{index} ラベルでフィルタリング", "visDefaultEditor.controls.filters.filterLabel": "{index} でフィルタリング", @@ -2898,10 +3105,10 @@ "visDefaultEditor.controls.ipRanges.ipRangeToAriaLabel": "IP 範囲の終了値: {value}", "visDefaultEditor.controls.ipRanges.removeCidrMaskButtonAriaLabel": "{mask} の CIDR マスクの値を削除", "visDefaultEditor.controls.ipRanges.removeEmptyCidrMaskButtonAriaLabel": "CIDR マスクのデフォルトの値を削除", - "visDefaultEditor.controls.ipRanges.removeRangeAriaLabel": "{from} から {to} の範囲を削除", + "visDefaultEditor.controls.ipRanges.removeRangeAriaLabel": "{from}から{to}の範囲を削除", "visDefaultEditor.controls.ipRangesAriaLabel": "IP 範囲", "visDefaultEditor.controls.jsonInputLabel": "JSON インプット", - "visDefaultEditor.controls.jsonInputTooltip": "ここに追加された JSON フォーマットのプロパティは、すべてこのセクションの Elasticsearch アグリゲーション定義に融合されます。用語集約における「shard_size」がその例です。", + "visDefaultEditor.controls.jsonInputTooltip": "ここに追加された JSON 形式のプロパティは、すべてこのセクションの Elasticsearch 集約定義に融合されます。用語集約における「shard_size」がその例です。", "visDefaultEditor.controls.metricLabel": "メトリック", "visDefaultEditor.controls.metrics.bucketTitle": "バケット", "visDefaultEditor.controls.metrics.metricTitle": "メトリック", @@ -2909,8 +3116,9 @@ "visDefaultEditor.controls.numberInterval.minimumIntervalTooltip": "入力された値により高度な設定の {histogramMaxBars} で指定されたよりも多くのバケットが作成される場合、間隔は自動的にスケーリングされます。", "visDefaultEditor.controls.numberInterval.selectIntervalPlaceholder": "間隔を入力", "visDefaultEditor.controls.numberList.addUnitButtonLabel": "{unitName} を追加", + "visDefaultEditor.controls.numberList.duplicateValueErrorMessage": "重複値。", "visDefaultEditor.controls.numberList.enterValuePlaceholder": "値を入力", - "visDefaultEditor.controls.numberList.invalidAscOrderErrorMessage": "値は昇順でなければなりません。", + "visDefaultEditor.controls.numberList.invalidAscOrderErrorMessage": "値は昇順になっていません。", "visDefaultEditor.controls.numberList.invalidRangeErrorMessage": "値は {min} から {max} の範囲でなければなりません。", "visDefaultEditor.controls.numberList.removeUnitButtonAriaLabel": "{value} のランク値を削除", "visDefaultEditor.controls.onlyRequestDataAroundMapExtentLabel": "マップ範囲のデータのみリクエストしてください", @@ -2929,18 +3137,22 @@ "visDefaultEditor.controls.placeMarkersOffGridLabel": "グリッド外にマーカーを配置 (ジオセントロイドを使用)", "visDefaultEditor.controls.precisionLabel": "精度", "visDefaultEditor.controls.ranges.addRangeButtonLabel": "範囲を追加", - "visDefaultEditor.controls.ranges.fromLabel": "開始値:", - "visDefaultEditor.controls.ranges.greaterThanOrEqualPrepend": "≥", + "visDefaultEditor.controls.ranges.fromLabel": "開始:", + "visDefaultEditor.controls.ranges.greaterThanOrEqualPrepend": "≧", + "visDefaultEditor.controls.ranges.greaterThanOrEqualTooltip": "よりも大きいまたは等しい", "visDefaultEditor.controls.ranges.lessThanPrepend": "<", - "visDefaultEditor.controls.ranges.removeRangeButtonAriaLabel": "{from} から {to} の範囲を削除", - "visDefaultEditor.controls.ranges.toLabel": "To", - "visDefaultEditor.controls.scaleMetricsLabel": "メトリック値のスケーリング (廃止)", + "visDefaultEditor.controls.ranges.lessThanTooltip": "より小さい", + "visDefaultEditor.controls.ranges.removeRangeButtonAriaLabel": "{from}から{to}の範囲を削除", + "visDefaultEditor.controls.ranges.toLabel": "終了:", + "visDefaultEditor.controls.rowsLabel": "行", + "visDefaultEditor.controls.scaleMetricsLabel": "メトリック値のスケーリング (非推奨)", "visDefaultEditor.controls.scaleMetricsTooltip": "これを有効にすると、手動最低間隔を選択し、広い間隔が使用された場合、カウントと合計メトリックが手動で選択された間隔にスケーリングされます。", "visDefaultEditor.controls.showEmptyBucketsLabel": "空のバケットを表示", "visDefaultEditor.controls.showEmptyBucketsTooltip": "結果のあるバケットだけでなくすべてのバケットを表示します", "visDefaultEditor.controls.sizeLabel": "サイズ", "visDefaultEditor.controls.sizeTooltip": "トップ K のヒットをリクエスト。複数ヒットは「集約基準」でまとめられます。", "visDefaultEditor.controls.sortOnLabel": "並べ替えオン", + "visDefaultEditor.controls.splitByLegend": "行または列でチャートを分割します。", "visDefaultEditor.controls.timeInterval.createsTooLargeBucketsTooltip": "この間隔は、選択された時間範囲に表示するには大きすぎるバケットが作成されるため、にスケーリングされています。", "visDefaultEditor.controls.timeInterval.createsTooManyBucketsTooltip": "この間隔は選択された時間範囲に表示しきれない数のバケットが作成されるため、にスケーリングされています。", "visDefaultEditor.controls.timeInterval.invalidFormatErrorMessage": "無効な間隔フォーマット。", @@ -2948,24 +3160,26 @@ "visDefaultEditor.controls.timeInterval.scaledHelpText": "現在 {bucketDescription} にスケーリングされています", "visDefaultEditor.controls.timeInterval.selectIntervalPlaceholder": "間隔を選択", "visDefaultEditor.controls.timeInterval.selectOptionHelpText": "オプションを選択するかカスタム値を作成します。例30s、20m、24h、2d、1w、1M", - "visDefaultEditor.advancedToggle.advancedLinkLabel": "高度な設定", - "visDefaultEditor.agg.disableAggButtonTooltip": "集約を無効にする", - "visDefaultEditor.agg.enableAggButtonTooltip": "集約を有効にする", - "visDefaultEditor.agg.errorsAriaLabel": "集約にエラーがあります", - "visDefaultEditor.agg.modifyPriorityButtonTooltip": "ドラッグして優先順位を変更します", - "visDefaultEditor.agg.removeDimensionButtonTooltip": "ディメンションを削除", - "visDefaultEditor.agg.toggleEditorButtonAriaLabel": "{schema} エディターを切り替える", - "visDefaultEditor.aggAdd.addButtonLabel": "追加", - "visDefaultEditor.aggAdd.addGroupButtonLabel": "{groupNameLabel} を追加", - "visDefaultEditor.aggAdd.addSubGroupButtonLabel": "サブ {groupNameLabel} を追加", - "visDefaultEditor.aggAdd.bucketLabel": "バケット", - "visDefaultEditor.aggAdd.metricLabel": "メトリック", - "visDefaultEditor.aggParams.errors.aggWrongRunOrderErrorMessage": "「{schema}」集約は他のバケットの前に実行する必要があります!", - "visDefaultEditor.sidebar.autoApplyChangesAriaLabel": "変更されるごとにビジュアライゼーションを自動的に更新します", + "visDefaultEditor.editorConfig.dateHistogram.customInterval.helpText": "構成間隔の倍数でなければなりません: {interval}", + "visDefaultEditor.editorConfig.histogram.interval.helpText": "構成間隔の倍数でなければなりません: {interval}", + "visDefaultEditor.metrics.wrongLastBucketTypeErrorMessage": "「{type}」メトリック集約を使用する場合、最後のバケット集約は「Date Histogram」または「Histogram」でなければなりません。", + "visDefaultEditor.sidebar.autoApplyChangesAriaLabel": "エディターの変更を自動適用します", + "visDefaultEditor.sidebar.autoApplyChangesOffLabel": "自動適用がオフです", + "visDefaultEditor.sidebar.autoApplyChangesOnLabel": "自動適用がオンです", + "visDefaultEditor.sidebar.autoApplyChangesTooltip": "変更されるごとにビジュアライゼーションを自動的に更新します。", + "visDefaultEditor.sidebar.collapseButtonAriaLabel": "サイドバーを切り替える", + "visDefaultEditor.sidebar.discardChangesButtonLabel": "破棄", "visDefaultEditor.sidebar.errorButtonTooltip": "ハイライトされたフィールドのエラーを解決する必要があります。", + "visDefaultEditor.sidebar.indexPatternAriaLabel": "インデックスパターン: {title}", + "visDefaultEditor.sidebar.savedSearch.goToDiscoverButtonText": "Discover にこの検索を表示", + "visDefaultEditor.sidebar.savedSearch.linkButtonAriaLabel": "保存された検索へのリンク。クリックして詳細を確認するかリンクを解除します。", + "visDefaultEditor.sidebar.savedSearch.popoverHelpText": "保存したこの検索に今後加える修正は、ビジュアライゼーションに反映されます。自動更新を無効にするには、リンクを削除します。", + "visDefaultEditor.sidebar.savedSearch.popoverTitle": "保存された検索にリンクされています", + "visDefaultEditor.sidebar.savedSearch.titleAriaLabel": "保存された検索: {title}", + "visDefaultEditor.sidebar.savedSearch.unlinkSavedSearchButtonText": "保存された検索へのリンクを削除", "visDefaultEditor.sidebar.tabs.dataLabel": "データ", "visDefaultEditor.sidebar.tabs.optionsLabel": "オプション", - "visDefaultEditor.metrics.wrongLastBucketTypeErrorMessage": "「{type}」メトリック集約を使用する場合、最後のバケット集約は「Date Histogram」または「Histogram」でなければなりません。", + "visDefaultEditor.sidebar.updateChartButtonLabel": "更新", "visTypeMarkdown.function.font.help": "フォント設定です。", "visTypeMarkdown.function.help": "マークダウンビジュアライゼーション", "visTypeMarkdown.function.markdown.help": "レンダリングするマークダウン", @@ -2988,7 +3202,7 @@ "visTypeMetric.function.help": "メトリックビジュアライゼーション", "visTypeMetric.function.invertColors.help": "色範囲を反転します", "visTypeMetric.function.metric.help": "メトリックディメンションの構成です。", - "visTypeMetric.function.percentageMode.help": "パーセンテージモードでメトリックを表示します。colorRange を設定する必要があります。", + "visTypeMetric.function.percentageMode.help": "百分率モードでメトリックを表示します。colorRange を設定する必要があります。", "visTypeMetric.function.showLabels.help": "メトリック値の下にラベルを表示します。", "visTypeMetric.function.subText.help": "メトリックの下に表示するカスタムテキスト", "visTypeMetric.function.useRanges.help": "有効な色範囲です。", @@ -3613,11 +3827,272 @@ "visTypeVega.visualization.renderErrorTitle": "Vega エラー", "visTypeVega.visualization.unableToFindDefaultIndexErrorMessage": "デフォルトのインデックスが見つかりません", "visTypeVega.visualization.unableToRenderWithoutDataWarningMessage": "データなしにはレンダリングできません", + "visTypeVislib.area.areaDescription": "折れ線グラフの下の数量を強調します。", + "visTypeVislib.area.areaTitle": "エリア", + "visTypeVislib.area.countText": "カウント", + "visTypeVislib.area.groupTitle": "系列を分割", + "visTypeVislib.area.metricsTitle": "Y 軸", + "visTypeVislib.area.radiusTitle": "点のサイズ", + "visTypeVislib.area.segmentTitle": "X 軸", + "visTypeVislib.area.splitTitle": "チャートを分割", + "visTypeVislib.area.tabs.metricsAxesTitle": "メトリックと軸", + "visTypeVislib.area.tabs.panelSettingsTitle": "パネル設定", + "visTypeVislib.axisModes.normalText": "標準", + "visTypeVislib.axisModes.percentageText": "割合 (%)", + "visTypeVislib.axisModes.silhouetteText": "シルエット", + "visTypeVislib.axisModes.wiggleText": "振動", + "visTypeVislib.categoryAxis.rotate.angledText": "傾斜", + "visTypeVislib.categoryAxis.rotate.horizontalText": "横", + "visTypeVislib.categoryAxis.rotate.verticalText": "縦", + "visTypeVislib.chartModes.normalText": "標準", + "visTypeVislib.chartModes.stackedText": "スタック", + "visTypeVislib.chartTypes.areaText": "エリア", + "visTypeVislib.chartTypes.barText": "バー", + "visTypeVislib.chartTypes.lineText": "折れ線", + "visTypeVislib.controls.colorRanges.errorText": "各範囲は前の範囲よりも大きくなければなりません。", + "visTypeVislib.controls.colorSchema.colorSchemaLabel": "配色", + "visTypeVislib.controls.colorSchema.howToChangeColorsDescription": "それぞれの色は凡例で変更できます。", + "visTypeVislib.controls.colorSchema.resetColorsButtonLabel": "色をリセット", + "visTypeVislib.controls.colorSchema.reverseColorSchemaLabel": "図表を反転", + "visTypeVislib.controls.gaugeOptions.alignmentLabel": "アラインメント", + "visTypeVislib.controls.gaugeOptions.autoExtendRangeLabel": "範囲を自動拡張", + "visTypeVislib.controls.gaugeOptions.displayWarningsLabel": "警告を表示", + "visTypeVislib.controls.gaugeOptions.extendRangeTooltip": "範囲をデータの最高値に広げます。", + "visTypeVislib.controls.gaugeOptions.gaugeTypeLabel": "ゲージタイプ", + "visTypeVislib.controls.gaugeOptions.labelsTitle": "ラベル", + "visTypeVislib.controls.gaugeOptions.percentageModeLabel": "百分率モード", + "visTypeVislib.controls.gaugeOptions.rangesTitle": "範囲", + "visTypeVislib.controls.gaugeOptions.showLabelsLabel": "ラベルを表示", + "visTypeVislib.controls.gaugeOptions.showLegendLabel": "凡例を表示", + "visTypeVislib.controls.gaugeOptions.showOutline": "アウトラインを表示", + "visTypeVislib.controls.gaugeOptions.showScaleLabel": "縮尺を表示", + "visTypeVislib.controls.gaugeOptions.styleTitle": "スタイル", + "visTypeVislib.controls.gaugeOptions.subTextLabel": "サブラベル", + "visTypeVislib.controls.gaugeOptions.switchWarningsTooltip": "警告のオン/オフを切り替えます。オンにすると、すべてのラベルを表示できない際に警告が表示されます。", + "visTypeVislib.controls.heatmapOptions.colorLabel": "色", + "visTypeVislib.controls.heatmapOptions.colorScaleLabel": "カラースケール", + "visTypeVislib.controls.heatmapOptions.colorsNumberLabel": "色の数", + "visTypeVislib.controls.heatmapOptions.labelsTitle": "ラベル", + "visTypeVislib.controls.heatmapOptions.overwriteAutomaticColorLabel": "自動からーを上書きする", + "visTypeVislib.controls.heatmapOptions.percentageModeLabel": "百分率モード", + "visTypeVislib.controls.heatmapOptions.rotateLabel": "回転", + "visTypeVislib.controls.heatmapOptions.scaleToDataBoundsLabel": "データバウンドに合わせる", + "visTypeVislib.controls.heatmapOptions.showLabelsTitle": "ラベルを表示", + "visTypeVislib.controls.heatmapOptions.useCustomRangesLabel": "カスタム範囲を使用", + "visTypeVislib.controls.pointSeries.categoryAxis.alignLabel": "配置", + "visTypeVislib.controls.pointSeries.categoryAxis.filterLabelsLabel": "フィルターラベル", + "visTypeVislib.controls.pointSeries.categoryAxis.labelsTitle": "ラベル", + "visTypeVislib.controls.pointSeries.categoryAxis.positionLabel": "位置", + "visTypeVislib.controls.pointSeries.categoryAxis.showLabel": "軸線とラベルを表示", + "visTypeVislib.controls.pointSeries.categoryAxis.showLabelsLabel": "ラベルを表示", + "visTypeVislib.controls.pointSeries.categoryAxis.xAxisTitle": "X 軸", + "visTypeVislib.controls.pointSeries.gridAxis.dontShowLabel": "非表示", + "visTypeVislib.controls.pointSeries.gridAxis.gridText": "グリッド", + "visTypeVislib.controls.pointSeries.gridAxis.xAxisLinesLabel": "X 軸線を表示", + "visTypeVislib.controls.pointSeries.gridAxis.yAxisLinesDisabledTooltip": "ヒストグラムに X 軸線は表示できません。", + "visTypeVislib.controls.pointSeries.gridAxis.yAxisLinesLabel": "Y 軸線を表示", + "visTypeVislib.controls.pointSeries.series.chartTypeLabel": "チャートタイプ", + "visTypeVislib.controls.pointSeries.series.lineModeLabel": "線のモード", + "visTypeVislib.controls.pointSeries.series.lineWidthLabel": "線の幅", + "visTypeVislib.controls.pointSeries.series.metricsTitle": "メトリック", + "visTypeVislib.controls.pointSeries.series.modeLabel": "モード", + "visTypeVislib.controls.pointSeries.series.newAxisLabel": "新規軸…", + "visTypeVislib.controls.pointSeries.series.showDotsLabel": "点を表示", + "visTypeVislib.controls.pointSeries.series.showLineLabel": "線を表示", + "visTypeVislib.controls.pointSeries.series.valueAxisLabel": "値軸", + "visTypeVislib.controls.pointSeries.seriesAccordionAriaLabel": "{agg} オプションを切り替える", + "visTypeVislib.controls.pointSeries.valueAxes.addButtonTooltip": "Y 軸を追加します", + "visTypeVislib.controls.pointSeries.valueAxes.customExtentsLabel": "カスタム範囲", + "visTypeVislib.controls.pointSeries.valueAxes.maxLabel": "最高", + "visTypeVislib.controls.pointSeries.valueAxes.minErrorMessage": "最低値は最高値よりも低く設定する必要があります", + "visTypeVislib.controls.pointSeries.valueAxes.minLabel": "最低", + "visTypeVislib.controls.pointSeries.valueAxes.minNeededScaleText": "ログスケールが選択されている場合、最低値は 0 よりも大きいものである必要があります", + "visTypeVislib.controls.pointSeries.valueAxes.modeLabel": "モード", + "visTypeVislib.controls.pointSeries.valueAxes.positionLabel": "位置", + "visTypeVislib.controls.pointSeries.valueAxes.removeButtonTooltip": "Y 軸を削除します", + "visTypeVislib.controls.pointSeries.valueAxes.scaleToDataBounds.boundsMargin": "境界マージン", + "visTypeVislib.controls.pointSeries.valueAxes.scaleToDataBounds.minNeededBoundsMargin": "境界マージンは 0 以上でなければなりません", + "visTypeVislib.controls.pointSeries.valueAxes.scaleToDataBoundsLabel": "データバウンドに合わせる", + "visTypeVislib.controls.pointSeries.valueAxes.scaleTypeLabel": "スケールタイプ", + "visTypeVislib.controls.pointSeries.valueAxes.setAxisExtentsLabel": "軸範囲を設定", + "visTypeVislib.controls.pointSeries.valueAxes.showLabel": "軸線とラベルを表示", + "visTypeVislib.controls.pointSeries.valueAxes.titleLabel": "タイトル", + "visTypeVislib.controls.pointSeries.valueAxes.toggleCustomExtendsAriaLabel": "カスタム範囲を切り替える", + "visTypeVislib.controls.pointSeries.valueAxes.toggleOptionsAriaLabel": "{axisName} オプションを切り替える", + "visTypeVislib.controls.pointSeries.valueAxes.yAxisTitle": "Y 軸", + "visTypeVislib.controls.rangeErrorMessage": "値は {min} と {max} の間でなければなりません", + "visTypeVislib.controls.truncateLabel": "切り捨て", + "visTypeVislib.controls.vislibBasicOptions.legendPositionLabel": "凡例位置", + "visTypeVislib.controls.vislibBasicOptions.showTooltipLabel": "ツールヒントを表示", + "visTypeVislib.editors.heatmap.basicSettingsTitle": "基本設定", + "visTypeVislib.editors.heatmap.heatmapSettingsTitle": "ヒートマップ設定", + "visTypeVislib.editors.heatmap.highlightLabel": "ハイライト範囲", + "visTypeVislib.editors.heatmap.highlightLabelTooltip": "チャートのカーソルを当てた部分と凡例の対応するラベルをハイライトします。", + "visTypeVislib.editors.pie.donutLabel": "ドーナッツ", + "visTypeVislib.editors.pie.labelsSettingsTitle": "ラベル設定", + "visTypeVislib.editors.pie.pieSettingsTitle": "パイ設定", + "visTypeVislib.editors.pie.showLabelsLabel": "ラベルを表示", + "visTypeVislib.editors.pie.showTopLevelOnlyLabel": "トップレベルのみ表示", + "visTypeVislib.editors.pie.showValuesLabel": "値を表示", + "visTypeVislib.editors.pointSeries.currentTimeMarkerLabel": "現在時刻マーカー", + "visTypeVislib.editors.pointSeries.orderBucketsBySumLabel": "バケットを合計で並べ替え", + "visTypeVislib.editors.pointSeries.settingsTitle": "設定", + "visTypeVislib.editors.pointSeries.showLabels": "チャートに値を表示", + "visTypeVislib.editors.pointSeries.thresholdLine.colorLabel": "線の色", + "visTypeVislib.editors.pointSeries.thresholdLine.showLabel": "しきい線を表示", + "visTypeVislib.editors.pointSeries.thresholdLine.styleLabel": "ラインスタイル", + "visTypeVislib.editors.pointSeries.thresholdLine.valueLabel": "しきい値", + "visTypeVislib.editors.pointSeries.thresholdLine.widthLabel": "線の幅", + "visTypeVislib.editors.pointSeries.thresholdLineSettingsTitle": "しきい線", + "visTypeVislib.functions.pie.help": "パイビジュアライゼーション", + "visTypeVislib.functions.vislib.help": "Vislib ビジュアライゼーション", + "visTypeVislib.gauge.alignmentAutomaticTitle": "自動", + "visTypeVislib.gauge.alignmentHorizontalTitle": "横", + "visTypeVislib.gauge.alignmentVerticalTitle": "縦", + "visTypeVislib.gauge.gaugeDescription": "ゲージはメトリックのステータスを示します。メトリックの値とリファレンスしきい値との関連性を示すのに使用します。", + "visTypeVislib.gauge.gaugeTitle": "ゲージ", + "visTypeVislib.gauge.gaugeTypes.arcText": "弧形", + "visTypeVislib.gauge.gaugeTypes.circleText": "円", + "visTypeVislib.gauge.groupTitle": "グループを分割", + "visTypeVislib.gauge.metricTitle": "メトリック", + "visTypeVislib.goal.goalDescription": "ゴールチャートは、最終目標にどれだけ近いかを示します。", + "visTypeVislib.goal.goalTitle": "ゴール", + "visTypeVislib.goal.groupTitle": "グループを分割", + "visTypeVislib.goal.metricTitle": "メトリック", + "visTypeVislib.heatmap.groupTitle": "Y 軸", + "visTypeVislib.heatmap.heatmapDescription": "マトリックス内のセルに影をつける。", + "visTypeVislib.heatmap.heatmapTitle": "ヒートマップ", + "visTypeVislib.heatmap.metricTitle": "値", + "visTypeVislib.heatmap.segmentTitle": "X 軸", + "visTypeVislib.heatmap.splitTitle": "チャートを分割", + "visTypeVislib.histogram.groupTitle": "系列を分割", + "visTypeVislib.histogram.histogramDescription": "連続変数を各軸に割り当てる。", + "visTypeVislib.histogram.histogramTitle": "縦棒", + "visTypeVislib.histogram.metricTitle": "Y 軸", + "visTypeVislib.histogram.radiusTitle": "点のサイズ", + "visTypeVislib.histogram.segmentTitle": "X 軸", + "visTypeVislib.histogram.splitTitle": "チャートを分割", + "visTypeVislib.horizontalBar.groupTitle": "系列を分割", + "visTypeVislib.horizontalBar.horizontalBarDescription": "連続変数を各軸に割り当てる。", + "visTypeVislib.horizontalBar.horizontalBarTitle": "横棒", + "visTypeVislib.horizontalBar.metricTitle": "Y 軸", + "visTypeVislib.horizontalBar.radiusTitle": "点のサイズ", + "visTypeVislib.horizontalBar.segmentTitle": "X 軸", + "visTypeVislib.horizontalBar.splitTitle": "チャートを分割", + "visTypeVislib.interpolationModes.smoothedText": "スムーズ", + "visTypeVislib.interpolationModes.steppedText": "ステップ", + "visTypeVislib.interpolationModes.straightText": "直線", + "visTypeVislib.legendPositions.bottomText": "一番下", + "visTypeVislib.legendPositions.leftText": "左", + "visTypeVislib.legendPositions.rightText": "右", + "visTypeVislib.legendPositions.topText": "トップ", + "visTypeVislib.line.groupTitle": "系列を分割", + "visTypeVislib.line.lineDescription": "トレンドを強調します。", + "visTypeVislib.line.lineTitle": "折れ線", + "visTypeVislib.line.metricTitle": "Y 軸", + "visTypeVislib.line.radiusTitle": "点のサイズ", + "visTypeVislib.line.segmentTitle": "X 軸", + "visTypeVislib.line.splitTitle": "チャートを分割", + "visTypeVislib.pie.metricTitle": "サイズのスライス", + "visTypeVislib.pie.pieDescription": "全体に対する内訳を表現する。", + "visTypeVislib.pie.pieTitle": "パイ", + "visTypeVislib.pie.segmentTitle": "スライスの分割", + "visTypeVislib.pie.splitTitle": "チャートを分割", + "visTypeVislib.scaleTypes.linearText": "線形", + "visTypeVislib.scaleTypes.logText": "ログ", + "visTypeVislib.scaleTypes.squareRootText": "平方根", + "visTypeVislib.thresholdLine.style.dashedText": "鎖線", + "visTypeVislib.thresholdLine.style.dotdashedText": "点線", + "visTypeVislib.thresholdLine.style.fullText": "完全", + "visTypeVislib.vislib.heatmap.maxBucketsText": "定義された数列が多すぎます ({nr})。構成されている最大値は {max} です。", + "visTypeVislib.vislib.legend.filterForValueButtonAriaLabel": "値 {legendDataLabel} でフィルタリング", + "visTypeVislib.vislib.legend.filterOptionsLegend": "{legendDataLabel}、フィルターオプション", + "visTypeVislib.vislib.legend.filterOutValueButtonAriaLabel": "値 {legendDataLabel} を除外", + "visTypeVislib.vislib.legend.loadingLabel": "読み込み中…", + "visTypeVislib.vislib.legend.setColorScreenReaderDescription": "値 {legendDataLabel} の色を設定", + "visTypeVislib.vislib.legend.toggleLegendButtonAriaLabel": "凡例を切り替える", + "visTypeVislib.vislib.legend.toggleLegendButtonTitle": "凡例を切り替える", + "visTypeVislib.vislib.legend.toggleOptionsButtonAriaLabel": "{legendDataLabel}、トグルオプション", + "visTypeVislib.vislib.tooltip.fieldLabel": "フィールド", + "visTypeVislib.vislib.tooltip.valueLabel": "値", + "visualizations.disabledLabVisualizationMessage": "ラボビジュアライゼーションを表示するには、高度な設定でラボモードをオンにしてください。", + "visualizations.disabledLabVisualizationTitle": "{title} はラボビジュアライゼーションです。", + "visualizations.displayName": "ビジュアライゼーション", + "visualizations.function.range.from.help": "範囲の開始", + "visualizations.function.range.help": "範囲オブジェクトを生成します", + "visualizations.function.range.to.help": "範囲の終了", + "visualizations.function.visDimension.accessor.help": "使用するデータセット内の列 (列インデックスまたは列名)", + "visualizations.function.visDimension.error.accessor": "入力された列名は無効です。", + "visualizations.function.visDimension.format.help": "フォーマット", + "visualizations.function.visDimension.formatParams.help": "フォーマットパラメーター", + "visualizations.function.visDimension.help": "visConfig ディメンションオブジェクトを生成します", + "visualizations.functions.visualization.help": "シンプルなビジュアライゼーションです", + "visualizations.newVisWizard.betaDescription": "このビジュアライゼーションはベータ段階で、変更される可能性があります。デザインとコードはオフィシャル GA 機能よりも完成度が低く、現状のまま保証なしで提供されています。ベータ機能にはオフィシャル GA 機能の SLA が適用されません", + "visualizations.newVisWizard.betaTitle": "ベータ", + "visualizations.newVisWizard.chooseSourceTitle": "ソースの選択", + "visualizations.newVisWizard.experimentalDescription": "このビジュアライゼーションは実験的なものです。デザインと導入は安定したビジュアライゼーションよりも完成度が低く、変更される可能性があります。", + "visualizations.newVisWizard.experimentalTitle": "実験的", + "visualizations.newVisWizard.experimentalTooltip": "このビジュアライゼーションは今後のリリースで変更または削除される可能性があり、SLA のサポート対象になりません。", + "visualizations.newVisWizard.filterVisTypeAriaLabel": "ビジュアライゼーションのタイプでフィルタリング", + "visualizations.newVisWizard.helpText": "タイプを選択してビジュアライゼーションの作成を始めましょう。", + "visualizations.newVisWizard.helpTextAriaLabel": "タイプを選択してビジュアライゼーションの作成を始めましょう。ESC を押してこのモーダルを閉じます。Tab キーを押して次に進みます。", + "visualizations.newVisWizard.newVisTypeTitle": "新規 {visTypeName}", + "visualizations.newVisWizard.resultsFound": "{resultCount} 個の{resultCount, plural, one {タイプ} other {タイプ} } が見つかりました", + "visualizations.newVisWizard.searchSelection.notFoundLabel": "一致インデックスまたは保存した検索が見つかりません。", + "visualizations.newVisWizard.searchSelection.savedObjectType.indexPattern": "インデックスパターン", + "visualizations.newVisWizard.searchSelection.savedObjectType.search": "保存検索", + "visualizations.newVisWizard.selectVisType": "ビジュアライゼーションのタイプを選択してください", + "visualizations.newVisWizard.title": "新規ビジュアライゼーション", + "visualizations.newVisWizard.visTypeAliasDescription": "Visualize 外で Kibana アプリケーションを開きます。", + "visualizations.newVisWizard.visTypeAliasTitle": "Kibana アプリケーション", + "visualizations.savedObjectName": "ビジュアライゼーション", "xpack.actions.actionTypeRegistry.get.missingActionTypeErrorMessage": "アクションタイプ \"{id}\" は登録されていません。", "xpack.actions.actionTypeRegistry.register.duplicateActionTypeErrorMessage": "アクションタイプ \"{id}\" は既に登録されています。", + "xpack.actions.appName": "アクション", + "xpack.actions.builtin.email.errorSendingErrorMessage": "エラー送信メールアドレス", + "xpack.actions.builtin.emailTitle": "メール", + "xpack.actions.builtin.esIndex.errorIndexingErrorMessage": "エラーインデックス作成ドキュメント", + "xpack.actions.builtin.esIndexTitle": "インデックス", + "xpack.actions.builtin.pagerduty.pagerdutyConfigurationError": "pagerduty アクションの設定エラー: {message}", + "xpack.actions.builtin.pagerduty.postingErrorMessage": "pagerduty イベントの投稿エラー", + "xpack.actions.builtin.pagerduty.postingRetryErrorMessage": "pagerduty イベントの投稿エラー: http status {status}、後ほど再試行", + "xpack.actions.builtin.pagerduty.postingUnexpectedErrorMessage": "pagerduty イベントの投稿エラー: 予期せぬステータス {status}", + "xpack.actions.builtin.pagerdutyTitle": "PagerDuty", + "xpack.actions.builtin.serverLog.errorLoggingErrorMessage": "メッセージのロギングエラー", + "xpack.actions.builtin.serverLogTitle": "サーバーログ", + "xpack.actions.builtin.servicenow.emptyMapping": "[casesConfiguration.mapping]: 空以外の値が必要ですが空でした", + "xpack.actions.builtin.servicenow.informationAdded": "({date} に {user} が追加)", + "xpack.actions.builtin.servicenow.informationCreated": "({date} に {user} が作成)", + "xpack.actions.builtin.servicenow.informationDefault": "({date} に {user} が作成)", + "xpack.actions.builtin.servicenow.informationUpdated": "({date} に {user} が更新)", + "xpack.actions.builtin.servicenow.postingErrorMessage": "servicenow イベントの送信エラー", + "xpack.actions.builtin.servicenow.postingRetryErrorMessage": "servicenow イベントの送信エラー: http status {status}、後で再試行", + "xpack.actions.builtin.servicenow.postingUnexpectedErrorMessage": "servicenow イベントの送信エラー: 予期しないステータス {status}", + "xpack.actions.builtin.servicenow.servicenowApiNullError": "ServiceNow [apiUrl] が必要です", + "xpack.actions.builtin.servicenow.servicenowApiWhitelistError": "servicenow アクションの構成エラー: {message}", + "xpack.actions.builtin.servicenowTitle": "ServiceNow", + "xpack.actions.builtin.slack.errorPostingErrorMessage": "slack メッセージの投稿エラー", + "xpack.actions.builtin.slack.errorPostingRetryDateErrorMessage": "slack メッセージの投稿エラー、 {retryString} に再試行", + "xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage": "slack メッセージの投稿エラー、後ほど再試行", + "xpack.actions.builtin.slack.slackConfigurationError": "slack アクションの設定エラー: {message}", + "xpack.actions.builtin.slack.slackConfigurationErrorNoHostname": "slack アクションの構成エラー: Web フック URL からホスト名をパースできません", + "xpack.actions.builtin.slack.unexpectedHttpResponseErrorMessage": "slack からの予期せぬ http 応答: {httpStatus} {httpStatusText}", "xpack.actions.builtin.slack.unexpectedNullResponseErrorMessage": "Slack から予期せぬ null 応答", + "xpack.actions.builtin.slackTitle": "Slack", + "xpack.actions.builtin.webhook.invalidResponseErrorMessage": "Webフックの呼び出しエラー、無効な応答", + "xpack.actions.builtin.webhook.invalidResponseRetryDateErrorMessage": "Webフックの呼び出しエラー、{retryString} に再試行", + "xpack.actions.builtin.webhook.invalidResponseRetryLaterErrorMessage": "Webフックの呼び出しエラー、後ほど再試行", + "xpack.actions.builtin.webhook.invalidUsernamePassword": "ユーザーとパスワードの両方を指定する必要があります", + "xpack.actions.builtin.webhook.unreachableErrorMessage": "webhookの呼び出しエラー、予期せぬエラー", "xpack.actions.builtin.webhook.webhookConfigurationError": "Web フックアクションの構成中にエラーが発生: {message}", - "xpack.actions.urlWhitelistConfigurationError": "ターゲット {field} 「{value}」は Kibana のホワイトリストに登録されていません", + "xpack.actions.builtin.webhookTitle": "Web フック", + "xpack.actions.disabledActionTypeError": "アクションタイプ \"{actionType}\" は、Kibana 構成 xpack.actions.enabledActionTypes では有効化されません", + "xpack.actions.serverSideErrors.expirerdLicenseErrorMessage": "{licenseType} ライセンスの期限が切れたのでアクションタイプ {actionTypeId} は無効です。", + "xpack.actions.serverSideErrors.invalidLicenseErrorMessage": "{licenseType} ライセンスでサポートされないのでアクションタイプ {actionTypeId} は無効です。ライセンスをアップグレードしてください。", + "xpack.actions.serverSideErrors.unavailableLicenseErrorMessage": "現時点でライセンス情報を入手できないため、アクションタイプ {actionTypeId} は無効です。", + "xpack.actions.serverSideErrors.unavailableLicenseInformationErrorMessage": "グラフを利用できません。現在ライセンス情報が利用できません。", + "xpack.actions.urlWhitelistConfigurationError": "target {field} \"{value}\" は Kibana 構成 xpack.actions.whitelistedHosts にはホワイトリスト化されていません。", + "xpack.advancedUiActions.components.actionWizard.changeButton": "変更", "xpack.advancedUiActions.customizePanelTimeRange.modal.addToPanelButtonTitle": "パネルに追加", "xpack.advancedUiActions.customizePanelTimeRange.modal.cancelButtonTitle": "キャンセル", "xpack.advancedUiActions.customizePanelTimeRange.modal.optionsMenuForm.panelTitleFormRowLabel": "時間範囲", @@ -3625,9 +4100,135 @@ "xpack.advancedUiActions.customizePanelTimeRange.modal.updatePanelTimeRangeButtonTitle": "更新", "xpack.advancedUiActions.customizeTimeRange.modal.headerTitle": "パネルの時間範囲のカスタマイズ", "xpack.advancedUiActions.customizeTimeRangeMenuItem.displayName": "時間範囲のカスタマイズ", + "xpack.alerting.alertNavigationRegistry.get.missingNavigationError": "「{consumer}」内のアラートタイプ「{alertType}」のナビゲーションは登録されていません。", + "xpack.alerting.alertNavigationRegistry.register.duplicateDefaultError": "「{consumer}」内のデフォルトナビゲーションは既に登録されています。", + "xpack.alerting.alertNavigationRegistry.register.duplicateNavigationError": "「{consumer}」内のアラートタイプ「{alertType}」のナビゲーションは既に登録されています。", "xpack.alerting.alertsClient.validateActions.invalidGroups": "無効なアクショングループ: {groups}", "xpack.alerting.alertTypeRegistry.get.missingAlertTypeError": "アラートタイプ\"{id}\"は登録されていません。", "xpack.alerting.alertTypeRegistry.register.duplicateAlertTypeError": "アラートタイプ\"{id}\"は既に登録されています。", + "xpack.alerting.api.error.disabledApiKeys": "アラートは API キーに依存しますがキーが無効になっているようです", + "xpack.alerting.appName": "アラート", + "xpack.alerting.loadAlertType.missingAlertTypeError": "アラートタイプ「{id}」は登録されていません。", + "xpack.alerting.serverSideErrors.unavailableLicenseInformationErrorMessage": "アラートを利用できません。現在ライセンス情報が利用できません。", + "xpack.alertingBuiltins.indexThreshold.actionGroupThresholdMetTitle": "しきい値一致", + "xpack.alertingBuiltins.indexThreshold.actionVariableContextDateLabel": "アラートがしきい値を超えた日付。", + "xpack.alertingBuiltins.indexThreshold.actionVariableContextGroupLabel": "しきい値を超えたグループ。", + "xpack.alertingBuiltins.indexThreshold.actionVariableContextMessageLabel": "アラートの事前構成メッセージ。", + "xpack.alertingBuiltins.indexThreshold.actionVariableContextTitleLabel": "アラートの事前構成タイトル。", + "xpack.alertingBuiltins.indexThreshold.actionVariableContextValueLabel": "しきい値を超えた値。", + "xpack.alertingBuiltins.indexThreshold.aggTypeRequiredErrorMessage": "[aggType] が「{aggType}」のときには [aggField] に値が必要です", + "xpack.alertingBuiltins.indexThreshold.alertTypeContextMessageDescription": "アラート {name} グループ {group} 値 {value} が {date} に {window} にわたってしきい値 {function} を超えました", + "xpack.alertingBuiltins.indexThreshold.alertTypeContextSubjectTitle": "アラート {name} グループ {group} がしきい値を超えました", + "xpack.alertingBuiltins.indexThreshold.alertTypeTitle": "インデックスしきい値", + "xpack.alertingBuiltins.indexThreshold.dateStartGTdateEndErrorMessage": "[dateStart] が [dateEnd] よりも大です", + "xpack.alertingBuiltins.indexThreshold.formattedFieldErrorMessage": "{fieldName} の無効な {formatName} 形式:「{fieldValue}」", + "xpack.alertingBuiltins.indexThreshold.intervalRequiredErrorMessage": "[interval]: [dateStart] が [dateEnd] と等しくない場合に指定する必要があります", + "xpack.alertingBuiltins.indexThreshold.invalidAggTypeErrorMessage": "無効な aggType:「{aggType}」", + "xpack.alertingBuiltins.indexThreshold.invalidComparatorErrorMessage": "無効な thresholdComparator が指定されました: {comparator}", + "xpack.alertingBuiltins.indexThreshold.invalidDateErrorMessage": "無効な日付 {date}", + "xpack.alertingBuiltins.indexThreshold.invalidDurationErrorMessage": "無効な期間:「{duration}」", + "xpack.alertingBuiltins.indexThreshold.invalidGroupByErrorMessage": "無効な groupBy:「{groupBy}」", + "xpack.alertingBuiltins.indexThreshold.invalidTermSizeMaximumErrorMessage": "[termSize]: {maxGroups} 以下でなければなりません。", + "xpack.alertingBuiltins.indexThreshold.invalidThreshold2ErrorMessage": "[threshold]: 「{thresholdComparator}」比較子の場合には 2 つの要素が必要です", + "xpack.alertingBuiltins.indexThreshold.invalidTimeWindowUnitsErrorMessage": "無効な timeWindowUnit:「{timeWindowUnit}」", + "xpack.alertingBuiltins.indexThreshold.maxIntervalsErrorMessage": "間隔 {intervals} の計算値が {maxIntervals} よりも大です", + "xpack.alertingBuiltins.indexThreshold.termFieldRequiredErrorMessage": "[termField]: [groupBy] がトップのときには termField が必要です", + "xpack.alertingBuiltins.indexThreshold.termSizeRequiredErrorMessage": "[termSize]: [groupBy] がトップのときには termSize が必要です", + "xpack.apm.agentConfig.allOptionLabel": "すべて", + "xpack.apm.agentConfig.apiRequestSize.description": "チャンクエンコーディング (HTTP ストリーミング) を経由して APM Server インテーク API に送信されるリクエスト本文の最大合計圧縮サイズ。\nわずかなオーバーシュートの可能性があることに注意してください。\n\n使用できるバイト単位は、「b」、「kb」、「mb」です。「1kb」は「1024b」と等価です。", + "xpack.apm.agentConfig.apiRequestSize.label": "API リクエストサイズ", + "xpack.apm.agentConfig.apiRequestTime.description": "APM Server への HTTP リクエストを開いておく最大時間。\n\n注:この値は、APM Server の「read_timeout」設定よりも低くする必要があります。", + "xpack.apm.agentConfig.apiRequestTime.label": "API リクエスト時間", + "xpack.apm.agentConfig.bytes.errorText": "整数と単位を指定してください", + "xpack.apm.agentConfig.captureBody.description": "HTTP リクエストのトランザクションの場合、エージェントはオプションとしてリクエスト本文 (POST 変数など) をキャプチャすることができます。デフォルトは「off」です。", + "xpack.apm.agentConfig.captureBody.label": "本文をキャプチャ", + "xpack.apm.agentConfig.captureHeaders.description": "「true」に設定すると、エージェントは Cookie を含むリクエストヘッダーとレスポンスヘッダーをキャプチャします。\n\n注:これを「false」に設定すると、ネットワーク帯域幅、ディスク容量、およびオブジェクト割り当てが減少します。", + "xpack.apm.agentConfig.captureHeaders.label": "ヘッダーのキャプチャ", + "xpack.apm.agentConfig.chooseService.editButton": "編集", + "xpack.apm.agentConfig.chooseService.service.environment.label": "環境", + "xpack.apm.agentConfig.chooseService.service.name.label": "サービス名", + "xpack.apm.agentConfig.chooseService.title": "サービスを選択", + "xpack.apm.agentConfig.circuitBreakerEnabled.description": "Circuit Breaker を有効にすべきかどうかを指定するブール値。\n有効にすると、エージェントは定期的にストレス監視をポーリングして、システム/プロセス/JVM のストレス状態を検出します。\n監視のいずれかがストレスの兆候を検出した場合、<> 構成オプションの設定が「false」であるかのようにエージェントは非アクティブになり、リソース消費を最小限に抑えられます。 \n\n非アクティブな場合、エージェントはストレス状態が緩和されたかどうかを検出するために同じ監視のポーリングを継続します。 \nすべての監視でシステム/プロセス/JVM にストレスがないことが認められると、エージェントは再開して完全に機能します。", + "xpack.apm.agentConfig.circuitBreakerEnabled.label": "Cirtcuit Breaker が有効", + "xpack.apm.agentConfig.configTable.appliedTooltipMessage": "1 つ以上のエージェントにより適用されました", + "xpack.apm.agentConfig.configTable.configTable.failurePromptText": "エージェントの構成一覧を取得できませんでした。ユーザーに十分なパーミッションがない可能性があります。", + "xpack.apm.agentConfig.configTable.createConfigButtonLabel": "構成の作成", + "xpack.apm.agentConfig.configTable.emptyPromptText": "変更しましょう。Kibana からエージェント構成を直接的に微調整できます。再展開する必要はありません。まず、最初の構成を作成します。", + "xpack.apm.agentConfig.configTable.emptyPromptTitle": "構成が見つかりません。", + "xpack.apm.agentConfig.configTable.environmentColumnLabel": "サービス環境", + "xpack.apm.agentConfig.configTable.lastUpdatedColumnLabel": "最終更新", + "xpack.apm.agentConfig.configTable.notAppliedTooltipMessage": "まだエージェントにより適用されていません", + "xpack.apm.agentConfig.configTable.serviceNameColumnLabel": "サービス名", + "xpack.apm.agentConfig.configurationsPanelTitle": "構成", + "xpack.apm.agentConfig.createConfigButtonLabel": "構成の作成", + "xpack.apm.agentConfig.createConfigTitle": "構成の作成", + "xpack.apm.agentConfig.deleteModal.cancel": "キャンセル", + "xpack.apm.agentConfig.deleteModal.confirm": "削除", + "xpack.apm.agentConfig.deleteModal.text": "サービス「{serviceName}」と環境「{environment}」の構成を削除しようとしています。", + "xpack.apm.agentConfig.deleteModal.title": "構成を削除", + "xpack.apm.agentConfig.deleteSection.deleteConfigFailedText": "「{serviceName}」の構成を削除中に問題が発生しました。エラー: 「{errorMessage}」", + "xpack.apm.agentConfig.deleteSection.deleteConfigFailedTitle": "構成を削除できませんでした", + "xpack.apm.agentConfig.deleteSection.deleteConfigSucceededText": "「{serviceName}」の構成が正常に削除されました。エージェントに反映されるまでに少し時間がかかります。", + "xpack.apm.agentConfig.deleteSection.deleteConfigSucceededTitle": "構成が削除されました", + "xpack.apm.agentConfig.editConfigTitle": "構成の編集", + "xpack.apm.agentConfig.enableLogCorrelation.description": "エージェントが SLF4J のhttps://www.slf4j.org/api/org/slf4j/MDC.html[MDC] と融合してトレースログ相関を有効にすべきかどうかを指定するブール値。\n「true」に設定した場合、エージェントは現在アクティブなスパンとトランザクションの「trace.id」と「transaction.id」を MDC に設定します。\n詳細は <> をご覧ください。\n\n注:実行時にこの設定を有効にできますが、再起動しないと無効にはできません。", + "xpack.apm.agentConfig.enableLogCorrelation.label": "ログ相関を有効にする", + "xpack.apm.agentConfig.float.errorText": "0.000 から 1 までの数字でなければなりません", + "xpack.apm.agentConfig.integer.errorText": "整数でなければなりません", + "xpack.apm.agentConfig.logLevel.description": "エージェントのログ記録レベルを設定します", + "xpack.apm.agentConfig.logLevel.label": "ログレベル", + "xpack.apm.agentConfig.newConfig.description": "これで Kibana でエージェント構成を直接的に微調整できます。\n しかも、変更は APM エージェントに自動的に伝達されるので、再デプロイする必要はありません。", + "xpack.apm.agentConfig.profilingInferredSpansEnabled.description": "「true」に設定すると、エージェントは、別名統計プロファイラーと呼ばれるサンプリングプロファイラーである https://github.com/jvm-profiling-tools/async-profiler[async-profiler] に基づいてメソッド実行用のスパンを作成します。サンプリングプロファイラーのしくみの性質上、推定スパンの期間は厳密ではなく見込みのみです。<<config-profiling-inferred-spans-sampling-interval, `profiling_inferred_spans_sampling_interval`>> で精度とオーバーヘッドのトレードオフを微調整できます。\n推定スパンは、プロファイルセッションの終了後に作成されます。つまり、通常のスパンと推定スパンの間には UI に表示されるタイミングに遅延があります。注:この機能は Windows で使用できません。", + "xpack.apm.agentConfig.profilingInferredSpansEnabled.label": "プロファイル推定スパンが有効です", + "xpack.apm.agentConfig.profilingInferredSpansExcludedClasses.description": "プロファイラー推定スパンを作成する必要がないクラスを除外します。\n\nこのオプションは、0 文字以上に一致するワイルドカード「*」をサポートします。\n例: 「/foo/*/bar/*/baz*」、「*foo*」\nデフォルトでは、照合時に大文字と小文字の区別はありません。\n要素の前に「(?-i)」を付けると、照合時に大文字と小文字が区別されます。", + "xpack.apm.agentConfig.profilingInferredSpansExcludedClasses.label": "プロファイル推定スパンでクラスを除外しました", + "xpack.apm.agentConfig.profilingInferredSpansIncludedClasses.description": "設定した場合、エージェントは、このリストに一致するメソッドの推定スパンのみを作成します。\n値を設定すると、パフォーマンスがわずかに向上することがあり、関心あるクラスのスパンのみを作成することによって煩雑になるのを防止できます。\n例:「org.example.myapp.*」\n\nこのオプションは、0 文字以上に一致するワイルドカード「*」をサポートします。\n例: 「/foo/*/bar/*/baz*」、「*foo*」\nデフォルトでは、照合時に大文字と小文字の区別はありません。\n要素の前に「(?-i)」を付けると、照合時に大文字と小文字が区別されます。", + "xpack.apm.agentConfig.profilingInferredSpansIncludedClasses.label": "プロファイル推定スパンでクラスを包含しました", + "xpack.apm.agentConfig.profilingInferredSpansMinDuration.description": "推定スパンの最小期間。\n最小期間もサンプリング間隔によって暗黙的に設定されることに注意してください。\nただし、サンプリング間隔を大きくすると、推定スパンの期間の精度も低下します。", + "xpack.apm.agentConfig.profilingInferredSpansMinDuration.label": "プロファイル推定スパン最小期間", + "xpack.apm.agentConfig.profilingInferredSpansSamplingInterval.description": "プロファイルセッション内でスタックトレースを収集する頻度。\n低い値に設定するほど継続時間の精度が上がります。\nその代わり、オーバーヘッドが増し、潜在的に無関係なオペレーションのスパンが増えるという犠牲が伴います。\nプロファイル推定スパンの最小期間は、この設定値と同じです。", + "xpack.apm.agentConfig.profilingInferredSpansSamplingInterval.label": "プロファイル推定サンプリング間隔", + "xpack.apm.agentConfig.saveConfig.failed.text": "「{serviceName}」の構成を保存中に問題が発生しました。エラー: 「{errorMessage}」", + "xpack.apm.agentConfig.saveConfig.failed.title": "構成を保存できませんでした", + "xpack.apm.agentConfig.saveConfig.succeeded.text": "「{serviceName}」の構成を保存しました。エージェントに反映されるまでに少し時間がかかります。", + "xpack.apm.agentConfig.saveConfig.succeeded.title": "構成が保存されました", + "xpack.apm.agentConfig.saveConfigurationButtonLabel": "次のステップ", + "xpack.apm.agentConfig.serverTimeout.description": "APM Server へのリクエストにかかる時間が設定したタイムアウトよりも長い場合、リクエストは中止され、イベント (例外またはトランザクション) は破棄されます。\n\n0 に設定するとタイムアウトが無効になります。\n\n警告:タイムアウトが無効か高い値に設定されている場合、APM Server がタイムアウトになると、アプリでメモリの問題が発生する可能性があります。", + "xpack.apm.agentConfig.serverTimeout.label": "サーバータイムアウト", + "xpack.apm.agentConfig.servicePage.alreadyConfiguredOption": "既に構成済み", + "xpack.apm.agentConfig.servicePage.cancelButton": "キャンセル", + "xpack.apm.agentConfig.servicePage.environment.description": "構成ごとに 1 つの環境のみがサポートされます。", + "xpack.apm.agentConfig.servicePage.environment.fieldLabel": "サービス環境", + "xpack.apm.agentConfig.servicePage.environment.title": "環境", + "xpack.apm.agentConfig.servicePage.service.description": "構成するサービスを選択してください。", + "xpack.apm.agentConfig.servicePage.service.fieldLabel": "サービス名", + "xpack.apm.agentConfig.servicePage.service.title": "サービス", + "xpack.apm.agentConfig.servicePage.title": "サービスを選択", + "xpack.apm.agentConfig.settings.title": "構成オプション", + "xpack.apm.agentConfig.settingsPage.discardChangesButton": "変更を破棄", + "xpack.apm.agentConfig.settingsPage.notFound.message": "リクエストされた構成が存在しません", + "xpack.apm.agentConfig.settingsPage.notFound.title": "申し訳ございません、エラーが発生しました", + "xpack.apm.agentConfig.settingsPage.saveButton": "構成を保存", + "xpack.apm.agentConfig.spanFramesMinDuration.description": "デフォルト設定では、APM エージェントは記録されたすべてのスパンでスタックトレースを収集します。\nこれはコード内でスパンの原因になる厳密な場所を見つけるうえで非常に役立ちますが、このスタックトレースを収集するとオーバーヘッドが生じます。\nこのオプションを負の値 (「-1ms」など) に設定すると、すべてのスパンのスタックトレースが収集されます。正の値 (たとえば、「5 ms」) に設定すると、スタックトレース収集を、指定値 (たとえば、5ミリ秒) 以上の期間にわたるスパンに制限されます。\n\nスパンのスタックトレース収集を完全に無効にするには、値を「0ms」に設定します。", + "xpack.apm.agentConfig.spanFramesMinDuration.label": "スパンフレーム最小期間", + "xpack.apm.agentConfig.stackTraceLimit.description": "0 に設定するとスタックトレース収集が無効になります。収集するフレームの最大数として正の整数値が使用されます。 -1 に設定すると、すべてのフレームが収集されます。", + "xpack.apm.agentConfig.stackTraceLimit.label": "スタックトレース制限", + "xpack.apm.agentConfig.stressMonitorCpuDurationThreshold.description": "システムに現在ストレスがかかっているか、それとも以前に検出したストレスが緩和されたかを判断するために必要な最小時間。 \n\nこの時期のすべての測定は、関連しきい値と比較してストレス状態の変化を検出できるように一貫性が必要です。 \n「1m」以上にする必要があります。", + "xpack.apm.agentConfig.stressMonitorCpuDurationThreshold.label": "ストレス監視 CPU 期間しきい値", + "xpack.apm.agentConfig.stressMonitorGcReliefThreshold.description": "ヒープにストレスがかからない時期を特定するために GC 監視で使用するしきい値。\n「stress_monitor_gc_stress_threshold」を超えた場合、エージェントはそれをヒープストレス状態と見なします。\nストレス状態が収まったことを確認するには、すべてのヒーププールで占有メモリの割合がこのしきい値よりも低いことを確認します。 \nGC 監視は、直近の GC の後で測定したメモリ消費のみに依存します。", + "xpack.apm.agentConfig.stressMonitorGcReliefThreshold.label": "ストレス監視システム GC 緩和しきい値", + "xpack.apm.agentConfig.stressMonitorGcStressThreshold.description": "ヒープストレスを特定するために GC 監視で使用するしきい値。\nすべてのヒーププールに同じしきい値が使用され、いずれかの使用率がその値を超える場合、エージェントはそれをヒープストレスと見なします。\nGC 監視は、直近の GC の後で測定したメモリ消費のみに依存します。", + "xpack.apm.agentConfig.stressMonitorGcStressThreshold.label": "ストレス監視システム GC ストレスしきい値", + "xpack.apm.agentConfig.stressMonitorSystemCpuReliefThreshold.description": "システムに CPU ストレスがかかっていないことを判断するためにシステム CPU 監視で使用するしきい値。 \n監視機能で CPU ストレスを検出した場合に CPU ストレスが緩和されたと判断するには、測定されたシステム CPU が「stress_monitor_cpu_duration_threshold」と同じ長さ以上の期間にわたってこのしきい値を下回る必要があります。", + "xpack.apm.agentConfig.stressMonitorSystemCpuReliefThreshold.label": "ストレス監視システム CPU 緩和しきい値", + "xpack.apm.agentConfig.stressMonitorSystemCpuStressThreshold.description": "システム CPU 監視でシステム CPU ストレスの検出に使用するしきい値。\nシステム CPU が少なくとも「stress_monitor_cpu_duration_threshold」と同じ長さ以上の期間にわたってこのしきい値を超えると、監視機能はこれをストレス状態と見なします。", + "xpack.apm.agentConfig.stressMonitorSystemCpuStressThreshold.label": "ストレス監視システム CPU ストレスしきい値", + "xpack.apm.agentConfig.transactionMaxSpans.description": "トランザクションごとに記録される範囲を制限します。デフォルトは 500 です。", + "xpack.apm.agentConfig.transactionMaxSpans.errorText": "0 と 32000 の間でなければなりません", + "xpack.apm.agentConfig.transactionMaxSpans.label": "トランザクションの最大範囲", + "xpack.apm.agentConfig.transactionSampleRate.description": "デフォルトでは、エージェントはすべてのトランザクション (例えば、サービスへのリクエストなど) をサンプリングします。オーバーヘッドやストレージ要件を減らすには、サンプルレートの値を 0.0〜1.0 に設定します。全体的な時間とサンプリングされないトランザクションの結果は記録されますが、コンテキスト情報、ラベル、スパンは記録されません。", + "xpack.apm.agentConfig.transactionSampleRate.label": "トランザクションのサンプルレート", + "xpack.apm.agentConfig.unsavedSetting.tooltip": "未保存", "xpack.apm.agentMetrics.java.gcRate": "GC レート", "xpack.apm.agentMetrics.java.gcRateChartTitle": "1 分ごとのごみ収集レート", "xpack.apm.agentMetrics.java.gcTime": "GC 時間", @@ -3642,6 +4243,8 @@ "xpack.apm.agentMetrics.java.threadCount": "平均カウント", "xpack.apm.agentMetrics.java.threadCountChartTitle": "スレッド数", "xpack.apm.agentMetrics.java.threadCountMax": "最高カウント", + "xpack.apm.alertTypes.errorRate": "エラー率", + "xpack.apm.alertTypes.transactionDuration": "トランザクション期間", "xpack.apm.apmDescription": "アプリケーション内から自動的に詳細なパフォーマンスメトリックやエラーを集めます。", "xpack.apm.apmForESDescription": "Elastic Stack 用の APM", "xpack.apm.applyFilter": "{title} フィルターを適用", @@ -3652,6 +4255,11 @@ "xpack.apm.breadcrumb.nodesTitle": "JVM", "xpack.apm.breadcrumb.serviceMapTitle": "サービスマップ", "xpack.apm.breadcrumb.servicesTitle": "サービス", + "xpack.apm.breadcrumb.settings.agentConfigurationTitle": "エージェントの編集", + "xpack.apm.breadcrumb.settings.createAgentConfigurationTitle": "エージェント構成の作成", + "xpack.apm.breadcrumb.settings.customizeUI": "UI をカスタマイズ", + "xpack.apm.breadcrumb.settings.editAgentConfigurationTitle": "エージェント構成の編集", + "xpack.apm.breadcrumb.settings.indicesTitle": "インデックス", "xpack.apm.breadcrumb.tracesTitle": "トレース", "xpack.apm.breadcrumb.transactionsTitle": "トランザクション", "xpack.apm.chart.cpuSeries.processAverageLabel": "プロセス平均", @@ -3661,6 +4269,10 @@ "xpack.apm.chart.memorySeries.systemAverageLabel": "平均", "xpack.apm.chart.memorySeries.systemMaxLabel": "最高", "xpack.apm.clearFilters": "フィルターを消去", + "xpack.apm.customLink.buttom.create": "カスタムリンクを作成", + "xpack.apm.customLink.buttom.create.title": "作成", + "xpack.apm.customLink.buttom.manage": "カスタムリンクを管理", + "xpack.apm.customLink.empty": "カスタムリンクが見つかりません。独自のカスタムリンク、つまり特定のダッシュボードまたは外部リンクへのリンクをセットアップします。", "xpack.apm.datePicker.last15MinutesLabel": "過去 15 分間", "xpack.apm.datePicker.last1HourLabel": "過去 1 時間", "xpack.apm.datePicker.last1YearLabel": "過去 1 年間", @@ -3685,6 +4297,10 @@ "xpack.apm.errorGroupDetails.relatedTransactionSample": "関連トランザクションサンプル", "xpack.apm.errorGroupDetails.unhandledLabel": "未対応", "xpack.apm.errorGroupDetails.viewOccurrencesInDiscoverButtonLabel": "ディスカバリで {occurrencesCount} 件の{occurrencesCount, plural, one {ドキュメント} other {ドキュメント}}を表示。", + "xpack.apm.errorRateAlert.name": "エラー率しきい値", + "xpack.apm.errorRateAlert.thresholdMet": "しきい値一致", + "xpack.apm.errorRateAlertTrigger.errors": "エラー", + "xpack.apm.errorRateAlertTrigger.isAbove": "の下限は", "xpack.apm.errorsTable.errorMessageAndCulpritColumnLabel": "エラーメッセージと原因", "xpack.apm.errorsTable.groupIdColumnLabel": "グループ ID", "xpack.apm.errorsTable.latestOccurrenceColumnLabel": "最近のオカレンス", @@ -3692,6 +4308,7 @@ "xpack.apm.errorsTable.occurrencesColumnLabel": "オカレンス", "xpack.apm.errorsTable.unhandledLabel": "未対応", "xpack.apm.featureRegistry.apmFeatureName": "APM", + "xpack.apm.feedbackMenu.appName": "APM", "xpack.apm.fetcher.error.status": "エラー", "xpack.apm.fetcher.error.title": "リソースの取得中にエラーが発生しました", "xpack.apm.fetcher.error.url": "URL", @@ -3723,20 +4340,30 @@ "xpack.apm.jvmsTable.noJvmsLabel": "JVM が見つかりませんでした", "xpack.apm.jvmsTable.nonHeapMemoryColumnLabel": "非ヒープ領域の平均", "xpack.apm.jvmsTable.threadCountColumnLabel": "最大スレッド数", + "xpack.apm.kueryBar.disabledPlaceholder": "サービスマップの検索は利用できません", + "xpack.apm.kueryBar.placeholder": "検索 {event, select,\n トランザクション {transactions}\n メトリック: {metric}\n エラー {errors}\n その他 {transactions, errors and metrics}\n } (E.g. {queryExample})", + "xpack.apm.license.betaBadge": "ベータ", + "xpack.apm.license.betaTooltipMessage": "現在、この機能はベータです。不具合を見つけた場合やご意見がある場合、サポートに問い合わせるか、またはディスカッションフォーラムにご報告ください。", + "xpack.apm.license.button": "トライアルを開始", + "xpack.apm.license.title": "無料の 30 日トライアルを開始", + "xpack.apm.loadingServiceMap": "サービスマップを読み込み中...多少時間がかかる場合があります。", "xpack.apm.localFilters.titles.agentName": "エージェント名", "xpack.apm.localFilters.titles.containerId": "コンテナー ID", "xpack.apm.localFilters.titles.host": "ホスト", - "xpack.apm.localFilters.titles.podName": "ポッド", + "xpack.apm.localFilters.titles.podName": "Kubernetes ポッド", + "xpack.apm.localFilters.titles.serviceVersion": "サービスバージョン", "xpack.apm.localFilters.titles.transactionResult": "トランザクション結果", "xpack.apm.localFilters.titles.transactionType": "トランザクションタイプ", "xpack.apm.localFiltersTitle": "各種フィルター", "xpack.apm.metadataTable.section.agentLabel": "エージェント", + "xpack.apm.metadataTable.section.clientLabel": "クライアント", "xpack.apm.metadataTable.section.containerLabel": "コンテナー", "xpack.apm.metadataTable.section.customLabel": "カスタム", "xpack.apm.metadataTable.section.errorLabel": "エラー", "xpack.apm.metadataTable.section.hostLabel": "ホスト", "xpack.apm.metadataTable.section.httpLabel": "HTTP", "xpack.apm.metadataTable.section.labelsLabel": "ラベル", + "xpack.apm.metadataTable.section.messageLabel": "メッセージ", "xpack.apm.metadataTable.section.pageLabel": "ページ", "xpack.apm.metadataTable.section.processLabel": "プロセス", "xpack.apm.metadataTable.section.serviceLabel": "サービス", @@ -3749,6 +4376,7 @@ "xpack.apm.metrics.durationByCountryMap.avgPageLoadByCountryLabel": "国ごとの平均ページ読み込み時間の分布", "xpack.apm.metrics.durationByCountryMap.RegionMapChart.ToolTip.avgPageLoadDuration": "平均ページ読み込み時間:", "xpack.apm.metrics.durationByCountryMap.RegionMapChart.ToolTip.countPageLoads": "{docCount} ページの読み込み", + "xpack.apm.metrics.pageLoadCharts.avgPageLoadByBrowser": "ブラウザごとの平均ページ読み込み時間の分布", "xpack.apm.metrics.plot.noDataLabel": "この時間範囲のデータがありません。", "xpack.apm.metrics.transactionChart.machineLearningLabel": "機械学習:", "xpack.apm.metrics.transactionChart.machineLearningTooltip": "平均期間の周りのストリームには予測バウンドが表示されます。異常スコアが >= 75 の場合、注釈が表示されます。", @@ -3759,17 +4387,30 @@ "xpack.apm.metrics.transactionChart.transactionsPerMinuteLabel": "1 分あたりのトランザクション数", "xpack.apm.notAvailableLabel": "N/A", "xpack.apm.percentOfParent": "({parentType, select, transaction { 件中 {value} 件のトランザクション} トレース {trace} })", + "xpack.apm.permission.apm": "APM", + "xpack.apm.permission.description": "このユーザーには、すべての APM インデックスへのアクセス権がありません。APM アプリを使用できますが、一部のデータが欠けることがあります。以下のインデックスへのアクセス権が必要です。", + "xpack.apm.permission.dismissWarning": "閉じる", + "xpack.apm.permission.learnMore": "APM パーミッションの詳細を表示", + "xpack.apm.permission.title": "APM へのアクセス権がありません", "xpack.apm.propertiesTable.agentFeature.noDataAvailableLabel": "利用可能なデータがありません", + "xpack.apm.propertiesTable.agentFeature.noResultFound": "\"{value}\"に対する結果が見つかりませんでした。", "xpack.apm.propertiesTable.tabs.exceptionStacktraceLabel": "例外のスタックトレース", - "xpack.apm.propertiesTable.tabs.logStacktraceLabel": "ログのスタックトレース", + "xpack.apm.propertiesTable.tabs.logStacktraceLabel": "スタックトレース", "xpack.apm.propertiesTable.tabs.metadataLabel": "メタデータ", "xpack.apm.propertiesTable.tabs.timelineLabel": "タイムライン", + "xpack.apm.searchInput.filter": "フィルター...", + "xpack.apm.selectPlaceholder": "オプションを選択:", + "xpack.apm.serviceDetails.alertsMenu.alerts": "アラート", + "xpack.apm.serviceDetails.alertsMenu.createThresholdAlert": "しきい値アラートを作成", + "xpack.apm.serviceDetails.alertsMenu.errorRate": "エラー率", + "xpack.apm.serviceDetails.alertsMenu.transactionDuration": "トランザクション期間", + "xpack.apm.serviceDetails.alertsMenu.viewActiveAlerts": "アクティブアラートを表示", "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsDescription": "現在 {serviceName} ({transactionType}) の実行中のジョブがあります。", "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsDescription.viewJobLinkText": "既存のジョブを表示", "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.callout.jobExistsTitle": "ジョブが既に存在します", "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.createMLJobDescription": "ここでは、{serviceName} 数列内の APM トランザクションの期間の異常スコアを計算する機械学習ジョブを作成できます。有効にすると、{transactionDurationGraphText} が予測バウンドを表示し、異常スコアが >=75 の場合グラフに注釈が追加されます。", "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.createMLJobDescription.transactionDurationGraphText": "トランザクション時間のグラフ", - "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.createNewJobButtonLabel": "新規ジョブを作成", + "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.createNewJobButtonLabel": "ジョブを作成", "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.enableAnomalyDetectionTitle": "異常検知を有効にする", "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreatedNotificationText": "現在 {serviceName} ({transactionType}) の分析を実行中です。応答時間グラフに結果が追加されるまで少し時間がかかる場合があります。", "xpack.apm.serviceDetails.enableAnomalyDetectionPanel.jobCreatedNotificationText.viewJobLinkText": "ジョブを表示", @@ -3826,10 +4467,29 @@ "xpack.apm.serviceDetails.metricsTabLabel": "メトリック", "xpack.apm.serviceDetails.nodesTabLabel": "JVM", "xpack.apm.serviceDetails.transactionsTabLabel": "トランザクション", + "xpack.apm.serviceMap.avgCpuUsagePopoverMetric": "CPU使用状況 (平均)", + "xpack.apm.serviceMap.avgErrorsPerMinutePopoverMetric": "1分あたりのエラー(平均)", + "xpack.apm.serviceMap.avgMemoryUsagePopoverMetric": "メモリー使用状況(平均)", + "xpack.apm.serviceMap.avgReqPerMinutePopoverMetric": "1分あたりのリクエスト(平均)", + "xpack.apm.serviceMap.avgTransDurationPopoverMetric": "トランザクションの長さ(平均)", + "xpack.apm.serviceMap.betaBadge": "ベータ", + "xpack.apm.serviceMap.betaTooltipMessage": "現在、この機能はベータです。不具合を見つけた場合やご意見がある場合、サポートに問い合わせるか、またはディスカッションフォーラムにご報告ください。", + "xpack.apm.serviceMap.center": "中央", + "xpack.apm.serviceMap.emptyBanner.docsLink": "詳細はドキュメントをご覧ください", + "xpack.apm.serviceMap.emptyBanner.message": "接続されているサービスや外部リクエストを検出できる場合、システムはそれらをマップします。最新版の APM エージェントが動作していることを確認してください。", + "xpack.apm.serviceMap.emptyBanner.title": "単一のサービスしかないようです。", + "xpack.apm.serviceMap.focusMapButtonText": "焦点マップ", + "xpack.apm.serviceMap.invalidLicenseMessage": "サービスマップを利用するには、Elastic Platinum ライセンスが必要です。これにより、APM データとともにアプリケーションスタック全てを可視化することができるようになります。", + "xpack.apm.serviceMap.numInstancesMetric": "{numInstances}インスタンス", + "xpack.apm.serviceMap.serviceDetailsButtonText": "サービス詳細", + "xpack.apm.serviceMap.subtypePopoverMetric": "サブタイプ", + "xpack.apm.serviceMap.typePopoverMetric": "タイプ", + "xpack.apm.serviceMap.viewFullMap": "サービスの全体マップを表示", "xpack.apm.serviceMap.zoomIn": "ズームイン", "xpack.apm.serviceMap.zoomOut": "ズームアウト", "xpack.apm.serviceNodeMetrics.containerId": "コンテナー ID", "xpack.apm.serviceNodeMetrics.host": "ホスト", + "xpack.apm.serviceNodeMetrics.serviceName": "サービス名", "xpack.apm.serviceNodeMetrics.unidentifiedServiceNodesWarningDocumentationLink": "APM Server のドキュメンテーション", "xpack.apm.serviceNodeMetrics.unidentifiedServiceNodesWarningText": "これらのメトリックが所属する JVM を特定できませんでした。7.5 よりも古い APM Server を実行していることが原因である可能性が高いです。この問題は APM Server 7.5 以降にアップグレードすることで解決されます。アップグレードに関する詳細は、{link} をご覧ください。代わりに Kibana クエリバーを使ってホスト名、コンテナー ID、またはその他フィールドでフィルタリングすることもできます。", "xpack.apm.serviceNodeMetrics.unidentifiedServiceNodesWarningTitle": "JVM を特定できませんでした", @@ -3851,8 +4511,75 @@ "xpack.apm.servicesTable.transactionsPerMinuteColumnLabel": "1 分あたりのトランザクション", "xpack.apm.servicesTable.transactionsPerMinuteUnitLabel": "1分あたりトランザクション数", "xpack.apm.servicesTable.UpgradeAssistantLink": "Kibana アップグレードアシスタントで詳細をご覧ください", + "xpack.apm.serviceVersion": "サービスバージョン", + "xpack.apm.settings.agentConfig": "エージェントの編集", + "xpack.apm.settings.apmIndices.applyButton": "変更を適用", + "xpack.apm.settings.apmIndices.applyChanges.failed.text": "インデックスの適用時に何か問題が発生しました。エラー: {errorMessage}", + "xpack.apm.settings.apmIndices.applyChanges.failed.title": "インデックスが適用できませんでした。", + "xpack.apm.settings.apmIndices.applyChanges.succeeded.text": "インデックスの変更の適用に成功しました。これらの変更は、APM UIでただちに反映されます。", + "xpack.apm.settings.apmIndices.applyChanges.succeeded.title": "適用されるインデックス", + "xpack.apm.settings.apmIndices.cancelButton": "キャンセル", + "xpack.apm.settings.apmIndices.description": "APM UI は、APM インデックスをクエリするためにインデックスパターンを使用しています。APM サーバーがイベントを書き込むインデックス名をカスタマイズした場合、APM UI が機能するにはこれらパターンをアップデートする必要がある場合があります。ここの設定は、 kibana.yml で設定されたものよりも優先します。", + "xpack.apm.settings.apmIndices.errorIndicesLabel": "エラーインデックス", + "xpack.apm.settings.apmIndices.helpText": "上書き {configurationName}: {defaultValue}", + "xpack.apm.settings.apmIndices.metricsIndicesLabel": "メトリックインデックス", + "xpack.apm.settings.apmIndices.onboardingIndicesLabel": "オンボーディングインデックス", + "xpack.apm.settings.apmIndices.sourcemapIndicesLabel": "ソースマップインデックス", + "xpack.apm.settings.apmIndices.spanIndicesLabel": "スパンインデックス", + "xpack.apm.settings.apmIndices.title": "インデックス", + "xpack.apm.settings.apmIndices.transactionIndicesLabel": "トランザクションインデックス", + "xpack.apm.settings.customizeUI.customLink": "カスタムリンク", + "xpack.apm.settings.customizeUI.customLink.create.failed": "リンクを保存できませんでした!", + "xpack.apm.settings.customizeUI.customLink.create.failed.message": "リンクを保存するときに問題が発生しました。エラー: 「{errorMessage}」", + "xpack.apm.settings.customizeUI.customLink.create.successed": "リンクを保存しました。", + "xpack.apm.settings.customizeUI.customLink.createCustomLink": "カスタムリンクを作成", + "xpack.apm.settings.customizeUI.customLink.default.label": "Elastic.co", + "xpack.apm.settings.customizeUI.customLink.default.url": "https://www.elastic.co", + "xpack.apm.settings.customizeUI.customLink.delete": "削除", + "xpack.apm.settings.customizeUI.customLink.delete.failed": "カスタムリンクを削除できませんでした", + "xpack.apm.settings.customizeUI.customLink.delete.successed": "カスタムリンクを削除しました。", + "xpack.apm.settings.customizeUI.customLink.emptyPromptText": "変更しましょう。サービスごとのトランザクションの詳細でアクションコンテキストメニューにカスタムリンクを追加できます。自社のサポートポータルへの役立つリンクを作成するか、新しい不具合レポートを発行します。詳細はドキュメントをご覧ください", + "xpack.apm.settings.customizeUI.customLink.emptyPromptTitle": "リンクが見つかりません。", + "xpack.apm.settings.customizeUI.customLink.flyout.action.title": "リンク", + "xpack.apm.settings.customizeUI.customLink.flyout.close": "閉じる", + "xpack.apm.settings.customizeUI.customLink.flyout.filters.addAnotherFilter": "別のフィルターを追加", + "xpack.apm.settings.customizeUI.customLink.flyOut.filters.defaultOption": "フィールドを選択してください...", + "xpack.apm.settings.customizeUI.customLink.flyOut.filters.defaultOption.value": "値", + "xpack.apm.settings.customizeUI.customLink.flyout.filters.prepend": "フィールド", + "xpack.apm.settings.customizeUI.customLink.flyout.filters.subtitle": "フィルターオプションを使用すると、特定のサービスについてのみ表示されるようにスコープを設定できます。", + "xpack.apm.settings.customizeUI.customLink.flyout.filters.title": "フィルター", + "xpack.apm.settings.customizeUI.customLink.flyout.label": "リンクは APM アプリ全体にわたるトランザクション詳細のコンテキストで利用できるようになります。作成できるリンクの数は無制限です。トランザクションメタデータのいずれかを使用することで、動的変数を参照して URL を入力できます。さらなる詳細および例がドキュメンテーションに記載されています。", + "xpack.apm.settings.customizeUI.customLink.flyout.label.doc": "", + "xpack.apm.settings.customizeUI.customLink.flyout.link.label": "ラベル", + "xpack.apm.settings.customizeUI.customLink.flyout.link.label.helpText": "これはアクションコンテキストメニューに表示されるラベルです。できるだけ短くしてください。", + "xpack.apm.settings.customizeUI.customLink.flyout.link.label.placeholder": "例: サポートチケット", + "xpack.apm.settings.customizeUI.customLink.flyout.link.url": "URL", + "xpack.apm.settings.customizeUI.customLink.flyout.link.url.doc": "詳細はドキュメントをご覧ください。", + "xpack.apm.settings.customizeUI.customLink.flyout.link.url.helpText": "URL にフィールド名変数 (例:{sample}) を追加すると値を適用できます。", + "xpack.apm.settings.customizeUI.customLink.flyout.link.url.placeholder": "例: https://www.elastic.co/", + "xpack.apm.settings.customizeUI.customLink.flyout.required": "必須", + "xpack.apm.settings.customizeUI.customLink.flyout.save": "保存", + "xpack.apm.settings.customizeUI.customLink.flyout.title": "リンクを作成", + "xpack.apm.settings.customizeUI.customLink.info": "これらのリンクは、トランザクションの詳細に関する「アクション」コンテキストメニューに表示されます。", + "xpack.apm.settings.customizeUI.customLink.license.text": "カスタムリンクを作成するには、Elastic Gold 以上のライセンスが必要です。適切なライセンスがあれば、カスタムリンクを作成してサービスを分析する際にワークフローを改良できます。", + "xpack.apm.settings.customizeUI.customLink.linkPreview.descrition": "上記のフィルターに基づき、サンプルトランザクションドキュメントの値でリンクをテストしてください。", + "xpack.apm.settings.customizeUI.customLink.preview.contextVariable.invalid": "無効な変数が定義されているため、サンプルトランザクションドキュメントが見つかりませんでした。", + "xpack.apm.settings.customizeUI.customLink.preview.contextVariable.noMatch": "{variables} に一致する値がサンプルトランザクションドキュメント内にありませんでした。", + "xpack.apm.settings.customizeUI.customLink.preview.transaction.notFound": "定義されたフィルターに基づき、一致するトランザクションドキュメントが見つかりませんでした。", + "xpack.apm.settings.customizeUI.customLink.searchInput.filter": "名前と URL でリンクをフィルタリング...", + "xpack.apm.settings.customizeUI.customLink.table.editButtonDescription": "このカスタムリンクを編集", + "xpack.apm.settings.customizeUI.customLink.table.editButtonLabel": "編集", + "xpack.apm.settings.customizeUI.customLink.table.lastUpdated": "最終更新", + "xpack.apm.settings.customizeUI.customLink.table.name": "名前", + "xpack.apm.settings.customizeUI.customLink.table.noResultFound": "\"{value}\"に対する結果が見つかりませんでした。", + "xpack.apm.settings.customizeUI.customLink.table.url": "URL", + "xpack.apm.settings.indices": "インデックス", + "xpack.apm.settings.pageTitle": "設定", + "xpack.apm.settings.returnToOverviewLinkLabel": "概要に戻る", "xpack.apm.settingsLinkLabel": "設定", "xpack.apm.setupInstructionsButtonLabel": "セットアップの手順", + "xpack.apm.stacktraceTab.causedByFramesToogleButtonLabel": "作成元", + "xpack.apm.stacktraceTab.libraryFramesToogleButtonLabel": "{count, plural, one {# library frame} other {# library frames}}", "xpack.apm.stacktraceTab.localVariablesToogleButtonLabel": "ローカル変数", "xpack.apm.stacktraceTab.noStacktraceAvailableLabel": "利用可能なスタックトレースがありません", "xpack.apm.toggleHeight.showLessButtonLabel": "表示する行数を減らす", @@ -3866,14 +4593,28 @@ "xpack.apm.tracesTable.tracesPerMinuteColumnLabel": "1 分あたりのトレース", "xpack.apm.tracesTable.tracesPerMinuteUnitLabel": "1分あたりトランザクション数", "xpack.apm.transactionActionMenu.actionsButtonLabel": "アクション", - "xpack.apm.transactionActionMenu.showContainerLogsLinkLabel": "コンテナーログを表示", - "xpack.apm.transactionActionMenu.showContainerMetricsLinkLabel": "コンテナーメトリックを表示", - "xpack.apm.transactionActionMenu.showHostLogsLinkLabel": "ホストログを表示", - "xpack.apm.transactionActionMenu.showHostMetricsLinkLabel": "ホストメトリックを表示", - "xpack.apm.transactionActionMenu.showPodLogsLinkLabel": "ポッドログを表示", - "xpack.apm.transactionActionMenu.showPodMetricsLinkLabel": "ポッドメトリックを表示", - "xpack.apm.transactionActionMenu.showTraceLogsLinkLabel": "トレースログを表示", - "xpack.apm.transactionActionMenu.viewInUptime": "監視ステータスを表示", + "xpack.apm.transactionActionMenu.container.subtitle": "このコンテナーのログとインデックスを表示し、さらに詳細を確認できます。", + "xpack.apm.transactionActionMenu.container.title": "コンテナーの詳細", + "xpack.apm.transactionActionMenu.customLink.popover.title": "カスタムリンク", + "xpack.apm.transactionActionMenu.customLink.section": "カスタムリンク", + "xpack.apm.transactionActionMenu.customLink.seeMore": "詳細を表示", + "xpack.apm.transactionActionMenu.customLink.subtitle": "リンクは新しいウィンドウで開きます。", + "xpack.apm.transactionActionMenu.host.subtitle": "ホストログとメトリックを表示し、さらに詳細を確認できます。", + "xpack.apm.transactionActionMenu.host.title": "ホストの詳細", + "xpack.apm.transactionActionMenu.pod.subtitle": "このポッドのログとメトリックを表示し、さらに詳細を確認できます。", + "xpack.apm.transactionActionMenu.pod.title": "ポッドの詳細", + "xpack.apm.transactionActionMenu.showContainerLogsLinkLabel": "コンテナーログ", + "xpack.apm.transactionActionMenu.showContainerMetricsLinkLabel": "コンテナーメトリック", + "xpack.apm.transactionActionMenu.showHostLogsLinkLabel": "ホストログ", + "xpack.apm.transactionActionMenu.showHostMetricsLinkLabel": "ホストメトリック", + "xpack.apm.transactionActionMenu.showPodLogsLinkLabel": "ポッドログ", + "xpack.apm.transactionActionMenu.showPodMetricsLinkLabel": "ポッドメトリック", + "xpack.apm.transactionActionMenu.showTraceLogsLinkLabel": "トレースログ", + "xpack.apm.transactionActionMenu.status.subtitle": "ステータスを表示し、さらに詳細を確認できます。", + "xpack.apm.transactionActionMenu.status.title": "ステータスの詳細", + "xpack.apm.transactionActionMenu.trace.subtitle": "トレースログを表示し、さらに詳細を確認できます。", + "xpack.apm.transactionActionMenu.trace.title": "トレースの詳細", + "xpack.apm.transactionActionMenu.viewInUptime": "ステータス", "xpack.apm.transactionActionMenu.viewSampleDocumentLinkLabel": "サンプルドキュメントを表示", "xpack.apm.transactionBreakdown.chartTitle": "スパンタイプ別時間", "xpack.apm.transactionBreakdown.hideChart": "グラフを非表示", @@ -3898,6 +4639,8 @@ "xpack.apm.transactionDetails.spanFlyout.stackTraceTabLabel": "スタックトレース", "xpack.apm.transactionDetails.spanFlyout.viewSpanInDiscoverButtonLabel": "ディスカバリでスパンを表示", "xpack.apm.transactionDetails.statusCode": "ステータスコード", + "xpack.apm.transactionDetails.syncBadgeAsync": "非同期", + "xpack.apm.transactionDetails.syncBadgeBlocking": "ブロック", "xpack.apm.transactionDetails.traceNotFound": "選択されたトレースが見つかりません", "xpack.apm.transactionDetails.traceSampleTitle": "トレースのサンプル", "xpack.apm.transactionDetails.transactionLabel": "トランザクション", @@ -3914,6 +4657,15 @@ "xpack.apm.transactionDetails.userAgentAndVersionLabel": "ユーザーエージェントとバージョン", "xpack.apm.transactionDetails.viewFullTraceButtonLabel": "完全なトレースを表示", "xpack.apm.transactionDetails.viewingFullTraceButtonTooltip": "現在完全なトレースが表示されています", + "xpack.apm.transactionDurationAlert.aggregationType.95th": "95 パーセンタイル", + "xpack.apm.transactionDurationAlert.aggregationType.99th": "99 パーセンタイル", + "xpack.apm.transactionDurationAlert.aggregationType.avg": "平均", + "xpack.apm.transactionDurationAlert.name": "トランザクション期間のしきい値", + "xpack.apm.transactionDurationAlert.thresholdMet": "しきい値一致", + "xpack.apm.transactionDurationAlertTrigger.isAbove": "の下限は", + "xpack.apm.transactionDurationAlertTrigger.ms": "ms", + "xpack.apm.transactionDurationAlertTrigger.type": "タイプ", + "xpack.apm.transactionDurationAlertTrigger.when": "タイミング", "xpack.apm.transactionDurationLabel": "期間", "xpack.apm.transactions.chart.95thPercentileLabel": "95 パーセンタイル", "xpack.apm.transactions.chart.99thPercentileLabel": "99 パーセンタイル", @@ -3934,26 +4686,28 @@ "xpack.apm.tutorial.apmAgents.statusCheck.text": "アプリケーションが実行されていてエージェントがデータを送信していることを確認してください。", "xpack.apm.tutorial.apmAgents.statusCheck.title": "エージェントステータス", "xpack.apm.tutorial.apmAgents.title": "APM エージェント", + "xpack.apm.tutorial.apmServer.callOut.message": "ご使用の APM Server を 7.0 以上に更新してあることを確認してください。 Kibana の管理セクションにある移行アシスタントで 6.x データを移行することもできます。", + "xpack.apm.tutorial.apmServer.callOut.title": "重要:7.0 以上に更新中", "xpack.apm.tutorial.apmServer.statusCheck.btnLabel": "APM Server ステータスを確認", - "xpack.apm.tutorial.apmServer.statusCheck.errorMessage": "APM Server がまだ Elasticsearch に接続されていません", - "xpack.apm.tutorial.apmServer.statusCheck.successMessage": "APM Server が正常にセットアップされました", - "xpack.apm.tutorial.apmServer.statusCheck.text": "APM エージェントの導入を開始する前に、APM Server が実行されていることを確認してください。", + "xpack.apm.tutorial.apmServer.statusCheck.errorMessage": "APM Server が検出されました。7.0 以上に更新され、動作中であることを確認してください。", + "xpack.apm.tutorial.apmServer.statusCheck.successMessage": "APM Server が正しくセットアップされました", + "xpack.apm.tutorial.apmServer.statusCheck.text": "APM エージェントの導入を開始する前に、APM Server が動作していることを確認してください。", "xpack.apm.tutorial.apmServer.statusCheck.title": "APM Server ステータス", "xpack.apm.tutorial.apmServer.title": "APM Server", "xpack.apm.tutorial.djangoClient.configure.commands.addAgentComment": "インストールされたアプリにエージェントを追加します", - "xpack.apm.tutorial.djangoClient.configure.commands.addTracingMiddlewareComment": "パフォーマンスメトリックを送信するには、追跡ミドルウェアを追加します:", + "xpack.apm.tutorial.djangoClient.configure.commands.addTracingMiddlewareComment": "パフォーマンスメトリックを送信するには、追跡ミドルウェアを追加します。", "xpack.apm.tutorial.djangoClient.configure.commands.allowedCharactersComment": "a-z、A-Z、0-9、-、_、スペース", "xpack.apm.tutorial.djangoClient.configure.commands.setCustomApmServerUrlComment": "カスタム APM Server URL (デフォルト: {defaultApmServerUrl})", "xpack.apm.tutorial.djangoClient.configure.commands.setRequiredServiceNameComment": "必要なサーバー名を設定します。使用できる文字:", - "xpack.apm.tutorial.djangoClient.configure.commands.useIfApmServerRequiresTokenComment": "APM Serverにトークンが必要な場合に使います", - "xpack.apm.tutorial.djangoClient.configure.textPost": "高度な用途に関しては [ドキュメンテーション]({documentationLink}) を参照してください。", - "xpack.apm.tutorial.djangoClient.configure.textPre": "エージェントとは、アプリケーションプロセス内で実行されるライブラリです。APM サービスは「SERVICE_NAME」に基づきプログラムに従って作成されます。", + "xpack.apm.tutorial.djangoClient.configure.commands.useIfApmServerRequiresTokenComment": "APM Server にトークンが必要な場合に使います", + "xpack.apm.tutorial.djangoClient.configure.textPost": "高度な用途に関しては [ドキュメンテーション]({documentationLink}) をご覧ください。", + "xpack.apm.tutorial.djangoClient.configure.textPre": "エージェントとは、アプリケーションプロセス内で実行されるライブラリです。APM サービスは「SERVICE_NAME」に基づいてプログラムで作成されます。", "xpack.apm.tutorial.djangoClient.configure.title": "エージェントの構成", "xpack.apm.tutorial.djangoClient.install.textPre": "Python 用の APM エージェントを依存関係としてインストールします。", "xpack.apm.tutorial.djangoClient.install.title": "APM エージェントのインストール", - "xpack.apm.tutorial.dotNetClient.configureAgent.textPost": "エージェントに「IConfiguration」インスタンスが渡されていない場合、(例: 非 ASP.NET Core アプリケーションの場合)、エージェントを環境変数で構成することもできます。\n 高度な用途に関しては [ドキュメンテーション]({documentationLink}) を参照してください。", + "xpack.apm.tutorial.dotNetClient.configureAgent.textPost": "エージェントに「IConfiguration」インスタンスが渡されていない場合、(例: 非 ASP.NET Core アプリケーションの場合)、エージェントを環境変数で構成することもできます。\n 高度な用途に関しては [ドキュメンテーション]({documentationLink}) をご覧ください。", "xpack.apm.tutorial.dotNetClient.configureAgent.title": "appsettings.json ファイルの例:", - "xpack.apm.tutorial.dotNetClient.configureApplication.textPost": "「IConfiguration」インスタンスを渡すのはオプションで、これにより、エージェントはこの「IConfiguration」インスタンス (例: 「appsettings.json」ファイル) から構成を読み込みます。", + "xpack.apm.tutorial.dotNetClient.configureApplication.textPost": "「IConfiguration」インスタンスを渡すのは任意であり、これにより、エージェントはこの「IConfiguration」インスタンス (例: 「appsettings.json」ファイル) から構成を読み込みます。", "xpack.apm.tutorial.dotNetClient.configureApplication.textPre": "「Elastic.Apm.NetCoreAll」パッケージの ASP.NET Core の場合、「Startup.cs」ファイル内の「Configure」メソドの「UseElasticApm」メソドを呼び出します。", "xpack.apm.tutorial.dotNetClient.configureApplication.title": "エージェントをアプリケーションに追加", "xpack.apm.tutorial.dotNetClient.download.textPre": "[NuGet]({allNuGetPackagesLink}) から .NET アプリケーションにエージェントパッケージを追加してください。用途の異なる複数の NuGet パッケージがあります。\n\nEntity Framework Core の ASP.NET Core アプリケーションの場合は、[Elastic.Apm.NetCoreAll]({netCoreAllApmPackageLink}) パッケージをダウンロードしてください。このパッケージは、自動的にすべてのエージェントコンポーネントをアプリケーションに追加します。\n\n 依存性を最低限に抑えたい場合、ASP.NET Core の監視のみに [Elastic.Apm.AspNetCore]({aspNetCorePackageLink}) パッケージ、または Entity Framework Core の監視のみに [Elastic.Apm.EfCore]({efCorePackageLink}) パッケージを使用することができます。\n\n 手動インストルメンテーションのみにパブリック Agent API を使用する場合は、[Elastic.Apm]({elasticApmPackageLink}) パッケージを使用してください。", @@ -3962,17 +4716,17 @@ "xpack.apm.tutorial.downloadServerRpm": "32 ビットパッケージをお探しですか?[ダウンロードページ]({downloadPageLink}) をご覧ください。", "xpack.apm.tutorial.downloadServerTitle": "32 ビットパッケージをお探しですか?[ダウンロードページ]({downloadPageLink}) をご覧ください。", "xpack.apm.tutorial.editConfig.textPre": "Elastic Stack の X-Pack セキュアバージョンをご使用の場合、「apm-server.yml」構成ファイルで認証情報を指定する必要があります。", - "xpack.apm.tutorial.editConfig.title": "構成の変更", - "xpack.apm.tutorial.elasticCloud.textPre": "APM サーバーを有効にするには、[the Elastic Cloud console](https://cloud.elastic.co/deployments?q={cloudId}) に移動し、展開設定で APM を有効にします。有効にした後、このページを更新してください。", + "xpack.apm.tutorial.editConfig.title": "構成を編集する", + "xpack.apm.tutorial.elasticCloud.textPre": "APM Server を有効にするには、[the Elastic Cloud console](https://cloud.elastic.co/deployments?q={cloudId}) に移動し、展開設定で APM を有効にします。有効になったら、このページを更新してください。", "xpack.apm.tutorial.elasticCloudInstructions.title": "APM エージェント", "xpack.apm.tutorial.flaskClient.configure.commands.allowedCharactersComment": "a-z、A-Z、0-9、-、_、スペース", "xpack.apm.tutorial.flaskClient.configure.commands.configureElasticApmComment": "またはアプリケーションの設定で ELASTIC_APM を使用するよう構成します。", "xpack.apm.tutorial.flaskClient.configure.commands.initializeUsingEnvironmentVariablesComment": "環境変数を使用して初期化します", "xpack.apm.tutorial.flaskClient.configure.commands.setCustomApmServerUrlComment": "カスタム APM Server URL (デフォルト: {defaultApmServerUrl})", "xpack.apm.tutorial.flaskClient.configure.commands.setRequiredServiceNameComment": "必要なサーバー名を設定します。使用できる文字:", - "xpack.apm.tutorial.flaskClient.configure.commands.useIfApmServerRequiresTokenComment": "APM Serverにトークンが必要な場合に使います", - "xpack.apm.tutorial.flaskClient.configure.textPost": "高度な用途に関しては [ドキュメンテーション]({documentationLink}) を参照してください。", - "xpack.apm.tutorial.flaskClient.configure.textPre": "エージェントとは、アプリケーションプロセス内で実行されるライブラリです。APM サービスは「SERVICE_NAME」に基づきプログラムに従って作成されます。", + "xpack.apm.tutorial.flaskClient.configure.commands.useIfApmServerRequiresTokenComment": "APM Server にトークンが必要な場合に使います", + "xpack.apm.tutorial.flaskClient.configure.textPost": "高度な用途に関しては [ドキュメンテーション]({documentationLink}) をご覧ください。", + "xpack.apm.tutorial.flaskClient.configure.textPre": "エージェントとは、アプリケーションプロセス内で実行されるライブラリです。APM サービスは「SERVICE_NAME」に基づいてプログラムで作成されます。", "xpack.apm.tutorial.flaskClient.configure.title": "エージェントの構成", "xpack.apm.tutorial.flaskClient.install.textPre": "Python 用の APM エージェントを依存関係としてインストールします。", "xpack.apm.tutorial.flaskClient.install.title": "APM エージェントのインストール", @@ -3980,41 +4734,49 @@ "xpack.apm.tutorial.goClient.configure.commands.setCustomApmServerUrlComment": "カスタム APM Server URL (デフォルト: {defaultApmServerUrl})", "xpack.apm.tutorial.goClient.configure.commands.setServiceNameComment": "サービス名を設定します。使用できる文字は # a-z、A-Z、0-9、-、_、スペースです。", "xpack.apm.tutorial.goClient.configure.commands.usedExecutableNameComment": "ELASTIC_APM_SERVICE_NAME が指定されていない場合、実行可能な名前が使用されます。", - "xpack.apm.tutorial.goClient.configure.commands.useIfApmRequiresTokenComment": "APM Serverにトークンが必要な場合に使います", - "xpack.apm.tutorial.goClient.configure.textPost": "高度な構成に関しては [ドキュメンテーション]({documentationLink}) を参照してください。", - "xpack.apm.tutorial.goClient.configure.textPre": "エージェントとは、アプリケーションプロセス内で実行されるライブラリです。APM サービスは実行ファイル名または「ELASTIC_APM_SERVICE_NAME」の環境変数に基づきプログラムに従って作成されます。", + "xpack.apm.tutorial.goClient.configure.commands.useIfApmRequiresTokenComment": "APM Server にトークンが必要な場合に使います", + "xpack.apm.tutorial.goClient.configure.textPost": "高度な構成に関しては [ドキュメンテーション]({documentationLink}) をご覧ください。", + "xpack.apm.tutorial.goClient.configure.textPre": "エージェントとは、アプリケーションプロセス内で実行されるライブラリです。APM サービスは実行ファイル名または「ELASTIC_APM_SERVICE_NAME」環境変数に基づいてプログラムで作成されます。", "xpack.apm.tutorial.goClient.configure.title": "エージェントの構成", "xpack.apm.tutorial.goClient.install.textPre": "Go の APM エージェントパッケージをインストールします。", "xpack.apm.tutorial.goClient.install.title": "APM エージェントのインストール", - "xpack.apm.tutorial.goClient.instrument.textPost": "Go のソースコードのインストルメンテーションの詳細ガイドは、[ドキュメンテーション]({documentationLink}) をご参照ください。", + "xpack.apm.tutorial.goClient.instrument.textPost": "Go のソースコードのインストルメンテーションの詳細ガイドは、[ドキュメンテーション]({documentationLink}) をご覧ください。", "xpack.apm.tutorial.goClient.instrument.textPre": "提供されたインストルメンテーションモジュールの 1 つ、またはトレーサー API を直接使用して、Go アプリケーションにインストルメンテーションを設定します。", "xpack.apm.tutorial.goClient.instrument.title": "アプリケーションのインストルメンテーション", - "xpack.apm.tutorial.introduction": "アプリケーション内から詳細なパフォーマンスメトリックやエラーを集めます。", - "xpack.apm.tutorial.javaClient.download.textPre": "[Maven Central]({mavenCentralLink}) からエージェントジャーをダウンロード。アプリケーションにエージェントを依存関係として追加**しない**でください。", + "xpack.apm.tutorial.introduction": "アプリケーション内から詳細なパフォーマンスメトリックやエラーを収集します。", + "xpack.apm.tutorial.javaClient.download.textPre": "[Maven Central]({mavenCentralLink}) からエージェントをダウンロードします。アプリケーションにエージェントを依存関係として「追加しない」でください。", "xpack.apm.tutorial.javaClient.download.title": "APM エージェントのダウンロード", "xpack.apm.tutorial.javaClient.startApplication.textPost": "構成オプションと高度な用途に関しては、[ドキュメンテーション]({documentationLink}) をご覧ください。", - "xpack.apm.tutorial.javaClient.startApplication.textPre": "「-javaagent」フラグを追加してエージェントをシステムプロパティで構成します。\n\n * 必要なサービス名を設定します (使用可能な文字は a-z、A-Z、0-9、-、_、スペースです)\n * カスタム APM Server URL を設定します (デフォルト: {customApmServerUrl})\n * アプリケーションのベースパッケージを設定します", - "xpack.apm.tutorial.javaClient.startApplication.title": "javaagent フラグのアプリケーションの起動", - "xpack.apm.tutorial.jsClient.enableRealUserMonitoring.textPre": "[ドキュメンテーション({documentationLink}) をご覧ください。", - "xpack.apm.tutorial.jsClient.enableRealUserMonitoring.title": "APM Server の Real User 監視エージェントを有効にする", - "xpack.apm.tutorial.nodeClient.configure.commands.addThisToTheFileTopComment": "アプリに読み込まれたファイルの一番上にこれを追加します", + "xpack.apm.tutorial.javaClient.startApplication.textPre": "「-javaagent」フラグを追加してエージェントをシステムプロパティで構成します。\n\n * 必要なサービス名を設定します (使用可能な文字は a-z、A-Z、0-9、-、_、スペースです)\n * カスタム APM Server URL (デフォルト: {customApmServerUrl})\n * アプリケーションのベースパッケージを設定します", + "xpack.apm.tutorial.javaClient.startApplication.title": "javaagent フラグでアプリケーションを起動", + "xpack.apm.tutorial.jsClient.enableRealUserMonitoring.textPre": "デフォルトでは、APM Server を実行すると RUM サポートは無効になります。RUM サポートを有効にする手順については、[ドキュメンテーション]({documentationLink}) をご覧ください。", + "xpack.apm.tutorial.jsClient.enableRealUserMonitoring.title": "APM Server のリアルユーザー監視エージェントを有効にする", + "xpack.apm.tutorial.jsClient.installDependency.commands.setCustomApmServerUrlComment": "カスタム APM Server URL (デフォルト: {defaultApmServerUrl})", + "xpack.apm.tutorial.jsClient.installDependency.commands.setRequiredServiceNameComment": "必要なサービス名を設定します (使用可能な文字は a-z、A-Z、0-9、-、_、スペースです)", + "xpack.apm.tutorial.jsClient.installDependency.commands.setServiceVersionComment": "サービスバージョンを設定します (ソースマップ機能に必要)", + "xpack.apm.tutorial.jsClient.installDependency.textPost": "React や Angular などのフレームワーク統合には、カスタム依存関係があります。詳細は [統合ドキュメント]({docLink}) をご覧ください。", + "xpack.apm.tutorial.jsClient.installDependency.textPre": "「npm install @elastic/apm-rum --save」でエージェントをアプリケーションへの依存関係としてインストールできます。\n\nその後で以下のようにアプリケーションでエージェントを初期化して構成できます。", + "xpack.apm.tutorial.jsClient.installDependency.title": "エージェントを依存関係としてセットアップ", + "xpack.apm.tutorial.jsClient.scriptTags.textPre": "または、スクリプトタグを使用してエージェントのセットアップと構成ができます。` を追加