diff --git a/.backportrc.json b/.backportrc.json index 731f49183dba5c..0894909d2aac40 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -3,6 +3,7 @@ "targetBranchChoices": [ { "name": "master", "checked": true }, { "name": "7.x", "checked": true }, + "7.8", "7.7", "7.6", "7.5", diff --git a/.eslintrc.js b/.eslintrc.js index dde0ce010d4d44..56c06902e062b5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -238,6 +238,7 @@ module.exports = { ], from: [ '(src|x-pack)/plugins/**/(public|server)/**/*', + '!(src|x-pack)/plugins/**/(public|server)/mocks/index.{js,ts}', '!(src|x-pack)/plugins/**/(public|server)/(index|mocks).{js,ts,tsx}', ], allowSameFolder: true, diff --git a/.i18nrc.json b/.i18nrc.json index be3c043b6e52f7..3b2e628f7226f8 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -43,7 +43,7 @@ "src/plugins/telemetry", "src/plugins/telemetry_management_section" ], - "tileMap": "src/legacy/core_plugins/tile_map", + "tileMap": "src/plugins/tile_map", "timelion": ["src/legacy/core_plugins/timelion", "src/plugins/vis_type_timelion"], "uiActions": "src/plugins/ui_actions", "visDefaultEditor": "src/plugins/vis_default_editor", diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.md index 24b56a9b986216..a79244a24acf57 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.iindexpattern.md @@ -21,3 +21,9 @@ export interface IIndexPattern | [title](./kibana-plugin-plugins-data-server.iindexpattern.title.md) | string | | | [type](./kibana-plugin-plugins-data-server.iindexpattern.type.md) | string | | +## Methods + +| Method | Description | +| --- | --- | +| [getTimeField()](./kibana-plugin-plugins-data-server.iindexpattern.gettimefield.md) | | + diff --git a/docs/management/alerting/images/alerts-and-actions-ui.png b/docs/management/alerting/images/alerts-and-actions-ui.png index acf3f3b1f0be95..d46df21e6f6b04 100644 Binary files a/docs/management/alerting/images/alerts-and-actions-ui.png and b/docs/management/alerting/images/alerts-and-actions-ui.png differ diff --git a/docs/management/alerting/images/alerts-details-instance-muting.png b/docs/management/alerting/images/alerts-details-instance-muting.png index 9d26fad419e4f2..fd59e79d07279a 100644 Binary files a/docs/management/alerting/images/alerts-details-instance-muting.png and b/docs/management/alerting/images/alerts-details-instance-muting.png differ diff --git a/docs/management/alerting/images/alerts-details-instances-active.png b/docs/management/alerting/images/alerts-details-instances-active.png index d6895bd4952b8c..7506d1cb8c65ea 100644 Binary files a/docs/management/alerting/images/alerts-details-instances-active.png and b/docs/management/alerting/images/alerts-details-instances-active.png differ diff --git a/docs/management/alerting/images/alerts-details-instances-inactive.png b/docs/management/alerting/images/alerts-details-instances-inactive.png index b049b4ba082f66..a757d59e123600 100644 Binary files a/docs/management/alerting/images/alerts-details-instances-inactive.png and b/docs/management/alerting/images/alerts-details-instances-inactive.png differ diff --git a/docs/management/alerting/images/alerts-details-muting.png b/docs/management/alerting/images/alerts-details-muting.png index 9b47d82a746398..29cdf707b4912a 100644 Binary files a/docs/management/alerting/images/alerts-details-muting.png and b/docs/management/alerting/images/alerts-details-muting.png differ diff --git a/docs/management/alerting/images/alerts-filter-by-action-type.png b/docs/management/alerting/images/alerts-filter-by-action-type.png index 94336a20e1d6cc..c0e495a87ecd31 100644 Binary files a/docs/management/alerting/images/alerts-filter-by-action-type.png and b/docs/management/alerting/images/alerts-filter-by-action-type.png differ diff --git a/docs/management/alerting/images/alerts-filter-by-type.png b/docs/management/alerting/images/alerts-filter-by-type.png index 75ffb3ff69babc..859274e9b6613c 100644 Binary files a/docs/management/alerting/images/alerts-filter-by-type.png and b/docs/management/alerting/images/alerts-filter-by-type.png differ diff --git a/docs/management/alerting/images/individual-mute-disable.png b/docs/management/alerting/images/individual-mute-disable.png index ca00240a4af618..dc187c97de3097 100644 Binary files a/docs/management/alerting/images/individual-mute-disable.png and b/docs/management/alerting/images/individual-mute-disable.png differ diff --git a/docs/user/alerting/action-types.asciidoc b/docs/user/alerting/action-types.asciidoc index 8794c389d72bcb..09878b3059ac87 100644 --- a/docs/user/alerting/action-types.asciidoc +++ b/docs/user/alerting/action-types.asciidoc @@ -43,11 +43,10 @@ see https://www.elastic.co/subscriptions[the subscription page]. [[create-connectors]] === Preconfigured connectors and action types -You can create connectors for actions in <> or via the action API. -For out-of-the-box and standardized connectors, you can <> +For out-of-the-box and standardized connectors, you can <> before {kib} starts. -Action type with only preconfigured connectors could be specified as a <>. +If you preconfigure a connector, you can also <>. include::action-types/email.asciidoc[] include::action-types/index.asciidoc[] @@ -56,4 +55,3 @@ include::action-types/server-log.asciidoc[] include::action-types/slack.asciidoc[] include::action-types/webhook.asciidoc[] include::pre-configured-connectors.asciidoc[] -include::pre-configured-action-types.asciidoc[] diff --git a/docs/user/alerting/action-types/email.asciidoc b/docs/user/alerting/action-types/email.asciidoc index 794fc14005f2ff..689d870d9cadc3 100644 --- a/docs/user/alerting/action-types/email.asciidoc +++ b/docs/user/alerting/action-types/email.asciidoc @@ -19,6 +19,37 @@ Username:: username for 'login' type authentication. Password:: password for 'login' type authentication. [float] +[[Preconfigured-email-configuration]] +==== Preconfigured action type + +[source,text] +-- + id: 'my-email' + name: preconfigured-email-action-type + actionTypeId: .email + config: + from: testsender@test.com <1.1> + host: validhostname <1.2> + port: 8080 <1.3> + secure: false <1.4> + secrets: + user: testuser <2.1> + password: passwordkeystorevalue <2.2> +-- + +`config` defines the action type specific to the configuration and contains the following properties: + +<1.1> `from:` is an email address and correspond to *Sender*. +<1.2> `host:` is a string and correspond to *Host*. +<1.3> `port:` is a number and correspond to *Port*. +<1.4> `secure:` is a boolean and correspond to *Secure*. + +`secrets` defines action type sensitive configuration: + +<2.1> `user:` is a string and correspond to *User*. +<2.2> `password:` is a string and correspond to *Password*. Should be stored in the <>. + + [[email-action-configuration]] ==== Action configuration diff --git a/docs/user/alerting/action-types/index.asciidoc b/docs/user/alerting/action-types/index.asciidoc index 625b8f704b7c6d..4f5254e3311d81 100644 --- a/docs/user/alerting/action-types/index.asciidoc +++ b/docs/user/alerting/action-types/index.asciidoc @@ -15,6 +15,28 @@ Index:: The {es} index to be written to. Refresh:: Setting for the {ref}/docs-refresh.html[refresh] policy for the write request. Execution time field:: This field will be automatically set to the time the alert condition was detected. +[float] +[[Preconfigured-index-configuration]] +==== Preconfigured action type + +[source,text] +-- + id: 'my-index' + name: action-type-index + actionTypeId: .index + config: + index: .kibana <1> + refresh: true <2> + executionTimeField: somedate <3> +-- + +`config` defines the action type specific to the configuration and contains the following properties: + +<1> `index:` is a string and correspond to *Index*. +<2> `refresh:` is a boolean and correspond to *Refresh*. +<3> `executionTimeField:` is a string and correspond to *Execution time field*. + + [float] [[index-action-configuration]] ==== Action configuration diff --git a/docs/user/alerting/action-types/pagerduty.asciidoc b/docs/user/alerting/action-types/pagerduty.asciidoc index 673b4f6263e18f..957c035b028f62 100644 --- a/docs/user/alerting/action-types/pagerduty.asciidoc +++ b/docs/user/alerting/action-types/pagerduty.asciidoc @@ -135,6 +135,29 @@ Name:: The name of the connector. The name is used to identify a connector API URL:: An optional PagerDuty event URL. Defaults to `https://events.pagerduty.com/v2/enqueue`. If you are using the <> setting, make sure the hostname is whitelisted. Integration Key:: A 32 character PagerDuty Integration Key for an integration on a service, also referred to as the routing key. +[float] +[[Preconfigured-pagerduty-configuration]] +==== Preconfigured action type + +[source,text] +-- + id: 'my-pagerduty' + name: preconfigured-pagerduty-action-type + actionTypeId: .pagerduty + config: + apiUrl: https://test.host <1.1> + secrets: + routingKey: testroutingkey <2.1> +-- + +`config` defines the action type specific to the configuration and contains the following properties: + +<1.1> `apiUrl:` is URL string and correspond to *API URL*. + +`secrets` defines action type sensitive configuration: + +<2.1> `routingKey:` is a string and correspond to *Integration Key*. + [float] [[pagerduty-action-configuration]] ==== Action configuration diff --git a/docs/user/alerting/action-types/server-log.asciidoc b/docs/user/alerting/action-types/server-log.asciidoc index 8f888785626c9b..f08dbe5542f0fe 100644 --- a/docs/user/alerting/action-types/server-log.asciidoc +++ b/docs/user/alerting/action-types/server-log.asciidoc @@ -12,6 +12,17 @@ Server log connectors have the following configuration properties: Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. +[float] +[[Preconfigured-server-log-configuration]] +==== Preconfigured action type + +[source,text] +-- + id: 'my-server-log' + name: test + actionTypeId: .server-log +-- + [float] [[server-log-action-configuration]] ==== Action configuration diff --git a/docs/user/alerting/action-types/slack.asciidoc b/docs/user/alerting/action-types/slack.asciidoc index c0965d65bfdbec..195093536bc044 100644 --- a/docs/user/alerting/action-types/slack.asciidoc +++ b/docs/user/alerting/action-types/slack.asciidoc @@ -13,6 +13,24 @@ Slack connectors have the following configuration properties: Name:: The name of the connector. The name is used to identify a connector in the management UI connector listing, or in the connector list when configuring an action. Webhook URL:: The URL of the incoming webhook. See https://api.slack.com/messaging/webhooks#getting_started[Slack Incoming Webhooks] for instructions on generating this URL. If you are using the <> setting, make sure the hostname is whitelisted. +[float] +[[Preconfigured-slack-configuration]] +==== Preconfigured action type + +[source,text] +-- + id: 'my-slack' + name: preconfigured-slack-action-type + actionTypeId: .slack + config: + webhookUrl: 'https://hooks.slack.com/services/abcd/efgh/ijklmnopqrstuvwxyz' <1> +-- + +`config` defines the action type specific to the configuration and contains the following properties: + +<1> `webhookUrl:` is URL string and correspond to *Webhook URL*. + + [float] [[slack-action-configuration]] ==== Action configuration diff --git a/docs/user/alerting/action-types/webhook.asciidoc b/docs/user/alerting/action-types/webhook.asciidoc index 64bfa6a1d6364b..f4c108426642d1 100644 --- a/docs/user/alerting/action-types/webhook.asciidoc +++ b/docs/user/alerting/action-types/webhook.asciidoc @@ -17,6 +17,36 @@ Headers:: A set of key-value pairs sent as headers with the request User:: An optional username. If set, HTTP basic authentication is used. Currently only basic authentication is supported. Password:: An optional password. If set, HTTP basic authentication is used. Currently only basic authentication is supported. +[float] +[[Preconfigured-webhook-configuration]] +==== Preconfigured action type + +[source,text] +-- + id: 'my-webhook' + name: preconfigured-webhook-action-type + actionTypeId: .webhook + config: + url: https://test.host <1.1> + method: POST <1.2> + headers: <1.3> + testheader: testvalue + secrets: + user: testuser <2.1> + password: passwordkeystorevalue <2.2> +-- + +`config` defines the action type specific to the configuration and contains the following properties: + +<1.1> `url:` is URL string and correspond to *URL*. +<1.2> `method:` is a string and correspond to *Method*. +<1.3> `headers:` is Record and correspond to *Headers*. + +`secrets` defines action type sensitive configuration: + +<2.1> `user:` is a string and correspond to *User*. +<2.2> `password:` is a string and correspond to *Password*. Should be stored in the <>. + [float] [[webhook-action-configuration]] ==== Action configuration diff --git a/docs/user/alerting/defining-alerts.asciidoc b/docs/user/alerting/defining-alerts.asciidoc index f05afac34e5957..d05a727016455f 100644 --- a/docs/user/alerting/defining-alerts.asciidoc +++ b/docs/user/alerting/defining-alerts.asciidoc @@ -22,12 +22,12 @@ image::images/alert-flyout-sections.png[The three sections of an alert definitio All alert share the following four properties in common: [role="screenshot"] -image::images/alert-flyout-general-details.png[All alerts have name, tags, check every, and re-notify every properties in common] +image::images/alert-flyout-general-details.png[alt='All alerts have name, tags, check every, and notify every properties in common'] Name:: The name of the alert. While this name does not have to be unique, the name can be referenced in actions and also appears in the searchable alert listing in the management UI. A distinctive name can help identify and find an alert. Tags:: A list of tag names that can be applied to an alert. Tags can help you organize and find alerts, because tags appear in the alert listing in the management UI which is searchable by tag. Check every:: This value determines how frequently the alert conditions below are checked. Note that the timing of background alert checks are not guaranteed, particularly for intervals of less than 10 seconds. See <> for more information. -Re-notify every:: This value limits how often actions are repeated when an alert instance remains active across alert checks. See <> for more information. +Notify every:: This value limits how often actions are repeated when an alert instance remains active across alert checks. See <> for more information. [float] [[defining-alerts-type-conditions]] diff --git a/docs/user/alerting/images/alert-flyout-action-type-selection.png b/docs/user/alerting/images/alert-flyout-action-type-selection.png index e4448ca5f3fcd3..2df2a031c66614 100644 Binary files a/docs/user/alerting/images/alert-flyout-action-type-selection.png and b/docs/user/alerting/images/alert-flyout-action-type-selection.png differ diff --git a/docs/user/alerting/images/alert-flyout-alert-conditions.png b/docs/user/alerting/images/alert-flyout-alert-conditions.png index f3e8f42ff0f374..8e0eff0224363c 100644 Binary files a/docs/user/alerting/images/alert-flyout-alert-conditions.png and b/docs/user/alerting/images/alert-flyout-alert-conditions.png differ diff --git a/docs/user/alerting/images/alert-flyout-alert-type-selection.png b/docs/user/alerting/images/alert-flyout-alert-type-selection.png index a0a25dc5f1bbc9..ccd3f07f07c943 100644 Binary files a/docs/user/alerting/images/alert-flyout-alert-type-selection.png and b/docs/user/alerting/images/alert-flyout-alert-type-selection.png differ diff --git a/docs/user/alerting/images/alert-flyout-general-details.png b/docs/user/alerting/images/alert-flyout-general-details.png index db56c16c1c308f..883c2348ecc8ad 100644 Binary files a/docs/user/alerting/images/alert-flyout-general-details.png and b/docs/user/alerting/images/alert-flyout-general-details.png differ diff --git a/docs/user/alerting/images/alert-types-index-threshold-conditions.png b/docs/user/alerting/images/alert-types-index-threshold-conditions.png index 356732dfb97775..5d66123ac733ea 100644 Binary files a/docs/user/alerting/images/alert-types-index-threshold-conditions.png and b/docs/user/alerting/images/alert-types-index-threshold-conditions.png differ diff --git a/docs/user/alerting/images/alert-types-index-threshold-example-aggregation.png b/docs/user/alerting/images/alert-types-index-threshold-example-aggregation.png index fc40da74365474..055b643ec34586 100644 Binary files a/docs/user/alerting/images/alert-types-index-threshold-example-aggregation.png and b/docs/user/alerting/images/alert-types-index-threshold-example-aggregation.png differ diff --git a/docs/user/alerting/images/alert-types-index-threshold-example-grouping.png b/docs/user/alerting/images/alert-types-index-threshold-example-grouping.png index ea3a3849c8927f..5be81b45612bcc 100644 Binary files a/docs/user/alerting/images/alert-types-index-threshold-example-grouping.png and b/docs/user/alerting/images/alert-types-index-threshold-example-grouping.png differ diff --git a/docs/user/alerting/images/alert-types-index-threshold-example-index.png b/docs/user/alerting/images/alert-types-index-threshold-example-index.png index 8f818f70012784..b13201ce5d38a5 100644 Binary files a/docs/user/alerting/images/alert-types-index-threshold-example-index.png and b/docs/user/alerting/images/alert-types-index-threshold-example-index.png differ diff --git a/docs/user/alerting/images/alert-types-index-threshold-example-preview.png b/docs/user/alerting/images/alert-types-index-threshold-example-preview.png index b5d9c38d998108..70e1355004c473 100644 Binary files a/docs/user/alerting/images/alert-types-index-threshold-example-preview.png and b/docs/user/alerting/images/alert-types-index-threshold-example-preview.png differ diff --git a/docs/user/alerting/images/alert-types-index-threshold-example-threshold.png b/docs/user/alerting/images/alert-types-index-threshold-example-threshold.png index 9c51807b8d2199..7e9432d8c8678e 100644 Binary files a/docs/user/alerting/images/alert-types-index-threshold-example-threshold.png and b/docs/user/alerting/images/alert-types-index-threshold-example-threshold.png differ diff --git a/docs/user/alerting/images/alert-types-index-threshold-example-timefield.png b/docs/user/alerting/images/alert-types-index-threshold-example-timefield.png index 24e4e03f829ce6..4b1eaa631dc988 100644 Binary files a/docs/user/alerting/images/alert-types-index-threshold-example-timefield.png and b/docs/user/alerting/images/alert-types-index-threshold-example-timefield.png differ diff --git a/docs/user/alerting/images/alert-types-index-threshold-example-window.png b/docs/user/alerting/images/alert-types-index-threshold-example-window.png index 54054159584855..b4b272d2a241ad 100644 Binary files a/docs/user/alerting/images/alert-types-index-threshold-example-window.png and b/docs/user/alerting/images/alert-types-index-threshold-example-window.png differ diff --git a/docs/user/alerting/images/alert-types-index-threshold-preview.png b/docs/user/alerting/images/alert-types-index-threshold-preview.png index 3709f162b612b2..b3b868dbc41e80 100644 Binary files a/docs/user/alerting/images/alert-types-index-threshold-preview.png and b/docs/user/alerting/images/alert-types-index-threshold-preview.png differ diff --git a/docs/user/alerting/images/alert-types-index-threshold-select.png b/docs/user/alerting/images/alert-types-index-threshold-select.png index 0c2776e01b962b..18c28a703e9669 100644 Binary files a/docs/user/alerting/images/alert-types-index-threshold-select.png and b/docs/user/alerting/images/alert-types-index-threshold-select.png differ diff --git a/docs/user/alerting/images/alerting-overview.png b/docs/user/alerting/images/alerting-overview.png index 383bc8c2ce015d..b4ec6f3df60281 100644 Binary files a/docs/user/alerting/images/alerting-overview.png and b/docs/user/alerting/images/alerting-overview.png differ diff --git a/docs/user/alerting/images/pre-configured-action-type-select-type.png b/docs/user/alerting/images/pre-configured-action-type-select-type.png index 5f555f851cd816..29e5a29edc7c06 100644 Binary files a/docs/user/alerting/images/pre-configured-action-type-select-type.png and b/docs/user/alerting/images/pre-configured-action-type-select-type.png differ diff --git a/docs/user/alerting/pre-configured-action-types.asciidoc b/docs/user/alerting/pre-configured-action-types.asciidoc deleted file mode 100644 index 780a2119037b1a..00000000000000 --- a/docs/user/alerting/pre-configured-action-types.asciidoc +++ /dev/null @@ -1,61 +0,0 @@ -[role="xpack"] -[[pre-configured-action-types]] - -== Preconfigured action types - -A preconfigure an action type has all the information it needs prior to startup. -A preconfigured action type offers the following capabilities: - -- Requires no setup. Configuration and credentials needed to execute an -action are predefined. -- Has only <>. -- Connectors of the preconfigured action type cannot be edited or deleted. - -[float] -[[preconfigured-action-type-example]] -=== Creating a preconfigured action - -In the `kibana.yml` file: - -. Exclude the action type from `xpack.actions.enabledActionTypes`. -. Add all its connectors. - -The following example shows a valid configuration of preconfigured action type with one out-of-the box connector. - -```js - xpack.actions.enabledActionTypes: ['.slack', '.email', '.index'] <1> - xpack.actions.preconfigured: <2> - - id: 'my-server-log' - actionTypeId: .server-log - name: 'Server log #xyz' -``` - -<1> `enabledActionTypes` should exclude preconfigured action type to prevent creating and deleting connectors. -<2> `preconfigured` is the setting for defining the list of available connectors for the preconfigured action type. - -[float] -[[pre-configured-action-type-alert-form]] -=== Attaching a preconfigured action to an alert - -To attach an action to an alert, -select from a list of available action types, and -then select the *Server log* type. This action type was configured previously. - -[role="screenshot"] -image::images/pre-configured-action-type-alert-form.png[Create alert with selected Server log action type] - -[float] -[[managing-pre-configured-action-types]] -=== Managing preconfigured actions - -Connectors with preconfigured actions appear in the connector list, regardless of which space the user is in. -They are tagged as “preconfigured” and cannot be deleted. - -[role="screenshot"] -image::images/pre-configured-action-type-managing.png[Connectors managing tab with pre-cofigured] - -Clicking *Create connector* shows the list of available action types. -Preconfigured action types are not included because you can't create a connector with a preconfigured action type. - -[role="screenshot"] -image::images/pre-configured-action-type-select-type.png[Pre-configured connector create menu] diff --git a/docs/user/alerting/pre-configured-connectors.asciidoc b/docs/user/alerting/pre-configured-connectors.asciidoc index 4c408da92f5791..5ff4ea15df561f 100644 --- a/docs/user/alerting/pre-configured-connectors.asciidoc +++ b/docs/user/alerting/pre-configured-connectors.asciidoc @@ -1,11 +1,10 @@ [role="xpack"] -[[pre-configured-connectors]] +[[pre-configured-action-types-and-connectors]] -== Preconfigured connectors +== Preconfigured connectors and action types -You can preconfigure an action connector to have all the information it needs prior to startup +You can preconfigure an action type or a connector to have all the information it needs prior to startup by adding it to the `kibana.yml` file. -Sensitive configuration information, such as credentials, can use the {kib} keystore. Preconfigured connectors offer the following capabilities: @@ -14,11 +13,15 @@ action are predefined, including the connector name and ID. - Appear in all spaces because they are not saved objects. - Cannot be edited or deleted. +Sensitive configuration information, such as credentials, can use the <>. + +A preconfigured action types has only preconfigured connectors. Preconfigured connectors can belong to either the preconfigured action type or to the regular action type. + [float] [[preconfigured-connector-example]] -=== Example of a preconfigured connector +=== Creating a preconfigured connector -The following example shows a valid configuration 2 out-of-the box connector. +The following example shows a valid configuration of two out-of-the box connectors: <> and <>. ```js xpack.actions.preconfigured: @@ -49,26 +52,30 @@ The following example shows a valid configuration 2 out-of-the box connector. [NOTE] ============================================== -Sensitive properties, such as passwords, can also be stored in the {kib} keystore. +Sensitive properties, such as passwords, can also be stored in the <>. ============================================== [float] -[[pre-configured-connector-alert-form]] -=== Creating an alert with a preconfigured connector +[[preconfigured-action-type-example]] +=== Creating a preconfigured action type -When attaching an action to an alert, -select from a list of available action types, and -then select the Slack or Webhook type. Those action types were configured previously. -The preconfigured connector is installed and is automatically selected. +In the `kibana.yml` file: -[role="screenshot"] -image::images/alert-pre-configured-slack-connector.png[Create alert with selected Slack action type] +. Exclude the action type from `xpack.actions.enabledActionTypes`. +. Add all its preconfigured connectors. -The dropdown is populated with additional preconfigured Slack connectors. -The `preconfigured` label distinguishes them from space-aware connectors that use saved objects. +The following example shows a valid configuration of preconfigured action type with one out-of-the box connector. -[role="screenshot"] -image::images/alert-pre-configured-connectors-dropdown.png[Dropdown list with pre-cofigured connectors] +```js + xpack.actions.enabledActionTypes: ['.slack', '.email', '.index'] <1> + xpack.actions.preconfigured: <2> + - id: 'my-server-log' + actionTypeId: .server-log + name: 'Server log #xyz' +``` + +<1> `enabledActionTypes` should exclude preconfigured action type to prevent creating and deleting connectors. +<2> `preconfigured` is the setting for defining the list of available connectors for the preconfigured action type. [float] [[managing-pre-configured-connectors]] @@ -85,3 +92,37 @@ A message indicates that this is a preconfigured connector. [role="screenshot"] image::images/pre-configured-connectors-view-screen.png[Pre-configured connector view details] + +The connector details preview is disabled for preconfigured connectors. + +[role="screenshot"] +image::images/pre-configured-action-type-managing.png[Connectors managing tab with pre-cofigured] + + +[float] +[[managing-pre-configured-action-types]] +=== Managing preconfigured action types + +Clicking *Create connector* shows the list of available action types. +Disabled action types are not included. + +[role="screenshot"] +image::images/pre-configured-action-type-select-type.png[Pre-configured connector create menu] + +[float] +[[pre-configured-connector-alert-form]] +=== Alert with a preconfigured connector + +When attaching an action to an alert, +select from a list of available action types, and +then select the Slack or Webhook type. Those action types were configured previously. +The preconfigured connector is installed and is automatically selected. + +[role="screenshot"] +image::images/alert-pre-configured-slack-connector.png[Create alert with selected Slack action type] + +The dropdown is populated with additional preconfigured Slack connectors. +The `preconfigured` label distinguishes them from space-aware connectors that use saved objects. + +[role="screenshot"] +image::images/alert-pre-configured-connectors-dropdown.png[Dropdown list with pre-cofigured connectors] diff --git a/docs/visualize/timelion.asciidoc b/docs/visualize/timelion.asciidoc index 852c3e1ecdeca1..9e41cce561454b 100644 --- a/docs/visualize/timelion.asciidoc +++ b/docs/visualize/timelion.asciidoc @@ -32,7 +32,9 @@ To start tracking the real-time percentage of CPU, enter the following in the *T [source,text] ---------------------------------- -.es(index=metricbeat-*, timefield='@timestamp', metric='avg:system.cpu.user.pct') +.es(index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct') ---------------------------------- [role="screenshot"] @@ -70,7 +72,12 @@ To easily distinguish between the two data sets, add the label names: [source,text] ---------------------------------- -.es(offset=-1h,index=metricbeat-*, timefield='@timestamp', metric='avg:system.cpu.user.pct').label('last hour'), .es(index=metricbeat-*, timefield='@timestamp', metric='avg:system.cpu.user.pct').label('current hour') <1> +.es(offset=-1h,index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct').label('last hour'), +.es(index=metricbeat-*, + timefield='@timestamp', + metric='avg:system.cpu.user.pct').label('current hour') <1> ---------------------------------- <1> `.label()` adds custom labels to the visualization. diff --git a/src/core/public/chrome/ui/_loading_indicator.scss b/src/core/public/chrome/ui/_loading_indicator.scss index 026c23b93b040f..ad934717b4b766 100644 --- a/src/core/public/chrome/ui/_loading_indicator.scss +++ b/src/core/public/chrome/ui/_loading_indicator.scss @@ -11,7 +11,7 @@ $kbnLoadingIndicatorColor2: tint($euiColorAccent, 60%); top: 0; // 1 left: 0; // 1 right: 0; // 1 - z-index: $euiZLevel1; // 1 + z-index: $euiZLevel2; // 1 overflow: hidden; // 2 height: $euiSizeXS / 2; @@ -28,7 +28,7 @@ $kbnLoadingIndicatorColor2: tint($euiColorAccent, 60%); right: 0; bottom: 0; position: absolute; - z-index: $euiZLevel1 + 1; + z-index: $euiZLevel2 + 1; visibility: visible; display: block; animation: kbn-animate-loading-indicator 2s linear infinite; diff --git a/src/core/server/saved_objects/migrations/types.ts b/src/core/server/saved_objects/migrations/types.ts index 85f15b4c18b66a..5e55a34193a962 100644 --- a/src/core/server/saved_objects/migrations/types.ts +++ b/src/core/server/saved_objects/migrations/types.ts @@ -88,5 +88,5 @@ export interface SavedObjectMigrationContext { * @public */ export interface SavedObjectMigrationMap { - [version: string]: SavedObjectMigrationFn; + [version: string]: SavedObjectMigrationFn; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 62d11ee7cf9a7a..bd6046b5ec2812 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1735,7 +1735,7 @@ export type SavedObjectMigrationFn; } // @public diff --git a/src/dev/typescript/projects.ts b/src/dev/typescript/projects.ts index a13f61af601733..5019c8bd223411 100644 --- a/src/dev/typescript/projects.ts +++ b/src/dev/typescript/projects.ts @@ -50,6 +50,9 @@ export const PROJECTS = [ ...glob .sync('test/plugin_functional/plugins/*/tsconfig.json', { cwd: REPO_ROOT }) .map(path => new Project(resolve(REPO_ROOT, path))), + ...glob + .sync('test/interpreter_functional/plugins/*/tsconfig.json', { cwd: REPO_ROOT }) + .map(path => new Project(resolve(REPO_ROOT, path))), ]; export function filterProjectsByFlag(projectFlag?: string) { diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js index 9f5f4b764f9b05..691318e32245b4 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js @@ -21,6 +21,9 @@ import Bluebird from 'bluebird'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import $ from 'jquery'; + +import 'leaflet/dist/leaflet.js'; +import 'leaflet-vega'; // Will be replaced with new path when tests are moved // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { createVegaVisualization } from '../../../../../../plugins/vis_type_vega/public/vega_visualization'; @@ -100,6 +103,39 @@ describe('VegaVisualizations', () => { setSavedObjects(npStart.core.savedObjects); setNotifications(npStart.core.notifications); + const mockMapConfig = { + includeElasticMapsService: true, + proxyElasticMapsServiceInMaps: false, + tilemap: { + deprecated: { + config: { + options: { + attribution: '', + }, + }, + }, + options: { + attribution: '', + minZoom: 0, + maxZoom: 10, + }, + }, + regionmap: { + includeElasticMapsService: true, + layers: [], + }, + manifestServiceUrl: '', + emsFileApiUrl: 'https://vector.maps.elastic.co', + emsTileApiUrl: 'https://tiles.maps.elastic.co', + emsLandingPageUrl: 'https://maps.elastic.co/v7.7', + emsFontLibraryUrl: 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf', + emsTileLayerId: { + bright: 'road_map', + desaturated: 'road_map_desaturated', + dark: 'dark_map', + }, + }; + beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(() => { @@ -127,7 +163,7 @@ describe('VegaVisualizations', () => { return 'not found'; } }); - const serviceSettings = new ServiceSettings(); + const serviceSettings = new ServiceSettings(mockMapConfig, mockMapConfig.tilemap); vegaVisualizationDependencies = { serviceSettings, core: { diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index ad67a74121cc94..4e97d46ab1773e 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -45,7 +45,6 @@ import 'ui/autoload/all'; import './management'; import './dev_tools'; import { showAppRedirectNotification } from '../../../../plugins/kibana_legacy/public'; -import 'leaflet'; import { localApplicationService } from './local_application_service'; npSetup.plugins.kibanaLegacy.registerLegacyAppAlias('doc', 'discover', { keepPrefix: true }); diff --git a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js index 87592cf4e750e4..7271f39debb39e 100644 --- a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js +++ b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js @@ -20,6 +20,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import _ from 'lodash'; + import ChoroplethLayer from '../choropleth_layer'; import { ImageComparator } from 'test_utils/image_comparator'; import worldJson from './world.json'; @@ -103,31 +104,29 @@ describe('RegionMapsVisualizationTests', function() { let getManifestStub; beforeEach( ngMock.inject(() => { + const mapConfig = { + emsFileApiUrl: '', + emsTileApiUrl: '', + emsLandingPageUrl: '', + }; + const tilemapsConfig = { + deprecated: { + config: { + options: { + attribution: '123', + }, + }, + }, + }; setInjectedVarFunc(injectedVar => { switch (injectedVar) { - case 'mapConfig': - return { - emsFileApiUrl: '', - emsTileApiUrl: '', - emsLandingPageUrl: '', - }; - case 'tilemapsConfig': - return { - deprecated: { - config: { - options: { - attribution: '123', - }, - }, - }, - }; case 'version': return '123'; default: return 'not found'; } }); - const serviceSettings = new ServiceSettings(); + const serviceSettings = new ServiceSettings(mapConfig, tilemapsConfig); const regionmapsConfig = { includeElasticMapsService: true, layers: [], diff --git a/src/legacy/core_plugins/region_map/public/choropleth_layer.js b/src/legacy/core_plugins/region_map/public/choropleth_layer.js index 4ea9cc1f7bfbf1..f8c48958a1b9bc 100644 --- a/src/legacy/core_plugins/region_map/public/choropleth_layer.js +++ b/src/legacy/core_plugins/region_map/public/choropleth_layer.js @@ -18,7 +18,6 @@ */ import $ from 'jquery'; -import L from 'leaflet'; import _ from 'lodash'; import d3 from 'd3'; import { i18n } from '@kbn/i18n'; @@ -86,6 +85,7 @@ export default class ChoroplethLayer extends KibanaMapLayer { this._layerName = name; this._layerConfig = layerConfig; + // eslint-disable-next-line no-undef this._leafletLayer = L.geoJson(null, { onEachFeature: (feature, layer) => { layer.on('click', () => { @@ -96,6 +96,7 @@ export default class ChoroplethLayer extends KibanaMapLayer { mouseover: () => { const tooltipContents = this._tooltipFormatter(feature); if (!location) { + // eslint-disable-next-line no-undef const leafletGeojson = L.geoJson(feature); location = leafletGeojson.getBounds().getCenter(); } @@ -428,6 +429,7 @@ CORS configuration of the server permits requests from the Kibana application on const { min, max } = getMinMax(this._metrics); + // eslint-disable-next-line no-undef const boundsOfAllFeatures = new L.LatLngBounds(); return { leafletStyleFunction: geojsonFeature => { @@ -435,6 +437,7 @@ CORS configuration of the server permits requests from the Kibana application on if (!match) { return emptyStyle(); } + // eslint-disable-next-line no-undef const boundsOfFeature = L.geoJson(geojsonFeature).getBounds(); boundsOfAllFeatures.extend(boundsOfFeature); diff --git a/src/legacy/core_plugins/tile_map/index.ts b/src/legacy/core_plugins/tile_map/index.ts deleted file mode 100644 index 27f019318a82b4..00000000000000 --- a/src/legacy/core_plugins/tile_map/index.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import { Legacy } from 'kibana'; - -import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; - -const tileMapPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => - new Plugin({ - id: 'tile_map', - require: ['kibana', 'elasticsearch'], - publicDir: resolve(__dirname, 'public'), - uiExports: { - styleSheetPaths: resolve(__dirname, 'public/index.scss'), - hacks: [resolve(__dirname, 'public/legacy')], - injectDefaultVars: server => { - const serverConfig = server.config(); - const mapConfig: Record = serverConfig.get('map'); - - return { - emsTileLayerId: mapConfig.emsTileLayerId, - }; - }, - }, - 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 tileMapPluginInitializer; diff --git a/src/plugins/discover/public/application/angular/_index.scss b/src/plugins/discover/public/application/angular/_index.scss index 9e00ade3d41f6d..b0e5b6e3edf7b3 100644 --- a/src/plugins/discover/public/application/angular/_index.scss +++ b/src/plugins/discover/public/application/angular/_index.scss @@ -1,3 +1,2 @@ @import 'directives/index'; -@import 'doc_table/index'; @import 'context/index'; diff --git a/src/plugins/discover/public/application/angular/doc_table/_doc_table.scss b/src/plugins/discover/public/application/angular/doc_table/_doc_table.scss index 8b754d23f96044..3e30214acd2a9e 100644 --- a/src/plugins/discover/public/application/angular/doc_table/_doc_table.scss +++ b/src/plugins/discover/public/application/angular/doc_table/_doc_table.scss @@ -2,6 +2,7 @@ * 1. Stack content vertically so the table can scroll when its constrained by a fixed container height. */ doc-table { + @include euiScrollBar; overflow: auto; flex: 1 1 100%; flex-direction: column; /* 1 */ diff --git a/src/plugins/discover/public/application/angular/doc_table/doc_table.ts b/src/plugins/discover/public/application/angular/doc_table/doc_table.ts index 66b162a4584a77..8af7380afcdc97 100644 --- a/src/plugins/discover/public/application/angular/doc_table/doc_table.ts +++ b/src/plugins/discover/public/application/angular/doc_table/doc_table.ts @@ -22,6 +22,7 @@ import { dispatchRenderComplete } from '../../../../../kibana_utils/public'; // @ts-ignore import { getLimitedSearchResultsMessage } from './doc_table_strings'; import { getServices } from '../../../kibana_services'; +import './index.scss'; export interface LazyScope extends ng.IScope { [key: string]: any; diff --git a/src/plugins/discover/public/application/angular/doc_table/_index.scss b/src/plugins/discover/public/application/angular/doc_table/index.scss similarity index 66% rename from src/plugins/discover/public/application/angular/doc_table/_index.scss rename to src/plugins/discover/public/application/angular/doc_table/index.scss index 3663d807851c48..4e6cb83c5fe5a5 100644 --- a/src/plugins/discover/public/application/angular/doc_table/_index.scss +++ b/src/plugins/discover/public/application/angular/doc_table/index.scss @@ -1,2 +1,4 @@ +@import '../../mixins'; + @import 'doc_table'; @import 'components/index'; diff --git a/src/plugins/discover/public/application/components/_index.scss b/src/plugins/discover/public/application/components/_index.scss deleted file mode 100644 index 91fb3df79b1777..00000000000000 --- a/src/plugins/discover/public/application/components/_index.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import 'doc_viewer/index'; -@import 'fetch_error/index'; -@import 'sidebar/index'; diff --git a/src/plugins/discover/public/application/components/doc_viewer/_index.scss b/src/plugins/discover/public/application/components/doc_viewer/_index.scss deleted file mode 100644 index aaf925f435d818..00000000000000 --- a/src/plugins/discover/public/application/components/doc_viewer/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'doc_viewer'; diff --git a/src/plugins/discover/public/application/components/doc_viewer/_doc_viewer.scss b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss similarity index 100% rename from src/plugins/discover/public/application/components/doc_viewer/_doc_viewer.scss rename to src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss diff --git a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.tsx b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.tsx index dce6de150155c1..80c43db0166989 100644 --- a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.tsx +++ b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import './doc_viewer.scss'; import React from 'react'; import { EuiTabbedContent } from '@elastic/eui'; import { getDocViewsRegistry } from '../../../kibana_services'; diff --git a/src/plugins/discover/public/application/components/fetch_error/_index.scss b/src/plugins/discover/public/application/components/fetch_error/_index.scss deleted file mode 100644 index 596f7b66866e3b..00000000000000 --- a/src/plugins/discover/public/application/components/fetch_error/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'fetch_error'; diff --git a/src/plugins/discover/public/application/components/fetch_error/_fetch_error.scss b/src/plugins/discover/public/application/components/fetch_error/fetch_error.scss similarity index 100% rename from src/plugins/discover/public/application/components/fetch_error/_fetch_error.scss rename to src/plugins/discover/public/application/components/fetch_error/fetch_error.scss diff --git a/src/plugins/discover/public/application/components/fetch_error/fetch_error.tsx b/src/plugins/discover/public/application/components/fetch_error/fetch_error.tsx index e16089500d3e5f..0bae2456f743c4 100644 --- a/src/plugins/discover/public/application/components/fetch_error/fetch_error.tsx +++ b/src/plugins/discover/public/application/components/fetch_error/fetch_error.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import './fetch_error.scss'; import React, { Fragment } from 'react'; import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiCallOut, EuiCodeBlock, EuiSpacer } from '@elastic/eui'; diff --git a/src/plugins/discover/public/application/components/sidebar/_index.scss b/src/plugins/discover/public/application/components/sidebar/_index.scss deleted file mode 100644 index 17b0a6c9cfe4e6..00000000000000 --- a/src/plugins/discover/public/application/components/sidebar/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './_sidebar'; diff --git a/src/plugins/discover/public/application/components/sidebar/_sidebar.scss b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss similarity index 100% rename from src/plugins/discover/public/application/components/sidebar/_sidebar.scss rename to src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx index 74d1347b1694cb..56597dd31e572c 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import './discover_sidebar.scss'; import React, { useCallback, useEffect, useState, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButtonIcon, EuiTitle } from '@elastic/eui'; diff --git a/src/plugins/discover/public/application/embeddable/_index.scss b/src/plugins/discover/public/application/embeddable/_index.scss deleted file mode 100644 index 6d64040e9e7a31..00000000000000 --- a/src/plugins/discover/public/application/embeddable/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ - -@import 'embeddables'; diff --git a/src/plugins/discover/public/application/embeddable/_embeddables.scss b/src/plugins/discover/public/application/embeddable/search_embeddable.scss similarity index 100% rename from src/plugins/discover/public/application/embeddable/_embeddables.scss rename to src/plugins/discover/public/application/embeddable/search_embeddable.scss diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts index b650672ccaea71..2f8ac40bdf52c0 100644 --- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts +++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import './search_embeddable.scss'; import angular from 'angular'; import _ from 'lodash'; import * as Rx from 'rxjs'; diff --git a/src/plugins/discover/public/application/index.scss b/src/plugins/discover/public/application/index.scss index 0de036b1e17074..aaec7ab387e966 100644 --- a/src/plugins/discover/public/application/index.scss +++ b/src/plugins/discover/public/application/index.scss @@ -10,6 +10,4 @@ // monChart__legend--small // monChart__legend-isLoading -@import 'components/index'; @import 'angular/index'; -@import 'embeddable/index'; diff --git a/src/plugins/maps_legacy/config.ts b/src/plugins/maps_legacy/config.ts new file mode 100644 index 00000000000000..13a0ad6b393a31 --- /dev/null +++ b/src/plugins/maps_legacy/config.ts @@ -0,0 +1,67 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { schema, TypeOf } from '@kbn/config-schema'; +import { configSchema as tilemapSchema } from '../tile_map/config'; + +// TODO: Pull this portion from region_map +export const regionmapSchema = schema.object({ + includeElasticMapsService: schema.boolean({ defaultValue: true }), + layers: schema.arrayOf( + schema.object({ + url: schema.string(), + format: schema.object({ + type: schema.string({ defaultValue: 'geojson' }), + }), + meta: schema.object({ + feature_collection_path: schema.string({ defaultValue: 'data' }), + }), + attribution: schema.string(), + name: schema.string(), + fields: schema.arrayOf( + schema.object({ + name: schema.string(), + description: schema.string(), + }) + ), + }), + { defaultValue: [] } + ), +}); + +export const configSchema = schema.object({ + includeElasticMapsService: schema.boolean({ defaultValue: true }), + proxyElasticMapsServiceInMaps: schema.boolean({ defaultValue: false }), + tilemap: tilemapSchema, + regionmap: regionmapSchema, + manifestServiceUrl: schema.string({ defaultValue: '' }), + emsFileApiUrl: schema.string({ defaultValue: 'https://vector.maps.elastic.co' }), + emsTileApiUrl: schema.string({ defaultValue: 'https://tiles.maps.elastic.co' }), + emsLandingPageUrl: schema.string({ defaultValue: 'https://maps.elastic.co/v7.7' }), + emsFontLibraryUrl: schema.string({ + defaultValue: 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf', + }), + emsTileLayerId: schema.object({ + bright: schema.string({ defaultValue: 'road_map' }), + desaturated: schema.string({ defaultValue: 'road_map_desaturated' }), + dark: schema.string({ defaultValue: 'dark_map' }), + }), +}); + +export type ConfigSchema = TypeOf; diff --git a/src/plugins/maps_legacy/kibana.json b/src/plugins/maps_legacy/kibana.json index d66be2b156bb92..cd503883164ace 100644 --- a/src/plugins/maps_legacy/kibana.json +++ b/src/plugins/maps_legacy/kibana.json @@ -2,5 +2,7 @@ "id": "mapsLegacy", "version": "8.0.0", "kibanaVersion": "kibana", - "ui": true + "configPath": ["map"], + "ui": true, + "server": true } diff --git a/src/plugins/maps_legacy/public/__tests__/map/kibana_map.js b/src/plugins/maps_legacy/public/__tests__/map/kibana_map.js index 83b5359362e4ce..1002a8e9eedc89 100644 --- a/src/plugins/maps_legacy/public/__tests__/map/kibana_map.js +++ b/src/plugins/maps_legacy/public/__tests__/map/kibana_map.js @@ -20,7 +20,6 @@ import expect from '@kbn/expect'; import { KibanaMap } from '../../map/kibana_map'; import { KibanaMapLayer } from '../../map/kibana_map_layer'; -import L from 'leaflet'; describe('kibana_map tests', function() { let domNode; @@ -218,6 +217,7 @@ describe('kibana_map tests', function() { function makeMockLayer(attribution) { const layer = new KibanaMapLayer(); layer._attribution = attribution; + // eslint-disable-next-line no-undef layer._leafletLayer = L.geoJson(null); return layer; } diff --git a/src/plugins/maps_legacy/public/index.ts b/src/plugins/maps_legacy/public/index.ts index 17cecab9f74596..3fe377fbdc41fb 100644 --- a/src/plugins/maps_legacy/public/index.ts +++ b/src/plugins/maps_legacy/public/index.ts @@ -17,12 +17,15 @@ * under the License. */ -import { CoreSetup } from 'kibana/public'; -import { bindSetupCoreAndPlugins, MapsLegacyPlugin } from './plugin'; // @ts-ignore -import * as colorUtil from './map/color_util'; +import { CoreSetup, PluginInitializerContext } from 'kibana/public'; +// @ts-ignore +import { L } from './leaflet'; // @ts-ignore import { KibanaMap } from './map/kibana_map'; +import { bindSetupCoreAndPlugins, MapsLegacyPlugin } from './plugin'; +// @ts-ignore +import * as colorUtil from './map/color_util'; // @ts-ignore import { KibanaMapLayer } from './map/kibana_map_layer'; // @ts-ignore @@ -41,8 +44,15 @@ import { // @ts-ignore import { mapTooltipProvider } from './tooltip_provider'; -export function plugin() { - return new MapsLegacyPlugin(); +export interface MapsLegacyConfigType { + emsTileLayerId: string; + includeElasticMapsService: boolean; + proxyElasticMapsServiceInMaps: boolean; + tilemap: any; +} + +export function plugin(initializerContext: PluginInitializerContext) { + return new MapsLegacyPlugin(initializerContext); } /** @public */ @@ -59,6 +69,7 @@ export { FileLayer, TmsLayer, mapTooltipProvider, + L, }; // Due to a leaflet/leaflet-draw bug, it's not possible to consume leaflet maps w/ draw control diff --git a/src/plugins/maps_legacy/public/leaflet.js b/src/plugins/maps_legacy/public/leaflet.js new file mode 100644 index 00000000000000..e36da2c52b8c51 --- /dev/null +++ b/src/plugins/maps_legacy/public/leaflet.js @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 let L; + +if (!window.hasOwnProperty('L')) { + require('leaflet/dist/leaflet.css'); + window.L = require('leaflet/dist/leaflet.js'); + window.L.Browser.touch = false; + window.L.Browser.pointer = false; + + require('leaflet-vega'); + require('leaflet.heat/dist/leaflet-heat.js'); + require('leaflet-draw/dist/leaflet.draw.css'); + require('leaflet-draw/dist/leaflet.draw.js'); + require('leaflet-responsive-popup/leaflet.responsive.popup.css'); + require('leaflet-responsive-popup/leaflet.responsive.popup.js'); +} else { + L = window.L; +} diff --git a/src/plugins/maps_legacy/public/map/kibana_map.js b/src/plugins/maps_legacy/public/map/kibana_map.js index c7cec1b14159aa..85dafc318db8d0 100644 --- a/src/plugins/maps_legacy/public/map/kibana_map.js +++ b/src/plugins/maps_legacy/public/map/kibana_map.js @@ -19,15 +19,16 @@ import { EventEmitter } from 'events'; import { createZoomWarningMsg } from './map_messages'; -import L from 'leaflet'; import $ from 'jquery'; import _ from 'lodash'; import { zoomToPrecision } from './zoom_to_precision'; import { i18n } from '@kbn/i18n'; import { ORIGIN } from '../common/constants/origin'; import { getToasts } from '../kibana_services'; +import { L } from '../leaflet'; function makeFitControl(fitContainer, kibanaMap) { + // eslint-disable-next-line no-undef const FitControl = L.Control.extend({ options: { position: 'topleft', @@ -63,6 +64,7 @@ function makeFitControl(fitContainer, kibanaMap) { } function makeLegendControl(container, kibanaMap, position) { + // eslint-disable-next-line no-undef const LegendControl = L.Control.extend({ options: { position: 'topright', @@ -123,11 +125,13 @@ export class KibanaMap extends EventEmitter { maxZoom: options.maxZoom, center: options.center ? options.center : [0, 0], zoom: options.zoom ? options.zoom : 2, + // eslint-disable-next-line no-undef renderer: L.canvas(), zoomAnimation: false, // Desaturate map tiles causes animation rendering artifacts zoomControl: options.zoomControl === undefined ? true : options.zoomControl, }; + // eslint-disable-next-line no-undef this._leafletMap = L.map(containerNode, leafletOptions); this._leafletMap.attributionControl.setPrefix(''); @@ -228,10 +232,11 @@ export class KibanaMap extends EventEmitter { } if (!this._popup) { - this._popup = L.responsivePopup({ autoPan: false }); + // eslint-disable-next-line no-undef + this._popup = new L.ResponsivePopup({ autoPan: false }); this._popup.setLatLng(event.position); this._popup.setContent(event.content); - this._popup.openOn(this._leafletMap); + this._leafletMap.openPopup(this._popup); } else { if (!this._popup.getLatLng().equals(event.position)) { this._popup.setLatLng(event.position); @@ -335,6 +340,7 @@ export class KibanaMap extends EventEmitter { } setCenter(latitude, longitude) { + // eslint-disable-next-line no-undef const latLong = L.latLng(latitude, longitude); if (latLong.equals && !latLong.equals(this._leafletMap.getCenter())) { this._leafletMap.setView(latLong); @@ -461,6 +467,7 @@ export class KibanaMap extends EventEmitter { circlemarker: false, }, }; + // eslint-disable-next-line no-undef this._leafletDrawControl = new L.Control.Draw(drawOptions); this._leafletMap.addControl(this._leafletDrawControl); } @@ -470,6 +477,7 @@ export class KibanaMap extends EventEmitter { return; } + // eslint-disable-next-line no-undef const fitContainer = L.DomUtil.create('div', 'leaflet-control leaflet-bar leaflet-control-fit'); this._leafletFitControl = makeFitControl(fitContainer, this); this._leafletMap.addControl(this._leafletFitControl); @@ -621,6 +629,7 @@ export class KibanaMap extends EventEmitter { } _getTMSBaseLayer(options) { + // eslint-disable-next-line no-undef return L.tileLayer(options.url, { minZoom: options.minZoom, maxZoom: options.maxZoom, @@ -640,7 +649,8 @@ export class KibanaMap extends EventEmitter { }; return typeof options.url === 'string' && options.url.length - ? L.tileLayer.wms(options.url, wmsOptions) + ? // eslint-disable-next-line no-undef + L.tileLayer.wms(options.url, wmsOptions) : null; } diff --git a/src/plugins/maps_legacy/public/map/service_settings.js b/src/plugins/maps_legacy/public/map/service_settings.js index 8e3a0648e99d4b..437b78a3c3472c 100644 --- a/src/plugins/maps_legacy/public/map/service_settings.js +++ b/src/plugins/maps_legacy/public/map/service_settings.js @@ -27,10 +27,10 @@ import { ORIGIN } from '../common/constants/origin'; const TMS_IN_YML_ID = 'TMS in config/kibana.yml'; export class ServiceSettings { - constructor() { + constructor(mapConfig, tilemapsConfig) { const getInjectedVar = getInjectedVarFunc(); - this.mapConfig = getInjectedVar('mapConfig'); - this.tilemapsConfig = getInjectedVar('tilemapsConfig'); + this._mapConfig = mapConfig; + this._tilemapsConfig = tilemapsConfig; const kbnVersion = getInjectedVar('version'); this._showZoomMessage = true; @@ -38,9 +38,9 @@ export class ServiceSettings { language: i18n.getLocale(), appVersion: kbnVersion, appName: 'kibana', - fileApiUrl: this.mapConfig.emsFileApiUrl, - tileApiUrl: this.mapConfig.emsTileApiUrl, - landingPageUrl: this.mapConfig.emsLandingPageUrl, + fileApiUrl: this._mapConfig.emsFileApiUrl, + tileApiUrl: this._mapConfig.emsTileApiUrl, + landingPageUrl: this._mapConfig.emsLandingPageUrl, // Wrap to avoid errors passing window fetch fetchFunction: function(...args) { return fetch(...args); @@ -57,10 +57,10 @@ export class ServiceSettings { // TMS attribution const attributionFromConfig = _.escape( - markdownIt.render(this.tilemapsConfig.deprecated.config.options.attribution || '') + markdownIt.render(this._tilemapsConfig.deprecated.config.options.attribution || '') ); // TMS Options - this.tmsOptionsFromConfig = _.assign({}, this.tilemapsConfig.deprecated.config.options, { + this.tmsOptionsFromConfig = _.assign({}, this._tilemapsConfig.deprecated.config.options, { attribution: attributionFromConfig, }); } @@ -92,7 +92,7 @@ export class ServiceSettings { } async getFileLayers() { - if (!this.mapConfig.includeElasticMapsService) { + if (!this._mapConfig.includeElasticMapsService) { return []; } @@ -121,7 +121,7 @@ export class ServiceSettings { */ async getTMSServices() { let allServices = []; - if (this.tilemapsConfig.deprecated.isOverridden) { + if (this._tilemapsConfig.deprecated.isOverridden) { //use tilemap.* settings from yml const tmsService = _.cloneDeep(this.tmsOptionsFromConfig); tmsService.id = TMS_IN_YML_ID; @@ -129,11 +129,11 @@ export class ServiceSettings { allServices.push(tmsService); } - if (this.mapConfig.includeElasticMapsService) { + if (this._mapConfig.includeElasticMapsService) { const servicesFromManifest = await this._emsClient.getTMSServices(); const strippedServiceFromManifest = await Promise.all( servicesFromManifest - .filter(tmsService => tmsService.getId() === this.mapConfig.emsTileLayerId.bright) + .filter(tmsService => tmsService.getId() === this._mapConfig.emsTileLayerId.bright) .map(async tmsService => { //shim for compatibility return { @@ -173,7 +173,7 @@ export class ServiceSettings { async _getAttributesForEMSTMSLayer(isDesaturated, isDarkMode) { const tmsServices = await this._emsClient.getTMSServices(); - const emsTileLayerId = this.mapConfig.emsTileLayerId; + const emsTileLayerId = this._mapConfig.emsTileLayerId; let serviceId; if (isDarkMode) { serviceId = emsTileLayerId.dark; @@ -200,13 +200,13 @@ export class ServiceSettings { if (tmsServiceConfig.origin === ORIGIN.EMS) { return this._getAttributesForEMSTMSLayer(isDesaturated, isDarkMode); } else if (tmsServiceConfig.origin === ORIGIN.KIBANA_YML) { - const config = this.tilemapsConfig.deprecated.config; + const config = this._tilemapsConfig.deprecated.config; const attrs = _.pick(config, ['url', 'minzoom', 'maxzoom', 'attribution']); return { ...attrs, ...{ origin: ORIGIN.KIBANA_YML } }; } else { //this is an older config. need to resolve this dynamically. if (tmsServiceConfig.id === TMS_IN_YML_ID) { - const config = this.tilemapsConfig.deprecated.config; + const config = this._tilemapsConfig.deprecated.config; const attrs = _.pick(config, ['url', 'minzoom', 'maxzoom', 'attribution']); return { ...attrs, ...{ origin: ORIGIN.KIBANA_YML } }; } else { diff --git a/src/plugins/maps_legacy/public/plugin.ts b/src/plugins/maps_legacy/public/plugin.ts index acc7655a5e2636..78c2498b9ee900 100644 --- a/src/plugins/maps_legacy/public/plugin.ts +++ b/src/plugins/maps_legacy/public/plugin.ts @@ -18,14 +18,15 @@ */ // @ts-ignore -import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; +import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public'; // @ts-ignore import { setToasts, setUiSettings, setInjectedVarFunc } from './kibana_services'; // @ts-ignore import { ServiceSettings } from './map/service_settings'; // @ts-ignore import { getPrecision, getZoomPrecision } from './map/precision'; -import { MapsLegacyPluginSetup, MapsLegacyPluginStart } from './index'; +import { MapsLegacyConfigType, MapsLegacyPluginSetup, MapsLegacyPluginStart } from './index'; +import { ConfigSchema } from '../config'; /** * These are the interfaces with your public contracts. You should export these @@ -45,13 +46,22 @@ export interface MapsLegacySetupDependencies {} export interface MapsLegacyStartDependencies {} export class MapsLegacyPlugin implements Plugin { + readonly _initializerContext: PluginInitializerContext; + + constructor(initializerContext: PluginInitializerContext) { + this._initializerContext = initializerContext; + } + public setup(core: CoreSetup, plugins: MapsLegacySetupDependencies) { bindSetupCoreAndPlugins(core); + const config = this._initializerContext.config.get(); + return { - serviceSettings: new ServiceSettings(), + serviceSettings: new ServiceSettings(config, config.tilemap), getZoomPrecision, getPrecision, + config, }; } diff --git a/src/legacy/core_plugins/tile_map/public/legacy.ts b/src/plugins/maps_legacy/server/index.ts similarity index 54% rename from src/legacy/core_plugins/tile_map/public/legacy.ts rename to src/plugins/maps_legacy/server/index.ts index dd8d4c6e9311e6..18f58189fc6077 100644 --- a/src/legacy/core_plugins/tile_map/public/legacy.ts +++ b/src/plugins/maps_legacy/server/index.ts @@ -17,19 +17,33 @@ * under the License. */ +import { PluginConfigDescriptor } from 'kibana/server'; import { PluginInitializerContext } from 'kibana/public'; -import { npSetup, npStart } from 'ui/new_platform'; +import { configSchema, ConfigSchema } from '../config'; -import { TileMapPluginSetupDependencies } from './plugin'; -import { plugin } from '.'; - -const plugins: Readonly = { - expressions: npSetup.plugins.expressions, - visualizations: npSetup.plugins.visualizations, - mapsLegacy: npSetup.plugins.mapsLegacy, +export const config: PluginConfigDescriptor = { + exposeToBrowser: { + includeElasticMapsService: true, + proxyElasticMapsServiceInMaps: true, + tilemap: true, + regionmap: true, + manifestServiceUrl: true, + emsFileApiUrl: true, + emsTileApiUrl: true, + emsLandingPageUrl: true, + emsFontLibraryUrl: true, + emsTileLayerId: true, + }, + schema: configSchema, }; -const pluginInstance = plugin({} as PluginInitializerContext); - -export const setup = pluginInstance.setup(npSetup.core, plugins); -export const start = pluginInstance.start(npStart.core); +export const plugin = (initializerContext: PluginInitializerContext) => ({ + setup() { + // @ts-ignore + const config$ = initializerContext.config.create(); + return { + config: config$, + }; + }, + start() {}, +}); diff --git a/src/plugins/tile_map/config.ts b/src/plugins/tile_map/config.ts new file mode 100644 index 00000000000000..435e52103d156d --- /dev/null +++ b/src/plugins/tile_map/config.ts @@ -0,0 +1,47 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + url: schema.maybe(schema.string()), + deprecated: schema.any({ + defaultValue: { + config: { + options: { + attribution: '', + }, + }, + }, + }), + options: schema.object({ + attribution: schema.string({ defaultValue: '' }), + minZoom: schema.number({ defaultValue: 0, min: 0 }), + maxZoom: schema.number({ defaultValue: 10 }), + tileSize: schema.maybe(schema.number()), + subdomains: schema.maybe(schema.arrayOf(schema.string())), + errorTileUrl: schema.maybe(schema.string()), + tms: schema.maybe(schema.boolean()), + reuseTiles: schema.maybe(schema.boolean()), + bounds: schema.maybe(schema.arrayOf(schema.number({ min: 2 }))), + default: schema.maybe(schema.boolean()), + }), +}); + +export type ConfigSchema = TypeOf; diff --git a/src/plugins/tile_map/kibana.json b/src/plugins/tile_map/kibana.json new file mode 100644 index 00000000000000..71ae0bb29d17f1 --- /dev/null +++ b/src/plugins/tile_map/kibana.json @@ -0,0 +1,14 @@ +{ + "id": "tileMap", + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": ["map", "tilemap"], + "ui": true, + "server": true, + "requiredPlugins": [ + "visualizations", + "expressions", + "mapsLegacy", + "data" + ] +} diff --git a/src/legacy/core_plugins/tile_map/package.json b/src/plugins/tile_map/package.json similarity index 100% rename from src/legacy/core_plugins/tile_map/package.json rename to src/plugins/tile_map/package.json diff --git a/src/legacy/core_plugins/tile_map/public/__snapshots__/tilemap_fn.test.js.snap b/src/plugins/tile_map/public/__snapshots__/tilemap_fn.test.js.snap similarity index 100% rename from src/legacy/core_plugins/tile_map/public/__snapshots__/tilemap_fn.test.js.snap rename to src/plugins/tile_map/public/__snapshots__/tilemap_fn.test.js.snap diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/blues.png b/src/plugins/tile_map/public/__tests__/blues.png similarity index 100% rename from src/legacy/core_plugins/tile_map/public/__tests__/blues.png rename to src/plugins/tile_map/public/__tests__/blues.png diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js b/src/plugins/tile_map/public/__tests__/coordinate_maps_visualization.js similarity index 85% rename from src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js rename to src/plugins/tile_map/public/__tests__/coordinate_maps_visualization.js index bce2e157ebbc8d..303ce67be71021 100644 --- a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js +++ b/src/plugins/tile_map/public/__tests__/coordinate_maps_visualization.js @@ -23,37 +23,37 @@ import { ImageComparator } from 'test_utils/image_comparator'; import dummyESResponse from './dummy_es_response.json'; import initial from './initial.png'; import blues from './blues.png'; -import shadedGeohashGrid from './shadedGeohashGrid.png'; +import shadedGeohashGrid from './shaded_geohash_grid.png'; import heatmapRaw from './heatmap_raw.png'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import EMS_CATALOGUE from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_manifest.json'; +import EMS_CATALOGUE from '../../../maps_legacy/public/__tests__/map/ems_mocks/sample_manifest.json'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import EMS_FILES from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_files.json'; +import EMS_FILES from '../../../maps_legacy/public/__tests__/map/ems_mocks/sample_files.json'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import EMS_TILES from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_tiles.json'; +import EMS_TILES from '../../../maps_legacy/public/__tests__/map/ems_mocks/sample_tiles.json'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import EMS_STYLE_ROAD_MAP_BRIGHT from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_bright'; +import EMS_STYLE_ROAD_MAP_BRIGHT from '../../../maps_legacy/public/__tests__/map/ems_mocks/sample_style_bright'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import EMS_STYLE_ROAD_MAP_DESATURATED from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_desaturated'; +import EMS_STYLE_ROAD_MAP_DESATURATED from '../../../maps_legacy/public/__tests__/map/ems_mocks/sample_style_desaturated'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import EMS_STYLE_DARK_MAP from '../../../../../plugins/maps_legacy/public/__tests__/map/ems_mocks/sample_style_dark'; +import EMS_STYLE_DARK_MAP from '../../../maps_legacy/public/__tests__/map/ems_mocks/sample_style_dark'; import { createTileMapVisualization } from '../tile_map_visualization'; import { createTileMapTypeDefinition } from '../tile_map_type'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ExprVis } from '../../../../../plugins/visualizations/public/expressions/vis'; +import { ExprVis } from '../../../visualizations/public/expressions/vis'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { BaseVisType } from '../../../../../plugins/visualizations/public/vis_types/base_vis_type'; +import { BaseVisType } from '../../../visualizations/public/vis_types/base_vis_type'; import { getPrecision, getZoomPrecision, // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../../plugins/maps_legacy/public/map/precision'; +} from '../../../maps_legacy/public/map/precision'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ServiceSettings } from '../../../../../plugins/maps_legacy/public/map/service_settings'; +import { ServiceSettings } from '../../../maps_legacy/public/map/service_settings'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { setInjectedVarFunc } from '../../../../../plugins/maps_legacy/public/kibana_services'; -import { getBaseMapsVis } from '../../../../../plugins/maps_legacy/public'; +import { setInjectedVarFunc } from '../../../maps_legacy/public/kibana_services'; +import { getBaseMapsVis } from '../../../maps_legacy/public'; function mockRawData() { const stack = [dummyESResponse]; @@ -91,24 +91,22 @@ describe('CoordinateMapsVisualizationTest', function() { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject((Private, $injector) => { + const mapConfig = { + emsFileApiUrl: '', + emsTileApiUrl: '', + emsLandingPageUrl: '', + }; + const tilemapsConfig = { + deprecated: { + config: { + options: { + attribution: '123', + }, + }, + }, + }; setInjectedVarFunc(injectedVar => { switch (injectedVar) { - case 'mapConfig': - return { - emsFileApiUrl: '', - emsTileApiUrl: '', - emsLandingPageUrl: '', - }; - case 'tilemapsConfig': - return { - deprecated: { - config: { - options: { - attribution: '123', - }, - }, - }, - }; case 'version': return '123'; default: @@ -125,7 +123,7 @@ describe('CoordinateMapsVisualizationTest', function() { getInjectedVar: () => {}, }, }; - const serviceSettings = new ServiceSettings(); + const serviceSettings = new ServiceSettings(mapConfig, tilemapsConfig); const BaseMapsVisualization = getBaseMapsVis(coreSetupMock, serviceSettings); const uiSettings = $injector.get('config'); diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/dummy_es_response.json b/src/plugins/tile_map/public/__tests__/dummy_es_response.json similarity index 100% rename from src/legacy/core_plugins/tile_map/public/__tests__/dummy_es_response.json rename to src/plugins/tile_map/public/__tests__/dummy_es_response.json diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/geohash_layer.js b/src/plugins/tile_map/public/__tests__/geohash_layer.js similarity index 96% rename from src/legacy/core_plugins/tile_map/public/__tests__/geohash_layer.js rename to src/plugins/tile_map/public/__tests__/geohash_layer.js index bdf9cd806eb8b5..a288e78ef00c18 100644 --- a/src/legacy/core_plugins/tile_map/public/__tests__/geohash_layer.js +++ b/src/plugins/tile_map/public/__tests__/geohash_layer.js @@ -20,12 +20,12 @@ import expect from '@kbn/expect'; import { GeohashLayer } from '../geohash_layer'; // import heatmapPng from './heatmap.png'; -import scaledCircleMarkersPng from './scaledCircleMarkers.png'; +import scaledCircleMarkersPng from './scaled_circle_markers.png'; // import shadedCircleMarkersPng from './shadedCircleMarkers.png'; import { ImageComparator } from 'test_utils/image_comparator'; import GeoHashSampleData from './dummy_es_response.json'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { KibanaMap } from '../../../../../plugins/maps_legacy/public/map/kibana_map'; +import { KibanaMap } from '../../../maps_legacy/public/map/kibana_map'; describe('geohash_layer', function() { let domNode; diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/heatmap.png b/src/plugins/tile_map/public/__tests__/heatmap.png similarity index 100% rename from src/legacy/core_plugins/tile_map/public/__tests__/heatmap.png rename to src/plugins/tile_map/public/__tests__/heatmap.png diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/heatmap_raw.png b/src/plugins/tile_map/public/__tests__/heatmap_raw.png similarity index 100% rename from src/legacy/core_plugins/tile_map/public/__tests__/heatmap_raw.png rename to src/plugins/tile_map/public/__tests__/heatmap_raw.png diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/initial.png b/src/plugins/tile_map/public/__tests__/initial.png similarity index 100% rename from src/legacy/core_plugins/tile_map/public/__tests__/initial.png rename to src/plugins/tile_map/public/__tests__/initial.png diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/scaledCircleMarkers.png b/src/plugins/tile_map/public/__tests__/scaled_circle_markers.png similarity index 100% rename from src/legacy/core_plugins/tile_map/public/__tests__/scaledCircleMarkers.png rename to src/plugins/tile_map/public/__tests__/scaled_circle_markers.png diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/shadedCircleMarkers.png b/src/plugins/tile_map/public/__tests__/shaded_circle_markers.png similarity index 100% rename from src/legacy/core_plugins/tile_map/public/__tests__/shadedCircleMarkers.png rename to src/plugins/tile_map/public/__tests__/shaded_circle_markers.png diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/shadedGeohashGrid.png b/src/plugins/tile_map/public/__tests__/shaded_geohash_grid.png similarity index 100% rename from src/legacy/core_plugins/tile_map/public/__tests__/shadedGeohashGrid.png rename to src/plugins/tile_map/public/__tests__/shaded_geohash_grid.png diff --git a/src/legacy/core_plugins/tile_map/public/_tile_map.scss b/src/plugins/tile_map/public/_tile_map.scss similarity index 100% rename from src/legacy/core_plugins/tile_map/public/_tile_map.scss rename to src/plugins/tile_map/public/_tile_map.scss diff --git a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx b/src/plugins/tile_map/public/components/tile_map_options.tsx similarity index 95% rename from src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx rename to src/plugins/tile_map/public/components/tile_map_options.tsx index 1efb0b2f884f85..f7fb4daff63f01 100644 --- a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx +++ b/src/plugins/tile_map/public/components/tile_map_options.tsx @@ -22,13 +22,8 @@ import { EuiPanel, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; -import { - BasicOptions, - RangeOption, - SelectOption, - SwitchOption, -} from '../../../../../plugins/charts/public'; -import { WmsOptions, TileMapVisParams, MapTypes } from '../../../../../plugins/maps_legacy/public'; +import { BasicOptions, RangeOption, SelectOption, SwitchOption } from '../../../charts/public'; +import { WmsOptions, TileMapVisParams, MapTypes } from '../../../maps_legacy/public'; export type TileMapOptionsProps = VisOptionsProps; diff --git a/src/legacy/core_plugins/tile_map/public/css_filters.js b/src/plugins/tile_map/public/css_filters.js similarity index 100% rename from src/legacy/core_plugins/tile_map/public/css_filters.js rename to src/plugins/tile_map/public/css_filters.js diff --git a/src/legacy/core_plugins/tile_map/public/geohash_layer.js b/src/plugins/tile_map/public/geohash_layer.js similarity index 98% rename from src/legacy/core_plugins/tile_map/public/geohash_layer.js rename to src/plugins/tile_map/public/geohash_layer.js index f0261483d302d2..dbe64871265b1c 100644 --- a/src/legacy/core_plugins/tile_map/public/geohash_layer.js +++ b/src/plugins/tile_map/public/geohash_layer.js @@ -17,10 +17,9 @@ * under the License. */ -import L from 'leaflet'; import { min, isEqual } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { KibanaMapLayer, MapTypes } from '../../../../plugins/maps_legacy/public'; +import { L, KibanaMapLayer, MapTypes } from '../../maps_legacy/public'; import { HeatmapMarkers } from './markers/heatmap'; import { ScaledCirclesMarkers } from './markers/scaled_circles'; import { ShadedCirclesMarkers } from './markers/shaded_circles'; diff --git a/src/legacy/core_plugins/tile_map/public/index.scss b/src/plugins/tile_map/public/index.scss similarity index 90% rename from src/legacy/core_plugins/tile_map/public/index.scss rename to src/plugins/tile_map/public/index.scss index 767a71225a7d86..4ce500b2da4d22 100644 --- a/src/legacy/core_plugins/tile_map/public/index.scss +++ b/src/plugins/tile_map/public/index.scss @@ -7,4 +7,4 @@ // tlmChart__legend--small // tlmChart__legend-isLoading -@import './tile_map'; +@import 'tile_map'; diff --git a/src/legacy/core_plugins/tile_map/public/index.ts b/src/plugins/tile_map/public/index.ts similarity index 93% rename from src/legacy/core_plugins/tile_map/public/index.ts rename to src/plugins/tile_map/public/index.ts index 3d0d970e4dc202..d2b9a15a6ad3c2 100644 --- a/src/legacy/core_plugins/tile_map/public/index.ts +++ b/src/plugins/tile_map/public/index.ts @@ -17,7 +17,7 @@ * under the License. */ -import { PluginInitializerContext } from '../../../../core/public'; +import { PluginInitializerContext } from 'kibana/public'; import { TileMapPlugin as Plugin } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { diff --git a/src/legacy/core_plugins/tile_map/public/markers/geohash_grid.js b/src/plugins/tile_map/public/markers/geohash_grid.js similarity index 96% rename from src/legacy/core_plugins/tile_map/public/markers/geohash_grid.js rename to src/plugins/tile_map/public/markers/geohash_grid.js index 406a50ccde9665..0150f6d2c54c95 100644 --- a/src/legacy/core_plugins/tile_map/public/markers/geohash_grid.js +++ b/src/plugins/tile_map/public/markers/geohash_grid.js @@ -17,8 +17,8 @@ * under the License. */ -import L from 'leaflet'; import { ScaledCirclesMarkers } from './scaled_circles'; +import { L } from '../../../maps_legacy/public'; export class GeohashGridMarkers extends ScaledCirclesMarkers { getMarkerFunction() { diff --git a/src/legacy/core_plugins/tile_map/public/markers/heatmap.js b/src/plugins/tile_map/public/markers/heatmap.js similarity index 98% rename from src/legacy/core_plugins/tile_map/public/markers/heatmap.js rename to src/plugins/tile_map/public/markers/heatmap.js index 0ae26bfcf032b1..ed9dbccbfbcdeb 100644 --- a/src/legacy/core_plugins/tile_map/public/markers/heatmap.js +++ b/src/plugins/tile_map/public/markers/heatmap.js @@ -17,10 +17,10 @@ * under the License. */ -import L from 'leaflet'; import _ from 'lodash'; import d3 from 'd3'; import { EventEmitter } from 'events'; +import { L } from '../../../maps_legacy/public'; /** * Map overlay: canvas layer with leaflet.heat plugin @@ -34,7 +34,7 @@ export class HeatmapMarkers extends EventEmitter { super(); this._geojsonFeatureCollection = featureCollection; const points = dataToHeatArray(featureCollection, max); - this._leafletLayer = L.heatLayer(points, options); + this._leafletLayer = new L.HeatLayer(points, options); this._tooltipFormatter = options.tooltipFormatter; this._zoom = zoom; this._disableTooltips = false; diff --git a/src/legacy/core_plugins/tile_map/public/markers/scaled_circles.js b/src/plugins/tile_map/public/markers/scaled_circles.js similarity index 97% rename from src/legacy/core_plugins/tile_map/public/markers/scaled_circles.js rename to src/plugins/tile_map/public/markers/scaled_circles.js index f39de6ca7d1797..028d3de515ae70 100644 --- a/src/legacy/core_plugins/tile_map/public/markers/scaled_circles.js +++ b/src/plugins/tile_map/public/markers/scaled_circles.js @@ -17,13 +17,12 @@ * under the License. */ -import L from 'leaflet'; import _ from 'lodash'; import d3 from 'd3'; import $ from 'jquery'; import { EventEmitter } from 'events'; -import { colorUtil } from '../../../../../plugins/maps_legacy/public'; -import { truncatedColorMaps } from '../../../../../plugins/charts/public'; +import { L, colorUtil } from '../../../maps_legacy/public'; +import { truncatedColorMaps } from '../../../charts/public'; export class ScaledCirclesMarkers extends EventEmitter { constructor( diff --git a/src/legacy/core_plugins/tile_map/public/markers/shaded_circles.js b/src/plugins/tile_map/public/markers/shaded_circles.js similarity index 97% rename from src/legacy/core_plugins/tile_map/public/markers/shaded_circles.js rename to src/plugins/tile_map/public/markers/shaded_circles.js index e21d753f7001ab..745d0422856c68 100644 --- a/src/legacy/core_plugins/tile_map/public/markers/shaded_circles.js +++ b/src/plugins/tile_map/public/markers/shaded_circles.js @@ -17,9 +17,9 @@ * under the License. */ -import L from 'leaflet'; import _ from 'lodash'; import { ScaledCirclesMarkers } from './scaled_circles'; +import { L } from '../../../maps_legacy/public'; export class ShadedCirclesMarkers extends ScaledCirclesMarkers { getMarkerFunction() { diff --git a/src/legacy/core_plugins/tile_map/public/plugin.ts b/src/plugins/tile_map/public/plugin.ts similarity index 68% rename from src/legacy/core_plugins/tile_map/public/plugin.ts rename to src/plugins/tile_map/public/plugin.ts index aa1460a7e2890a..e55f7189929dfc 100644 --- a/src/legacy/core_plugins/tile_map/public/plugin.ts +++ b/src/plugins/tile_map/public/plugin.ts @@ -22,9 +22,9 @@ import { Plugin, PluginInitializerContext, IUiSettingsClient, -} from '../../../../core/public'; -import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; -import { VisualizationsSetup } from '../../../../plugins/visualizations/public'; +} from 'kibana/public'; +import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; +import { VisualizationsSetup } from '../../visualizations/public'; // TODO: Determine why visualizations don't populate without this import 'angular-sanitize'; @@ -32,7 +32,13 @@ import 'angular-sanitize'; import { createTileMapFn } from './tile_map_fn'; // @ts-ignore import { createTileMapTypeDefinition } from './tile_map_type'; -import { getBaseMapsVis, MapsLegacyPluginSetup } from '../../../../plugins/maps_legacy/public'; +import { getBaseMapsVis, MapsLegacyPluginSetup } from '../../maps_legacy/public'; +import { DataPublicPluginStart } from '../../data/public'; +import { setFormatService, setQueryService } from './services'; + +export interface TileMapConfigType { + tilemap: any; +} /** @private */ interface TileMapVisualizationDependencies { @@ -50,7 +56,18 @@ export interface TileMapPluginSetupDependencies { } /** @internal */ -export class TileMapPlugin implements Plugin, void> { +export interface TileMapPluginStartDependencies { + data: DataPublicPluginStart; +} + +export interface TileMapPluginSetup { + config: any; +} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface TileMapPluginStart {} + +/** @internal */ +export class TileMapPlugin implements Plugin { initializerContext: PluginInitializerContext; constructor(initializerContext: PluginInitializerContext) { @@ -72,9 +89,16 @@ export class TileMapPlugin implements Plugin, void> { expressions.registerFunction(() => createTileMapFn(visualizationDependencies)); visualizations.createBaseVisualization(createTileMapTypeDefinition(visualizationDependencies)); + + const config = this.initializerContext.config.get(); + return { + config, + }; } - public start(core: CoreStart) { - // nothing to do here yet + public start(core: CoreStart, { data }: TileMapPluginStartDependencies) { + setFormatService(data.fieldFormats); + setQueryService(data.query); + return {}; } } diff --git a/test/plugin_functional/plugins/core_provider_plugin/index.ts b/src/plugins/tile_map/public/services.ts similarity index 62% rename from test/plugin_functional/plugins/core_provider_plugin/index.ts rename to src/plugins/tile_map/public/services.ts index 01f3a67c6b5541..fd075a041ac9bf 100644 --- a/test/plugin_functional/plugins/core_provider_plugin/index.ts +++ b/src/plugins/tile_map/public/services.ts @@ -17,20 +17,13 @@ * under the License. */ -import { resolve } from 'path'; -import { Legacy } from '../../../../kibana'; +import { createGetterSetter } from '../../kibana_utils/public'; +import { DataPublicPluginStart } from '../../data/public'; -// eslint-disable-next-line import/no-default-export -export default function CoreProviderPlugin(kibana: any) { - const config: Legacy.PluginSpecOptions = { - id: 'core-provider', - require: [], - publicDir: resolve(__dirname, 'public'), - init: (server: Legacy.Server) => ({}), - uiExports: { - hacks: [resolve(__dirname, 'public/index')], - }, - }; +export const [getFormatService, setFormatService] = createGetterSetter< + DataPublicPluginStart['fieldFormats'] +>('vislib data.fieldFormats'); - return new kibana.Plugin(config); -} +export const [getQueryService, setQueryService] = createGetterSetter< + DataPublicPluginStart['query'] +>('Query'); diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_fn.js b/src/plugins/tile_map/public/tile_map_fn.js similarity index 95% rename from src/legacy/core_plugins/tile_map/public/tile_map_fn.js rename to src/plugins/tile_map/public/tile_map_fn.js index 5ad4a2c33db256..5f43077bcb24b7 100644 --- a/src/legacy/core_plugins/tile_map/public/tile_map_fn.js +++ b/src/plugins/tile_map/public/tile_map_fn.js @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { convertToGeoJson } from '../../../../plugins/maps_legacy/public'; +import { convertToGeoJson } from '../../maps_legacy/public'; import { i18n } from '@kbn/i18n'; export const createTileMapFn = () => ({ diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_type.js b/src/plugins/tile_map/public/tile_map_type.js similarity index 95% rename from src/legacy/core_plugins/tile_map/public/tile_map_type.js rename to src/plugins/tile_map/public/tile_map_type.js index ca6a586d220080..aa0160f3f5a9d8 100644 --- a/src/legacy/core_plugins/tile_map/public/tile_map_type.js +++ b/src/plugins/tile_map/public/tile_map_type.js @@ -19,12 +19,12 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { convertToGeoJson, MapTypes } from '../../../../plugins/maps_legacy/public'; -import { Schemas } from '../../../../plugins/vis_default_editor/public'; +import { convertToGeoJson, MapTypes } from '../../maps_legacy/public'; +import { Schemas } from '../../vis_default_editor/public'; import { createTileMapVisualization } from './tile_map_visualization'; import { TileMapOptions } from './components/tile_map_options'; import { supportsCssFilters } from './css_filters'; -import { truncatedColorSchemas } from '../../../../plugins/charts/public'; +import { truncatedColorSchemas } from '../../charts/public'; export function createTileMapTypeDefinition(dependencies) { const CoordinateMapsVisualization = createTileMapVisualization(dependencies); diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js b/src/plugins/tile_map/public/tile_map_visualization.js similarity index 95% rename from src/legacy/core_plugins/tile_map/public/tile_map_visualization.js rename to src/plugins/tile_map/public/tile_map_visualization.js index 6a7bda5e188831..f96c7291b34cf2 100644 --- a/src/legacy/core_plugins/tile_map/public/tile_map_visualization.js +++ b/src/plugins/tile_map/public/tile_map_visualization.js @@ -19,13 +19,8 @@ import { get } from 'lodash'; import { GeohashLayer } from './geohash_layer'; -import { npStart } from 'ui/new_platform'; -import { getFormat } from '../../../ui/public/visualize/loader/pipeline_helpers/utilities'; -import { - scaleBounds, - geoContains, - mapTooltipProvider, -} from '../../../../plugins/maps_legacy/public'; +import { getFormatService, getQueryService } from './services'; +import { scaleBounds, geoContains, mapTooltipProvider } from '../../maps_legacy/public'; import { tooltipFormatter } from './tooltip_formatter'; export const createTileMapVisualization = dependencies => { @@ -183,7 +178,9 @@ export const createTileMapVisualization = dependencies => { const newParams = this._getMapsParams(); const metricDimension = this._params.dimensions.metric; const metricLabel = metricDimension ? metricDimension.label : ''; - const metricFormat = getFormat(metricDimension && metricDimension.format); + const metricFormat = getFormatService().deserialize( + metricDimension && metricDimension.format + ); return { label: metricLabel, @@ -213,7 +210,7 @@ export const createTileMapVisualization = dependencies => { filter[filterName] = { ignore_unmapped: true }; filter[filterName][field] = filterData; - const { filterManager } = npStart.plugins.data.query; + const { filterManager } = getQueryService(); filterManager.addFilters([filter]); this.vis.updateState(); diff --git a/src/legacy/core_plugins/tile_map/public/tilemap_fn.test.js b/src/plugins/tile_map/public/tilemap_fn.test.js similarity index 90% rename from src/legacy/core_plugins/tile_map/public/tilemap_fn.test.js rename to src/plugins/tile_map/public/tilemap_fn.test.js index 6da37f4c5ef86c..8fa12c9f9dbbe6 100644 --- a/src/legacy/core_plugins/tile_map/public/tilemap_fn.test.js +++ b/src/plugins/tile_map/public/tilemap_fn.test.js @@ -18,11 +18,10 @@ */ // eslint-disable-next-line -import { functionWrapper } from '../../../../plugins/expressions/common/expression_functions/specs/tests/utils'; +import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils'; import { createTileMapFn } from './tile_map_fn'; -jest.mock('ui/new_platform'); -jest.mock('../../../../plugins/maps_legacy/public', () => ({ +jest.mock('../../maps_legacy/public', () => ({ convertToGeoJson: jest.fn().mockReturnValue({ featureCollection: { type: 'FeatureCollection', @@ -37,7 +36,7 @@ jest.mock('../../../../plugins/maps_legacy/public', () => ({ }), })); -import { convertToGeoJson } from '../../../../plugins/maps_legacy/public'; +import { convertToGeoJson } from '../../maps_legacy/public'; describe('interpreter/functions#tilemap', () => { const fn = functionWrapper(createTileMapFn()); diff --git a/src/legacy/core_plugins/tile_map/public/tooltip_formatter.js b/src/plugins/tile_map/public/tooltip_formatter.js similarity index 100% rename from src/legacy/core_plugins/tile_map/public/tooltip_formatter.js rename to src/plugins/tile_map/public/tooltip_formatter.js diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/index.ts b/src/plugins/tile_map/server/index.ts similarity index 70% rename from test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/index.ts rename to src/plugins/tile_map/server/index.ts index d7a764b581c01d..3381553fe93644 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/index.ts +++ b/src/plugins/tile_map/server/index.ts @@ -17,12 +17,19 @@ * under the License. */ -import { PluginInitializer, PluginInitializerContext } from 'src/core/public'; -import { Plugin, StartDeps } from './plugin'; -export { StartDeps }; +import { PluginConfigDescriptor } from 'kibana/server'; +import { configSchema, ConfigSchema } from '../config'; -export const plugin: PluginInitializer = ( - initializerContext: PluginInitializerContext -) => { - return new Plugin(initializerContext); +export const config: PluginConfigDescriptor = { + exposeToBrowser: { + url: true, + deprecated: true, + options: true, + }, + schema: configSchema, }; + +export const plugin = () => ({ + setup() {}, + start() {}, +}); diff --git a/src/plugins/vis_type_vega/public/plugin.ts b/src/plugins/vis_type_vega/public/plugin.ts index b52dcfbd914f9a..1bce7ac92e5640 100644 --- a/src/plugins/vis_type_vega/public/plugin.ts +++ b/src/plugins/vis_type_vega/public/plugin.ts @@ -27,6 +27,7 @@ import { setInjectedVars, setUISettings, setKibanaMapFactory, + setMapsLegacyConfig, } from './services'; import { createVegaFn } from './vega_fn'; @@ -76,6 +77,7 @@ export class VegaPlugin implements Plugin, void> { }); setUISettings(core.uiSettings); setKibanaMapFactory(getKibanaMapFactoryProvider(core)); + setMapsLegacyConfig(mapsLegacy.config); const visualizationDependencies: Readonly = { core, diff --git a/src/plugins/vis_type_vega/public/services.ts b/src/plugins/vis_type_vega/public/services.ts index f81f87d7ad2e17..f2fddb41cf72b2 100644 --- a/src/plugins/vis_type_vega/public/services.ts +++ b/src/plugins/vis_type_vega/public/services.ts @@ -21,6 +21,7 @@ import { SavedObjectsStart } from 'kibana/public'; import { NotificationsStart, IUiSettingsClient } from 'src/core/public'; import { DataPublicPluginStart } from '../../data/public'; import { createGetterSetter } from '../../kibana_utils/public'; +import { MapsLegacyConfigType } from '../../maps_legacy/public'; export const [getData, setData] = createGetterSetter('Data'); @@ -43,6 +44,10 @@ export const [getInjectedVars, setInjectedVars] = createGetterSetter<{ emsTileLayerId: unknown; }>('InjectedVars'); +export const [getMapsLegacyConfig, setMapsLegacyConfig] = createGetterSetter( + 'MapsLegacyConfig' +); + export const getEsShardTimeout = () => getInjectedVars().esShardTimeout; export const getEnableExternalUrls = () => getInjectedVars().enableExternalUrls; -export const getEmsTileLayerId = () => getInjectedVars().emsTileLayerId; +export const getEmsTileLayerId = () => getMapsLegacyConfig().emsTileLayerId; diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_map_layer.js b/src/plugins/vis_type_vega/public/vega_view/vega_map_layer.js index 8e4009eab84884..bc1cb4e4734c7d 100644 --- a/src/plugins/vis_type_vega/public/vega_view/vega_map_layer.js +++ b/src/plugins/vis_type_vega/public/vega_view/vega_map_layer.js @@ -17,9 +17,7 @@ * under the License. */ -import L from 'leaflet'; -import 'leaflet-vega'; -import { KibanaMapLayer } from '../../../maps_legacy/public'; +import { KibanaMapLayer, L } from '../../../maps_legacy/public'; export class VegaMapLayer extends KibanaMapLayer { constructor(spec, options) { @@ -28,7 +26,6 @@ export class VegaMapLayer extends KibanaMapLayer { // Used by super.getAttributions() this._attribution = options.attribution; delete options.attribution; - this._leafletLayer = L.vega(spec, options); } diff --git a/src/plugins/vis_type_vega/public/vega_view/vega_map_view.js b/src/plugins/vis_type_vega/public/vega_view/vega_map_view.js index 895d496a896aa3..4cd3eea503cb03 100644 --- a/src/plugins/vis_type_vega/public/vega_view/vega_map_view.js +++ b/src/plugins/vis_type_vega/public/vega_view/vega_map_view.js @@ -102,6 +102,7 @@ export class VegaMapView extends VegaBaseView { // let maxBounds = null; // if (mapConfig.maxBounds) { // const b = mapConfig.maxBounds; + // eslint-disable-next-line no-undef // maxBounds = L.latLngBounds(L.latLng(b[1], b[0]), L.latLng(b[3], b[2])); // } diff --git a/test/functional/apps/timelion/index.js b/test/functional/apps/timelion/index.js index 3b5167addf4e6f..021fa243978506 100644 --- a/test/functional/apps/timelion/index.js +++ b/test/functional/apps/timelion/index.js @@ -28,7 +28,7 @@ export default function({ getService, loadTestFile }) { before(async function() { log.debug('Starting timelion before method'); - browser.setWindowSize(1280, 800); + await browser.setWindowSize(1280, 800); await esArchiver.loadIfNeeded('logstash_functional'); await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); }); diff --git a/test/functional/page_objects/settings_page.ts b/test/functional/page_objects/settings_page.ts index 81d22838d1e8b7..b7a6e10efd7dc1 100644 --- a/test/functional/page_objects/settings_page.ts +++ b/test/functional/page_objects/settings_page.ts @@ -33,7 +33,7 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider class SettingsPage { async clickNavigation() { - find.clickDisplayedByCssSelector('.app-link:nth-child(5) a'); + await find.clickDisplayedByCssSelector('.app-link:nth-child(5) a'); } async clickLinkText(text: string) { @@ -110,7 +110,7 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider } async toggleAdvancedSettingCheckbox(propertyName: string) { - testSubjects.click(`advancedSetting-editField-${propertyName}`); + await testSubjects.click(`advancedSetting-editField-${propertyName}`); await PageObjects.header.waitUntilLoadingHasFinished(); await testSubjects.click(`advancedSetting-saveButton`); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/test/functional/services/find.ts b/test/functional/services/find.ts index 312668b718dc0c..bdcc5ba95e9fbd 100644 --- a/test/functional/services/find.ts +++ b/test/functional/services/find.ts @@ -476,7 +476,7 @@ export async function FindProvider({ getService }: FtrProviderContext) { value: string ): Promise { log.debug(`Find.waitForAttributeToChange('${selector}', '${attribute}', '${value}')`); - retry.waitFor(`${attribute} to equal "${value}"`, async () => { + await retry.waitFor(`${attribute} to equal "${value}"`, async () => { const el = await this.byCssSelector(selector); return value === (await el.getAttribute(attribute)); }); diff --git a/test/functional/services/remote/webdriver.ts b/test/functional/services/remote/webdriver.ts index 1b7ef2c1855d0b..df79db50b8683e 100644 --- a/test/functional/services/remote/webdriver.ts +++ b/test/functional/services/remote/webdriver.ts @@ -17,7 +17,8 @@ * under the License. */ -import { delimiter } from 'path'; +import { delimiter, resolve } from 'path'; +import Fs from 'fs'; import * as Rx from 'rxjs'; import { mergeMap, map, takeUntil } from 'rxjs/operators'; @@ -37,6 +38,7 @@ import { Executor } from 'selenium-webdriver/lib/http'; import { getLogger } from 'selenium-webdriver/lib/logging'; import { installDriver } from 'ms-chromium-edge-driver'; +import { REPO_ROOT } from '@kbn/dev-utils'; import { pollForLogEntry$ } from './poll_for_log_entry'; import { createStdoutSocket } from './create_stdout_stream'; import { preventParallelCalls } from './prevent_parallel_calls'; @@ -50,6 +52,13 @@ const certValidation: string = process.env.NODE_TLS_REJECT_UNAUTHORIZED as strin const SECOND = 1000; const MINUTE = 60 * SECOND; const NO_QUEUE_COMMANDS = ['getLog', 'getStatus', 'newSession', 'quit']; +const downloadDir = resolve(REPO_ROOT, 'target/functional-tests/downloads'); +const chromiumDownloadPrefs = { + prefs: { + 'download.default_directory': downloadDir, + 'download.prompt_for_download': false, + }, +}; /** * Best we can tell WebDriver locks up sometimes when we send too many @@ -112,6 +121,7 @@ async function attemptToCreateCommand( chromeCapabilities.set('goog:chromeOptions', { w3c: true, args: chromeOptions, + ...chromiumDownloadPrefs, }); chromeCapabilities.set('unexpectedAlertBehaviour', 'accept'); chromeCapabilities.set('goog:loggingPrefs', { browser: 'ALL' }); @@ -150,6 +160,10 @@ async function attemptToCreateCommand( edgeOptions.setEdgeChromium(true); // @ts-ignore internal modules are not typed edgeOptions.setBinaryPath(edgePaths.browserPath); + const options = edgeOptions.get('ms:edgeOptions'); + // overriding options to include preferences + Object.assign(options, chromiumDownloadPrefs); + edgeOptions.set('ms:edgeOptions', options); const session = await new Builder() .forBrowser('MicrosoftEdge') .setEdgeOptions(edgeOptions) @@ -185,6 +199,14 @@ async function attemptToCreateCommand( firefoxOptions.set('moz:firefoxOptions', { prefs: { 'devtools.console.stdout.content': true }, }); + firefoxOptions.setPreference('browser.download.folderList', 2); + firefoxOptions.setPreference('browser.download.manager.showWhenStarting', false); + firefoxOptions.setPreference('browser.download.dir', downloadDir); + firefoxOptions.setPreference( + 'browser.helperApps.neverAsk.saveToDisk', + 'application/comma-separated-values, text/csv, text/plain' + ); + if (headlessBrowser === '1') { // See: https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode firefoxOptions.headless(); @@ -308,6 +330,9 @@ export async function initWebDriver( log.verbose(entry.message); }); + // create browser download folder + Fs.mkdirSync(downloadDir, { recursive: true }); + // download Edge driver only in case of usage if (browserType === Browsers.ChromiumEdge) { edgePaths = await installDriver(); diff --git a/test/interpreter_functional/config.ts b/test/interpreter_functional/config.ts index 0fe7df4d507154..d3cfcea9823e9c 100644 --- a/test/interpreter_functional/config.ts +++ b/test/interpreter_functional/config.ts @@ -50,6 +50,9 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { ...functionalConfig.get('kbnTestServer'), serverArgs: [ ...functionalConfig.get('kbnTestServer.serverArgs'), + + // Required to load new platform plugins via `--plugin-path` flag. + '--env.name=development', ...plugins.map( pluginDir => `--plugin-path=${path.resolve(__dirname, 'plugins', pluginDir)}` ), diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/index.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/index.ts deleted file mode 100644 index 1d5564ec06e4ef..00000000000000 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/index.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { Legacy } from 'kibana'; -import { - ArrayOrItem, - LegacyPluginApi, - LegacyPluginSpec, - LegacyPluginOptions, -} from 'src/legacy/plugin_discovery/types'; - -// eslint-disable-next-line import/no-default-export -export default function(kibana: LegacyPluginApi): ArrayOrItem { - const pluginSpec: Partial = { - id: 'kbn_tp_run_pipeline', - uiExports: { - app: { - title: 'Run Pipeline', - description: 'This is a sample plugin to test running pipeline expressions', - main: 'plugins/kbn_tp_run_pipeline/legacy', - }, - }, - - init(server: Legacy.Server) { - // The following lines copy over some configuration variables from Kibana - // to this plugin. This will be needed when embedding visualizations, so that e.g. - // region map is able to get its configuration. - server.injectUiAppVars('kbn_tp_run_pipeline', async () => { - return server.getInjectedUiAppVars('kibana'); - }); - }, - }; - return new kibana.Plugin(pluginSpec); -} diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/kibana.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/kibana.json new file mode 100644 index 00000000000000..f0c1c3a34fbc09 --- /dev/null +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/kibana.json @@ -0,0 +1,13 @@ +{ + "id": "kbn_tp_run_pipeline", + "version": "0.0.1", + "kibanaVersion": "kibana", + "requiredPlugins": [ + "data", + "savedObjects", + "kibanaUtils", + "expressions" + ], + "server": false, + "ui": true +} diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index 338e85038922de..ebc74be937ef06 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -1,6 +1,7 @@ { "name": "kbn_tp_run_pipeline", "version": "1.0.0", + "main": "target/test/interpreter_functional/plugins/kbn_tp_run_pipeline", "kibana": { "version": "kibana", "templateVersion": "1.0.0" @@ -10,5 +11,13 @@ "@elastic/eui": "22.3.1", "react": "^16.12.0", "react-dom": "^16.12.0" + }, + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "@kbn/plugin-helpers": "9.0.2", + "typescript": "3.7.2" } } diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/app.tsx b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/app.tsx similarity index 100% rename from test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/app.tsx rename to test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/app.tsx diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/components/main.tsx b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/components/main.tsx similarity index 99% rename from test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/components/main.tsx rename to test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/components/main.tsx index a50248a5b6fa32..ace2af2b4f0cfe 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/components/main.tsx +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app/components/main.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { EuiPage, EuiPageBody, EuiPageContent, EuiPageContentHeader } from '@elastic/eui'; import { first } from 'rxjs/operators'; import { IInterpreterRenderHandlers, ExpressionValue } from 'src/plugins/expressions'; -import { RequestAdapter, DataAdapter } from '../../../../../../../../src/plugins/inspector'; +import { RequestAdapter, DataAdapter } from '../../../../../../../src/plugins/inspector'; import { Adapters, ExpressionRenderHandler } from '../../types'; import { getExpressions } from '../../services'; diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/index.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/index.ts index c4cc7175d61570..d7a764b581c01d 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/index.ts +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/index.ts @@ -17,4 +17,12 @@ * under the License. */ -export * from './np_ready'; +import { PluginInitializer, PluginInitializerContext } from 'src/core/public'; +import { Plugin, StartDeps } from './plugin'; +export { StartDeps }; + +export const plugin: PluginInitializer = ( + initializerContext: PluginInitializerContext +) => { + return new Plugin(initializerContext); +}; diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/legacy.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/legacy.ts deleted file mode 100644 index a7cd313038d69c..00000000000000 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/legacy.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { PluginInitializerContext } from 'src/core/public'; -import { npSetup, npStart } from 'ui/new_platform'; - -import { plugin } from './np_ready'; - -// This is required so some default styles and required scripts/Angular modules are loaded, -// or the timezone setting is correctly applied. -import 'ui/autoload/all'; -// Used to run esaggs queries -import 'uiExports/fieldFormats'; -import 'uiExports/search'; -// Used for kibana_context function - -import 'uiExports/savedObjectTypes'; -import 'uiExports/interpreter'; - -const pluginInstance = plugin({} as PluginInitializerContext); - -export const setup = pluginInstance.setup(npSetup.core, npSetup.plugins); -export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/plugin.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/plugin.ts similarity index 100% rename from test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/plugin.ts rename to test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/plugin.ts diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/services.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/services.ts similarity index 91% rename from test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/services.ts rename to test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/services.ts index a700727d87299d..4972911d5894f0 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/services.ts +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/services.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createGetterSetter } from '../../../../../../src/plugins/kibana_utils/public'; +import { createGetterSetter } from '../../../../../src/plugins/kibana_utils/public'; import { ExpressionsStart } from './types'; export const [getExpressions, setExpressions] = createGetterSetter('Expressions'); diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/types.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/types.ts similarity index 100% rename from test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/types.ts rename to test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/types.ts diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/tsconfig.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/tsconfig.json new file mode 100644 index 00000000000000..5fcaeafbb0d852 --- /dev/null +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "../../../../typings/**/*", + ], + "exclude": [] +} diff --git a/test/interpreter_functional/test_suites/run_pipeline/basic.ts b/test/interpreter_functional/test_suites/run_pipeline/basic.ts index a2172dd2da1ba3..51ad789143c541 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/basic.ts +++ b/test/interpreter_functional/test_suites/run_pipeline/basic.ts @@ -113,10 +113,11 @@ export default function({ await expectExpression('partial_test_2', metricExpr, context).toMatchSnapshot() ).toMatchScreenshot(); - const regionMapExpr = `regionmap visConfig='{"metric":{"accessor":1,"format":{"id":"number"}},"bucket":{"accessor":0}}'`; - await ( - await expectExpression('partial_test_3', regionMapExpr, context).toMatchSnapshot() - ).toMatchScreenshot(); + // TODO: should be uncommented when the region map is migrated to the new platform + // const regionMapExpr = `regionmap visConfig='{"metric":{"accessor":1,"format":{"id":"number"}},"bucket":{"accessor":0}}'`; + // await ( + // await expectExpression('partial_test_3', regionMapExpr, context).toMatchSnapshot() + // ).toMatchScreenshot(); }); }); }); diff --git a/test/interpreter_functional/test_suites/run_pipeline/helpers.ts b/test/interpreter_functional/test_suites/run_pipeline/helpers.ts index 00693845bb2662..2486fb0e1fbd04 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/helpers.ts +++ b/test/interpreter_functional/test_suites/run_pipeline/helpers.ts @@ -21,6 +21,17 @@ import expect from '@kbn/expect'; import { ExpressionValue } from 'src/plugins/expressions'; import { FtrProviderContext } from '../../../functional/ftr_provider_context'; +declare global { + interface Window { + runPipeline: ( + expressions: string, + context?: ExpressionValue, + initialContext?: ExpressionValue + ) => any; + renderPipelineResponse: (context?: ExpressionValue) => Promise; + } +} + export type ExpressionResult = any; export type ExpectExpression = ( @@ -165,7 +176,7 @@ export function expectExpressionProvider({ log.debug('starting to render'); const result = await browser.executeAsync( (_context: ExpressionResult, done: (renderResult: any) => void) => - window.renderPipelineResponse(_context).then(renderResult => { + window.renderPipelineResponse(_context).then((renderResult: any) => { done(renderResult); return renderResult; }), diff --git a/test/plugin_functional/plugins/core_provider_plugin/kibana.json b/test/plugin_functional/plugins/core_provider_plugin/kibana.json new file mode 100644 index 00000000000000..1d5c5824d6b970 --- /dev/null +++ b/test/plugin_functional/plugins/core_provider_plugin/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "core_provider_plugin", + "version": "0.0.1", + "kibanaVersion": "kibana", + "optionalPlugins": ["core_plugin_a", "core_plugin_b", "licensing"], + "server": false, + "ui": true +} diff --git a/test/plugin_functional/plugins/core_provider_plugin/public/index.ts b/test/plugin_functional/plugins/core_provider_plugin/public/index.ts index c74928203db56f..2f271fe5ef65b3 100644 --- a/test/plugin_functional/plugins/core_provider_plugin/public/index.ts +++ b/test/plugin_functional/plugins/core_provider_plugin/public/index.ts @@ -16,13 +16,31 @@ * specific language governing permissions and limitations * under the License. */ -import { npSetup, npStart } from 'ui/new_platform'; +import { Plugin, CoreSetup, CoreStart } from 'kibana/public'; import '../types'; -window.__coreProvider = { - setup: npSetup, - start: npStart, - testUtils: { - delay: (ms: number) => new Promise(res => setTimeout(res, ms)), - }, -}; +export const plugin = () => new CoreProviderPlugin(); + +class CoreProviderPlugin implements Plugin { + private setupDeps?: { core: CoreSetup; plugins: Record }; + public setup(core: CoreSetup, plugins: Record) { + this.setupDeps = { + core, + plugins, + }; + } + + public start(core: CoreStart, plugins: Record) { + window.__coreProvider = { + setup: this.setupDeps!, + start: { + core, + plugins, + }, + testUtils: { + delay: (ms: number) => new Promise(res => setTimeout(res, ms)), + }, + }; + } + public stop() {} +} diff --git a/test/plugin_functional/plugins/core_provider_plugin/tsconfig.json b/test/plugin_functional/plugins/core_provider_plugin/tsconfig.json index c29959197958df..baedb5f2f621bf 100644 --- a/test/plugin_functional/plugins/core_provider_plugin/tsconfig.json +++ b/test/plugin_functional/plugins/core_provider_plugin/tsconfig.json @@ -8,7 +8,7 @@ "index.ts", "types.ts", "public/**/*.ts", - "../../../../typings/**/*", + "../../../../typings/**/*" ], "exclude": [] } diff --git a/test/plugin_functional/plugins/core_provider_plugin/types.ts b/test/plugin_functional/plugins/core_provider_plugin/types.ts index bf19578c37baab..cae3b604ecd959 100644 --- a/test/plugin_functional/plugins/core_provider_plugin/types.ts +++ b/test/plugin_functional/plugins/core_provider_plugin/types.ts @@ -16,17 +16,17 @@ * specific language governing permissions and limitations * under the License. */ -import { LegacyCoreSetup, LegacyCoreStart } from 'kibana/public'; +import { CoreSetup, CoreStart } from 'kibana/public'; declare global { interface Window { __coreProvider: { setup: { - core: LegacyCoreSetup; + core: CoreSetup; plugins: Record; }; start: { - core: LegacyCoreStart; + core: CoreStart; plugins: Record; }; testUtils: { diff --git a/test/plugin_functional/test_suites/core_plugins/application_status.ts b/test/plugin_functional/test_suites/core_plugins/application_status.ts index 0781cf8a4f5bdf..b2c0413c5024b7 100644 --- a/test/plugin_functional/test_suites/core_plugins/application_status.ts +++ b/test/plugin_functional/test_suites/core_plugins/application_status.ts @@ -57,7 +57,8 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider }, i)) as any; }; - describe('application status management', () => { + // FLAKY: https://github.com/elastic/kibana/issues/65423 + describe.skip('application status management', () => { beforeEach(async () => { await PageObjects.common.navigateToApp('app_status_start'); }); diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 097833750bc800..b8e26b8e6ffcbd 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -40,8 +40,8 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider const find = getService('find'); const testSubjects = getService('testSubjects'); - const navigateTo = (path: string) => - browser.navigateTo(`${PageObjects.common.getHostPort()}${path}`); + const navigateTo = async (path: string) => + await browser.navigateTo(`${PageObjects.common.getHostPort()}${path}`); const navigateToApp = async (title: string) => { await appsMenu.clickLink(title); return browser.execute(() => { diff --git a/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts b/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts index 8ddd0ff96ba8f5..b2393443989f94 100644 --- a/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts +++ b/test/plugin_functional/test_suites/core_plugins/ui_plugins.ts @@ -47,14 +47,6 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider await PageObjects.common.navigateToApp('settings'); }); - it('to injectedMetadata service', async () => { - expect( - await browser.execute(() => { - return window.__coreProvider.setup.core.injectedMetadata.getKibanaBuildNumber(); - }) - ).to.be.a('number'); - }); - it('to start services via coreSetup.getStartServices', async () => { expect( await browser.executeAsync(async cb => { diff --git a/test/scripts/jenkins_build_kibana.sh b/test/scripts/jenkins_build_kibana.sh index 1f6e09fad19e95..e3f46e7a6ada41 100755 --- a/test/scripts/jenkins_build_kibana.sh +++ b/test/scripts/jenkins_build_kibana.sh @@ -6,6 +6,7 @@ echo " -> building kibana platform plugins" node scripts/build_kibana_platform_plugins \ --oss \ --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ + --scan-dir "$KIBANA_DIR/test/interpreter_functional/plugins" \ --verbose; # doesn't persist, also set in kibanaPipeline.groovy diff --git a/test/scripts/jenkins_xpack_build_kibana.sh b/test/scripts/jenkins_xpack_build_kibana.sh index 8dc41639fa9468..c962b962b1e5e6 100755 --- a/test/scripts/jenkins_xpack_build_kibana.sh +++ b/test/scripts/jenkins_xpack_build_kibana.sh @@ -5,6 +5,7 @@ source src/dev/ci_setup/setup_env.sh echo " -> building kibana platform plugins" node scripts/build_kibana_platform_plugins \ + --scan-dir "$KIBANA_DIR/test/plugin_functional/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/alerting_api_integration/plugins" \ diff --git a/test/tsconfig.json b/test/tsconfig.json index 5a3716e620fed8..a270144bd49fea 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -19,6 +19,7 @@ "typings/**/*" ], "exclude": [ - "plugin_functional/plugins/**/*" + "plugin_functional/plugins/**/*", + "interpreter_functional/plugins/**/*" ] } diff --git a/webpackShims/leaflet.js b/webpackShims/leaflet.js deleted file mode 100644 index c35076e1295339..00000000000000 --- a/webpackShims/leaflet.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -require('../node_modules/leaflet/dist/leaflet.css'); -window.L = module.exports = require('../node_modules/leaflet/dist/leaflet'); -window.L.Browser.touch = false; -window.L.Browser.pointer = false; - -require('../node_modules/leaflet.heat/dist/leaflet-heat.js'); - -require('../node_modules/leaflet-draw/dist/leaflet.draw.css'); -require('../node_modules/leaflet-draw/dist/leaflet.draw.js'); - -require('../node_modules/leaflet-responsive-popup/leaflet.responsive.popup.css'); -require('../node_modules/leaflet-responsive-popup/leaflet.responsive.popup.js'); 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 532c49803e7b0b..746fa693e435eb 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -27,7 +27,6 @@ import 'uiExports/search'; import 'uiExports/shareContextMenuExtensions'; import _ from 'lodash'; import 'ui/autoload/all'; -import 'leaflet'; import { npStart } from 'ui/new_platform'; import { localApplicationService } from 'plugins/kibana/local_application_service'; diff --git a/x-pack/legacy/plugins/maps/index.js b/x-pack/legacy/plugins/maps/index.js index 40123040764b73..a46cdfe35e32dc 100644 --- a/x-pack/legacy/plugins/maps/index.js +++ b/x-pack/legacy/plugins/maps/index.js @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; import mappings from './mappings.json'; import { i18n } from '@kbn/i18n'; import { resolve } from 'path'; @@ -39,23 +38,13 @@ export function maps(kibana) { }, injectDefaultVars(server) { const serverConfig = server.config(); - const mapConfig = serverConfig.get('map'); return { showMapVisualizationTypes: serverConfig.get('xpack.maps.showMapVisualizationTypes'), showMapsInspectorAdapter: serverConfig.get('xpack.maps.showMapsInspectorAdapter'), enableVectorTiles: serverConfig.get('xpack.maps.enableVectorTiles'), preserveDrawingBuffer: serverConfig.get('xpack.maps.preserveDrawingBuffer'), - isEmsEnabled: mapConfig.includeElasticMapsService, - emsFontLibraryUrl: mapConfig.emsFontLibraryUrl, - emsTileLayerId: mapConfig.emsTileLayerId, - proxyElasticMapsServiceInMaps: mapConfig.proxyElasticMapsServiceInMaps, - emsFileApiUrl: mapConfig.emsFileApiUrl, - emsTileApiUrl: mapConfig.emsTileApiUrl, - emsLandingPageUrl: mapConfig.emsLandingPageUrl, kbnPkgVersion: serverConfig.get('pkg.version'), - regionmapLayers: _.get(mapConfig, 'regionmap.layers', []), - tilemap: _.get(mapConfig, 'tilemap', {}), }; }, styleSheetPaths: `${__dirname}/public/index.scss`, @@ -112,14 +101,12 @@ export function maps(kibana) { licensing: newPlatformPlugins.licensing, home: newPlatformPlugins.home, usageCollection: newPlatformPlugins.usageCollection, + mapsLegacy: newPlatformPlugins.mapsLegacy, }; // legacy dependencies const __LEGACY = { config: server.config, - mapConfig() { - return server.config().get('map'); - }, route: server.route.bind(server), plugins: { elasticsearch: server.plugins.elasticsearch, @@ -132,8 +119,8 @@ export function maps(kibana) { getInjectedUiAppVars: server.getInjectedUiAppVars, }; - const mapPluginSetup = new MapPlugin().setup(coreSetup, pluginsSetup, __LEGACY); - server.expose('getMapConfig', mapPluginSetup.getMapConfig); + const mapPlugin = new MapPlugin(); + mapPlugin.setup(coreSetup, pluginsSetup, __LEGACY); }, }); } diff --git a/x-pack/legacy/plugins/maps/server/plugin.js b/x-pack/legacy/plugins/maps/server/plugin.js index 79f3dcf76b82eb..d2d5309606cde1 100644 --- a/x-pack/legacy/plugins/maps/server/plugin.js +++ b/x-pack/legacy/plugins/maps/server/plugin.js @@ -19,8 +19,9 @@ import { emsBoundariesSpecProvider } from './tutorials/ems'; export class MapPlugin { setup(core, plugins, __LEGACY) { - const { featuresPlugin, home, licensing, usageCollection } = plugins; + const { featuresPlugin, home, licensing, usageCollection, mapsLegacy } = plugins; let routesInitialized = false; + const mapConfig = mapsLegacy.config; featuresPlugin.registerFeature({ id: APP_ID, @@ -58,7 +59,7 @@ export class MapPlugin { const { state } = license.check('maps', 'basic'); if (state === 'valid' && !routesInitialized) { routesInitialized = true; - initRoutes(__LEGACY, license.uid); + initRoutes(__LEGACY, license.uid, mapConfig); } }); @@ -134,7 +135,7 @@ export class MapPlugin { home.tutorials.registerTutorial( emsBoundariesSpecProvider({ prependBasePath: core.http.basePath.prepend, - emsLandingPageUrl: __LEGACY.mapConfig().emsLandingPageUrl, + emsLandingPageUrl: mapConfig.emsLandingPageUrl, }) ); } @@ -142,11 +143,5 @@ export class MapPlugin { __LEGACY.injectUiAppVars(APP_ID, async () => { return await __LEGACY.getInjectedUiAppVars('kibana'); }); - - return { - getMapConfig() { - return __LEGACY.mapConfig(); - }, - }; } } diff --git a/x-pack/legacy/plugins/maps/server/routes.js b/x-pack/legacy/plugins/maps/server/routes.js index d49f9827e3ea04..6b83f4026f1db9 100644 --- a/x-pack/legacy/plugins/maps/server/routes.js +++ b/x-pack/legacy/plugins/maps/server/routes.js @@ -31,9 +31,8 @@ import Boom from 'boom'; const ROOT = `/${GIS_API_PATH}`; -export function initRoutes(server, licenseUid) { +export function initRoutes(server, licenseUid, mapConfig) { const serverConfig = server.config(); - const mapConfig = serverConfig.get('map'); let emsClient; if (mapConfig.includeElasticMapsService) { diff --git a/x-pack/plugins/actions/server/action_type_registry.test.ts b/x-pack/plugins/actions/server/action_type_registry.test.ts index 3be2f265570791..a8f50ec3535e2c 100644 --- a/x-pack/plugins/actions/server/action_type_registry.test.ts +++ b/x-pack/plugins/actions/server/action_type_registry.test.ts @@ -71,6 +71,19 @@ describe('register()', () => { `); }); + test('shallow clones the given action type', () => { + const myType: ActionType = { + id: 'my-action-type', + name: 'My action type', + minimumLicenseRequired: 'basic', + executor, + }; + const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams); + actionTypeRegistry.register(myType); + myType.name = 'Changed'; + expect(actionTypeRegistry.get('my-action-type').name).toEqual('My action type'); + }); + test('throws error if action type already registered', () => { const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams); actionTypeRegistry.register({ diff --git a/x-pack/plugins/actions/server/action_type_registry.ts b/x-pack/plugins/actions/server/action_type_registry.ts index 723982b11e1ccd..73ae49a7e69c2f 100644 --- a/x-pack/plugins/actions/server/action_type_registry.ts +++ b/x-pack/plugins/actions/server/action_type_registry.ts @@ -91,7 +91,7 @@ export class ActionTypeRegistry { ) ); } - this.actionTypes.set(actionType.id, actionType); + this.actionTypes.set(actionType.id, { ...actionType }); this.taskManager.registerTaskDefinitions({ [`actions:${actionType.id}`]: { title: actionType.name, diff --git a/x-pack/plugins/alerting/server/alert_type_registry.test.ts b/x-pack/plugins/alerting/server/alert_type_registry.test.ts index f9df390242cd45..f556287703347c 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.test.ts +++ b/x-pack/plugins/alerting/server/alert_type_registry.test.ts @@ -72,6 +72,25 @@ describe('register()', () => { `); }); + test('shallow clones the given alert type', () => { + const alertType: AlertType = { + id: 'test', + name: 'Test', + actionGroups: [ + { + id: 'default', + name: 'Default', + }, + ], + defaultActionGroupId: 'default', + executor: jest.fn(), + }; + const registry = new AlertTypeRegistry(alertTypeRegistryParams); + registry.register(alertType); + alertType.name = 'Changed'; + expect(registry.get('test').name).toEqual('Test'); + }); + test('should throw an error if type is already registered', () => { const registry = new AlertTypeRegistry(alertTypeRegistryParams); registry.register({ diff --git a/x-pack/plugins/alerting/server/alert_type_registry.ts b/x-pack/plugins/alerting/server/alert_type_registry.ts index 55e39b6a817dbf..8bcb4d838ca1bb 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.ts +++ b/x-pack/plugins/alerting/server/alert_type_registry.ts @@ -41,7 +41,7 @@ export class AlertTypeRegistry { ); } alertType.actionVariables = normalizedActionVariables(alertType.actionVariables); - this.alertTypes.set(alertType.id, alertType); + this.alertTypes.set(alertType.id, { ...alertType }); this.taskManager.registerTaskDefinitions({ [`alerting:${alertType.id}`]: { title: alertType.name, diff --git a/x-pack/plugins/apm/common/service_map.ts b/x-pack/plugins/apm/common/service_map.ts index 2ff30a61499b6b..dc457c38a52af4 100644 --- a/x-pack/plugins/apm/common/service_map.ts +++ b/x-pack/plugins/apm/common/service_map.ts @@ -34,7 +34,6 @@ export interface Connection { } export interface ServiceNodeMetrics { - numInstances: number; avgMemoryUsage: number | null; avgCpuUsage: number | null; avgTransactionDuration: number | null; diff --git a/x-pack/plugins/apm/public/components/app/Main/__test__/ProvideBreadcrumbs.test.tsx b/x-pack/plugins/apm/public/components/app/Main/ProvideBreadcrumbs.test.tsx similarity index 94% rename from x-pack/plugins/apm/public/components/app/Main/__test__/ProvideBreadcrumbs.test.tsx rename to x-pack/plugins/apm/public/components/app/Main/ProvideBreadcrumbs.test.tsx index cb983cdffa0282..1e3a73acfab57f 100644 --- a/x-pack/plugins/apm/public/components/app/Main/__test__/ProvideBreadcrumbs.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/ProvideBreadcrumbs.test.tsx @@ -5,8 +5,8 @@ */ import { Location } from 'history'; -import { BreadcrumbRoute, getBreadcrumbs } from '../ProvideBreadcrumbs'; -import { RouteName } from '../route_config/route_names'; +import { BreadcrumbRoute, getBreadcrumbs } from './ProvideBreadcrumbs'; +import { RouteName } from './route_config/route_names'; describe('getBreadcrumbs', () => { const getTestRoutes = (): BreadcrumbRoute[] => [ diff --git a/x-pack/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx b/x-pack/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx index 8960af0f21fd29..b4a556c497c1bb 100644 --- a/x-pack/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/UpdateBreadcrumbs.tsx @@ -30,10 +30,18 @@ function getTitleFromBreadCrumbs(breadcrumbs: Breadcrumb[]) { class UpdateBreadcrumbsComponent extends React.Component { public updateHeaderBreadcrumbs() { - const breadcrumbs = this.props.breadcrumbs.map(({ value, match }) => ({ - text: value, - href: getAPMHref(match.url, this.props.location.search) - })); + const breadcrumbs = this.props.breadcrumbs.map( + ({ value, match }, index) => { + const isLastBreadcrumbItem = + index === this.props.breadcrumbs.length - 1; + return { + text: value, + href: isLastBreadcrumbItem + ? undefined // makes the breadcrumb item not clickable + : getAPMHref(match.url, this.props.location.search) + }; + } + ); document.title = getTitleFromBreadCrumbs(this.props.breadcrumbs); this.props.core.chrome.setBreadcrumbs(breadcrumbs); diff --git a/x-pack/plugins/apm/public/components/app/Main/__snapshots__/UpdateBreadcrumbs.test.tsx.snap b/x-pack/plugins/apm/public/components/app/Main/__snapshots__/UpdateBreadcrumbs.test.tsx.snap index 51bdb63874e633..e7f6cba59318a5 100644 --- a/x-pack/plugins/apm/public/components/app/Main/__snapshots__/UpdateBreadcrumbs.test.tsx.snap +++ b/x-pack/plugins/apm/public/components/app/Main/__snapshots__/UpdateBreadcrumbs.test.tsx.snap @@ -15,7 +15,7 @@ Array [ "text": "opbeans-node", }, Object { - "href": "#/services/opbeans-node/errors?rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0&kuery=myKuery", + "href": undefined, "text": "Errors", }, ] @@ -40,7 +40,7 @@ Array [ "text": "Errors", }, Object { - "href": "#/services/opbeans-node/errors/myGroupId?rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0&kuery=myKuery", + "href": undefined, "text": "myGroupId", }, ] @@ -61,7 +61,7 @@ Array [ "text": "opbeans-node", }, Object { - "href": "#/services/opbeans-node/transactions?rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0&kuery=myKuery", + "href": undefined, "text": "Transactions", }, ] @@ -86,7 +86,7 @@ Array [ "text": "Transactions", }, Object { - "href": "#/services/opbeans-node/transactions/view?rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0&kuery=myKuery", + "href": undefined, "text": "my-transaction-name", }, ] @@ -95,7 +95,7 @@ Array [ exports[`UpdateBreadcrumbs Homepage 1`] = ` Array [ Object { - "href": "#/?rangeFrom=now-24h&rangeTo=now&refreshPaused=true&refreshInterval=0&kuery=myKuery", + "href": undefined, "text": "APM", }, ] diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx index 7e15d0116b84d1..b5bfa63c1bdde0 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Contents.tsx @@ -90,11 +90,11 @@ const ANOMALY_DETECTION_TITLE = i18n.translate( { defaultMessage: 'Anomaly Detection' } ); -const ANOMALY_DETECTION_INFO = i18n.translate( - 'xpack.apm.serviceMap.anomalyDetectionPopoverInfo', +const ANOMALY_DETECTION_TOOLTIP = i18n.translate( + 'xpack.apm.serviceMap.anomalyDetectionPopoverTooltip', { defaultMessage: - 'Display the health of your service by enabling the anomaly detection feature in Machine Learning.' + 'Service health indicators are powered by the anomaly detection feature in machine learning' } ); @@ -108,11 +108,11 @@ const ANOMALY_DETECTION_LINK = i18n.translate( { defaultMessage: 'View anomalies' } ); -const ANOMALY_DETECTION_ENABLE_TEXT = i18n.translate( - 'xpack.apm.serviceMap.anomalyDetectionPopoverEnable', +const ANOMALY_DETECTION_DISABLED_TEXT = i18n.translate( + 'xpack.apm.serviceMap.anomalyDetectionPopoverDisabled', { defaultMessage: - 'Enable anomaly detection from the Integrations menu in the Service details view.' + 'Display service health indicators by enabling anomaly detection from the Integrations menu in the Service details view.' } ); @@ -154,15 +154,18 @@ export function Contents({ {isService && ( -
- -

{ANOMALY_DETECTION_TITLE}

-
-   - -
{hasAnomalyDetection ? ( <> +
+ +

{ANOMALY_DETECTION_TITLE}

+
+   + +
@@ -188,7 +191,12 @@ export function Contents({ ) : ( - {ANOMALY_DETECTION_ENABLE_TEXT} + <> + +

{ANOMALY_DETECTION_TITLE}

+
+ {ANOMALY_DETECTION_DISABLED_TEXT} + )}
diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx index e5962afd76eb8d..2edd36f0d13807 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx @@ -16,7 +16,6 @@ storiesOf('app/ServiceMap/Popover/ServiceMetricList', module) avgRequestsPerMinute={164.47222031860858} avgCpuUsage={0.32809666568309237} avgMemoryUsage={0.5504868173242986} - numInstances={2} isLoading={false} /> )) @@ -27,7 +26,6 @@ storiesOf('app/ServiceMap/Popover/ServiceMetricList', module) avgRequestsPerMinute={null} avgCpuUsage={null} avgMemoryUsage={null} - numInstances={1} isLoading={true} /> )) @@ -38,7 +36,6 @@ storiesOf('app/ServiceMap/Popover/ServiceMetricList', module) avgRequestsPerMinute={8.439583235652972} avgCpuUsage={null} avgMemoryUsage={null} - numInstances={1} isLoading={false} /> )) @@ -49,7 +46,6 @@ storiesOf('app/ServiceMap/Popover/ServiceMetricList', module) avgRequestsPerMinute={null} avgCpuUsage={null} avgMemoryUsage={null} - numInstances={1} isLoading={false} /> )); diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx index 5c28fc0a5a7d0e..39d54dc5801d27 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx @@ -4,12 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiBadge, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingSpinner -} from '@elastic/eui'; +import { EuiFlexGroup, EuiLoadingSpinner } from '@elastic/eui'; import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; import { isNumber } from 'lodash'; @@ -30,10 +25,6 @@ function LoadingSpinner() { ); } -const BadgeRow = styled(EuiFlexItem)` - padding-bottom: ${lightTheme.gutterTypes.gutterSmall}; -`; - export const ItemRow = styled('tr')` line-height: 2; `; @@ -57,7 +48,6 @@ export function ServiceMetricList({ avgErrorsPerMinute, avgCpuUsage, avgMemoryUsage, - numInstances, isLoading }: ServiceMetricListProps) { const listItems = [ @@ -110,39 +100,22 @@ export function ServiceMetricList({ : null } ]; - const showBadgeRow = numInstances > 1; return isLoading ? ( ) : ( - <> - {showBadgeRow && ( - - - {numInstances > 1 && ( - - {i18n.translate('xpack.apm.serviceMap.numInstancesMetric', { - values: { numInstances }, - defaultMessage: '{numInstances} instances' - })} - - )} - - - )} - - - {listItems.map( - ({ title, description }) => - description && ( - - {title} - {description} - - ) - )} - -
- + + + {listItems.map( + ({ title, description }) => + description && ( + + {title} + {description} + + ) + )} + +
); } diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx index 3a6f94b9758002..79a6370b4be46a 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx @@ -132,7 +132,10 @@ export function AgentConfigurationCreateEdit({ setPage('choose-settings-step')} + onClickNext={() => { + resetSettings(); + setPage('choose-settings-step'); + }} /> )} 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 d7e28828572d55..7e8dccb8aff060 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 @@ -14,8 +14,7 @@ import { TRANSACTION_DURATION, METRIC_SYSTEM_CPU_PERCENT, METRIC_SYSTEM_FREE_MEMORY, - METRIC_SYSTEM_TOTAL_MEMORY, - SERVICE_NODE_NAME + METRIC_SYSTEM_TOTAL_MEMORY } from '../../../common/elasticsearch_fieldnames'; import { percentMemoryUsedScript } from '../metrics/by_agent/shared/memory'; @@ -56,22 +55,19 @@ export async function getServiceMapServiceNodeInfo({ errorMetrics, transactionMetrics, cpuMetrics, - memoryMetrics, - instanceMetrics + memoryMetrics ] = await Promise.all([ getErrorMetrics(taskParams), getTransactionMetrics(taskParams), getCpuMetrics(taskParams), - getMemoryMetrics(taskParams), - getNumInstances(taskParams) + getMemoryMetrics(taskParams) ]); return { ...errorMetrics, ...transactionMetrics, ...cpuMetrics, - ...memoryMetrics, - ...instanceMetrics + ...memoryMetrics }; } @@ -226,47 +222,3 @@ async function getMemoryMetrics({ avgMemoryUsage: response.aggregations?.avgMemoryUsage.value ?? null }; } - -async function getNumInstances({ - setup, - filter -}: TaskParameters): Promise<{ numInstances: number }> { - const { client, indices } = setup; - const response = await client.search({ - index: indices['apm_oss.transactionIndices'], - body: { - query: { - bool: { - filter: filter.concat([ - { - term: { - [PROCESSOR_EVENT]: 'transaction' - } - }, - { - exists: { - field: SERVICE_NODE_NAME - } - }, - { - exists: { - field: METRIC_SYSTEM_TOTAL_MEMORY - } - } - ]) - } - }, - aggs: { - instances: { - cardinality: { - field: SERVICE_NODE_NAME - } - } - } - } - }); - - return { - numInstances: response.aggregations?.instances.value || 1 - }; -} diff --git a/x-pack/plugins/canvas/public/plugin.tsx b/x-pack/plugins/canvas/public/plugin.tsx index bd39dcfb39fe22..ba57d1475bc4ff 100644 --- a/x-pack/plugins/canvas/public/plugin.tsx +++ b/x-pack/plugins/canvas/public/plugin.tsx @@ -73,7 +73,7 @@ export class CanvasPlugin id: 'canvas', title: 'Canvas', euiIconType: 'canvasApp', - order: 0, // need to figure out if this is the proper order for us + order: 3000, mount: async (params: AppMountParameters) => { // Load application bundle const { renderApp, initializeCanvas, teardownCanvas } = await import('./application'); diff --git a/x-pack/plugins/endpoint/common/generate_data.ts b/x-pack/plugins/endpoint/common/generate_data.ts index 58393b88e37a35..9e7aedcc90bb5e 100644 --- a/x-pack/plugins/endpoint/common/generate_data.ts +++ b/x-pack/plugins/endpoint/common/generate_data.ts @@ -560,7 +560,7 @@ export class EndpointDocGenerator { applied: { actions: { configure_elasticsearch_connection: { - message: 'elasticsearch comms configured successfully', + message: 'elasticsearch comes configured successfully', status: HostPolicyResponseActionStatus.success, }, configure_kernel: { @@ -648,7 +648,7 @@ export class EndpointDocGenerator { response: { configurations: { events: { - concerned_actions: this.randomHostPolicyResponseActions(), + concerned_actions: ['download_model'], status: this.randomHostPolicyResponseActionStatus(), }, logging: { diff --git a/x-pack/plugins/endpoint/common/types.ts b/x-pack/plugins/endpoint/common/types.ts index a1ddc97a90d298..181b0e7ab38846 100644 --- a/x-pack/plugins/endpoint/common/types.ts +++ b/x-pack/plugins/endpoint/common/types.ts @@ -25,7 +25,7 @@ export type Immutable = T extends undefined | null | boolean | string | numbe ? ImmutableSet : ImmutableObject; -type ImmutableArray = ReadonlyArray>; +export type ImmutableArray = ReadonlyArray>; type ImmutableMap = ReadonlyMap, Immutable>; type ImmutableSet = ReadonlySet>; type ImmutableObject = { readonly [K in keyof T]: Immutable }; @@ -644,6 +644,8 @@ export interface HostPolicyResponseActions { read_malware_config: HostPolicyResponseActionDetails; } +export type HostPolicyResponseConfiguration = HostPolicyResponse['endpoint']['policy']['applied']['response']['configurations']; + interface HostPolicyResponseConfigurationStatus { status: HostPolicyResponseActionStatus; concerned_actions: Array; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts index bcfd6b96c9eb88..a5378a02ed6fb1 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/middleware.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, HostPolicyResponseActionStatus } from '../../../../../common/types'; import { isOnHostPage, hasSelectedHost, uiQueryParams, listData } from './selectors'; import { HostState } from '../../types'; import { ImmutableMiddlewareFactory } from '../../types'; @@ -77,7 +77,31 @@ export const hostMiddlewareFactory: ImmutableMiddlewareFactory = core endpoint: { policy: { applied: { - status: 'success', + version: '1.0.0', + status: HostPolicyResponseActionStatus.success, + id: '17d4b81d-9940-4b64-9de5-3e03ef1fb5cf', + actions: { + download_model: { + status: 'success', + message: 'Model downloaded', + }, + ingest_events_config: { + status: 'failure', + message: 'No action taken', + }, + }, + response: { + configurations: { + malware: { + status: 'success', + concerned_actions: ['download_model'], + }, + events: { + status: 'failure', + concerned_actions: ['ingest_events_config'], + }, + }, + }, }, }, }, diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts index b0711baf9cdfff..e16d4ff5d18c26 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/hosts/selectors.ts @@ -5,7 +5,12 @@ */ import querystring from 'querystring'; import { createSelector } from 'reselect'; -import { Immutable } from '../../../../../common/types'; +import { + Immutable, + HostPolicyResponseActions, + HostPolicyResponseConfiguration, + HostPolicyResponseActionStatus, +} from '../../../../../common/types'; import { HostState, HostIndexUIQueryParams } from '../../types'; const PAGE_SIZES = Object.freeze([10, 20, 50]); @@ -28,6 +33,61 @@ export const detailsLoading = (state: Immutable): boolean => state.de export const detailsError = (state: Immutable) => state.detailsError; +/** + * Returns the full policy response from the endpoint after a user modifies a policy. + */ +const detailsPolicyAppliedResponse = (state: Immutable) => + state.policyResponse && state.policyResponse.endpoint.policy.applied; + +/** + * Returns the response configurations from the endpoint after a user modifies a policy. + */ +export const policyResponseConfigurations: ( + state: Immutable +) => undefined | Immutable = createSelector( + detailsPolicyAppliedResponse, + applied => { + return applied?.response?.configurations; + } +); + +/** + * Returns a map of the number of failed and warning policy response actions per configuration. + */ +export const policyResponseFailedOrWarningActionCount: ( + state: Immutable +) => Map = createSelector(detailsPolicyAppliedResponse, applied => { + const failureOrWarningByConfigType = new Map(); + if (applied?.response?.configurations !== undefined && applied?.actions !== undefined) { + Object.entries(applied.response.configurations).map(([key, val]) => { + let count = 0; + for (const action of val.concerned_actions) { + const actionStatus = applied.actions[action]?.status; + if ( + actionStatus === HostPolicyResponseActionStatus.failure || + actionStatus === HostPolicyResponseActionStatus.warning + ) { + count += 1; + } + } + return failureOrWarningByConfigType.set(key, count); + }); + } + return failureOrWarningByConfigType; +}); + +/** + * Returns the actions taken by the endpoint for each response configuration after a user modifies a policy. + */ +export const policyResponseActions: ( + state: Immutable +) => undefined | Partial = createSelector( + detailsPolicyAppliedResponse, + applied => { + return applied?.actions; + } +); + export const isOnHostPage = (state: Immutable) => state.location ? state.location.pathname === '/hosts' : false; diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/components/flyout_sub_header.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/components/flyout_sub_header.tsx index 02f91307c988ec..9abb54e8b18075 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/components/flyout_sub_header.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/components/flyout_sub_header.tsx @@ -9,7 +9,7 @@ import { EuiFlyoutHeader, CommonProps, EuiButtonEmpty } from '@elastic/eui'; import styled from 'styled-components'; export type FlyoutSubHeaderProps = CommonProps & { - children: React.ReactNode; + children?: React.ReactNode; backButton?: { title: string; onClick: MouseEventHandler; @@ -25,6 +25,9 @@ const StyledEuiFlyoutHeader = styled(EuiFlyoutHeader)` padding-bottom: ${props => props.theme.eui.paddingSizes.s}; } + .flyoutSubHeaderBackButton { + font-size: ${props => props.theme.eui.euiFontSizeXS}; + } .back-button-content { padding-left: 0; &-text { @@ -48,7 +51,7 @@ const BUTTON_TEXT_PROPS = Object.freeze({ className: 'back-button-content-text' export const FlyoutSubHeader = memo( ({ children, backButton, ...otherProps }) => { return ( - + {backButton && (
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */} @@ -60,6 +63,7 @@ export const FlyoutSubHeader = memo( size="xs" href={backButton?.href ?? ''} onClick={backButton?.onClick} + className="flyoutSubHeaderBackButton" > {backButton?.title} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_constants.ts b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_constants.ts new file mode 100644 index 00000000000000..5250eeaf028d50 --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_constants.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HostPolicyResponseActionStatus } from '../../../../../../common/types'; + +export const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze< + { [key in keyof typeof HostPolicyResponseActionStatus]: string } +>({ + success: 'success', + warning: 'warning', + failure: 'danger', +}); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx index 7d948f54bd0bce..ee1c7543d7e0aa 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/host_details.tsx @@ -16,13 +16,14 @@ import { import React, { memo, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { HostMetadata, HostPolicyResponseActionStatus } from '../../../../../../common/types'; +import { HostMetadata } from '../../../../../../common/types'; import { FormattedDateAndTime } from '../../formatted_date_time'; import { LinkToApp } from '../../components/link_to_app'; import { useHostSelector, useHostLogsUrl } from '../hooks'; import { urlFromQueryParams } from '../url_from_query_params'; import { policyResponseStatus, uiQueryParams } from '../../../store/hosts/selectors'; import { useNavigateByRouterEventHandler } from '../../hooks/use_navigate_by_router_event_handler'; +import { POLICY_STATUS_TO_HEALTH_COLOR } from './host_constants'; const HostIds = styled(EuiListGroupItem)` margin-top: 0; @@ -31,14 +32,6 @@ const HostIds = styled(EuiListGroupItem)` } `; -const POLICY_STATUS_TO_HEALTH_COLOR = Object.freeze< - { [key in keyof typeof HostPolicyResponseActionStatus]: string } ->({ - success: 'success', - warning: 'warning', - failure: 'danger', -}); - export const HostDetails = memo(({ details }: { details: HostMetadata }) => { const { appId, appPath, url } = useHostLogsUrl(details.host.id); const queryParams = useHostSelector(uiQueryParams); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx index e44a45f300daa2..017ce9a66f8c5d 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/index.tsx @@ -9,8 +9,9 @@ import { EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, - EuiTitle, EuiLoadingContent, + EuiTitle, + EuiText, EuiSpacer, } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; @@ -25,6 +26,9 @@ import { detailsError, showView, detailsLoading, + policyResponseConfigurations, + policyResponseActions, + policyResponseFailedOrWarningActionCount, } from '../../../store/hosts/selectors'; import { HostDetails } from './host_details'; import { PolicyResponse } from './policy_response'; @@ -101,6 +105,9 @@ const PolicyResponseFlyoutPanel = memo<{ hostMeta: HostMetadata; }>(({ hostMeta }) => { const { show, ...queryParams } = useHostSelector(uiQueryParams); + const responseConfig = useHostSelector(policyResponseConfigurations); + const responseActionStatus = useHostSelector(policyResponseActions); + const responseAttentionCount = useHostSelector(policyResponseFailedOrWarningActionCount); const detailsUri = useMemo( () => urlFromQueryParams({ @@ -125,18 +132,28 @@ const PolicyResponseFlyoutPanel = memo<{ - -

+ /> + + +

-

- - - - +

+ + {responseConfig !== undefined && responseActionStatus !== undefined ? ( + + ) : ( + + )} ); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/policy_response.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/policy_response.tsx index eacb6a52d31841..aa04f2fdff57f4 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/policy_response.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/policy_response.tsx @@ -3,8 +3,145 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { memo } from 'react'; +import React, { memo, useMemo } from 'react'; +import styled from 'styled-components'; +import { EuiAccordion, EuiNotificationBadge, EuiHealth } from '@elastic/eui'; +import { EuiText } from '@elastic/eui'; +import { htmlIdGenerator } from '@elastic/eui'; +import { + HostPolicyResponseActions, + HostPolicyResponseConfiguration, + Immutable, + ImmutableArray, +} from '../../../../../../common/types'; +import { formatResponse } from './policy_response_friendly_names'; +import { POLICY_STATUS_TO_HEALTH_COLOR } from './host_constants'; -export const PolicyResponse = memo(() => { - return
Policy Status to be displayed here soon.
; -}); +/** + * Nested accordion in the policy response detailing any concerned + * actions the endpoint took to apply the policy configuration. + */ +const PolicyResponseConfigAccordion = styled(EuiAccordion)` + > .euiAccordion__triggerWrapper { + padding: ${props => props.theme.eui.paddingSizes.s}; + } + &.euiAccordion-isOpen { + background-color: ${props => props.theme.eui.euiFocusBackgroundColor}; + } + .euiAccordion__childWrapper { + background-color: ${props => props.theme.eui.euiColorLightestShade}; + } + .policyResponseAttentionBadge { + background-color: ${props => props.theme.eui.euiColorDanger}; + color: ${props => props.theme.eui.euiColorEmptyShade}; + } + .euiAccordion__button { + :hover, + :focus { + text-decoration: none; + } + } + :hover:not(.euiAccordion-isOpen) { + background-color: ${props => props.theme.eui.euiColorLightestShade}; + } +`; + +const ResponseActions = memo( + ({ + actions, + actionStatus, + }: { + actions: ImmutableArray; + actionStatus: Partial; + }) => { + return ( + <> + {actions.map((action, index) => { + const statuses = actionStatus[action]; + if (statuses === undefined) { + return undefined; + } + return ( + +

{formatResponse(action)}

+ + } + paddingSize="s" + extraAction={ + + +

{formatResponse(statuses.status)}

+
+
+ } + > + +

{statuses.message}

+
+
+ ); + })} + + ); + } +); + +/** + * A policy response is returned by the endpoint and shown in the host details after a user modifies a policy + */ +export const PolicyResponse = memo( + ({ + responseConfig, + responseActionStatus, + responseAttentionCount, + }: { + responseConfig: Immutable; + responseActionStatus: Partial; + responseAttentionCount: Map; + }) => { + return ( + <> + {Object.entries(responseConfig).map(([key, val]) => { + const attentionCount = responseAttentionCount.get(key); + return ( + htmlIdGenerator()(), [])} + key={useMemo(() => htmlIdGenerator()(), [])} + data-test-subj="hostDetailsPolicyResponseConfigAccordion" + buttonContent={ + +

{formatResponse(key)}

+
+ } + paddingSize="m" + extraAction={ + attentionCount && + attentionCount > 0 && ( + + {attentionCount} + + ) + } + > + +
+ ); + })} + + ); + } +); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/policy_response_friendly_names.ts b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/policy_response_friendly_names.ts new file mode 100644 index 00000000000000..251b3e86bc3f9d --- /dev/null +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/details/policy_response_friendly_names.ts @@ -0,0 +1,170 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +const responseMap = new Map(); +responseMap.set( + 'success', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.success', { + defaultMessage: 'Success', + }) +); +responseMap.set( + 'warning', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.warning', { + defaultMessage: 'Warning', + }) +); +responseMap.set( + 'failure', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.failed', { + defaultMessage: 'Failed', + }) +); +responseMap.set( + 'malware', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.malware', { + defaultMessage: 'Malware', + }) +); +responseMap.set( + 'events', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.events', { + defaultMessage: 'Events', + }) +); +responseMap.set( + 'configure_elasticsearch_connection', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.configureElasticSearchConnection', { + defaultMessage: 'Configure Elastic Search Connection', + }) +); +responseMap.set( + 'configure_logging', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.configureLogging', { + defaultMessage: 'Configure Logging', + }) +); +responseMap.set( + 'configure_kernel', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.configureKernel', { + defaultMessage: 'Configure Kernel', + }) +); +responseMap.set( + 'configure_malware', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.configureMalware', { + defaultMessage: 'Configure Malware', + }) +); +responseMap.set( + 'connect_kernel', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.connectKernel', { + defaultMessage: 'Connect Kernel', + }) +); +responseMap.set( + 'detect_file_open_events', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.detectFileOpenEvents', { + defaultMessage: 'Detect File Open Events', + }) +); +responseMap.set( + 'detect_file_write_events', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.detectFileWriteEvents', { + defaultMessage: 'Detect File Write Events', + }) +); +responseMap.set( + 'detect_image_load_events', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.detectImageLoadEvents', { + defaultMessage: 'Detect Image Load Events', + }) +); +responseMap.set( + 'detect_process_events', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.detectProcessEvents', { + defaultMessage: 'Detect Process Events', + }) +); +responseMap.set( + 'download_global_artifacts', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.downloadGlobalArtifacts', { + defaultMessage: 'Download Global Artifacts', + }) +); +responseMap.set( + 'load_config', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.loadConfig', { + defaultMessage: 'Load Config', + }) +); +responseMap.set( + 'load_malware_model', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.loadMalwareModel', { + defaultMessage: 'Load Malware Model', + }) +); +responseMap.set( + 'read_elasticsearch_config', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.readElasticSearchConfig', { + defaultMessage: 'Read ElasticSearch Config', + }) +); +responseMap.set( + 'read_events_config', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.readEventsConfig', { + defaultMessage: 'Read Events Config', + }) +); +responseMap.set( + 'read_kernel_config', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.readKernelConfig', { + defaultMessage: 'Read Kernel Config', + }) +); +responseMap.set( + 'read_logging_config', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.readLoggingConfig', { + defaultMessage: 'Read Logging Config', + }) +); +responseMap.set( + 'read_malware_config', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.readMalwareConfig', { + defaultMessage: 'Read Malware Config', + }) +); +responseMap.set( + 'workflow', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.workflow', { + defaultMessage: 'Workflow', + }) +); +responseMap.set( + 'download_model', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.downloadModel', { + defaultMessage: 'Download Model', + }) +); +responseMap.set( + 'ingest_events_config', + i18n.translate('xpack.endpoint.hostDetails.policyResponse.injestEventsConfig', { + defaultMessage: 'Injest Events Config', + }) +); + +/** + * Takes in the snake-cased response from the API and + * removes the underscores and capitalizes the string. + */ +export function formatResponse(responseString: string) { + if (responseMap.has(responseString)) { + return responseMap.get(responseString); + } + return responseString; +} diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx index 5a8765110c9097..aaeff935b32b45 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/hosts/index.test.tsx @@ -25,7 +25,7 @@ describe('when on the hosts page', () => { let coreStart: AppContextTestRender['coreStart']; let middlewareSpy: AppContextTestRender['middlewareSpy']; - beforeEach(async () => { + beforeEach(() => { const mockedContext = createAppRootMockRenderer(); ({ history, store, coreStart, middlewareSpy } = mockedContext); render = () => mockedContext.render(); @@ -127,6 +127,14 @@ describe('when on the hosts page', () => { ) => { const policyResponse = docGenerator.generatePolicyResponse(); policyResponse.endpoint.policy.applied.status = overallStatus; + policyResponse.endpoint.policy.applied.response.configurations.malware.status = overallStatus; + policyResponse.endpoint.policy.applied.actions.download_model!.status = overallStatus; + if ( + overallStatus === HostPolicyResponseActionStatus.failure || + overallStatus === HostPolicyResponseActionStatus.warning + ) { + policyResponse.endpoint.policy.applied.actions.download_model!.message = 'no action taken'; + } store.dispatch({ type: 'serverReturnedHostPolicyResponse', payload: { @@ -281,6 +289,9 @@ describe('when on the hosts page', () => { fireEvent.click(policyStatusLink); }); await userChangedUrlChecker; + reactTestingLibrary.act(() => { + dispatchServerReturnedHostPolicyResponse(); + }); }); it('should hide the host details panel', async () => { const hostDetailsFlyout = await renderResult.queryByTestId('hostDetailsFlyoutBody'); @@ -299,6 +310,43 @@ describe('when on the hosts page', () => { (await renderResult.findByTestId('hostDetailsPolicyResponseFlyoutTitle')).textContent ).toBe('Policy Response'); }); + it('should show a configuration section for each protection', async () => { + const configAccordions = await renderResult.findAllByTestId( + 'hostDetailsPolicyResponseConfigAccordion' + ); + expect(configAccordions).not.toBeNull(); + }); + it('should show an actions section for each configuration', async () => { + const actionAccordions = await renderResult.findAllByTestId( + 'hostDetailsPolicyResponseActionsAccordion' + ); + const action = await renderResult.findAllByTestId('policyResponseAction'); + const statusHealth = await renderResult.findAllByTestId('policyResponseStatusHealth'); + const message = await renderResult.findAllByTestId('policyResponseMessage'); + expect(actionAccordions).not.toBeNull(); + expect(action).not.toBeNull(); + expect(statusHealth).not.toBeNull(); + expect(message).not.toBeNull(); + }); + it('should not show any numbered badges if all actions are succesful', () => { + return renderResult.findByTestId('hostDetailsPolicyResponseAttentionBadge').catch(e => { + expect(e).not.toBeNull(); + }); + }); + it('should show a numbered badge if at least one action failed', () => { + reactTestingLibrary.act(() => { + dispatchServerReturnedHostPolicyResponse(HostPolicyResponseActionStatus.failure); + }); + const attentionBadge = renderResult.findByTestId('hostDetailsPolicyResponseAttentionBadge'); + expect(attentionBadge).not.toBeNull(); + }); + it('should show a numbered badge if at least one action has a warning', () => { + reactTestingLibrary.act(() => { + dispatchServerReturnedHostPolicyResponse(HostPolicyResponseActionStatus.warning); + }); + const attentionBadge = renderResult.findByTestId('hostDetailsPolicyResponseAttentionBadge'); + expect(attentionBadge).not.toBeNull(); + }); it('should include the back to details link', async () => { const subHeaderBackLink = await renderResult.findByTestId('flyoutSubHeaderBackButton'); expect(subHeaderBackLink.textContent).toBe('Endpoint Details'); diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx index f7eafff137f519..39529e7c11ab1b 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx +++ b/x-pack/plugins/endpoint/public/applications/endpoint/view/policy/policy_list.tsx @@ -38,7 +38,7 @@ const PolicyLink: React.FC<{ name: string; route: string; href: string }> = ({ const clickHandler = useNavigateByRouterEventHandler(route); return ( // eslint-disable-next-line @elastic/eui/href-or-on-click - + {name} ); @@ -134,6 +134,7 @@ export const PolicyList = React.memo(() => { render(version: string) { return ( ({ - left: `${left}px`, - top: `${top}px`, + left: `${left + processNodeViewXOffset}px`, + top: `${top + processNodeViewYOffset}px`, // Width of symbol viewport scaled to fit - width: `${360 * magFactorX}px`, + width: `${logicalProcessNodeViewWidth * magFactorX}px`, // Height according to symbol viewbox AR - height: `${120 * magFactorX}px`, - // Adjusted to position/scale with camera - transform: `translateX(-${0.172413 * 360 * magFactorX + 10}px) translateY(-${0.73684 * - 120 * - magFactorX}px)`, + height: `${logicalProcessNodeViewHeight * magFactorX}px`, }), - [left, magFactorX, top] + [left, magFactorX, processNodeViewXOffset, processNodeViewYOffset, top] ); /** @@ -202,32 +209,26 @@ export const ProcessEventDot = styled( const dispatch = useResolverDispatch(); - const handleFocus = useCallback( - (focusEvent: React.FocusEvent) => { - dispatch({ - type: 'userFocusedOnResolverNode', - payload: { - nodeId, - }, - }); - }, - [dispatch, nodeId] - ); + const handleFocus = useCallback(() => { + dispatch({ + type: 'userFocusedOnResolverNode', + payload: { + nodeId, + }, + }); + }, [dispatch, nodeId]); - const handleClick = useCallback( - (clickEvent: React.MouseEvent) => { - if (animationTarget.current !== null) { - (animationTarget.current as any).beginElement(); - } - dispatch({ - type: 'userSelectedResolverNode', - payload: { - nodeId, - }, - }); - }, - [animationTarget, dispatch, nodeId] - ); + const handleClick = useCallback(() => { + if (animationTarget.current !== null) { + (animationTarget.current as any).beginElement(); + } + dispatch({ + type: 'userSelectedResolverNode', + payload: { + nodeId, + }, + }); + }, [animationTarget, dispatch, nodeId]); /* eslint-disable jsx-a11y/click-events-have-key-events */ /** @@ -375,13 +376,11 @@ export const ProcessEventDot = styled( ) )` position: absolute; - display: block; text-align: left; font-size: 10px; user-select: none; box-sizing: border-box; border-radius: 10%; - padding: 4px; white-space: nowrap; will-change: left, top, width, height; contain: strict; diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts new file mode 100644 index 00000000000000..995d415ef3c8f2 --- /dev/null +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/log_threshold_executor.test.ts @@ -0,0 +1,311 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createLogThresholdExecutor } from './log_threshold_executor'; +import { + Comparator, + AlertStates, + LogDocumentCountAlertParams, + Criterion, +} from '../../../../common/alerting/logs/types'; +import { AlertExecutorOptions } from '../../../../../alerting/server'; +import { + alertsMock, + AlertInstanceMock, + AlertServicesMock, +} from '../../../../../alerting/server/mocks'; +import { libsMock } from './mocks'; + +interface AlertTestInstance { + instance: AlertInstanceMock; + actionQueue: any[]; + state: any; +} + +/* + * Mocks + */ +const alertInstances = new Map(); + +const services: AlertServicesMock = alertsMock.createAlertServices(); +services.alertInstanceFactory.mockImplementation((instanceId: string) => { + const alertInstance: AlertTestInstance = { + instance: alertsMock.createAlertInstanceFactory(), + actionQueue: [], + state: {}, + }; + alertInstance.instance.replaceState.mockImplementation((newState: any) => { + alertInstance.state = newState; + return alertInstance.instance; + }); + alertInstance.instance.scheduleActions.mockImplementation((id: string, action: any) => { + alertInstance.actionQueue.push({ id, action }); + return alertInstance.instance; + }); + + alertInstances.set(instanceId, alertInstance); + + return alertInstance.instance; +}); + +/* + * Helper functions + */ +function getAlertState(instanceId: string): AlertStates { + const alert = alertInstances.get(instanceId); + if (alert) { + return alert.state.alertState; + } else { + throw new Error('Could not find alert instance `' + instanceId + '`'); + } +} + +/* + * Executor instance (our test subject) + */ +const executor = (createLogThresholdExecutor('test', libsMock) as unknown) as (opts: { + params: LogDocumentCountAlertParams; + services: { callCluster: AlertExecutorOptions['params']['callCluster'] }; +}) => Promise; + +// Wrapper to test +type Comparison = [number, Comparator, number]; +async function callExecutor( + [value, comparator, threshold]: Comparison, + criteria: Criterion[] = [] +) { + services.callCluster.mockImplementationOnce(async (..._) => ({ count: value })); + + return await executor({ + services, + params: { + count: { value: threshold, comparator }, + timeSize: 1, + timeUnit: 'm', + criteria, + }, + }); +} + +describe('Comparators trigger alerts correctly', () => { + it('does not alert when counts do not reach the threshold', async () => { + await callExecutor([0, Comparator.GT, 1]); + expect(getAlertState('test')).toBe(AlertStates.OK); + + await callExecutor([0, Comparator.GT_OR_EQ, 1]); + expect(getAlertState('test')).toBe(AlertStates.OK); + + await callExecutor([1, Comparator.LT, 0]); + expect(getAlertState('test')).toBe(AlertStates.OK); + + await callExecutor([1, Comparator.LT_OR_EQ, 0]); + expect(getAlertState('test')).toBe(AlertStates.OK); + }); + + it('alerts when counts reach the threshold', async () => { + await callExecutor([2, Comparator.GT, 1]); + expect(getAlertState('test')).toBe(AlertStates.ALERT); + + await callExecutor([1, Comparator.GT_OR_EQ, 1]); + expect(getAlertState('test')).toBe(AlertStates.ALERT); + + await callExecutor([1, Comparator.LT, 2]); + expect(getAlertState('test')).toBe(AlertStates.ALERT); + + await callExecutor([2, Comparator.LT_OR_EQ, 2]); + expect(getAlertState('test')).toBe(AlertStates.ALERT); + }); +}); + +describe('Comparators create the correct ES queries', () => { + beforeEach(() => { + services.callCluster.mockReset(); + }); + + it('Works with `Comparator.EQ`', async () => { + await callExecutor( + [2, Comparator.GT, 1], // Not relevant + [{ field: 'foo', comparator: Comparator.EQ, value: 'bar' }] + ); + + const query = services.callCluster.mock.calls[0][1]!; + expect(query.body).toMatchObject({ + query: { + bool: { + must: [{ term: { foo: { value: 'bar' } } }], + }, + }, + }); + }); + + it('works with `Comparator.NOT_EQ`', async () => { + await callExecutor( + [2, Comparator.GT, 1], // Not relevant + [{ field: 'foo', comparator: Comparator.NOT_EQ, value: 'bar' }] + ); + + const query = services.callCluster.mock.calls[0][1]!; + expect(query.body).toMatchObject({ + query: { + bool: { + must_not: [{ term: { foo: { value: 'bar' } } }], + }, + }, + }); + }); + + it('works with `Comparator.MATCH`', async () => { + await callExecutor( + [2, Comparator.GT, 1], // Not relevant + [{ field: 'foo', comparator: Comparator.MATCH, value: 'bar' }] + ); + + const query = services.callCluster.mock.calls[0][1]!; + expect(query.body).toMatchObject({ + query: { + bool: { + must: [{ match: { foo: 'bar' } }], + }, + }, + }); + }); + + it('works with `Comparator.NOT_MATCH`', async () => { + await callExecutor( + [2, Comparator.GT, 1], // Not relevant + [{ field: 'foo', comparator: Comparator.NOT_MATCH, value: 'bar' }] + ); + + const query = services.callCluster.mock.calls[0][1]!; + expect(query.body).toMatchObject({ + query: { + bool: { + must_not: [{ match: { foo: 'bar' } }], + }, + }, + }); + }); + + it('works with `Comparator.MATCH_PHRASE`', async () => { + await callExecutor( + [2, Comparator.GT, 1], // Not relevant + [{ field: 'foo', comparator: Comparator.MATCH_PHRASE, value: 'bar' }] + ); + + const query = services.callCluster.mock.calls[0][1]!; + expect(query.body).toMatchObject({ + query: { + bool: { + must: [{ match_phrase: { foo: 'bar' } }], + }, + }, + }); + }); + + it('works with `Comparator.NOT_MATCH_PHRASE`', async () => { + await callExecutor( + [2, Comparator.GT, 1], // Not relevant + [{ field: 'foo', comparator: Comparator.NOT_MATCH_PHRASE, value: 'bar' }] + ); + + const query = services.callCluster.mock.calls[0][1]!; + expect(query.body).toMatchObject({ + query: { + bool: { + must_not: [{ match_phrase: { foo: 'bar' } }], + }, + }, + }); + }); + + it('works with `Comparator.GT`', async () => { + await callExecutor( + [2, Comparator.GT, 1], // Not relevant + [{ field: 'foo', comparator: Comparator.GT, value: 1 }] + ); + + const query = services.callCluster.mock.calls[0][1]!; + expect(query.body).toMatchObject({ + query: { + bool: { + must: [{ range: { foo: { gt: 1 } } }], + }, + }, + }); + }); + + it('works with `Comparator.GT_OR_EQ`', async () => { + await callExecutor( + [2, Comparator.GT, 1], // Not relevant + [{ field: 'foo', comparator: Comparator.GT_OR_EQ, value: 1 }] + ); + + const query = services.callCluster.mock.calls[0][1]!; + expect(query.body).toMatchObject({ + query: { + bool: { + must: [{ range: { foo: { gte: 1 } } }], + }, + }, + }); + }); + + it('works with `Comparator.LT`', async () => { + await callExecutor( + [2, Comparator.GT, 1], // Not relevant + [{ field: 'foo', comparator: Comparator.LT, value: 1 }] + ); + + const query = services.callCluster.mock.calls[0][1]!; + expect(query.body).toMatchObject({ + query: { + bool: { + must: [{ range: { foo: { lt: 1 } } }], + }, + }, + }); + }); + + it('works with `Comparator.LT_OR_EQ`', async () => { + await callExecutor( + [2, Comparator.GT, 1], // Not relevant + [{ field: 'foo', comparator: Comparator.LT_OR_EQ, value: 1 }] + ); + + const query = services.callCluster.mock.calls[0][1]!; + expect(query.body).toMatchObject({ + query: { + bool: { + must: [{ range: { foo: { lte: 1 } } }], + }, + }, + }); + }); +}); + +describe('Multiple criteria create the right ES query', () => { + beforeEach(() => { + services.callCluster.mockReset(); + }); + it('works', async () => { + await callExecutor( + [2, Comparator.GT, 1], // Not relevant + [ + { field: 'foo', comparator: Comparator.EQ, value: 'bar' }, + { field: 'http.status', comparator: Comparator.LT, value: 400 }, + ] + ); + + const query = services.callCluster.mock.calls[0][1]!; + expect(query.body).toMatchObject({ + query: { + bool: { + must: [{ term: { foo: { value: 'bar' } } }, { range: { 'http.status': { lt: 400 } } }], + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/mocks/index.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/mocks/index.ts new file mode 100644 index 00000000000000..449bc03a922cfb --- /dev/null +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/mocks/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { InfraBackendLibs } from '../../../infra_types'; + +export const libsMock = { + sources: { + getSourceConfiguration: (savedObjectsClient: any, sourceId: string) => { + return Promise.resolve({ + id: sourceId, + configuration: { + logAlias: 'filebeat-*', + fields: { timestamp: '@timestamp' }, + }, + }); + }, + }, +} as InfraBackendLibs; diff --git a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx index f451a6b74e2993..0c27a3e4b44e31 100644 --- a/x-pack/plugins/lens/public/pie_visualization/render_function.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/render_function.tsx @@ -109,7 +109,10 @@ export function PieComponent( return String(d); }, fillLabel: - isDarkMode && shape === 'treemap' && layerIndex < columnGroups.length - 1 + isDarkMode && + shape === 'treemap' && + layerIndex < columnGroups.length - 1 && + categoryDisplay !== 'hide' ? { ...fillLabel, textColor: euiDarkVars.euiTextColor } : fillLabel, shape: { @@ -252,6 +255,7 @@ export function PieComponent( valueFormatter={(d: number) => (hideLabels ? '' : formatters[metricColumn.id].convert(d))} layers={layers} config={config} + topGroove={hideLabels || categoryDisplay === 'hide' ? 0 : undefined} /> diff --git a/x-pack/plugins/lens/public/pie_visualization/settings_widget.tsx b/x-pack/plugins/lens/public/pie_visualization/settings_widget.tsx index 5a02b91efc7497..bb63ceceb2b1b1 100644 --- a/x-pack/plugins/lens/public/pie_visualization/settings_widget.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/settings_widget.tsx @@ -66,6 +66,24 @@ const categoryOptions: Array<{ }, ]; +const categoryOptionsTreemap: Array<{ + value: SharedLayerState['categoryDisplay']; + inputDisplay: string; +}> = [ + { + value: 'default', + inputDisplay: i18n.translate('xpack.lens.pieChart.showTreemapCategoriesLabel', { + defaultMessage: 'Show labels', + }), + }, + { + value: 'hide', + inputDisplay: i18n.translate('xpack.lens.pieChart.categoriesInLegendLabel', { + defaultMessage: 'Hide labels', + }), + }, +]; + const legendOptions: Array<{ value: SharedLayerState['legendDisplay']; label: string; @@ -113,7 +131,7 @@ export function SettingsWidget(props: VisualizationLayerWidgetProps { setState({ ...state, diff --git a/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts b/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts index 7935d53f56845a..20b267caa9074a 100644 --- a/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts +++ b/x-pack/plugins/lens/public/pie_visualization/suggestions.test.ts @@ -508,7 +508,7 @@ describe('suggestions', () => { metric: 'b', numberDisplay: 'hidden', - categoryDisplay: 'inside', + categoryDisplay: 'default', // This is changed legendDisplay: 'show', percentDecimals: 0, nestedLegend: true, diff --git a/x-pack/plugins/lens/public/pie_visualization/suggestions.ts b/x-pack/plugins/lens/public/pie_visualization/suggestions.ts index e363cf922b356e..16c8fda3807db3 100644 --- a/x-pack/plugins/lens/public/pie_visualization/suggestions.ts +++ b/x-pack/plugins/lens/public/pie_visualization/suggestions.ts @@ -115,6 +115,10 @@ export function suggestions({ layerId: table.layerId, groups: groups.map(col => col.columnId), metric: metrics[0].columnId, + categoryDisplay: + state.layers[0].categoryDisplay === 'inside' + ? 'default' + : state.layers[0].categoryDisplay, } : { layerId: table.layerId, diff --git a/x-pack/plugins/lists/server/services/mocks/lists_services_mock_constants.ts b/x-pack/plugins/lists/common/constants.mock.ts similarity index 100% rename from x-pack/plugins/lists/server/services/mocks/lists_services_mock_constants.ts rename to x-pack/plugins/lists/common/constants.mock.ts diff --git a/x-pack/plugins/lists/server/services/mocks/get_call_cluster_mock.ts b/x-pack/plugins/lists/common/get_call_cluster.mock.ts similarity index 86% rename from x-pack/plugins/lists/server/services/mocks/get_call_cluster_mock.ts rename to x-pack/plugins/lists/common/get_call_cluster.mock.ts index 180ecbb7973392..f036605a6a1742 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_call_cluster_mock.ts +++ b/x-pack/plugins/lists/common/get_call_cluster.mock.ts @@ -7,8 +7,8 @@ import { CreateDocumentResponse } from 'elasticsearch'; import { APICaller } from 'kibana/server'; -import { LIST_INDEX } from './lists_services_mock_constants'; -import { getShardMock } from './get_shard_mock'; +import { LIST_INDEX } from './constants.mock'; +import { getShardMock } from './get_shard.mock'; export const getEmptyCreateDocumentResponseMock = (): CreateDocumentResponse => ({ _id: 'elastic-id-123', diff --git a/x-pack/plugins/lists/server/services/mocks/get_shard_mock.ts b/x-pack/plugins/lists/common/get_shard.mock.ts similarity index 100% rename from x-pack/plugins/lists/server/services/mocks/get_shard_mock.ts rename to x-pack/plugins/lists/common/get_shard.mock.ts diff --git a/x-pack/plugins/lists/server/services/mocks/get_index_es_list_item_mock.ts b/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_item_schema.mock.ts similarity index 94% rename from x-pack/plugins/lists/server/services/mocks/get_index_es_list_item_mock.ts rename to x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_item_schema.mock.ts index 574e4afcb36f05..1e27e48aac3103 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_index_es_list_item_mock.ts +++ b/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_item_schema.mock.ts @@ -5,8 +5,7 @@ */ import { IndexEsListItemSchema } from '../../../common/schemas'; - -import { DATE_NOW, LIST_ID, META, TIE_BREAKER, USER, VALUE } from './lists_services_mock_constants'; +import { DATE_NOW, LIST_ID, META, TIE_BREAKER, USER, VALUE } from '../../../common/constants.mock'; export const getIndexESListItemMock = (ip = VALUE): IndexEsListItemSchema => ({ created_at: DATE_NOW, diff --git a/x-pack/plugins/lists/server/services/mocks/get_index_es_list_mock.ts b/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_schema.mock.ts similarity index 93% rename from x-pack/plugins/lists/server/services/mocks/get_index_es_list_mock.ts rename to x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_schema.mock.ts index 4e4d8d9c572e44..a6411ebce84b63 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_index_es_list_mock.ts +++ b/x-pack/plugins/lists/common/schemas/elastic_query/index_es_list_schema.mock.ts @@ -5,7 +5,6 @@ */ import { IndexEsListSchema } from '../../../common/schemas'; - import { DATE_NOW, DESCRIPTION, @@ -14,7 +13,7 @@ import { TIE_BREAKER, TYPE, USER, -} from './lists_services_mock_constants'; +} from '../../../common/constants.mock'; export const getIndexESListMock = (): IndexEsListSchema => ({ created_at: DATE_NOW, diff --git a/x-pack/plugins/lists/server/services/mocks/get_search_list_item_mock.ts b/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_item_schema.mock.ts similarity index 61% rename from x-pack/plugins/lists/server/services/mocks/get_search_list_item_mock.ts rename to x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_item_schema.mock.ts index 9f877c8168cca9..ba69bee9ccf776 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_search_list_item_mock.ts +++ b/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_item_schema.mock.ts @@ -7,10 +7,29 @@ import { SearchResponse } from 'elasticsearch'; import { SearchEsListItemSchema } from '../../../common/schemas'; +import { + DATE_NOW, + LIST_ID, + LIST_INDEX, + LIST_ITEM_ID, + META, + TIE_BREAKER, + USER, + VALUE, +} from '../../../common/constants.mock'; +import { getShardMock } from '../../get_shard.mock'; -import { getShardMock } from './get_shard_mock'; -import { LIST_INDEX, LIST_ITEM_ID } from './lists_services_mock_constants'; -import { getSearchEsListItemMock } from './get_search_es_list_item_mock'; +export const getSearchEsListItemMock = (): SearchEsListItemSchema => ({ + created_at: DATE_NOW, + created_by: USER, + ip: VALUE, + keyword: undefined, + list_id: LIST_ID, + meta: META, + tie_breaker_id: TIE_BREAKER, + updated_at: DATE_NOW, + updated_by: USER, +}); export const getSearchListItemMock = (): SearchResponse => ({ _scroll_id: '123', diff --git a/x-pack/plugins/lists/server/services/mocks/get_search_list_mock.ts b/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_schema.mock.ts similarity index 61% rename from x-pack/plugins/lists/server/services/mocks/get_search_list_mock.ts rename to x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_schema.mock.ts index 9728139eab42ad..ca9c4e16c69396 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_search_list_mock.ts +++ b/x-pack/plugins/lists/common/schemas/elastic_response/search_es_list_schema.mock.ts @@ -7,10 +7,30 @@ import { SearchResponse } from 'elasticsearch'; import { SearchEsListSchema } from '../../../common/schemas'; +import { + DATE_NOW, + DESCRIPTION, + LIST_ID, + LIST_INDEX, + META, + NAME, + TIE_BREAKER, + TYPE, + USER, +} from '../../../common/constants.mock'; +import { getShardMock } from '../../get_shard.mock'; -import { getShardMock } from './get_shard_mock'; -import { LIST_ID, LIST_INDEX } from './lists_services_mock_constants'; -import { getSearchEsListMock } from './get_search_es_list_mock'; +export const getSearchEsListMock = (): SearchEsListSchema => ({ + created_at: DATE_NOW, + created_by: USER, + description: DESCRIPTION, + meta: META, + name: NAME, + tie_breaker_id: TIE_BREAKER, + type: TYPE, + updated_at: DATE_NOW, + updated_by: USER, +}); export const getSearchListMock = (): SearchResponse => ({ _scroll_id: '123', diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.mock.ts new file mode 100644 index 00000000000000..f0d4af520bdbbe --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.mock.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 { LIST_ID, LIST_ITEM_ID, META, VALUE } from '../../constants.mock'; + +import { CreateListItemSchema } from './create_list_item_schema'; + +export const getCreateListItemSchemaMock = (): CreateListItemSchema => ({ + id: LIST_ITEM_ID, + list_id: LIST_ID, + meta: META, + value: VALUE, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.test.ts new file mode 100644 index 00000000000000..8178d49690e399 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.test.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { getCreateListItemSchemaMock } from './create_list_item_schema.mock'; +import { CreateListItemSchema, createListItemSchema } from './create_list_item_schema'; + +describe('create_list_item_schema', () => { + test('it should validate a typical list item request', () => { + const payload = getCreateListItemSchemaMock(); + const decoded = createListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for an id', () => { + const payload = getCreateListItemSchemaMock(); + delete payload.id; + const decoded = createListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for meta', () => { + const payload = getCreateListItemSchemaMock(); + delete payload.meta; + const decoded = createListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: CreateListItemSchema & { extraKey?: string } = getCreateListItemSchemaMock(); + payload.extraKey = 'some new value'; + const decoded = createListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts index 8168e5a9838f20..6cba81e47fbcc2 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_item_schema.ts @@ -9,14 +9,17 @@ import * as t from 'io-ts'; import { idOrUndefined, list_id, metaOrUndefined, value } from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; -export const createListItemSchema = t.exact( - t.type({ - id: idOrUndefined, - list_id, - meta: metaOrUndefined, - value, - }) -); +export const createListItemSchema = t.intersection([ + t.exact( + t.type({ + list_id, + value, + }) + ), + t.exact(t.partial({ id: idOrUndefined, meta: metaOrUndefined })), +]); -export type CreateListItemSchema = t.TypeOf; +export type CreateListItemSchemaPartial = Identity>; +export type CreateListItemSchema = RequiredKeepUndefined>; diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/create_list_schema.mock.ts new file mode 100644 index 00000000000000..7e6d8bb5ad8034 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/create_list_schema.mock.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DESCRIPTION, LIST_ID, META, NAME, TYPE } from '../../constants.mock'; + +import { CreateListSchema } from './create_list_schema'; + +export const getCreateListSchemaMock = (): CreateListSchema => ({ + description: DESCRIPTION, + id: LIST_ID, + meta: META, + name: NAME, + type: TYPE, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/create_list_schema.test.ts index ba791a55d17eb8..c4456bf97865a9 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_schema.test.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_schema.test.ts @@ -9,23 +9,47 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; -import { getListRequest } from './mocks/utils'; -import { createListSchema } from './create_list_schema'; +import { CreateListSchema, createListSchema } from './create_list_schema'; +import { getCreateListSchemaMock } from './create_list_schema.mock'; describe('create_list_schema', () => { - // TODO: Finish the tests for this test('it should validate a typical lists request', () => { - const payload = getListRequest(); + const payload = getCreateListSchemaMock(); const decoded = createListSchema.decode(payload); const checked = exactCheck(payload, decoded); const message = pipe(checked, foldLeftRight); expect(getPaths(left(message.errors))).toEqual([]); - expect(message.schema).toEqual({ - description: 'Description of a list item', - id: 'some-list-id', - name: 'Name of a list item', - type: 'ip', - }); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for an id', () => { + const payload = getCreateListSchemaMock(); + delete payload.id; + const decoded = createListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for meta', () => { + const payload = getCreateListSchemaMock(); + delete payload.meta; + const decoded = createListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: CreateListSchema & { extraKey?: string } = getCreateListSchemaMock(); + payload.extraKey = 'some new value'; + const decoded = createListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); }); }); diff --git a/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts index 353a4ecdafa0ce..7a6e2a707873cb 100644 --- a/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/create_list_schema.ts @@ -4,20 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -/* eslint-disable @typescript-eslint/camelcase */ - import * as t from 'io-ts'; import { description, idOrUndefined, metaOrUndefined, name, type } from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; -export const createListSchema = t.exact( - t.type({ - description, - id: idOrUndefined, - meta: metaOrUndefined, - name, - type, - }) -); +export const createListSchema = t.intersection([ + t.exact( + t.type({ + description, + name, + type, + }) + ), + t.exact(t.partial({ id: idOrUndefined, meta: metaOrUndefined })), +]); -export type CreateListSchema = t.TypeOf; +export type CreateListSchemaPartial = Identity>; +export type CreateListSchema = RequiredKeepUndefined>; diff --git a/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts index f4c1fb5c43eb0b..96f054b304962c 100644 --- a/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_list_item_schema.ts @@ -9,13 +9,16 @@ import * as t from 'io-ts'; import { idOrUndefined, list_idOrUndefined, valueOrUndefined } from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; -export const deleteListItemSchema = t.exact( - t.type({ - id: idOrUndefined, - list_id: list_idOrUndefined, - value: valueOrUndefined, - }) -); +export const deleteListItemSchema = t.intersection([ + t.exact( + t.type({ + value: valueOrUndefined, + }) + ), + t.exact(t.partial({ id: idOrUndefined, list_id: list_idOrUndefined })), +]); -export type DeleteListItemSchema = t.TypeOf; +export type DeleteListItemSchemaPartial = Identity>; +export type DeleteListItemSchema = RequiredKeepUndefined>; diff --git a/x-pack/plugins/lists/common/schemas/request/mocks/utils.ts b/x-pack/plugins/lists/common/schemas/request/delete_list_schema.mock.ts similarity index 50% rename from x-pack/plugins/lists/common/schemas/request/mocks/utils.ts rename to x-pack/plugins/lists/common/schemas/request/delete_list_schema.mock.ts index e5d189db8490be..bc0fb7c479c50f 100644 --- a/x-pack/plugins/lists/common/schemas/request/mocks/utils.ts +++ b/x-pack/plugins/lists/common/schemas/request/delete_list_schema.mock.ts @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CreateListSchema } from '../create_list_schema'; +import { LIST_ID } from '../../constants.mock'; -export const getListRequest = (): CreateListSchema => ({ - description: 'Description of a list item', - id: 'some-list-id', - meta: undefined, - name: 'Name of a list item', - type: 'ip', +import { DeleteListSchema } from './delete_list_schema'; + +export const getDeleteListSchemaMock = (): DeleteListSchema => ({ + id: LIST_ID, }); diff --git a/x-pack/plugins/lists/common/schemas/request/delete_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/delete_list_schema.test.ts new file mode 100644 index 00000000000000..278508305c6f0a --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/delete_list_schema.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { DeleteListSchema, deleteListSchema } from './delete_list_schema'; +import { getDeleteListSchemaMock } from './delete_list_schema.mock'; + +describe('delete_list_schema', () => { + test('it should validate a typical lists request', () => { + const payload = getDeleteListSchemaMock(); + const decoded = deleteListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should NOT accept an undefined for an id', () => { + const payload = getDeleteListSchemaMock(); + delete payload.id; + const decoded = deleteListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['Invalid value "undefined" supplied to "id"']); + expect(message.schema).toEqual({}); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: DeleteListSchema & { extraKey?: string } = getDeleteListSchemaMock(); + payload.extraKey = 'some new value'; + const decoded = deleteListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/request/export_list_item_query_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/export_list_item_query_schema.mock.ts new file mode 100644 index 00000000000000..7914cc86328ede --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/export_list_item_query_schema.mock.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { LIST_ID } from '../../constants.mock'; + +import { ExportListItemQuerySchema } from './export_list_item_query_schema'; + +export const getExportListItemQuerySchemaMock = (): ExportListItemQuerySchema => ({ + list_id: LIST_ID, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/export_list_item_query_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/export_list_item_query_schema.test.ts new file mode 100644 index 00000000000000..1ffe2e2fc4ecc5 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/export_list_item_query_schema.test.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { + ExportListItemQuerySchema, + exportListItemQuerySchema, +} from './export_list_item_query_schema'; +import { getExportListItemQuerySchemaMock } from './export_list_item_query_schema.mock'; + +describe('export_list_item_schema', () => { + test('it should validate a typical lists request', () => { + const payload = getExportListItemQuerySchemaMock(); + const decoded = exportListItemQuerySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should NOT accept an undefined for an id', () => { + const payload = getExportListItemQuerySchemaMock(); + delete payload.list_id; + const decoded = exportListItemQuerySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "list_id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: ExportListItemQuerySchema & { + extraKey?: string; + } = getExportListItemQuerySchemaMock(); + payload.extraKey = 'some new value'; + const decoded = exportListItemQuerySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.mock.ts new file mode 100644 index 00000000000000..6713083e6a49be --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.mock.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 { LIST_ID, TYPE } from '../../constants.mock'; + +import { ImportListItemQuerySchema } from './import_list_item_query_schema'; + +export const getImportListItemQuerySchemaMock = (): ImportListItemQuerySchema => ({ + list_id: LIST_ID, + type: TYPE, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.test.ts new file mode 100644 index 00000000000000..ac007a704b92d2 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.test.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { + ImportListItemQuerySchema, + importListItemQuerySchema, +} from './import_list_item_query_schema'; +import { getImportListItemQuerySchemaMock } from './import_list_item_query_schema.mock'; + +describe('import_list_item_schema', () => { + test('it should validate a typical lists request', () => { + const payload = getImportListItemQuerySchemaMock(); + const decoded = importListItemQuerySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "list_id"', () => { + const payload = getImportListItemQuerySchemaMock(); + delete payload.list_id; + const decoded = importListItemQuerySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "type"', () => { + const payload = getImportListItemQuerySchemaMock(); + delete payload.type; + const decoded = importListItemQuerySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "type" and "list_id', () => { + const payload = getImportListItemQuerySchemaMock(); + delete payload.type; + delete payload.list_id; + const decoded = importListItemQuerySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: ImportListItemQuerySchema & { + extraKey?: string; + } = getImportListItemQuerySchemaMock(); + payload.extraKey = 'some new value'; + const decoded = importListItemQuerySchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts index b8467d141bdd89..c1745dda7afabe 100644 --- a/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/import_list_item_query_schema.ts @@ -9,9 +9,13 @@ import * as t from 'io-ts'; import { list_idOrUndefined, typeOrUndefined } from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; export const importListItemQuerySchema = t.exact( - t.type({ list_id: list_idOrUndefined, type: typeOrUndefined }) + t.partial({ list_id: list_idOrUndefined, type: typeOrUndefined }) ); -export type ImportListItemQuerySchema = t.TypeOf; +export type ImportListItemQuerySchemaPartial = Identity>; +export type ImportListItemQuerySchema = RequiredKeepUndefined< + t.TypeOf +>; diff --git a/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.mock.ts new file mode 100644 index 00000000000000..69e4d2f8293c77 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.mock.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ImportListItemSchema } from './import_list_item_schema'; + +export const getImportListItemSchemaMock = (): ImportListItemSchema => ({ + file: {}, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.test.ts new file mode 100644 index 00000000000000..7f7c6368a1c5e9 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.test.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { ImportListItemSchema, importListItemSchema } from './import_list_item_schema'; +import { getImportListItemSchemaMock } from './import_list_item_schema.mock'; + +describe('import_list_item_schema', () => { + test('it should validate a typical lists request', () => { + const payload = getImportListItemSchemaMock(); + const decoded = importListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should NOT accept an undefined for a file', () => { + const payload = getImportListItemSchemaMock(); + delete payload.file; + const decoded = importListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "file"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: ImportListItemSchema & { + extraKey?: string; + } = getImportListItemSchemaMock(); + payload.extraKey = 'some new value'; + const decoded = importListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.ts index 0cf01db8617f0e..94299c93b29d8d 100644 --- a/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/import_list_item_schema.ts @@ -18,6 +18,8 @@ export const importListItemSchema = t.exact( }) ); +export type ImportListItemSchema = t.TypeOf; + export interface HapiReadableStream extends Readable { hapi: { filename: string; @@ -27,6 +29,6 @@ export interface HapiReadableStream extends Readable { /** * Special interface since we are streaming in a file through a reader */ -export interface ImportListItemSchema { +export interface ImportListItemHapiFileSchema { file: HapiReadableStream; } diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.mock.ts new file mode 100644 index 00000000000000..f5113bd55d44f2 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.mock.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 { LIST_ITEM_ID, META, VALUE } from '../../constants.mock'; + +import { PatchListItemSchema } from './patch_list_item_schema'; + +export const getPathListItemSchemaMock = (): PatchListItemSchema => ({ + id: LIST_ITEM_ID, + meta: META, + value: VALUE, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.test.ts new file mode 100644 index 00000000000000..58c19e8f9cb4f2 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.test.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { getPathListItemSchemaMock } from './patch_list_item_schema.mock'; +import { PatchListItemSchema, patchListItemSchema } from './patch_list_item_schema'; + +describe('patch_list_item_schema', () => { + test('it should validate a typical list item request', () => { + const payload = getPathListItemSchemaMock(); + const decoded = patchListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should NOT accept an undefined for "id"', () => { + const payload = getPathListItemSchemaMock(); + delete payload.id; + const decoded = patchListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "undefined" supplied to "id"']); + expect(message.schema).toEqual({}); + }); + + test('it should accept an undefined for "meta"', () => { + const payload = getPathListItemSchemaMock(); + delete payload.meta; + const decoded = patchListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "value"', () => { + const payload = getPathListItemSchemaMock(); + delete payload.value; + const decoded = patchListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "meta" and "value"', () => { + const payload = getPathListItemSchemaMock(); + delete payload.meta; + delete payload.value; + const decoded = patchListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: PatchListItemSchema & { extraKey?: string } = getPathListItemSchemaMock(); + payload.extraKey = 'some new value'; + const decoded = patchListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts index 3e8198a5109b31..536931f715f3f6 100644 --- a/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_item_schema.ts @@ -9,13 +9,16 @@ import * as t from 'io-ts'; import { id, metaOrUndefined, valueOrUndefined } from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; -export const patchListItemSchema = t.exact( - t.type({ - id, - meta: metaOrUndefined, - value: valueOrUndefined, - }) -); +export const patchListItemSchema = t.intersection([ + t.exact( + t.type({ + id, + }) + ), + t.exact(t.partial({ meta: metaOrUndefined, value: valueOrUndefined })), +]); -export type PatchListItemSchema = t.TypeOf; +export type PatchListItemSchemaPartial = Identity>; +export type PatchListItemSchema = RequiredKeepUndefined>; diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.mock.ts new file mode 100644 index 00000000000000..70e02944a46de9 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.mock.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 { DESCRIPTION, LIST_ITEM_ID, META, NAME } from '../../constants.mock'; + +import { PatchListSchema } from './patch_list_schema'; + +export const getPathListSchemaMock = (): PatchListSchema => ({ + description: DESCRIPTION, + id: LIST_ITEM_ID, + meta: META, + name: NAME, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.test.ts new file mode 100644 index 00000000000000..3ab658014bbfaf --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.test.ts @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { getPathListSchemaMock } from './patch_list_schema.mock'; +import { PatchListSchema, patchListSchema } from './patch_list_schema'; + +describe('patch_list_schema', () => { + test('it should validate a typical list item request', () => { + const payload = getPathListSchemaMock(); + const decoded = patchListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should NOT accept an undefined for "id"', () => { + const payload = getPathListSchemaMock(); + delete payload.id; + const decoded = patchListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "undefined" supplied to "id"']); + expect(message.schema).toEqual({}); + }); + + test('it should accept an undefined for "meta"', () => { + const payload = getPathListSchemaMock(); + delete payload.meta; + const decoded = patchListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "name"', () => { + const payload = getPathListSchemaMock(); + delete payload.name; + const decoded = patchListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "description"', () => { + const payload = getPathListSchemaMock(); + delete payload.description; + const decoded = patchListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "description", "meta", "name', () => { + const payload = getPathListSchemaMock(); + delete payload.description; + delete payload.name; + delete payload.meta; + const decoded = patchListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "description", "meta"', () => { + const payload = getPathListSchemaMock(); + delete payload.description; + delete payload.meta; + const decoded = patchListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "description", "name"', () => { + const payload = getPathListSchemaMock(); + delete payload.description; + delete payload.name; + const decoded = patchListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "meta", "name"', () => { + const payload = getPathListSchemaMock(); + delete payload.meta; + delete payload.name; + const decoded = patchListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: PatchListSchema & { extraKey?: string } = getPathListSchemaMock(); + payload.extraKey = 'some new value'; + const decoded = patchListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts index efcb81fc8be2ac..59d1a66a581a0e 100644 --- a/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/patch_list_schema.ts @@ -9,14 +9,18 @@ import * as t from 'io-ts'; import { descriptionOrUndefined, id, metaOrUndefined, nameOrUndefined } from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; -export const patchListSchema = t.exact( - t.type({ - description: descriptionOrUndefined, - id, - meta: metaOrUndefined, - name: nameOrUndefined, - }) -); +export const patchListSchema = t.intersection([ + t.exact( + t.type({ + id, + }) + ), + t.exact( + t.partial({ description: descriptionOrUndefined, meta: metaOrUndefined, name: nameOrUndefined }) + ), +]); -export type PatchListSchema = t.TypeOf; +export type PatchListSchemaPartial = Identity>; +export type PatchListSchema = RequiredKeepUndefined>>; diff --git a/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.mock.ts new file mode 100644 index 00000000000000..51d5745b0364d5 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.mock.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 { LIST_ID, LIST_ITEM_ID, VALUE } from '../../constants.mock'; + +import { ReadListItemSchema } from './read_list_item_schema'; + +export const getReadListItemSchemaMock = (): ReadListItemSchema => ({ + id: LIST_ITEM_ID, + list_id: LIST_ID, + value: VALUE, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.test.ts new file mode 100644 index 00000000000000..5c71c9820cc1e4 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.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 { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { getReadListItemSchemaMock } from './read_list_item_schema.mock'; +import { ReadListItemSchema, readListItemSchema } from './read_list_item_schema'; + +describe('read_list_item_schema', () => { + test('it should validate a typical list item request', () => { + const payload = getReadListItemSchemaMock(); + const decoded = readListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "id"', () => { + const payload = getReadListItemSchemaMock(); + delete payload.id; + const decoded = readListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "list_id"', () => { + const payload = getReadListItemSchemaMock(); + delete payload.list_id; + const decoded = readListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "value"', () => { + const payload = getReadListItemSchemaMock(); + delete payload.value; + const decoded = readListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "id", "list_id", "value"', () => { + const payload = getReadListItemSchemaMock(); + delete payload.id; + delete payload.value; + delete payload.list_id; + const decoded = readListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "id", "list_id"', () => { + const payload = getReadListItemSchemaMock(); + delete payload.id; + delete payload.list_id; + const decoded = readListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "id", "value"', () => { + const payload = getReadListItemSchemaMock(); + delete payload.id; + delete payload.value; + const decoded = readListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should accept an undefined for "list_id", "value"', () => { + const payload = getReadListItemSchemaMock(); + delete payload.value; + delete payload.list_id; + const decoded = readListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: ReadListItemSchema & { extraKey?: string } = getReadListItemSchemaMock(); + payload.extraKey = 'some new value'; + const decoded = readListItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts index 9ea14a2a21ed8b..b69523b664fd70 100644 --- a/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/read_list_item_schema.ts @@ -9,9 +9,11 @@ import * as t from 'io-ts'; import { idOrUndefined, list_idOrUndefined, valueOrUndefined } from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; export const readListItemSchema = t.exact( - t.type({ id: idOrUndefined, list_id: list_idOrUndefined, value: valueOrUndefined }) + t.partial({ id: idOrUndefined, list_id: list_idOrUndefined, value: valueOrUndefined }) ); -export type ReadListItemSchema = t.TypeOf; +export type ReadListItemSchemaPartial = Identity>; +export type ReadListItemSchema = RequiredKeepUndefined>; diff --git a/x-pack/plugins/lists/common/schemas/request/read_list_schema.mock.ts b/x-pack/plugins/lists/common/schemas/request/read_list_schema.mock.ts new file mode 100644 index 00000000000000..bbe71488f59ded --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/read_list_schema.mock.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { LIST_ID } from '../../constants.mock'; + +import { ReadListSchema } from './read_list_schema'; + +export const getReadListSchemaMock = (): ReadListSchema => ({ + id: LIST_ID, +}); diff --git a/x-pack/plugins/lists/common/schemas/request/read_list_schema.test.ts b/x-pack/plugins/lists/common/schemas/request/read_list_schema.test.ts new file mode 100644 index 00000000000000..a1ba2655dd723b --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/request/read_list_schema.test.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { getReadListSchemaMock } from './read_list_schema.mock'; +import { ReadListSchema, readListSchema } from './read_list_schema'; + +describe('read_list_schema', () => { + test('it should validate a typical list item request', () => { + const payload = getReadListSchemaMock(); + const decoded = readListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should NOT accept an undefined for "id"', () => { + const payload = getReadListSchemaMock(); + delete payload.id; + const decoded = readListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "undefined" supplied to "id"']); + expect(message.schema).toEqual({}); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: ReadListSchema & { extraKey?: string } = getReadListSchemaMock(); + payload.extraKey = 'some new value'; + const decoded = readListSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts index e1f88bae66e0f8..23701ff753bc0d 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_list_item_schema.ts @@ -9,13 +9,17 @@ import * as t from 'io-ts'; import { id, metaOrUndefined, value } from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; -export const updateListItemSchema = t.exact( - t.type({ - id, - meta: metaOrUndefined, - value, - }) -); +export const updateListItemSchema = t.intersection([ + t.exact( + t.type({ + id, + value, + }) + ), + t.exact(t.partial({ meta: metaOrUndefined })), +]); -export type UpdateListItemSchema = t.TypeOf; +export type UpdateListItemSchemaPartial = Identity>; +export type UpdateListItemSchema = RequiredKeepUndefined>; diff --git a/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts b/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts index d51ed60c41b56f..8223a6a34b7716 100644 --- a/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts +++ b/x-pack/plugins/lists/common/schemas/request/update_list_schema.ts @@ -9,14 +9,18 @@ import * as t from 'io-ts'; import { description, id, metaOrUndefined, name } from '../common/schemas'; +import { Identity, RequiredKeepUndefined } from '../../types'; -export const updateListSchema = t.exact( - t.type({ - description, - id, - meta: metaOrUndefined, - name, - }) -); +export const updateListSchema = t.intersection([ + t.exact( + t.type({ + description, + id, + name, + }) + ), + t.exact(t.partial({ meta: metaOrUndefined })), +]); -export type UpdateListSchema = t.TypeOf; +export type UpdateListSchemaPartial = Identity>; +export type UpdateListSchema = RequiredKeepUndefined>; diff --git a/x-pack/plugins/lists/common/schemas/response/acknowledge_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/acknowledge_schema.mock.ts new file mode 100644 index 00000000000000..905b73cabda979 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/response/acknowledge_schema.mock.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AcknowledgeSchema } from './acknowledge_schema'; + +export const getAcknowledgeSchemaResponseMock = (): AcknowledgeSchema => ({ + acknowledged: true, +}); diff --git a/x-pack/plugins/lists/common/schemas/response/acknowledge_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/acknowledge_schema.test.ts new file mode 100644 index 00000000000000..6e7fb158767b50 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/response/acknowledge_schema.test.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { getAcknowledgeSchemaResponseMock } from './acknowledge_schema.mock'; +import { AcknowledgeSchema, acknowledgeSchema } from './acknowledge_schema'; + +describe('acknowledge_schema', () => { + test('it should validate a typical response', () => { + const payload = getAcknowledgeSchemaResponseMock(); + const decoded = acknowledgeSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + test('it should NOT accept an undefined for "ok"', () => { + const payload = getAcknowledgeSchemaResponseMock(); + delete payload.acknowledged; + const decoded = acknowledgeSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "acknowledged"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: AcknowledgeSchema & { extraKey?: string } = getAcknowledgeSchemaResponseMock(); + payload.extraKey = 'some new value'; + const decoded = acknowledgeSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/response/acknowledge_schema.ts b/x-pack/plugins/lists/common/schemas/response/acknowledge_schema.ts index 55aaf587ac06ba..bf74db516e1a93 100644 --- a/x-pack/plugins/lists/common/schemas/response/acknowledge_schema.ts +++ b/x-pack/plugins/lists/common/schemas/response/acknowledge_schema.ts @@ -6,6 +6,6 @@ import * as t from 'io-ts'; -export const acknowledgeSchema = t.type({ acknowledged: t.boolean }); +export const acknowledgeSchema = t.exact(t.type({ acknowledged: t.boolean })); export type AcknowledgeSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/common/schemas/response/list_item_index_exist_schema.mock.ts b/x-pack/plugins/lists/common/schemas/response/list_item_index_exist_schema.mock.ts new file mode 100644 index 00000000000000..2551020e3b5a43 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/response/list_item_index_exist_schema.mock.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 { ListItemIndexExistSchema } from './list_item_index_exist_schema'; + +export const getListItemIndexExistSchemaResponseMock = (): ListItemIndexExistSchema => ({ + list_index: true, + list_item_index: true, +}); diff --git a/x-pack/plugins/lists/common/schemas/response/list_item_index_exist_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/list_item_index_exist_schema.test.ts new file mode 100644 index 00000000000000..9cb130ec0e8ada --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/response/list_item_index_exist_schema.test.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { getListItemIndexExistSchemaResponseMock } from './list_item_index_exist_schema.mock'; +import { ListItemIndexExistSchema, listItemIndexExistSchema } from './list_item_index_exist_schema'; + +describe('list_item_index_exist_schema', () => { + test('it should validate a typical list item request', () => { + const payload = getListItemIndexExistSchemaResponseMock(); + const decoded = listItemIndexExistSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should NOT accept an undefined for "list_index"', () => { + const payload = getListItemIndexExistSchemaResponseMock(); + delete payload.list_index; + const decoded = listItemIndexExistSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "list_index"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "list_item_index"', () => { + const payload = getListItemIndexExistSchemaResponseMock(); + delete payload.list_item_index; + const decoded = listItemIndexExistSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "list_item_index"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: ListItemIndexExistSchema & { + extraKey?: string; + } = getListItemIndexExistSchemaResponseMock(); + payload.extraKey = 'some new value'; + const decoded = listItemIndexExistSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/common/schemas/response/list_item_index_exist_schema.ts b/x-pack/plugins/lists/common/schemas/response/list_item_index_exist_schema.ts index bf2bf21d2c216e..4c7a1fdaf8d4b8 100644 --- a/x-pack/plugins/lists/common/schemas/response/list_item_index_exist_schema.ts +++ b/x-pack/plugins/lists/common/schemas/response/list_item_index_exist_schema.ts @@ -6,9 +6,11 @@ import * as t from 'io-ts'; -export const listItemIndexExistSchema = t.type({ - list_index: t.boolean, - list_item_index: t.boolean, -}); +export const listItemIndexExistSchema = t.exact( + t.type({ + list_index: t.boolean, + list_item_index: t.boolean, + }) +); export type ListItemIndexExistSchema = t.TypeOf; diff --git a/x-pack/plugins/lists/server/services/mocks/get_list_item_response_mock.ts b/x-pack/plugins/lists/common/schemas/response/list_item_schema.mock.ts similarity index 72% rename from x-pack/plugins/lists/server/services/mocks/get_list_item_response_mock.ts rename to x-pack/plugins/lists/common/schemas/response/list_item_schema.mock.ts index 1a30282ddaebac..309aeaa477c667 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_list_item_response_mock.ts +++ b/x-pack/plugins/lists/common/schemas/response/list_item_schema.mock.ts @@ -5,17 +5,25 @@ */ import { ListItemSchema } from '../../../common/schemas'; - -import { DATE_NOW, LIST_ID, LIST_ITEM_ID, USER, VALUE } from './lists_services_mock_constants'; +import { + DATE_NOW, + LIST_ID, + LIST_ITEM_ID, + META, + TIE_BREAKER, + TYPE, + USER, + VALUE, +} from '../../../common/constants.mock'; export const getListItemResponseMock = (): ListItemSchema => ({ created_at: DATE_NOW, created_by: USER, id: LIST_ITEM_ID, list_id: LIST_ID, - meta: {}, - tie_breaker_id: '6a76b69d-80df-4ab2-8c3e-85f466b06a0e', - type: 'ip', + meta: META, + tie_breaker_id: TIE_BREAKER, + type: TYPE, updated_at: DATE_NOW, updated_by: USER, value: VALUE, diff --git a/x-pack/plugins/lists/common/schemas/response/list_item_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/list_item_schema.test.ts new file mode 100644 index 00000000000000..fbffd1d3ef2459 --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/response/list_item_schema.test.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { getListItemResponseMock } from './list_item_schema.mock'; +import { ListItemSchema, listItemSchema } from './list_item_schema'; + +describe('list_item_schema', () => { + test('it should validate a typical list item response', () => { + const payload = getListItemResponseMock(); + const decoded = listItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should NOT accept an undefined for "id"', () => { + const payload = getListItemResponseMock(); + delete payload.id; + const decoded = listItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "undefined" supplied to "id"']); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "list_id"', () => { + const payload = getListItemResponseMock(); + delete payload.list_id; + const decoded = listItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "list_id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should accept an undefined for "meta"', () => { + const payload = getListItemResponseMock(); + delete payload.meta; + const decoded = listItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should NOT accept an undefined for "created_at"', () => { + const payload = getListItemResponseMock(); + delete payload.created_at; + const decoded = listItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "created_at"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "created_by"', () => { + const payload = getListItemResponseMock(); + delete payload.created_by; + const decoded = listItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "created_by"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "tie_breaker_id"', () => { + const payload = getListItemResponseMock(); + delete payload.tie_breaker_id; + const decoded = listItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "tie_breaker_id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "type"', () => { + const payload = getListItemResponseMock(); + delete payload.type; + const decoded = listItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "type"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "updated_at"', () => { + const payload = getListItemResponseMock(); + delete payload.updated_at; + const decoded = listItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "updated_at"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "updated_by"', () => { + const payload = getListItemResponseMock(); + delete payload.updated_by; + const decoded = listItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "updated_by"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "value"', () => { + const payload = getListItemResponseMock(); + delete payload.value; + const decoded = listItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "value"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: ListItemSchema & { extraKey?: string } = getListItemResponseMock(); + payload.extraKey = 'some new value'; + const decoded = listItemSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/server/services/mocks/get_list_response_mock.ts b/x-pack/plugins/lists/common/schemas/response/list_schema.mock.ts similarity index 72% rename from x-pack/plugins/lists/server/services/mocks/get_list_response_mock.ts rename to x-pack/plugins/lists/common/schemas/response/list_schema.mock.ts index ea068d774c4edc..5016252bc564a0 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_list_response_mock.ts +++ b/x-pack/plugins/lists/common/schemas/response/list_schema.mock.ts @@ -5,18 +5,26 @@ */ import { ListSchema } from '../../../common/schemas'; - -import { DATE_NOW, DESCRIPTION, LIST_ID, NAME, USER } from './lists_services_mock_constants'; +import { + DATE_NOW, + DESCRIPTION, + LIST_ID, + META, + NAME, + TIE_BREAKER, + TYPE, + USER, +} from '../../../common/constants.mock'; export const getListResponseMock = (): ListSchema => ({ created_at: DATE_NOW, created_by: USER, description: DESCRIPTION, id: LIST_ID, - meta: {}, + meta: META, name: NAME, - tie_breaker_id: '6a76b69d-80df-4ab2-8c3e-85f466b06a0e', - type: 'ip', + tie_breaker_id: TIE_BREAKER, + type: TYPE, updated_at: DATE_NOW, updated_by: USER, }); diff --git a/x-pack/plugins/lists/common/schemas/response/list_schema.test.ts b/x-pack/plugins/lists/common/schemas/response/list_schema.test.ts new file mode 100644 index 00000000000000..a37207271c06eb --- /dev/null +++ b/x-pack/plugins/lists/common/schemas/response/list_schema.test.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { left } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { exactCheck, foldLeftRight, getPaths } from '../../siem_common_deps'; + +import { getListResponseMock } from './list_schema.mock'; +import { ListSchema, listSchema } from './list_schema'; + +describe('list_schema', () => { + test('it should validate a typical list response', () => { + const payload = getListResponseMock(); + const decoded = listSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should NOT accept an undefined for "id"', () => { + const payload = getListResponseMock(); + delete payload.id; + const decoded = listSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual(['Invalid value "undefined" supplied to "id"']); + expect(message.schema).toEqual({}); + }); + + test('it should accept an undefined for "meta"', () => { + const payload = getListResponseMock(); + delete payload.meta; + const decoded = listSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([]); + expect(message.schema).toEqual(payload); + }); + + test('it should NOT accept an undefined for "created_at"', () => { + const payload = getListResponseMock(); + delete payload.created_at; + const decoded = listSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "created_at"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "created_by"', () => { + const payload = getListResponseMock(); + delete payload.created_by; + const decoded = listSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "created_by"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "tie_breaker_id"', () => { + const payload = getListResponseMock(); + delete payload.tie_breaker_id; + const decoded = listSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "tie_breaker_id"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "type"', () => { + const payload = getListResponseMock(); + delete payload.type; + const decoded = listSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "type"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "updated_at"', () => { + const payload = getListResponseMock(); + delete payload.updated_at; + const decoded = listSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "updated_at"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "updated_by"', () => { + const payload = getListResponseMock(); + delete payload.updated_by; + const decoded = listSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "updated_by"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "name"', () => { + const payload = getListResponseMock(); + delete payload.name; + const decoded = listSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "name"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should NOT accept an undefined for "description"', () => { + const payload = getListResponseMock(); + delete payload.description; + const decoded = listSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + + expect(getPaths(left(message.errors))).toEqual([ + 'Invalid value "undefined" supplied to "description"', + ]); + expect(message.schema).toEqual({}); + }); + + test('it should not allow an extra key to be sent in', () => { + const payload: ListSchema & { extraKey?: string } = getListResponseMock(); + payload.extraKey = 'some new value'; + const decoded = listSchema.decode(payload); + const checked = exactCheck(payload, decoded); + const message = pipe(checked, foldLeftRight); + expect(getPaths(left(message.errors))).toEqual(['invalid keys "extraKey"']); + expect(message.schema).toEqual({}); + }); +}); diff --git a/x-pack/plugins/lists/server/services/mocks/test_readable.ts b/x-pack/plugins/lists/common/test_readable.mock.ts similarity index 100% rename from x-pack/plugins/lists/server/services/mocks/test_readable.ts rename to x-pack/plugins/lists/common/test_readable.mock.ts diff --git a/x-pack/plugins/lists/common/types.ts b/x-pack/plugins/lists/common/types.ts new file mode 100644 index 00000000000000..1539c5ae01ff50 --- /dev/null +++ b/x-pack/plugins/lists/common/types.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/** + * This makes any optional property the same as Required would but also has the + * added benefit of keeping your undefined. + * + * For example: + * type A = RequiredKeepUndefined<{ a?: undefined; b: number }>; + * + * will yield a type of: + * type A = { a: undefined; b: number; } + * + */ +export type RequiredKeepUndefined = { [K in keyof T]-?: [T[K]] } extends infer U + ? U extends Record + ? { [K in keyof U]: U[K][0] } + : never + : never; + +/** + * This is just a helper to cleanup nasty intersections and unions to make them + * readable from io.ts, it's an identity that strips away the uglyness of them. + */ +export type Identity = { + [P in keyof T]: T[P]; +}; diff --git a/x-pack/plugins/lists/server/plugin.ts b/x-pack/plugins/lists/server/plugin.ts index 2498c36967a536..5facf981c098e6 100644 --- a/x-pack/plugins/lists/server/plugin.ts +++ b/x-pack/plugins/lists/server/plugin.ts @@ -5,7 +5,7 @@ */ import { first } from 'rxjs/operators'; -import { Logger, PluginInitializerContext } from 'kibana/server'; +import { Logger, Plugin, PluginInitializerContext } from 'kibana/server'; import { CoreSetup } from 'src/core/server'; import { SecurityPluginSetup } from '../../security/server'; @@ -14,12 +14,19 @@ import { SpacesServiceSetup } from '../../spaces/server'; import { ConfigType } from './config'; import { initRoutes } from './routes/init_routes'; import { ListClient } from './services/lists/client'; -import { ContextProvider, ContextProviderReturn, PluginsSetup } from './types'; +import { + ContextProvider, + ContextProviderReturn, + ListPluginSetup, + ListsPluginStart, + PluginsSetup, +} from './types'; import { createConfig$ } from './create_config'; import { getSpaceId } from './get_space_id'; import { getUser } from './get_user'; -export class ListPlugin { +export class ListPlugin + implements Plugin, ListsPluginStart, PluginsSetup> { private readonly logger: Logger; private spaces: SpacesServiceSetup | undefined | null; private config: ConfigType | undefined | null; @@ -29,7 +36,7 @@ export class ListPlugin { this.logger = this.initializerContext.logger.get(); } - public async setup(core: CoreSetup, plugins: PluginsSetup): Promise { + public async setup(core: CoreSetup, plugins: PluginsSetup): Promise { const config = await createConfig$(this.initializerContext) .pipe(first()) .toPromise(); @@ -44,6 +51,17 @@ export class ListPlugin { core.http.registerRouteHandlerContext('lists', this.createRouteHandlerContext()); const router = core.http.createRouter(); initRoutes(router); + + return { + getListClient: (apiCaller, spaceId, user): ListClient => { + return new ListClient({ + callCluster: apiCaller, + config, + spaceId, + user, + }); + }, + }; } public start(): void { @@ -74,8 +92,6 @@ export class ListPlugin { new ListClient({ callCluster: callAsCurrentUser, config, - request, - security, spaceId, user, }), diff --git a/x-pack/plugins/lists/server/routes/import_list_item_route.ts b/x-pack/plugins/lists/server/routes/import_list_item_route.ts index a3b6a520a4ecfc..36cf9bac373eb8 100644 --- a/x-pack/plugins/lists/server/routes/import_list_item_route.ts +++ b/x-pack/plugins/lists/server/routes/import_list_item_route.ts @@ -14,7 +14,7 @@ import { validate, } from '../siem_server_deps'; import { - ImportListItemSchema, + ImportListItemHapiFileSchema, importListItemQuerySchema, importListItemSchema, listSchema, @@ -33,7 +33,7 @@ export const importListItemRoute = (router: IRouter): void => { }, path: `${LIST_ITEM_URL}/_import`, validate: { - body: buildRouteValidation( + body: buildRouteValidation( importListItemSchema ), query: buildRouteValidation(importListItemQuerySchema), diff --git a/x-pack/plugins/lists/server/services/items/buffer_lines.test.ts b/x-pack/plugins/lists/server/services/items/buffer_lines.test.ts index 48deb3ee86820d..50e690a3185a85 100644 --- a/x-pack/plugins/lists/server/services/items/buffer_lines.test.ts +++ b/x-pack/plugins/lists/server/services/items/buffer_lines.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TestReadable } from '../mocks'; +import { TestReadable } from '../../../common/test_readable.mock'; import { BufferLines } from './buffer_lines'; diff --git a/x-pack/plugins/lists/server/services/mocks/get_create_list_item_options_mock.ts b/x-pack/plugins/lists/server/services/items/create_list_item.mock.ts similarity index 85% rename from x-pack/plugins/lists/server/services/mocks/get_create_list_item_options_mock.ts rename to x-pack/plugins/lists/server/services/items/create_list_item.mock.ts index 17e3ad2f8de083..919aab5831440e 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_create_list_item_options_mock.ts +++ b/x-pack/plugins/lists/server/services/items/create_list_item.mock.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; import { CreateListItemOptions } from '../items'; - -import { getCallClusterMock } from './get_call_cluster_mock'; import { DATE_NOW, LIST_ID, @@ -16,7 +15,7 @@ import { TIE_BREAKER, TYPE, USER, -} from './lists_services_mock_constants'; +} from '../../../common/constants.mock'; export const getCreateListItemOptionsMock = (): CreateListItemOptions => ({ callCluster: getCallClusterMock(), diff --git a/x-pack/plugins/lists/server/services/items/create_list_item.test.ts b/x-pack/plugins/lists/server/services/items/create_list_item.test.ts index abbb2701499557..721d459bd7cc62 100644 --- a/x-pack/plugins/lists/server/services/items/create_list_item.test.ts +++ b/x-pack/plugins/lists/server/services/items/create_list_item.test.ts @@ -4,15 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - LIST_ITEM_ID, - LIST_ITEM_INDEX, - getCreateListItemOptionsMock, - getIndexESListItemMock, - getListItemResponseMock, -} from '../mocks'; +import { getListItemResponseMock } from '../../../common/schemas/response/list_item_schema.mock'; +import { getIndexESListItemMock } from '../../../common/schemas/elastic_query/index_es_list_item_schema.mock'; +import { LIST_ITEM_ID, LIST_ITEM_INDEX } from '../../../common/constants.mock'; import { createListItem } from './create_list_item'; +import { getCreateListItemOptionsMock } from './create_list_item.mock'; describe('crete_list_item', () => { beforeEach(() => { diff --git a/x-pack/plugins/lists/server/services/mocks/get_create_list_item_bulk_options_mock.ts b/x-pack/plugins/lists/server/services/items/create_list_items_bulk.mock.ts similarity index 85% rename from x-pack/plugins/lists/server/services/mocks/get_create_list_item_bulk_options_mock.ts rename to x-pack/plugins/lists/server/services/items/create_list_items_bulk.mock.ts index fcdad66d652518..dd15d6f74a2ab8 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_create_list_item_bulk_options_mock.ts +++ b/x-pack/plugins/lists/server/services/items/create_list_items_bulk.mock.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; import { CreateListItemsBulkOptions } from '../items'; - -import { getCallClusterMock } from './get_call_cluster_mock'; import { DATE_NOW, LIST_ID, @@ -17,7 +16,7 @@ import { USER, VALUE, VALUE_2, -} from './lists_services_mock_constants'; +} from '../../../common/constants.mock'; export const getCreateListItemBulkOptionsMock = (): CreateListItemsBulkOptions => ({ callCluster: getCallClusterMock(), diff --git a/x-pack/plugins/lists/server/services/items/create_list_items_bulk.test.ts b/x-pack/plugins/lists/server/services/items/create_list_items_bulk.test.ts index 94cc57b53b4e24..dbbb257f22d11f 100644 --- a/x-pack/plugins/lists/server/services/items/create_list_items_bulk.test.ts +++ b/x-pack/plugins/lists/server/services/items/create_list_items_bulk.test.ts @@ -4,16 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexEsListItemSchema } from '../../../common/schemas'; -import { - LIST_ITEM_INDEX, - TIE_BREAKERS, - VALUE_2, - getCreateListItemBulkOptionsMock, - getIndexESListItemMock, -} from '../mocks'; +import { getIndexESListItemMock } from '../../../common/schemas/elastic_query/index_es_list_item_schema.mock'; +import { LIST_ITEM_INDEX, TIE_BREAKERS, VALUE_2 } from '../../../common/constants.mock'; import { createListItemsBulk } from './create_list_items_bulk'; +import { getCreateListItemBulkOptionsMock } from './create_list_items_bulk.mock'; describe('crete_list_item_bulk', () => { beforeEach(() => { @@ -27,8 +22,8 @@ describe('crete_list_item_bulk', () => { test('It calls "callCluster" with body, index, and the bulk items', async () => { const options = getCreateListItemBulkOptionsMock(); await createListItemsBulk(options); - const firstRecord: IndexEsListItemSchema = getIndexESListItemMock(); - const secondRecord: IndexEsListItemSchema = getIndexESListItemMock(VALUE_2); + const firstRecord = getIndexESListItemMock(); + const secondRecord = getIndexESListItemMock(VALUE_2); [firstRecord.tie_breaker_id, secondRecord.tie_breaker_id] = TIE_BREAKERS; expect(options.callCluster).toBeCalledWith('bulk', { body: [ diff --git a/x-pack/plugins/lists/server/services/mocks/get_delete_list_item_options_mock.ts b/x-pack/plugins/lists/server/services/items/delete_list_item.mock.ts similarity index 74% rename from x-pack/plugins/lists/server/services/mocks/get_delete_list_item_options_mock.ts rename to x-pack/plugins/lists/server/services/items/delete_list_item.mock.ts index 271c185860b074..b62de4be9d24af 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_delete_list_item_options_mock.ts +++ b/x-pack/plugins/lists/server/services/items/delete_list_item.mock.ts @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; import { DeleteListItemOptions } from '../items'; - -import { getCallClusterMock } from './get_call_cluster_mock'; -import { LIST_ITEM_ID, LIST_ITEM_INDEX } from './lists_services_mock_constants'; +import { LIST_ITEM_ID, LIST_ITEM_INDEX } from '../../../common/constants.mock'; export const getDeleteListItemOptionsMock = (): DeleteListItemOptions => ({ callCluster: getCallClusterMock(), diff --git a/x-pack/plugins/lists/server/services/items/delete_list_item.test.ts b/x-pack/plugins/lists/server/services/items/delete_list_item.test.ts index 00fcefb2c379fc..ea338d9dd37917 100644 --- a/x-pack/plugins/lists/server/services/items/delete_list_item.test.ts +++ b/x-pack/plugins/lists/server/services/items/delete_list_item.test.ts @@ -4,15 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - LIST_ITEM_ID, - LIST_ITEM_INDEX, - getDeleteListItemOptionsMock, - getListItemResponseMock, -} from '../mocks'; +import { getListItemResponseMock } from '../../../common/schemas/response/list_item_schema.mock'; +import { LIST_ITEM_ID, LIST_ITEM_INDEX } from '../../../common/constants.mock'; import { getListItem } from './get_list_item'; import { deleteListItem } from './delete_list_item'; +import { getDeleteListItemOptionsMock } from './delete_list_item.mock'; jest.mock('./get_list_item', () => ({ getListItem: jest.fn(), diff --git a/x-pack/plugins/lists/server/services/mocks/get_delete_list_item_by_value_options_mock.ts b/x-pack/plugins/lists/server/services/items/delete_list_item_by_value.mock.ts similarity index 75% rename from x-pack/plugins/lists/server/services/mocks/get_delete_list_item_by_value_options_mock.ts rename to x-pack/plugins/lists/server/services/items/delete_list_item_by_value.mock.ts index f6859e72d71b35..4aec27031f71bc 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_delete_list_item_by_value_options_mock.ts +++ b/x-pack/plugins/lists/server/services/items/delete_list_item_by_value.mock.ts @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; import { DeleteListItemByValueOptions } from '../items'; - -import { getCallClusterMock } from './get_call_cluster_mock'; -import { LIST_ID, LIST_ITEM_INDEX, TYPE, VALUE } from './lists_services_mock_constants'; +import { LIST_ID, LIST_ITEM_INDEX, TYPE, VALUE } from '../../../common/constants.mock'; export const getDeleteListItemByValueOptionsMock = (): DeleteListItemByValueOptions => ({ callCluster: getCallClusterMock(), diff --git a/x-pack/plugins/lists/server/services/items/delete_list_item_by_value.test.ts b/x-pack/plugins/lists/server/services/items/delete_list_item_by_value.test.ts index c7c80638e4c374..bf1608334ef24b 100644 --- a/x-pack/plugins/lists/server/services/items/delete_list_item_by_value.test.ts +++ b/x-pack/plugins/lists/server/services/items/delete_list_item_by_value.test.ts @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getDeleteListItemByValueOptionsMock, getListItemResponseMock } from '../mocks'; +import { getListItemResponseMock } from '../../../common/schemas/response/list_item_schema.mock'; import { getListItemByValues } from './get_list_item_by_values'; import { deleteListItemByValue } from './delete_list_item_by_value'; +import { getDeleteListItemByValueOptionsMock } from './delete_list_item_by_value.mock'; jest.mock('./get_list_item_by_values', () => ({ getListItemByValues: jest.fn(), diff --git a/x-pack/plugins/lists/server/services/items/get_list_item.test.ts b/x-pack/plugins/lists/server/services/items/get_list_item.test.ts index 31a421c2e31bfe..c39d6cdc00ee1b 100644 --- a/x-pack/plugins/lists/server/services/items/get_list_item.test.ts +++ b/x-pack/plugins/lists/server/services/items/get_list_item.test.ts @@ -4,13 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - LIST_ID, - LIST_INDEX, - getCallClusterMock, - getListItemResponseMock, - getSearchListItemMock, -} from '../mocks'; +import { getSearchListItemMock } from '../../../common/schemas/elastic_response/search_es_list_item_schema.mock'; +import { getListItemResponseMock } from '../../../common/schemas/response/list_item_schema.mock'; +import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; +import { LIST_ID, LIST_INDEX } from '../../../common/constants.mock'; import { getListItem } from './get_list_item'; diff --git a/x-pack/plugins/lists/server/services/mocks/get_list_item_by_value_options_mock.ts b/x-pack/plugins/lists/server/services/items/get_list_item_by_value.mock.ts similarity index 75% rename from x-pack/plugins/lists/server/services/mocks/get_list_item_by_value_options_mock.ts rename to x-pack/plugins/lists/server/services/items/get_list_item_by_value.mock.ts index 96bc22ca7e6f27..bfa6b1c9380736 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_list_item_by_value_options_mock.ts +++ b/x-pack/plugins/lists/server/services/items/get_list_item_by_value.mock.ts @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; import { GetListItemByValueOptions } from '../items'; - -import { getCallClusterMock } from './get_call_cluster_mock'; -import { LIST_ID, LIST_ITEM_INDEX, TYPE, VALUE } from './lists_services_mock_constants'; +import { LIST_ID, LIST_ITEM_INDEX, TYPE, VALUE } from '../../../common/constants.mock'; export const getListItemByValueOptionsMocks = (): GetListItemByValueOptions => ({ callCluster: getCallClusterMock(), diff --git a/x-pack/plugins/lists/server/services/items/get_list_item_by_value.test.ts b/x-pack/plugins/lists/server/services/items/get_list_item_by_value.test.ts index d30b3c795550f1..342984b4bc2ef3 100644 --- a/x-pack/plugins/lists/server/services/items/get_list_item_by_value.test.ts +++ b/x-pack/plugins/lists/server/services/items/get_list_item_by_value.test.ts @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getListItemByValueOptionsMocks, getListItemResponseMock } from '../mocks'; +import { getListItemResponseMock } from '../../../common/schemas/response/list_item_schema.mock'; import { getListItemByValues } from './get_list_item_by_values'; import { getListItemByValue } from './get_list_item_by_value'; +import { getListItemByValueOptionsMocks } from './get_list_item_by_value.mock'; jest.mock('./get_list_item_by_values', () => ({ getListItemByValues: jest.fn(), diff --git a/x-pack/plugins/lists/server/services/mocks/get_list_item_by_values_options_mock.ts b/x-pack/plugins/lists/server/services/items/get_list_item_by_values.mock.ts similarity index 84% rename from x-pack/plugins/lists/server/services/mocks/get_list_item_by_values_options_mock.ts rename to x-pack/plugins/lists/server/services/items/get_list_item_by_values.mock.ts index f21f97dc8d15f2..fd5fa743832700 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_list_item_by_values_options_mock.ts +++ b/x-pack/plugins/lists/server/services/items/get_list_item_by_values.mock.ts @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; import { GetListItemByValuesOptions } from '../items'; - -import { getCallClusterMock } from './get_call_cluster_mock'; -import { LIST_ID, LIST_ITEM_INDEX, TYPE, VALUE, VALUE_2 } from './lists_services_mock_constants'; +import { LIST_ID, LIST_ITEM_INDEX, TYPE, VALUE, VALUE_2 } from '../../../common/constants.mock'; export const getListItemByValuesOptionsMocks = (): GetListItemByValuesOptions => ({ callCluster: getCallClusterMock(), diff --git a/x-pack/plugins/lists/server/services/items/get_list_item_by_values.test.ts b/x-pack/plugins/lists/server/services/items/get_list_item_by_values.test.ts index 7f5fff4dc3147a..5cf8b9e9d6c09c 100644 --- a/x-pack/plugins/lists/server/services/items/get_list_item_by_values.test.ts +++ b/x-pack/plugins/lists/server/services/items/get_list_item_by_values.test.ts @@ -4,15 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getSearchListItemMock } from '../../../common/schemas/elastic_response/search_es_list_item_schema.mock'; +import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; import { + DATE_NOW, LIST_ID, + LIST_ITEM_ID, LIST_ITEM_INDEX, + META, + TIE_BREAKER, TYPE, + USER, VALUE, VALUE_2, - getCallClusterMock, - getSearchListItemMock, -} from '../mocks'; +} from '../../../common/constants.mock'; import { getListItemByValues } from './get_list_item_by_values'; @@ -53,16 +58,16 @@ describe('get_list_item_by_values', () => { expect(listItem).toEqual([ { - created_at: '2020-04-20T15:25:31.830Z', - created_by: 'some user', - id: 'some-list-item-id', - list_id: 'some-list-id', - meta: {}, - tie_breaker_id: '6a76b69d-80df-4ab2-8c3e-85f466b06a0e', - type: 'ip', - updated_at: '2020-04-20T15:25:31.830Z', - updated_by: 'some user', - value: '127.0.0.1', + created_at: DATE_NOW, + created_by: USER, + id: LIST_ITEM_ID, + list_id: LIST_ID, + meta: META, + tie_breaker_id: TIE_BREAKER, + type: TYPE, + updated_at: DATE_NOW, + updated_by: USER, + value: VALUE, }, ]); }); diff --git a/x-pack/plugins/lists/server/services/mocks/get_update_list_item_options_mock.ts b/x-pack/plugins/lists/server/services/items/update_list_item.mock.ts similarity index 83% rename from x-pack/plugins/lists/server/services/mocks/get_update_list_item_options_mock.ts rename to x-pack/plugins/lists/server/services/items/update_list_item.mock.ts index 0555997941baa0..7ee8664b04d6b7 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_update_list_item_options_mock.ts +++ b/x-pack/plugins/lists/server/services/items/update_list_item.mock.ts @@ -3,9 +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 { getCallClusterMock } from '../../../common/get_call_cluster.mock'; import { UpdateListItemOptions } from '../items'; - -import { getCallClusterMock } from './get_call_cluster_mock'; import { DATE_NOW, LIST_ITEM_ID, @@ -13,7 +12,7 @@ import { META, USER, VALUE, -} from './lists_services_mock_constants'; +} from '../../../common/constants.mock'; export const getUpdateListItemOptionsMock = (): UpdateListItemOptions => ({ callCluster: getCallClusterMock(), diff --git a/x-pack/plugins/lists/server/services/items/update_list_item.test.ts b/x-pack/plugins/lists/server/services/items/update_list_item.test.ts index 4ef4110bc0742a..95b99dc87bab61 100644 --- a/x-pack/plugins/lists/server/services/items/update_list_item.test.ts +++ b/x-pack/plugins/lists/server/services/items/update_list_item.test.ts @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getListItemResponseMock, getUpdateListItemOptionsMock } from '../mocks'; +import { getListItemResponseMock } from '../../../common/schemas/response/list_item_schema.mock'; import { updateListItem } from './update_list_item'; import { getListItem } from './get_list_item'; +import { getUpdateListItemOptionsMock } from './update_list_item.mock'; jest.mock('./get_list_item', () => ({ getListItem: jest.fn(), diff --git a/x-pack/plugins/lists/server/services/items/write_lines_to_bulk_list_items.mock.ts b/x-pack/plugins/lists/server/services/items/write_lines_to_bulk_list_items.mock.ts new file mode 100644 index 00000000000000..3d9902e1d43dd0 --- /dev/null +++ b/x-pack/plugins/lists/server/services/items/write_lines_to_bulk_list_items.mock.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 { TestReadable } from '../../../common/test_readable.mock'; +import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; +import { ImportListItemsToStreamOptions, WriteBufferToItemsOptions } from '../items'; +import { LIST_ID, LIST_ITEM_INDEX, META, TYPE, USER } from '../../../common/constants.mock'; + +export const getImportListItemsToStreamOptionsMock = (): ImportListItemsToStreamOptions => ({ + callCluster: getCallClusterMock(), + listId: LIST_ID, + listItemIndex: LIST_ITEM_INDEX, + meta: META, + stream: new TestReadable(), + type: TYPE, + user: USER, +}); + +export const getWriteBufferToItemsOptionsMock = (): WriteBufferToItemsOptions => ({ + buffer: [], + callCluster: getCallClusterMock(), + listId: LIST_ID, + listItemIndex: LIST_ITEM_INDEX, + meta: META, + type: TYPE, + user: USER, +}); diff --git a/x-pack/plugins/lists/server/services/items/write_lines_to_bulk_list_items.test.ts b/x-pack/plugins/lists/server/services/items/write_lines_to_bulk_list_items.test.ts index f064543f1ec937..71db6fa2cf62c9 100644 --- a/x-pack/plugins/lists/server/services/items/write_lines_to_bulk_list_items.test.ts +++ b/x-pack/plugins/lists/server/services/items/write_lines_to_bulk_list_items.test.ts @@ -4,17 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - getImportListItemsToStreamOptionsMock, - getListItemResponseMock, - getWriteBufferToItemsOptionsMock, -} from '../mocks'; +import { getListItemResponseMock } from '../../../common/schemas/response/list_item_schema.mock'; import { LinesResult, importListItemsToStream, writeBufferToItems, } from './write_lines_to_bulk_list_items'; +import { + getImportListItemsToStreamOptionsMock, + getWriteBufferToItemsOptionsMock, +} from './write_lines_to_bulk_list_items.mock'; import { getListItemByValues } from '.'; diff --git a/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.test.ts b/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.test.ts index b08e5fa688b4b0..2f04353e0989b6 100644 --- a/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.test.ts +++ b/x-pack/plugins/lists/server/services/items/write_list_items_to_stream.test.ts @@ -4,16 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getSearchListItemMock } from '../../../common/schemas/elastic_response/search_es_list_item_schema.mock'; +import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; +import { LIST_ID, LIST_ITEM_INDEX } from '../../../common/constants.mock'; + import { - LIST_ID, - LIST_ITEM_INDEX, - getCallClusterMock, getExportListItemsToStreamOptionsMock, getResponseOptionsMock, - getSearchListItemMock, getWriteNextResponseOptions, getWriteResponseHitsToStreamOptionsMock, -} from '../mocks'; +} from './write_list_items_to_streams.mock'; import { exportListItemsToStream, diff --git a/x-pack/plugins/lists/server/services/mocks/get_write_list_items_to_stream_options_mock.ts b/x-pack/plugins/lists/server/services/items/write_list_items_to_streams.mock.ts similarity index 83% rename from x-pack/plugins/lists/server/services/mocks/get_write_list_items_to_stream_options_mock.ts rename to x-pack/plugins/lists/server/services/items/write_list_items_to_streams.mock.ts index c945818a83e8a8..34cdadd1e554f3 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_write_list_items_to_stream_options_mock.ts +++ b/x-pack/plugins/lists/server/services/items/write_list_items_to_streams.mock.ts @@ -6,16 +6,15 @@ import { Stream } from 'stream'; +import { getSearchListItemMock } from '../../../common/schemas/elastic_response/search_es_list_item_schema.mock'; +import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; import { ExportListItemsToStreamOptions, GetResponseOptions, WriteNextResponseOptions, WriteResponseHitsToStreamOptions, } from '../items'; - -import { LIST_ID, LIST_ITEM_INDEX } from './lists_services_mock_constants'; -import { getSearchListItemMock } from './get_search_list_item_mock'; -import { getCallClusterMock } from './get_call_cluster_mock'; +import { LIST_ID, LIST_ITEM_INDEX } from '../../../common/constants.mock'; export const getExportListItemsToStreamOptionsMock = (): ExportListItemsToStreamOptions => ({ callCluster: getCallClusterMock(getSearchListItemMock()), diff --git a/x-pack/plugins/lists/server/services/lists/client_types.ts b/x-pack/plugins/lists/server/services/lists/client_types.ts index 2cc58c02dbfcff..d66575e7a30dba 100644 --- a/x-pack/plugins/lists/server/services/lists/client_types.ts +++ b/x-pack/plugins/lists/server/services/lists/client_types.ts @@ -6,9 +6,8 @@ import { PassThrough, Readable } from 'stream'; -import { APICaller, KibanaRequest } from 'kibana/server'; +import { APICaller } from 'kibana/server'; -import { SecurityPluginSetup } from '../../../../security/server'; import { Description, DescriptionOrUndefined, @@ -24,10 +23,8 @@ import { ConfigType } from '../../config'; export interface ConstructorOptions { callCluster: APICaller; config: ConfigType; - request: KibanaRequest; spaceId: string; user: string; - security: SecurityPluginSetup | undefined | null; } export interface GetListOptions { diff --git a/x-pack/plugins/lists/server/services/mocks/get_create_list_options_mock.ts b/x-pack/plugins/lists/server/services/lists/create_list.mock.ts similarity index 85% rename from x-pack/plugins/lists/server/services/mocks/get_create_list_options_mock.ts rename to x-pack/plugins/lists/server/services/lists/create_list.mock.ts index 0ea6533fc122a8..f0fd023d018ae5 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_create_list_options_mock.ts +++ b/x-pack/plugins/lists/server/services/lists/create_list.mock.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; import { CreateListOptions } from '../lists'; - -import { getCallClusterMock } from './get_call_cluster_mock'; import { DATE_NOW, DESCRIPTION, @@ -17,7 +16,7 @@ import { TIE_BREAKER, TYPE, USER, -} from './lists_services_mock_constants'; +} from '../../../common/constants.mock'; export const getCreateListOptionsMock = (): CreateListOptions => ({ callCluster: getCallClusterMock(), diff --git a/x-pack/plugins/lists/server/services/lists/create_list.test.ts b/x-pack/plugins/lists/server/services/lists/create_list.test.ts index 36284a70fb97df..ef610ece1acc98 100644 --- a/x-pack/plugins/lists/server/services/lists/create_list.test.ts +++ b/x-pack/plugins/lists/server/services/lists/create_list.test.ts @@ -4,15 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - LIST_ID, - LIST_INDEX, - getCreateListOptionsMock, - getIndexESListMock, - getListResponseMock, -} from '../mocks'; +import { getListResponseMock } from '../../../common/schemas/response/list_schema.mock'; +import { getIndexESListMock } from '../../../common/schemas/elastic_query/index_es_list_schema.mock'; +import { LIST_ID, LIST_INDEX } from '../../../common/constants.mock'; import { createList } from './create_list'; +import { getCreateListOptionsMock } from './create_list.mock'; describe('crete_list', () => { beforeEach(() => { diff --git a/x-pack/plugins/lists/server/services/mocks/get_delete_list_options_mock.ts b/x-pack/plugins/lists/server/services/lists/delete_list.mock.ts similarity index 74% rename from x-pack/plugins/lists/server/services/mocks/get_delete_list_options_mock.ts rename to x-pack/plugins/lists/server/services/lists/delete_list.mock.ts index 8ec92dfa4ef775..fd2ab654b55f6b 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_delete_list_options_mock.ts +++ b/x-pack/plugins/lists/server/services/lists/delete_list.mock.ts @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; import { DeleteListOptions } from '../lists'; - -import { getCallClusterMock } from './get_call_cluster_mock'; -import { LIST_ID, LIST_INDEX, LIST_ITEM_INDEX } from './lists_services_mock_constants'; +import { LIST_ID, LIST_INDEX, LIST_ITEM_INDEX } from '../../../common/constants.mock'; export const getDeleteListOptionsMock = (): DeleteListOptions => ({ callCluster: getCallClusterMock(), diff --git a/x-pack/plugins/lists/server/services/lists/delete_list.test.ts b/x-pack/plugins/lists/server/services/lists/delete_list.test.ts index 62b5e7c7aec4a3..b9f1ec4d400be7 100644 --- a/x-pack/plugins/lists/server/services/lists/delete_list.test.ts +++ b/x-pack/plugins/lists/server/services/lists/delete_list.test.ts @@ -4,16 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - LIST_ID, - LIST_INDEX, - LIST_ITEM_INDEX, - getDeleteListOptionsMock, - getListResponseMock, -} from '../mocks'; +import { getListResponseMock } from '../../../common/schemas/response/list_schema.mock'; +import { LIST_ID, LIST_INDEX, LIST_ITEM_INDEX } from '../../../common/constants.mock'; import { getList } from './get_list'; import { deleteList } from './delete_list'; +import { getDeleteListOptionsMock } from './delete_list.mock'; jest.mock('./get_list', () => ({ getList: jest.fn(), diff --git a/x-pack/plugins/lists/server/services/lists/get_list.test.ts b/x-pack/plugins/lists/server/services/lists/get_list.test.ts index c997d5325296a6..94028565732889 100644 --- a/x-pack/plugins/lists/server/services/lists/get_list.test.ts +++ b/x-pack/plugins/lists/server/services/lists/get_list.test.ts @@ -4,13 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - LIST_ID, - LIST_INDEX, - getCallClusterMock, - getListResponseMock, - getSearchListMock, -} from '../mocks'; +import { getSearchListMock } from '../../../common/schemas/elastic_response/search_es_list_schema.mock'; +import { getListResponseMock } from '../../../common/schemas/response/list_schema.mock'; +import { getCallClusterMock } from '../../../common/get_call_cluster.mock'; +import { LIST_ID, LIST_INDEX } from '../../../common/constants.mock'; import { getList } from './get_list'; diff --git a/x-pack/plugins/lists/server/services/mocks/get_update_list_options_mock.ts b/x-pack/plugins/lists/server/services/lists/update_list.mock.ts similarity index 83% rename from x-pack/plugins/lists/server/services/mocks/get_update_list_options_mock.ts rename to x-pack/plugins/lists/server/services/lists/update_list.mock.ts index fe6fc37eaf81e6..ff974b6e7352b5 100644 --- a/x-pack/plugins/lists/server/services/mocks/get_update_list_options_mock.ts +++ b/x-pack/plugins/lists/server/services/lists/update_list.mock.ts @@ -3,9 +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 { getCallClusterMock } from '../../../common/get_call_cluster.mock'; import { UpdateListOptions } from '../lists'; - -import { getCallClusterMock } from './get_call_cluster_mock'; import { DATE_NOW, DESCRIPTION, @@ -14,7 +13,7 @@ import { META, NAME, USER, -} from './lists_services_mock_constants'; +} from '../../../common/constants.mock'; export const getUpdateListOptionsMock = (): UpdateListOptions => ({ callCluster: getCallClusterMock(), diff --git a/x-pack/plugins/lists/server/services/lists/update_list.test.ts b/x-pack/plugins/lists/server/services/lists/update_list.test.ts index 09bf0ee69c981f..1c4fde40a777ad 100644 --- a/x-pack/plugins/lists/server/services/lists/update_list.test.ts +++ b/x-pack/plugins/lists/server/services/lists/update_list.test.ts @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getListResponseMock, getUpdateListOptionsMock } from '../mocks'; +import { getListResponseMock } from '../../../common/schemas/response/list_schema.mock'; import { updateList } from './update_list'; import { getList } from './get_list'; +import { getUpdateListOptionsMock } from './update_list.mock'; jest.mock('./get_list', () => ({ getList: jest.fn(), diff --git a/x-pack/plugins/lists/server/services/mocks/get_import_list_items_to_stream_options_mock.ts b/x-pack/plugins/lists/server/services/mocks/get_import_list_items_to_stream_options_mock.ts deleted file mode 100644 index d7541f3e09e6cd..00000000000000 --- a/x-pack/plugins/lists/server/services/mocks/get_import_list_items_to_stream_options_mock.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { ImportListItemsToStreamOptions } from '../items'; - -import { getCallClusterMock } from './get_call_cluster_mock'; -import { LIST_ID, LIST_ITEM_INDEX, META, TYPE, USER } from './lists_services_mock_constants'; -import { TestReadable } from './test_readable'; - -export const getImportListItemsToStreamOptionsMock = (): ImportListItemsToStreamOptions => ({ - callCluster: getCallClusterMock(), - listId: LIST_ID, - listItemIndex: LIST_ITEM_INDEX, - meta: META, - stream: new TestReadable(), - type: TYPE, - user: USER, -}); diff --git a/x-pack/plugins/lists/server/services/mocks/get_search_es_list_item_mock.ts b/x-pack/plugins/lists/server/services/mocks/get_search_es_list_item_mock.ts deleted file mode 100644 index 5e9fd8995c0eb6..00000000000000 --- a/x-pack/plugins/lists/server/services/mocks/get_search_es_list_item_mock.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { SearchEsListItemSchema } from '../../../common/schemas'; - -import { DATE_NOW, LIST_ID, USER, VALUE } from './lists_services_mock_constants'; - -export const getSearchEsListItemMock = (): SearchEsListItemSchema => ({ - created_at: DATE_NOW, - created_by: USER, - ip: VALUE, - keyword: undefined, - list_id: LIST_ID, - meta: {}, - tie_breaker_id: '6a76b69d-80df-4ab2-8c3e-85f466b06a0e', - updated_at: DATE_NOW, - updated_by: USER, -}); diff --git a/x-pack/plugins/lists/server/services/mocks/get_search_es_list_mock.ts b/x-pack/plugins/lists/server/services/mocks/get_search_es_list_mock.ts deleted file mode 100644 index 6a565437617ba8..00000000000000 --- a/x-pack/plugins/lists/server/services/mocks/get_search_es_list_mock.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { SearchEsListSchema } from '../../../common/schemas'; - -import { DATE_NOW, DESCRIPTION, NAME, USER } from './lists_services_mock_constants'; - -export const getSearchEsListMock = (): SearchEsListSchema => ({ - created_at: DATE_NOW, - created_by: USER, - description: DESCRIPTION, - meta: {}, - name: NAME, - tie_breaker_id: '6a76b69d-80df-4ab2-8c3e-85f466b06a0e', - type: 'ip', - updated_at: DATE_NOW, - updated_by: USER, -}); diff --git a/x-pack/plugins/lists/server/services/mocks/get_write_buffer_to_items_options_mock.ts b/x-pack/plugins/lists/server/services/mocks/get_write_buffer_to_items_options_mock.ts deleted file mode 100644 index d6b7d70c1aa778..00000000000000 --- a/x-pack/plugins/lists/server/services/mocks/get_write_buffer_to_items_options_mock.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { WriteBufferToItemsOptions } from '../items'; - -import { getCallClusterMock } from './get_call_cluster_mock'; -import { LIST_ID, LIST_ITEM_INDEX, META, TYPE, USER } from './lists_services_mock_constants'; - -export const getWriteBufferToItemsOptionsMock = (): WriteBufferToItemsOptions => ({ - buffer: [], - callCluster: getCallClusterMock(), - listId: LIST_ID, - listItemIndex: LIST_ITEM_INDEX, - meta: META, - type: TYPE, - user: USER, -}); diff --git a/x-pack/plugins/lists/server/services/mocks/index.ts b/x-pack/plugins/lists/server/services/mocks/index.ts deleted file mode 100644 index c555ba322fa2bb..00000000000000 --- a/x-pack/plugins/lists/server/services/mocks/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './get_call_cluster_mock'; -export * from './get_delete_list_options_mock'; -export * from './get_create_list_options_mock'; -export * from './get_list_response_mock'; -export * from './get_search_list_mock'; -export * from './get_shard_mock'; -export * from './lists_services_mock_constants'; -export * from './get_update_list_options_mock'; -export * from './get_create_list_item_options_mock'; -export * from './get_list_item_response_mock'; -export * from './get_index_es_list_mock'; -export * from './get_index_es_list_item_mock'; -export * from './get_create_list_item_bulk_options_mock'; -export * from './get_delete_list_item_by_value_options_mock'; -export * from './get_delete_list_item_options_mock'; -export * from './get_list_item_by_values_options_mock'; -export * from './get_search_es_list_mock'; -export * from './get_search_es_list_item_mock'; -export * from './get_list_item_by_value_options_mock'; -export * from './get_update_list_item_options_mock'; -export * from './get_write_buffer_to_items_options_mock'; -export * from './get_import_list_items_to_stream_options_mock'; -export * from './get_write_list_items_to_stream_options_mock'; -export * from './get_search_list_item_mock'; -export * from './test_readable'; diff --git a/x-pack/plugins/lists/server/services/utils/derive_type_from_es_type.test.ts b/x-pack/plugins/lists/server/services/utils/derive_type_from_es_type.test.ts index 3b6f58479a2f22..8240e2965755ef 100644 --- a/x-pack/plugins/lists/server/services/utils/derive_type_from_es_type.test.ts +++ b/x-pack/plugins/lists/server/services/utils/derive_type_from_es_type.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getSearchEsListItemMock } from '../mocks'; +import { getSearchEsListItemMock } from '../../../common/schemas/elastic_response/search_es_list_item_schema.mock'; import { Type } from '../../../common/schemas'; import { deriveTypeFromItem } from './derive_type_from_es_type'; diff --git a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.test.ts b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.test.ts index 3b9864be6df538..8b32f09400719a 100644 --- a/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.test.ts +++ b/x-pack/plugins/lists/server/services/utils/transform_elastic_to_list_item.test.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getSearchListItemMock } from '../../../common/schemas/elastic_response/search_es_list_item_schema.mock'; +import { getListItemResponseMock } from '../../../common/schemas/response/list_item_schema.mock'; import { ListItemArraySchema } from '../../../common/schemas'; -import { getListItemResponseMock, getSearchListItemMock } from '../mocks'; import { transformElasticToListItem } from './transform_elastic_to_list_item'; diff --git a/x-pack/plugins/lists/server/types.ts b/x-pack/plugins/lists/server/types.ts index e0e4495d47c341..d7c3208e556fae 100644 --- a/x-pack/plugins/lists/server/types.ts +++ b/x-pack/plugins/lists/server/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IContextProvider, RequestHandler } from 'kibana/server'; +import { APICaller, IContextProvider, RequestHandler } from 'kibana/server'; import { SecurityPluginSetup } from '../../security/server'; import { SpacesPluginSetup } from '../../spaces/server'; @@ -12,12 +12,21 @@ import { SpacesPluginSetup } from '../../spaces/server'; import { ListClient } from './services/lists/client'; export type ContextProvider = IContextProvider, 'lists'>; - +export type ListsPluginStart = void; export interface PluginsSetup { security: SecurityPluginSetup | undefined | null; spaces: SpacesPluginSetup | undefined | null; } +export type GetListClientType = ( + dataClient: APICaller, + spaceId: string, + user: string +) => ListClient; +export interface ListPluginSetup { + getListClient: GetListClientType; +} + export type ContextProviderReturn = Promise<{ getListClient: () => ListClient }>; declare module 'src/core/server' { interface RequestHandlerContext { diff --git a/x-pack/plugins/logstash/kibana.json b/x-pack/plugins/logstash/kibana.json index 97dbf58865a882..1eb325dcc1610b 100644 --- a/x-pack/plugins/logstash/kibana.json +++ b/x-pack/plugins/logstash/kibana.json @@ -9,6 +9,7 @@ ], "optionalPlugins": [ "home", + "monitoring", "security" ], "server": true, diff --git a/x-pack/plugins/logstash/public/application/index.tsx b/x-pack/plugins/logstash/public/application/index.tsx index 438038d6c885e0..3588e1f6b2417d 100644 --- a/x-pack/plugins/logstash/public/application/index.tsx +++ b/x-pack/plugins/logstash/public/application/index.tsx @@ -31,16 +31,12 @@ import * as Breadcrumbs from './breadcrumbs'; export const renderApp = async ( core: CoreStart, { basePath, element, setBreadcrumbs }: ManagementAppMountParams, + isMonitoringEnabled: boolean, licenseService$: Observable ) => { const logstashLicenseService = await licenseService$.pipe(first()).toPromise(); const clusterService = new ClusterService(core.http); - const monitoringService = new MonitoringService( - core.http, - // When monitoring is migrated this should be fetched from monitoring's plugin contract - core.injectedMetadata.getInjectedVar('monitoringUiEnabled'), - clusterService - ); + const monitoringService = new MonitoringService(core.http, isMonitoringEnabled, clusterService); const pipelinesService = new PipelinesService(core.http, monitoringService); const pipelineService = new PipelineService(core.http, pipelinesService); const upgradeService = new UpgradeService(core.http); diff --git a/x-pack/plugins/logstash/public/plugin.ts b/x-pack/plugins/logstash/public/plugin.ts index 91d1a39d3970cf..7fbed5b3b86029 100644 --- a/x-pack/plugins/logstash/public/plugin.ts +++ b/x-pack/plugins/logstash/public/plugin.ts @@ -49,8 +49,9 @@ export class LogstashPlugin implements Plugin { mount: async params => { const [coreStart] = await core.getStartServices(); const { renderApp } = await import('./application'); + const isMonitoringEnabled = 'monitoring' in plugins; - return renderApp(coreStart, params, logstashLicense$); + return renderApp(coreStart, params, isMonitoringEnabled, logstashLicense$); }, }); diff --git a/x-pack/plugins/logstash/public/services/monitoring/monitoring_service.js b/x-pack/plugins/logstash/public/services/monitoring/monitoring_service.js index d551f4fba61d2e..4db2838cb53549 100755 --- a/x-pack/plugins/logstash/public/services/monitoring/monitoring_service.js +++ b/x-pack/plugins/logstash/public/services/monitoring/monitoring_service.js @@ -9,14 +9,14 @@ import { ROUTES, MONITORING } from '../../../common/constants'; import { PipelineListItem } from '../../models/pipeline_list_item'; export class MonitoringService { - constructor(http, monitoringUiEnabled, clusterService) { + constructor(http, isMonitoringEnabled, clusterService) { this.http = http; - this.monitoringUiEnabled = monitoringUiEnabled; + this._isMonitoringEnabled = isMonitoringEnabled; this.clusterService = clusterService; } isMonitoringEnabled() { - return this.monitoringUiEnabled; + return this._isMonitoringEnabled; } getPipelineList() { @@ -27,6 +27,8 @@ export class MonitoringService { return this.clusterService .loadCluster() .then(cluster => { + // This API call should live within the Monitoring plugin + // https://github.com/elastic/kibana/issues/63931 const url = `${ROUTES.MONITORING_API_ROOT}/v1/clusters/${cluster.uuid}/logstash/pipeline_ids`; const now = moment.utc(); const body = JSON.stringify({ diff --git a/x-pack/plugins/maps/common/descriptor_types/descriptor_types.d.ts b/x-pack/plugins/maps/common/descriptor_types/descriptor_types.d.ts index a9a9fa17c41fc8..722fdd03ebc438 100644 --- a/x-pack/plugins/maps/common/descriptor_types/descriptor_types.d.ts +++ b/x-pack/plugins/maps/common/descriptor_types/descriptor_types.d.ts @@ -28,7 +28,7 @@ export type EMSTMSSourceDescriptor = AbstractSourceDescriptor & { export type EMSFileSourceDescriptor = AbstractSourceDescriptor & { // id: EMS file id - + id: string; tooltipProperties: string[]; }; diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json index b8bad47327f223..077601204e3eeb 100644 --- a/x-pack/plugins/maps/kibana.json +++ b/x-pack/plugins/maps/kibana.json @@ -12,7 +12,8 @@ "uiActions", "navigation", "visualizations", - "embeddable" + "embeddable", + "mapsLegacy" ], "ui": true } diff --git a/x-pack/plugins/maps/public/angular/get_initial_layers.js b/x-pack/plugins/maps/public/angular/get_initial_layers.js index f02ded1704533f..09f66740af3721 100644 --- a/x-pack/plugins/maps/public/angular/get_initial_layers.js +++ b/x-pack/plugins/maps/public/angular/get_initial_layers.js @@ -16,7 +16,7 @@ import { KibanaTilemapSource } from '../layers/sources/kibana_tilemap_source'; import { TileLayer } from '../layers/tile_layer'; import { EMSTMSSource } from '../layers/sources/ems_tms_source'; import { VectorTileLayer } from '../layers/vector_tile_layer'; -import { getInjectedVarFunc } from '../kibana_services'; +import { getIsEmsEnabled } from '../kibana_services'; import { getKibanaTileMap } from '../meta'; export function getInitialLayers(layerListJSON, initialLayers = []) { @@ -32,7 +32,7 @@ export function getInitialLayers(layerListJSON, initialLayers = []) { return [layerDescriptor, ...initialLayers]; } - const isEmsEnabled = getInjectedVarFunc()('isEmsEnabled', true); + const isEmsEnabled = getIsEmsEnabled(); if (isEmsEnabled) { const layerDescriptor = VectorTileLayer.createDescriptor({ sourceDescriptor: EMSTMSSource.createDescriptor({ isAutoSelect: true }), diff --git a/x-pack/plugins/maps/public/angular/get_initial_layers.test.js b/x-pack/plugins/maps/public/angular/get_initial_layers.test.js index 4b5cad8d19260e..867025cd702134 100644 --- a/x-pack/plugins/maps/public/angular/get_initial_layers.test.js +++ b/x-pack/plugins/maps/public/angular/get_initial_layers.test.js @@ -65,6 +65,7 @@ describe('EMS is enabled', () => { require('../meta').getKibanaTileMap = () => { return null; }; + require('../kibana_services').getIsEmsEnabled = () => true; require('../kibana_services').getInjectedVarFunc = () => key => { switch (key) { case 'emsTileLayerId': @@ -73,8 +74,6 @@ describe('EMS is enabled', () => { desaturated: 'road_map_desaturated', dark: 'dark_map', }; - case 'isEmsEnabled': - return true; default: throw new Error(`Unexpected call to getInjectedVarFunc with key ${key}`); } @@ -109,15 +108,7 @@ describe('EMS is not enabled', () => { require('../meta').getKibanaTileMap = () => { return null; }; - - require('../kibana_services').getInjectedVarFunc = () => key => { - switch (key) { - case 'isEmsEnabled': - return false; - default: - throw new Error(`Unexpected call to getInjectedVarFunc with key ${key}`); - } - }; + require('../kibana_services').getIsEmsEnabled = () => false; }); it('Should return empty layer list since there are no configured tile layers', () => { diff --git a/x-pack/plugins/maps/public/components/_index.scss b/x-pack/plugins/maps/public/components/_index.scss index 161b3fefdb8f9e..76e27338bdcd43 100644 --- a/x-pack/plugins/maps/public/components/_index.scss +++ b/x-pack/plugins/maps/public/components/_index.scss @@ -1,3 +1,3 @@ @import 'metric_editors'; @import './geometry_filter'; -@import 'tooltip_selector'; +@import 'tooltip_selector/tooltip_selector'; diff --git a/x-pack/plugins/maps/public/components/tooltip_selector.js b/x-pack/plugins/maps/public/components/tooltip_selector.js deleted file mode 100644 index 953b711cef6c7c..00000000000000 --- a/x-pack/plugins/maps/public/components/tooltip_selector.js +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Component } from 'react'; -import classNames from 'classnames'; -import { - EuiButtonIcon, - EuiDragDropContext, - EuiDraggable, - EuiDroppable, - EuiText, - EuiTextAlign, - EuiSpacer, -} from '@elastic/eui'; -import { AddTooltipFieldPopover } from './add_tooltip_field_popover'; -import { i18n } from '@kbn/i18n'; - -// TODO import reorder from EUI once its exposed as service -// https://github.com/elastic/eui/issues/2372 -const reorder = (list, startIndex, endIndex) => { - const result = Array.from(list); - const [removed] = result.splice(startIndex, 1); - result.splice(endIndex, 0, removed); - - return result; -}; - -const getProps = async field => { - return new Promise(async (resolve, reject) => { - try { - const label = await field.getLabel(); - const type = await field.getDataType(); - resolve({ - label: label, - type: type, - name: field.getName(), - }); - } catch (e) { - reject(e); - } - }); -}; - -export class TooltipSelector extends Component { - state = { - fieldProps: [], - selectedFieldProps: [], - }; - - constructor() { - super(); - this._isMounted = false; - this._previousFields = null; - this._previousSelectedTooltips = null; - } - - componentDidMount() { - this._isMounted = true; - this._loadFieldProps(); - this._loadTooltipFieldProps(); - } - - componentWillUnmount() { - this._isMounted = false; - } - - componentDidUpdate() { - this._loadTooltipFieldProps(); - this._loadFieldProps(); - } - - async _loadTooltipFieldProps() { - if (!this.props.tooltipFields || this.props.tooltipFields === this._previousSelectedTooltips) { - return; - } - - this._previousSelectedTooltips = this.props.tooltipFields; - const selectedProps = this.props.tooltipFields.map(getProps); - const selectedFieldProps = await Promise.all(selectedProps); - if (this._isMounted) { - this.setState({ selectedFieldProps }); - } - } - - async _loadFieldProps() { - if (!this.props.fields || this.props.fields === this._previousFields) { - return; - } - - this._previousFields = this.props.fields; - const props = this.props.fields.map(getProps); - const fieldProps = await Promise.all(props); - if (this._isMounted) { - this.setState({ fieldProps }); - } - } - - _getPropertyLabel = propertyName => { - if (!this.state.fieldProps.length) { - return propertyName; - } - const prop = this.state.fieldProps.find(field => { - return field.name === propertyName; - }); - return prop.label ? prop.label : propertyName; - }; - - _getTooltipProperties() { - return this.props.tooltipFields.map(field => field.getName()); - } - - _onAdd = properties => { - if (!this.props.tooltipFields) { - this.props.onChange([...properties]); - } else { - const existingProperties = this._getTooltipProperties(); - this.props.onChange([...existingProperties, ...properties]); - } - }; - - _removeProperty = index => { - if (!this.props.tooltipFields) { - this.props.onChange([]); - } else { - const tooltipProperties = this._getTooltipProperties(); - tooltipProperties.splice(index, 1); - this.props.onChange(tooltipProperties); - } - }; - - _onDragEnd = ({ source, destination }) => { - // Dragging item out of EuiDroppable results in destination of null - if (!destination) { - return; - } - - this.props.onChange(reorder(this._getTooltipProperties(), source.index, destination.index)); - }; - - _renderProperties() { - if (!this.state.selectedFieldProps.length) { - return null; - } - - return ( - - - {(provided, snapshot) => - this.state.selectedFieldProps.map((field, idx) => ( - - {(provided, state) => ( -
- - {this._getPropertyLabel(field.name)} - -
- - -
-
- )} -
- )) - } -
-
- ); - } - - render() { - return ( -
- {this._renderProperties()} - - - - - - -
- ); - } -} diff --git a/x-pack/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap b/x-pack/plugins/maps/public/components/tooltip_selector/__snapshots__/add_tooltip_field_popover.test.tsx.snap similarity index 96% rename from x-pack/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap rename to x-pack/plugins/maps/public/components/tooltip_selector/__snapshots__/add_tooltip_field_popover.test.tsx.snap index d0cdbe7243abe4..be362c2ae0422e 100644 --- a/x-pack/plugins/maps/public/components/__snapshots__/add_tooltip_field_popover.test.js.snap +++ b/x-pack/plugins/maps/public/components/tooltip_selector/__snapshots__/add_tooltip_field_popover.test.tsx.snap @@ -30,7 +30,7 @@ exports[`Should remove selected fields from selectable 1`] = ` options={ Array [ Object { - "label": "@timestamp", + "label": "@timestamp-label", "prepend": {}, + onAdd: () => {}, }; test('Should render', () => { @@ -39,7 +41,10 @@ test('Should remove selected fields from selectable', () => { const component = shallow( ); diff --git a/x-pack/plugins/maps/public/components/add_tooltip_field_popover.js b/x-pack/plugins/maps/public/components/tooltip_selector/add_tooltip_field_popover.tsx similarity index 79% rename from x-pack/plugins/maps/public/components/add_tooltip_field_popover.js rename to x-pack/plugins/maps/public/components/tooltip_selector/add_tooltip_field_popover.tsx index 984ace4fd87082..782e5e878164ed 100644 --- a/x-pack/plugins/maps/public/components/add_tooltip_field_popover.js +++ b/x-pack/plugins/maps/public/components/tooltip_selector/add_tooltip_field_popover.tsx @@ -3,6 +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. */ +/* eslint-disable @typescript-eslint/consistent-type-definitions */ import React, { Component, Fragment } from 'react'; import { @@ -11,19 +12,26 @@ import { EuiPopoverTitle, EuiButtonEmpty, EuiSelectable, + EuiSelectableOption, EuiButton, EuiSpacer, EuiTextAlign, } 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); +export type FieldProps = { + label: string; + type: string; + name: string; }; -function getOptions(fields, selectedFields) { +function sortByLabel(a: EuiSelectableOption, b: EuiSelectableOption): number { + return a.label.localeCompare(b.label); +} + +function getOptions(fields: FieldProps[], selectedFields: FieldProps[]): EuiSelectableOption[] { if (!fields) { return []; } @@ -43,19 +51,33 @@ function getOptions(fields, selectedFields) { 'type' in field ? ( ) : null, - label: 'label' in field ? field.label : field.name, + label: field.label, }; }) .sort(sortByLabel); } -export class AddTooltipFieldPopover extends Component { - state = { +interface Props { + onAdd: (checkedFieldNames: string[]) => void; + fields: FieldProps[]; + selectedFields: FieldProps[]; +} + +interface State { + isPopoverOpen: boolean; + checkedFields: string[]; + options?: EuiSelectableOption[]; + prevFields?: FieldProps[]; + prevSelectedFields?: FieldProps[]; +} + +export class AddTooltipFieldPopover extends Component { + state: State = { isPopoverOpen: false, checkedFields: [], }; - static getDerivedStateFromProps(nextProps, prevState) { + static getDerivedStateFromProps(nextProps: Props, prevState: State) { if ( nextProps.fields !== prevState.prevFields || nextProps.selectedFields !== prevState.prevSelectedFields @@ -83,13 +105,13 @@ export class AddTooltipFieldPopover extends Component { }); }; - _onSelect = options => { - const checkedFields = options + _onSelect = (options: EuiSelectableOption[]) => { + const checkedFields: string[] = options .filter(option => { return option.checked === 'on'; }) .map(option => { - return option.value; + return option.value as string; }); this.setState({ diff --git a/x-pack/plugins/maps/public/components/tooltip_selector/index.ts b/x-pack/plugins/maps/public/components/tooltip_selector/index.ts new file mode 100644 index 00000000000000..7c5dc3d8a4c007 --- /dev/null +++ b/x-pack/plugins/maps/public/components/tooltip_selector/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { TooltipSelector } from './tooltip_selector'; diff --git a/x-pack/plugins/maps/public/components/tooltip_selector.test.js b/x-pack/plugins/maps/public/components/tooltip_selector/tooltip_selector.test.tsx similarity index 77% rename from x-pack/plugins/maps/public/components/tooltip_selector.test.js rename to x-pack/plugins/maps/public/components/tooltip_selector/tooltip_selector.test.tsx index 1a83f4a98bb6f0..10d3f6af633706 100644 --- a/x-pack/plugins/maps/public/components/tooltip_selector.test.js +++ b/x-pack/plugins/maps/public/components/tooltip_selector/tooltip_selector.test.tsx @@ -8,25 +8,19 @@ import React from 'react'; import { shallow } from 'enzyme'; import { TooltipSelector } from './tooltip_selector'; +import { AbstractField } from '../../layers/fields/field'; +import { FIELD_ORIGIN } from '../../../common/constants'; -class MockField { - constructor({ name, label, type }) { - this._name = name; +class MockField extends AbstractField { + private _label?: string; + constructor({ name, label }: { name: string; label?: string }) { + super({ fieldName: name, origin: FIELD_ORIGIN.SOURCE }); this._label = label; - this._type = type; - } - - getName() { - return this._name; } async getLabel() { return this._label || 'foobar_label'; } - - async getDataType() { - return this._type || 'foobar_type'; - } } const defaultProps = { @@ -36,11 +30,9 @@ const defaultProps = { new MockField({ name: 'iso2', label: 'ISO 3166-1 alpha-2 code', - type: 'string', }), new MockField({ name: 'iso3', - type: 'string', }), ], }; diff --git a/x-pack/plugins/maps/public/components/tooltip_selector/tooltip_selector.tsx b/x-pack/plugins/maps/public/components/tooltip_selector/tooltip_selector.tsx new file mode 100644 index 00000000000000..211276cda904a6 --- /dev/null +++ b/x-pack/plugins/maps/public/components/tooltip_selector/tooltip_selector.tsx @@ -0,0 +1,245 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component, Fragment } from 'react'; +import classNames from 'classnames'; +import { + EuiButtonIcon, + EuiDragDropContext, + EuiDraggable, + EuiDroppable, + EuiText, + EuiTextAlign, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { AddTooltipFieldPopover, FieldProps } from './add_tooltip_field_popover'; +import { IField } from '../../layers/fields/field'; + +// TODO import reorder from EUI once its exposed as service +// https://github.com/elastic/eui/issues/2372 +const reorder = (list: string[], startIndex: number, endIndex: number) => { + const result = Array.from(list); + const [removed] = result.splice(startIndex, 1); + result.splice(endIndex, 0, removed); + + return result; +}; + +async function getFieldProps(field: IField): Promise { + return { + label: await field.getLabel(), + type: await field.getDataType(), + name: field.getName(), + }; +} + +interface Props { + fields: IField[] | null; + onChange: (selectedFieldNames: string[]) => void; + tooltipFields: IField[]; +} + +interface State { + fieldProps: FieldProps[]; + selectedFieldProps: FieldProps[]; +} + +export class TooltipSelector extends Component { + private _isMounted: boolean; + private _previousFields: IField[] | null; + private _previousSelectedTooltips: IField[] | null; + + state = { + fieldProps: [], + selectedFieldProps: [], + }; + + constructor(props: Props) { + super(props); + this._isMounted = false; + this._previousFields = null; + this._previousSelectedTooltips = null; + } + + componentDidMount() { + this._isMounted = true; + this._loadFieldProps(); + this._loadTooltipFieldProps(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + componentDidUpdate() { + this._loadTooltipFieldProps(); + this._loadFieldProps(); + } + + async _loadTooltipFieldProps() { + if (!this.props.tooltipFields || this.props.tooltipFields === this._previousSelectedTooltips) { + return; + } + + this._previousSelectedTooltips = this.props.tooltipFields; + const promises = this.props.tooltipFields.map(getFieldProps); + const selectedFieldProps = await Promise.all(promises); + if (this._isMounted) { + this.setState({ selectedFieldProps }); + } + } + + async _loadFieldProps() { + if (!this.props.fields || this.props.fields === this._previousFields) { + return; + } + + this._previousFields = this.props.fields; + const promises = this.props.fields.map(getFieldProps); + const fieldProps = await Promise.all(promises); + if (this._isMounted) { + this.setState({ fieldProps }); + } + } + + _getPropertyLabel = (propertyName: string) => { + if (!this.state.fieldProps.length) { + return propertyName; + } + const prop: FieldProps | undefined = this.state.fieldProps.find((field: FieldProps) => { + return field.name === propertyName; + }); + return prop ? prop!.label : propertyName; + }; + + _getTooltipFieldNames(): string[] { + return this.props.tooltipFields ? this.props.tooltipFields.map(field => field.getName()) : []; + } + + _onAdd = (properties: string[]) => { + if (!this.props.tooltipFields) { + this.props.onChange([...properties]); + } else { + const existingProperties = this._getTooltipFieldNames(); + this.props.onChange([...existingProperties, ...properties]); + } + }; + + _removeProperty = (index: number) => { + if (!this.props.tooltipFields) { + this.props.onChange([]); + } else { + const tooltipProperties = this._getTooltipFieldNames(); + tooltipProperties.splice(index, 1); + this.props.onChange(tooltipProperties); + } + }; + + _onDragEnd = ({ + source, + destination, + }: { + source: { index: number }; + destination?: { index: number }; + }) => { + // Dragging item out of EuiDroppable results in destination of null + if (!destination) { + return; + } + + this.props.onChange(reorder(this._getTooltipFieldNames(), source.index, destination.index)); + }; + + _renderProperties() { + if (!this.state.selectedFieldProps.length) { + return null; + } + + return ( + + + {(droppableProvided, snapshot) => ( + + {this.state.selectedFieldProps.map((field: FieldProps, idx: number) => ( + + {(provided, state) => ( +
+ + {this._getPropertyLabel(field.name)} + +
+ + +
+
+ )} +
+ ))} +
+ )} +
+
+ ); + } + + render() { + return ( +
+ {this._renderProperties()} + + + + + + +
+ ); + } +} diff --git a/x-pack/plugins/maps/public/kibana_services.d.ts b/x-pack/plugins/maps/public/kibana_services.d.ts index 3d346fe1acdd52..454ba6ededcbd5 100644 --- a/x-pack/plugins/maps/public/kibana_services.d.ts +++ b/x-pack/plugins/maps/public/kibana_services.d.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { IIndexPattern, DataPublicPluginStart } from 'src/plugins/data/public'; +import _ from 'lodash'; export function getLicenseId(): any; export function getInspector(): any; @@ -30,6 +31,15 @@ export function getCore(): any; export function getNavigation(): any; export function getCoreI18n(): any; export function getSearchService(): DataPublicPluginStart['search']; +export function getMapConfig(): any; +export function getIsEmsEnabled(): any; +export function getEmsFontLibraryUrl(): any; +export function getEmsTileLayerId(): any; +export function getEmsFileApiUrl(): any; +export function getEmsTileApiUrl(): any; +export function getEmsLandingPageUrl(): any; +export function getRegionmapLayers(): any; +export function getTilemap(): any; export function setLicenseId(args: unknown): void; export function setInspector(args: unknown): void; @@ -54,3 +64,4 @@ export function setCore(args: unknown): void; export function setNavigation(args: unknown): void; export function setCoreI18n(args: unknown): void; export function setSearchService(args: DataPublicPluginStart['search']): void; +export function setMapConfig(args: unknown): void; diff --git a/x-pack/plugins/maps/public/kibana_services.js b/x-pack/plugins/maps/public/kibana_services.js index 431d7a3b339b7c..2f07c1c5d086de 100644 --- a/x-pack/plugins/maps/public/kibana_services.js +++ b/x-pack/plugins/maps/public/kibana_services.js @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { esFilters, search } from '../../../../src/plugins/data/public'; +import _ from 'lodash'; export const SPATIAL_FILTER_TYPE = esFilters.FILTERS.SPATIAL_FILTER; const { getRequestInspectorStats, getResponseInspectorStats } = search; @@ -139,3 +140,16 @@ export const getCoreI18n = () => coreI18n; let dataSearchService; export const setSearchService = searchService => (dataSearchService = searchService); export const getSearchService = () => dataSearchService; + +let mapConfig; +export const setMapConfig = config => (mapConfig = config); +export const getMapConfig = () => mapConfig; + +export const getIsEmsEnabled = () => getMapConfig().includeElasticMapsService; +export const getEmsFontLibraryUrl = () => getMapConfig().emsFontLibraryUrl; +export const getEmsTileLayerId = () => getMapConfig().emsTileLayerId; +export const getEmsFileApiUrl = () => getMapConfig().emsFileApiUrl; +export const getEmsTileApiUrl = () => getMapConfig().emsTileApiUrl; +export const getEmsLandingPageUrl = () => getMapConfig().emsLandingPageUrl; +export const getRegionmapLayers = () => _.get(getMapConfig(), 'regionmap.layers', []); +export const getTilemap = () => _.get(getMapConfig(), 'tilemap', []); diff --git a/x-pack/plugins/maps/public/layers/fields/ems_file_field.ts b/x-pack/plugins/maps/public/layers/fields/ems_file_field.ts index c14886bc37bfb6..7ed508199e64a0 100644 --- a/x-pack/plugins/maps/public/layers/fields/ems_file_field.ts +++ b/x-pack/plugins/maps/public/layers/fields/ems_file_field.ts @@ -7,7 +7,7 @@ import { FIELD_ORIGIN } from '../../../common/constants'; import { IField, AbstractField } from './field'; import { IVectorSource } from '../sources/vector_source'; -import { IEmsFileSource } from '../sources/ems_file_source/ems_file_source'; +import { IEmsFileSource } from '../sources/ems_file_source'; export class EMSFileField extends AbstractField implements IField { private readonly _source: IEmsFileSource; diff --git a/x-pack/plugins/maps/public/layers/layer.tsx b/x-pack/plugins/maps/public/layers/layer.tsx index dccf413b489f1b..8ecaf4d903251f 100644 --- a/x-pack/plugins/maps/public/layers/layer.tsx +++ b/x-pack/plugins/maps/public/layers/layer.tsx @@ -28,7 +28,7 @@ import { MapFilters, StyleDescriptor, } from '../../common/descriptor_types'; -import { Attribution, ImmutableSourceProperty, ISource } from './sources/source'; +import { Attribution, ImmutableSourceProperty, ISource, SourceEditorArgs } from './sources/source'; import { SyncContext } from '../actions/map_actions'; import { IStyle } from './styles/style'; @@ -58,7 +58,7 @@ export interface ILayer { getStyleForEditing(): IStyle; getCurrentStyle(): IStyle; getImmutableSourceProperties(): Promise; - renderSourceSettingsEditor({ onChange }: { onChange: () => void }): ReactElement | null; + renderSourceSettingsEditor({ onChange }: SourceEditorArgs): ReactElement | null; isLayerLoading(): boolean; hasErrors(): boolean; getErrors(): string; @@ -368,7 +368,7 @@ export class AbstractLayer implements ILayer { return await source.getImmutableProperties(); } - renderSourceSettingsEditor({ onChange }: { onChange: () => void }) { + renderSourceSettingsEditor({ onChange }: SourceEditorArgs) { const source = this.getSourceForEditing(); return source.renderSourceSettingsEditor({ onChange }); } diff --git a/x-pack/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.tsx similarity index 57% rename from x-pack/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.tsx index 47a4879acb58cd..b66918f93f521c 100644 --- a/x-pack/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.tsx @@ -4,31 +4,51 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { EuiComboBox, EuiFormRow } from '@elastic/eui'; +import React, { Component } from 'react'; +import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +// @ts-ignore import { getEMSClient } from '../../../meta'; import { getEmsUnavailableMessage } from '../ems_unavailable_message'; -import { i18n } from '@kbn/i18n'; +import { EMSFileSourceDescriptor } from '../../../../common/descriptor_types'; + +interface Props { + onSourceConfigChange: (sourceConfig: Partial) => void; +} + +interface State { + hasLoadedOptions: boolean; + emsFileOptions: Array>; + selectedOption: EuiComboBoxOptionOption | null; +} + +export class EMSFileCreateSourceEditor extends Component { + private _isMounted: boolean = false; -export class EMSFileCreateSourceEditor extends React.Component { state = { - emsFileOptionsRaw: null, + hasLoadedOptions: false, + emsFileOptions: [], selectedOption: null, }; _loadFileOptions = async () => { + // @ts-ignore const emsClient = getEMSClient(); - const fileLayers = await emsClient.getFileLayers(); + // @ts-ignore + const fileLayers: unknown[] = await emsClient.getFileLayers(); const options = fileLayers.map(fileLayer => { return { - id: fileLayer.getId(), - name: fileLayer.getDisplayName(), + // @ts-ignore + value: fileLayer.getId(), + // @ts-ignore + label: fileLayer.getDisplayName(), }; }); if (this._isMounted) { this.setState({ - emsFileOptionsRaw: options, + hasLoadedOptions: true, + emsFileOptions: options, }); } }; @@ -42,7 +62,7 @@ export class EMSFileCreateSourceEditor extends React.Component { this._loadFileOptions(); } - _onChange = selectedOptions => { + _onChange = (selectedOptions: Array>) => { if (selectedOptions.length === 0) { return; } @@ -54,32 +74,28 @@ export class EMSFileCreateSourceEditor extends React.Component { }; render() { - if (!this.state.emsFileOptionsRaw) { + if (!this.state.hasLoadedOptions) { // TODO display loading message return null; } - const options = this.state.emsFileOptionsRaw.map(({ id, name }) => { - return { label: name, value: id }; - }); - return ( diff --git a/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_boundaries_layer_wizard.tsx b/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_boundaries_layer_wizard.tsx index a6e2e7f42657c3..cc7e04a7313ac9 100644 --- a/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_boundaries_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_boundaries_layer_wizard.tsx @@ -8,24 +8,22 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { VectorLayer } from '../../vector_layer'; import { LayerWizard, RenderWizardArguments } from '../../layer_wizard_registry'; -// @ts-ignore import { EMSFileCreateSourceEditor } from './create_source_editor'; -// @ts-ignore import { EMSFileSource, sourceTitle } from './ems_file_source'; // @ts-ignore -import { isEmsEnabled } from '../../../meta'; +import { getIsEmsEnabled } from '../../../kibana_services'; +import { EMSFileSourceDescriptor } from '../../../../common/descriptor_types'; export const emsBoundariesLayerWizardConfig: LayerWizard = { checkVisibility: () => { - return isEmsEnabled(); + return getIsEmsEnabled(); }, description: i18n.translate('xpack.maps.source.emsFileDescription', { defaultMessage: 'Administrative boundaries from Elastic Maps Service', }), icon: 'emsApp', renderWizard: ({ previewLayer, mapColors }: RenderWizardArguments) => { - const onSourceConfigChange = (sourceConfig: unknown) => { - // @ts-ignore + const onSourceConfigChange = (sourceConfig: Partial) => { const sourceDescriptor = EMSFileSource.createDescriptor(sourceConfig); const layerDescriptor = VectorLayer.createDescriptor({ sourceDescriptor }, mapColors); previewLayer(layerDescriptor); diff --git a/x-pack/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 deleted file mode 100644 index 37c843d4a90609..00000000000000 --- a/x-pack/plugins/maps/public/layers/sources/ems_file_source/ems_file_source.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { AbstractVectorSource, IVectorSource } from '../vector_source'; - -export interface IEmsFileSource extends IVectorSource { - getEMSFileLayer(): Promise; -} - -export class EMSFileSource extends AbstractVectorSource implements IEmsFileSource { - getEMSFileLayer(): Promise; -} diff --git a/x-pack/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.tsx similarity index 90% rename from x-pack/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.tsx index 93c9af98eb17f1..03e3b2a8f49414 100644 --- a/x-pack/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.tsx @@ -9,11 +9,9 @@ import { EMSFileSource } from './ems_file_source'; jest.mock('ui/new_platform'); jest.mock('../../vector_layer', () => {}); -function makeEMSFileSource(tooltipProperties) { - const emsFileSource = new EMSFileSource({ - tooltipProperties: tooltipProperties, - }); - emsFileSource.getEMSFileLayer = () => { +function makeEMSFileSource(tooltipProperties: string[]) { + const emsFileSource = new EMSFileSource({ tooltipProperties }); + emsFileSource.getEMSFileLayer = async () => { return { getFieldsInLanguage() { return [ diff --git a/x-pack/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.tsx similarity index 63% rename from x-pack/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.tsx index 5802a223e48464..5115da510cc5b7 100644 --- a/x-pack/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.tsx @@ -4,40 +4,56 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AbstractVectorSource } from '../vector_source'; +import React, { ReactElement } from 'react'; +import { i18n } from '@kbn/i18n'; +import { Feature } from 'geojson'; +import { Adapters } from 'src/plugins/inspector/public'; +import { Attribution, ImmutableSourceProperty, SourceEditorArgs } from '../source'; +import { AbstractVectorSource, GeoJsonWithMeta, IVectorSource } from '../vector_source'; import { VECTOR_SHAPE_TYPES } from '../vector_feature_types'; -import React from 'react'; import { SOURCE_TYPES, FIELD_ORIGIN } from '../../../../common/constants'; +// @ts-ignore import { getEMSClient } from '../../../meta'; -import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { UpdateSourceEditor } from './update_source_editor'; import { EMSFileField } from '../../fields/ems_file_field'; import { registerSource } from '../source_registry'; +import { IField } from '../../fields/field'; +import { EMSFileSourceDescriptor } from '../../../../common/descriptor_types'; +import { ITooltipProperty } from '../../tooltips/tooltip_property'; + +export interface IEmsFileSource extends IVectorSource { + getEMSFileLayer(): Promise; + createField({ fieldName }: { fieldName: string }): IField; +} export const sourceTitle = i18n.translate('xpack.maps.source.emsFileTitle', { defaultMessage: 'EMS Boundaries', }); -export class EMSFileSource extends AbstractVectorSource { +export class EMSFileSource extends AbstractVectorSource implements IEmsFileSource { static type = SOURCE_TYPES.EMS_FILE; - static createDescriptor({ id, tooltipProperties = [] }) { + static createDescriptor({ id, tooltipProperties = [] }: Partial) { return { type: EMSFileSource.type, - id, + id: id!, tooltipProperties, }; } - constructor(descriptor, inspectorAdapters) { + private readonly _tooltipFields: IField[]; + readonly _descriptor: EMSFileSourceDescriptor; + + constructor(descriptor: Partial, inspectorAdapters?: Adapters) { super(EMSFileSource.createDescriptor(descriptor), inspectorAdapters); + this._descriptor = EMSFileSource.createDescriptor(descriptor); this._tooltipFields = this._descriptor.tooltipProperties.map(propertyKey => this.createField({ fieldName: propertyKey }) ); } - createField({ fieldName }) { + createField({ fieldName }: { fieldName: string }): IField { return new EMSFileField({ fieldName, source: this, @@ -45,7 +61,7 @@ export class EMSFileSource extends AbstractVectorSource { }); } - renderSourceSettingsEditor({ onChange }) { + renderSourceSettingsEditor({ onChange }: SourceEditorArgs): ReactElement | null { return ( { + // @ts-ignore const emsClient = getEMSClient(); + // @ts-ignore const emsFileLayers = await emsClient.getFileLayers(); + // @ts-ignore const emsFileLayer = emsFileLayers.find(fileLayer => fileLayer.getId() === this._descriptor.id); if (!emsFileLayer) { throw new Error( @@ -73,19 +92,23 @@ export class EMSFileSource extends AbstractVectorSource { return emsFileLayer; } - async getGeoJsonWithMeta() { + async getGeoJsonWithMeta(): Promise { const emsFileLayer = await this.getEMSFileLayer(); + // @ts-ignore const featureCollection = await AbstractVectorSource.getGeoJson({ + // @ts-ignore format: emsFileLayer.getDefaultFormatType(), featureCollectionPath: 'data', + // @ts-ignore fetchUrl: emsFileLayer.getDefaultFormatUrl(), }); + // @ts-ignore const emsIdField = emsFileLayer._config.fields.find(field => { return field.type === 'id'; }); - featureCollection.features.forEach((feature, index) => { - feature.id = emsIdField ? feature.properties[emsIdField.id] : index; + featureCollection.features.forEach((feature: Feature, index: number) => { + feature.id = emsIdField ? feature!.properties![emsIdField.id] : index; }); return { @@ -94,10 +117,11 @@ export class EMSFileSource extends AbstractVectorSource { }; } - async getImmutableProperties() { + async getImmutableProperties(): Promise { let emsLink; try { const emsFileLayer = await this.getEMSFileLayer(); + // @ts-ignore emsLink = emsFileLayer.getEMSHotLink(); } catch (error) { // ignore error if EMS layer id could not be found @@ -118,23 +142,27 @@ export class EMSFileSource extends AbstractVectorSource { ]; } - async getDisplayName() { + async getDisplayName(): Promise { try { const emsFileLayer = await this.getEMSFileLayer(); + // @ts-ignore return emsFileLayer.getDisplayName(); } catch (error) { return this._descriptor.id; } } - async getAttributions() { + async getAttributions(): Promise { const emsFileLayer = await this.getEMSFileLayer(); + // @ts-ignore return emsFileLayer.getAttributions(); } async getLeftJoinFields() { const emsFileLayer = await this.getEMSFileLayer(); + // @ts-ignore const fields = emsFileLayer.getFieldsInLanguage(); + // @ts-ignore return fields.map(f => this.createField({ fieldName: f.name })); } @@ -142,16 +170,17 @@ export class EMSFileSource extends AbstractVectorSource { return this._tooltipFields.length > 0; } - async filterAndFormatPropertiesToHtml(properties) { - const tooltipProperties = this._tooltipFields.map(field => { + async filterAndFormatPropertiesToHtml(properties: unknown): Promise { + const promises = this._tooltipFields.map(field => { + // @ts-ignore const value = properties[field.getName()]; return field.createTooltipProperty(value); }); - return Promise.all(tooltipProperties); + return Promise.all(promises); } - async getSupportedShapeTypes() { + async getSupportedShapeTypes(): Promise { return [VECTOR_SHAPE_TYPES.POLYGON]; } } diff --git a/x-pack/plugins/maps/public/layers/sources/ems_file_source/index.js b/x-pack/plugins/maps/public/layers/sources/ems_file_source/index.ts similarity index 82% rename from x-pack/plugins/maps/public/layers/sources/ems_file_source/index.js rename to x-pack/plugins/maps/public/layers/sources/ems_file_source/index.ts index e9bf592c6d2b7f..c1e6e0d76af1f6 100644 --- a/x-pack/plugins/maps/public/layers/sources/ems_file_source/index.js +++ b/x-pack/plugins/maps/public/layers/sources/ems_file_source/index.ts @@ -5,4 +5,4 @@ */ export { emsBoundariesLayerWizardConfig } from './ems_boundaries_layer_wizard'; -export { EMSFileSource } from './ems_file_source'; +export { EMSFileSource, IEmsFileSource } from './ems_file_source'; diff --git a/x-pack/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.tsx similarity index 61% rename from x-pack/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.tsx index b7687fec43272f..806213b667ba43 100644 --- a/x-pack/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.tsx @@ -5,18 +5,28 @@ */ import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { TooltipSelector } from '../../../components/tooltip_selector'; -import { getEMSClient } from '../../../meta'; import { EuiTitle, EuiPanel, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { TooltipSelector } from '../../../components/tooltip_selector'; +// @ts-ignore +import { getEMSClient } from '../../../meta'; +import { IEmsFileSource } from './ems_file_source'; +import { IField } from '../../fields/field'; +import { OnSourceChangeArgs } from '../../../connected_components/layer_panel/view'; -export class UpdateSourceEditor extends Component { - static propTypes = { - onChange: PropTypes.func.isRequired, - tooltipFields: PropTypes.arrayOf(PropTypes.object).isRequired, - source: PropTypes.object, - }; +interface Props { + layerId: string; + onChange: (args: OnSourceChangeArgs) => void; + source: IEmsFileSource; + tooltipFields: IField[]; +} + +interface State { + fields: IField[] | null; +} + +export class UpdateSourceEditor extends Component { + private _isMounted: boolean = false; state = { fields: null, @@ -34,23 +44,29 @@ export class UpdateSourceEditor extends Component { async loadFields() { let fields; try { + // @ts-ignore const emsClient = getEMSClient(); + // @ts-ignore const emsFiles = await emsClient.getFileLayers(); - const emsFile = emsFiles.find(emsFile => emsFile.getId() === this.props.layerId); - const emsFields = emsFile.getFieldsInLanguage(); + // @ts-ignore + const taregetEmsFile = emsFiles.find(emsFile => emsFile.getId() === this.props.layerId); + // @ts-ignore + const emsFields = taregetEmsFile.getFieldsInLanguage(); + // @ts-ignore fields = emsFields.map(field => this.props.source.createField({ fieldName: field.name })); } catch (e) { - //swallow this error. when a matching EMS-config cannot be found, the source already will have thrown errors during the data request. This will propagate to the vector-layer and be displayed in the UX + // When a matching EMS-config cannot be found, the source already will have thrown errors during the data request. + // This will propagate to the vector-layer and be displayed in the UX fields = []; } if (this._isMounted) { - this.setState({ fields: fields }); + this.setState({ fields }); } } - _onTooltipPropertiesSelect = propertyNames => { - this.props.onChange({ propName: 'tooltipProperties', value: propertyNames }); + _onTooltipPropertiesSelect = (selectedFieldNames: string[]) => { + this.props.onChange({ propName: 'tooltipProperties', value: selectedFieldNames }); }; render() { diff --git a/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_base_map_layer_wizard.tsx b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_base_map_layer_wizard.tsx index fc745edbabee87..391ab5691938db 100644 --- a/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_base_map_layer_wizard.tsx +++ b/x-pack/plugins/maps/public/layers/sources/ems_tms_source/ems_base_map_layer_wizard.tsx @@ -12,12 +12,11 @@ import { EMSTMSSource, sourceTitle } from './ems_tms_source'; import { VectorTileLayer } from '../../vector_tile_layer'; // @ts-ignore import { TileServiceSelect } from './tile_service_select'; -// @ts-ignore -import { isEmsEnabled } from '../../../meta'; +import { getIsEmsEnabled } from '../../../kibana_services'; export const emsBaseMapLayerWizardConfig: LayerWizard = { checkVisibility: () => { - return isEmsEnabled(); + return getIsEmsEnabled(); }, description: i18n.translate('xpack.maps.source.emsTileDescription', { defaultMessage: 'Tile map service from Elastic Maps Service', diff --git a/x-pack/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 index 3bed9b2c09570a..b20a3c80e0510b 100644 --- a/x-pack/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 @@ -7,13 +7,12 @@ import _ from 'lodash'; import React from 'react'; import { AbstractTMSSource } from '../tms_source'; - import { getEMSClient } from '../../../meta'; import { UpdateSourceEditor } from './update_source_editor'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { SOURCE_TYPES } from '../../../../common/constants'; -import { getInjectedVarFunc, getUiSettings } from '../../../kibana_services'; +import { getEmsTileLayerId, getUiSettings } from '../../../kibana_services'; import { registerSource } from '../source_registry'; export const sourceTitle = i18n.translate('xpack.maps.source.emsTileTitle', { @@ -125,7 +124,7 @@ export class EMSTMSSource extends AbstractTMSSource { } const isDarkMode = getUiSettings().get('theme:darkMode', false); - const emsTileLayerId = getInjectedVarFunc()('emsTileLayerId'); + const emsTileLayerId = getEmsTileLayerId(); return isDarkMode ? emsTileLayerId.dark : emsTileLayerId.bright; } } diff --git a/x-pack/plugins/maps/public/layers/sources/ems_unavailable_message.js b/x-pack/plugins/maps/public/layers/sources/ems_unavailable_message.ts similarity index 81% rename from x-pack/plugins/maps/public/layers/sources/ems_unavailable_message.js rename to x-pack/plugins/maps/public/layers/sources/ems_unavailable_message.ts index bc50890a0f4a30..748016cf889e25 100644 --- a/x-pack/plugins/maps/public/layers/sources/ems_unavailable_message.js +++ b/x-pack/plugins/maps/public/layers/sources/ems_unavailable_message.ts @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getInjectedVarFunc } from '../../kibana_services'; import { i18n } from '@kbn/i18n'; +// @ts-ignore +import { getIsEmsEnabled } from '../../kibana_services'; -export function getEmsUnavailableMessage() { - const isEmsEnabled = getInjectedVarFunc()('isEmsEnabled', true); +export function getEmsUnavailableMessage(): string { + const isEmsEnabled = getIsEmsEnabled(); if (isEmsEnabled) { return i18n.translate('xpack.maps.source.ems.noAccessDescription', { defaultMessage: diff --git a/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts b/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts index 58e6e39aaa1f96..5f6061b38678c0 100644 --- a/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts +++ b/x-pack/plugins/maps/public/layers/sources/mvt_single_layer_vector_source/mvt_single_layer_vector_source.ts @@ -20,6 +20,7 @@ import { VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; import { MVTSingleLayerVectorSourceConfig } from './mvt_single_layer_vector_source_editor'; +import { ITooltipProperty } from '../../tooltips/tooltip_property'; export const sourceTitle = i18n.translate( 'xpack.maps.source.MVTSingleLayerVectorSource.sourceTitle', @@ -152,6 +153,10 @@ export class MVTSingleLayerVectorSource extends AbstractSource getApplyGlobalQuery(): boolean { return false; } + + async filterAndFormatPropertiesToHtml(properties: unknown): Promise { + return []; + } } registerSource({ diff --git a/x-pack/plugins/maps/public/layers/sources/source.ts b/x-pack/plugins/maps/public/layers/sources/source.ts index af934d7464f618..f53cf689fbfe54 100644 --- a/x-pack/plugins/maps/public/layers/sources/source.ts +++ b/x-pack/plugins/maps/public/layers/sources/source.ts @@ -16,10 +16,16 @@ import { copyPersistentState } from '../../reducers/util'; import { SourceDescriptor } from '../../../common/descriptor_types'; import { IField } from '../fields/field'; import { MAX_ZOOM, MIN_ZOOM } from '../../../common/constants'; +import { OnSourceChangeArgs } from '../../connected_components/layer_panel/view'; + +export type SourceEditorArgs = { + onChange: (args: OnSourceChangeArgs) => void; +}; export type ImmutableSourceProperty = { label: string; value: string; + link?: string; }; export type Attribution = { @@ -48,7 +54,7 @@ export interface ISource { getImmutableProperties(): Promise; getAttributions(): Promise; isESSource(): boolean; - renderSourceSettingsEditor({ onChange }: { onChange: () => void }): ReactElement | null; + renderSourceSettingsEditor({ onChange }: SourceEditorArgs): ReactElement | null; supportsFitToBounds(): Promise; isJoinable(): boolean; cloneDescriptor(): SourceDescriptor; @@ -124,7 +130,7 @@ export class AbstractSource implements ISource { return []; } - renderSourceSettingsEditor() { + renderSourceSettingsEditor({ onChange }: SourceEditorArgs): ReactElement | null { return null; } diff --git a/x-pack/plugins/maps/public/layers/sources/vector_source/vector_source.d.ts b/x-pack/plugins/maps/public/layers/sources/vector_source/vector_source.d.ts index 804915dd73052a..2dd6bcd8581379 100644 --- a/x-pack/plugins/maps/public/layers/sources/vector_source/vector_source.d.ts +++ b/x-pack/plugins/maps/public/layers/sources/vector_source/vector_source.d.ts @@ -15,6 +15,7 @@ import { VectorSourceSyncMeta, } from '../../../../common/descriptor_types'; import { VECTOR_SHAPE_TYPES } from '../vector_feature_types'; +import { ITooltipProperty } from '../../tooltips/tooltip_property'; export type GeoJsonFetchMeta = ESSearchSourceResponseMeta; @@ -24,6 +25,7 @@ export type GeoJsonWithMeta = { }; export interface IVectorSource extends ISource { + filterAndFormatPropertiesToHtml(properties: unknown): Promise; getBoundsForFilters(searchFilters: VectorSourceRequestMeta): MapExtent; getGeoJsonWithMeta( layerName: 'string', @@ -39,6 +41,7 @@ export interface IVectorSource extends ISource { } export class AbstractVectorSource extends AbstractSource implements IVectorSource { + filterAndFormatPropertiesToHtml(properties: unknown): Promise; getBoundsForFilters(searchFilters: VectorSourceRequestMeta): MapExtent; getGeoJsonWithMeta( layerName: 'string', diff --git a/x-pack/plugins/maps/public/meta.js b/x-pack/plugins/maps/public/meta.js index c3245e8e98db2e..77183e334cb116 100644 --- a/x-pack/plugins/maps/public/meta.js +++ b/x-pack/plugins/maps/public/meta.js @@ -13,17 +13,27 @@ import { } from '../common/constants'; import { i18n } from '@kbn/i18n'; import { EMSClient } from '@elastic/ems-client'; -import { getInjectedVarFunc, getLicenseId } from './kibana_services'; +import { + getInjectedVarFunc, + getLicenseId, + getIsEmsEnabled, + getRegionmapLayers, + getTilemap, + getEmsFileApiUrl, + getEmsTileApiUrl, + getEmsLandingPageUrl, + getEmsFontLibraryUrl, +} from './kibana_services'; import fetch from 'node-fetch'; const GIS_API_RELATIVE = `../${GIS_API_PATH}`; export function getKibanaRegionList() { - return getInjectedVarFunc()('regionmapLayers'); + return getRegionmapLayers(); } export function getKibanaTileMap() { - return getInjectedVarFunc()('tilemap'); + return getTilemap(); } function relativeToAbsolute(url) { @@ -36,15 +46,12 @@ function fetchFunction(...args) { return fetch(...args); } -export function isEmsEnabled() { - return getInjectedVarFunc()('isEmsEnabled', true); -} - let emsClient = null; let latestLicenseId = null; export function getEMSClient() { if (!emsClient) { - if (isEmsEnabled()) { + const isEmsEnabled = getIsEmsEnabled(); + if (isEmsEnabled) { const proxyElasticMapsServiceInMaps = getInjectedVarFunc()( 'proxyElasticMapsServiceInMaps', false @@ -52,10 +59,10 @@ export function getEMSClient() { const proxyPath = ''; const tileApiUrl = proxyElasticMapsServiceInMaps ? relativeToAbsolute(`${GIS_API_RELATIVE}/${EMS_TILES_CATALOGUE_PATH}`) - : getInjectedVarFunc()('emsTileApiUrl'); + : getEmsTileApiUrl(); const fileApiUrl = proxyElasticMapsServiceInMaps ? relativeToAbsolute(`${GIS_API_RELATIVE}/${EMS_FILES_CATALOGUE_PATH}`) - : getInjectedVarFunc()('emsFileApiUrl'); + : getEmsFileApiUrl(); emsClient = new EMSClient({ language: i18n.getLocale(), @@ -63,7 +70,7 @@ export function getEMSClient() { appName: EMS_APP_NAME, tileApiUrl, fileApiUrl, - landingPageUrl: getInjectedVarFunc()('emsLandingPageUrl'), + landingPageUrl: getEmsLandingPageUrl(), fetchFunction: fetchFunction, //import this from client-side, so the right instance is returned (bootstrapped from common/* would not work proxyPath, }); @@ -89,13 +96,13 @@ export function getEMSClient() { } export function getGlyphUrl() { - if (!isEmsEnabled()) { + if (!getIsEmsEnabled()) { return ''; } return getInjectedVarFunc()('proxyElasticMapsServiceInMaps', false) ? relativeToAbsolute(`../${GIS_API_PATH}/${EMS_TILES_CATALOGUE_PATH}/${EMS_GLYPHS_PATH}`) + `/{fontstack}/{range}` - : getInjectedVarFunc()('emsFontLibraryUrl', true); + : getEmsFontLibraryUrl(); } export function isRetina() { diff --git a/x-pack/plugins/maps/public/meta.test.js b/x-pack/plugins/maps/public/meta.test.js index d83f2adb35ef7f..c6cc9b53b93019 100644 --- a/x-pack/plugins/maps/public/meta.test.js +++ b/x-pack/plugins/maps/public/meta.test.js @@ -25,6 +25,11 @@ describe('default use without proxy', () => { require('./kibana_services').getLicenseId = () => { return 'foobarlicenseid'; }; + require('./kibana_services').getIsEmsEnabled = () => true; + require('./kibana_services').getEmsTileLayerId = () => '123'; + require('./kibana_services').getEmsFileApiUrl = () => 'https://file-api'; + require('./kibana_services').getEmsTileApiUrl = () => 'https://tile-api'; + require('./kibana_services').getEmsLandingPageUrl = () => 'http://test.com'; }); it('should construct EMSClient with absolute file and tile API urls', async () => { diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index 21bff95731580f..8fe16c0d99d76f 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -32,6 +32,7 @@ import { setUiSettings, setVisualizations, setSearchService, + setMapConfig, } from './kibana_services'; import { featureCatalogueEntry } from './feature_catalogue_entry'; // @ts-ignore @@ -47,12 +48,13 @@ export interface MapsPluginSetupDependencies { home: HomePublicPluginSetup; visualizations: VisualizationsSetup; embeddable: EmbeddableSetup; + mapsLegacy: { config: unknown }; } // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface MapsPluginStartDependencies {} export const bindSetupCoreAndPlugins = (core: CoreSetup, plugins: any) => { - const { licensing } = plugins; + const { licensing, mapsLegacy } = plugins; const { injectedMetadata, uiSettings, http, notifications } = core; if (licensing) { licensing.license$.subscribe(({ uid }: { uid: string }) => setLicenseId(uid)); @@ -63,6 +65,7 @@ export const bindSetupCoreAndPlugins = (core: CoreSetup, plugins: any) => { setInjectedVarFunc(injectedMetadata.getInjectedVar); setVisualizations(plugins.visualizations); setUiSettings(uiSettings); + setMapConfig(mapsLegacy.config); }; export const bindStartCoreAndPlugins = (core: CoreStart, plugins: any) => { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts index 2463da054d1406..9221f8c5003268 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.test.ts @@ -92,6 +92,7 @@ describe('Analytics job clone action', () => { training_percent: 20, randomize_seed: -2228827740028660200, num_top_feature_importance_values: 4, + loss_function: 'mse', }, }, analyzed_fields: { @@ -192,6 +193,7 @@ describe('Analytics job clone action', () => { training_percent: 20, randomize_seed: -2228827740028660200, num_top_feature_importance_values: 4, + loss_function: 'mse', }, }, analyzed_fields: { diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx index cc75ddbe08cfb9..cfb11856670c4b 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_clone.tsx @@ -179,6 +179,10 @@ const getAnalyticsJobMeta = (config: CloneDataFrameAnalyticsConfig): AnalyticsJo // By default it is randomly generated ignore: true, }, + loss_function: { + optional: true, + defaultValue: 'mse', + }, }, } : {}), diff --git a/x-pack/plugins/siem/cypress/integration/cases_connectors.spec.ts b/x-pack/plugins/siem/cypress/integration/cases_connectors.spec.ts new file mode 100644 index 00000000000000..2d650b1bbd9d18 --- /dev/null +++ b/x-pack/plugins/siem/cypress/integration/cases_connectors.spec.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { serviceNowConnector } from '../objects/case'; + +import { TOASTER } from '../screens/configure_cases'; + +import { goToEditExternalConnection } from '../tasks/all_cases'; +import { + addServiceNowConnector, + openAddNewConnectorOption, + saveChanges, + selectLastConnectorCreated, +} from '../tasks/configure_cases'; +import { loginAndWaitForPageWithoutDateRange } from '../tasks/login'; + +import { CASES } from '../urls/navigation'; + +describe('Cases connectors', () => { + before(() => { + cy.server(); + cy.route('POST', '**/api/action').as('createConnector'); + cy.route('POST', '**/api/cases/configure').as('saveConnector'); + }); + + it('Configures a new connector', () => { + loginAndWaitForPageWithoutDateRange(CASES); + goToEditExternalConnection(); + openAddNewConnectorOption(); + addServiceNowConnector(serviceNowConnector); + + cy.wait('@createConnector') + .its('status') + .should('eql', 200); + cy.get(TOASTER).should('have.text', "Created 'New connector'"); + + selectLastConnectorCreated(); + saveChanges(); + + cy.wait('@saveConnector', { timeout: 10000 }) + .its('status') + .should('eql', 200); + cy.get(TOASTER).should('have.text', 'Saved external connection settings'); + }); +}); diff --git a/x-pack/plugins/siem/cypress/objects/case.ts b/x-pack/plugins/siem/cypress/objects/case.ts index 1c7bc34bca417a..12d3f925169afb 100644 --- a/x-pack/plugins/siem/cypress/objects/case.ts +++ b/x-pack/plugins/siem/cypress/objects/case.ts @@ -14,6 +14,13 @@ export interface TestCase { reporter: string; } +export interface Connector { + connectorName: string; + URL: string; + username: string; + password: string; +} + const caseTimeline: Timeline = { title: 'SIEM test', description: 'description', @@ -27,3 +34,10 @@ export const case1: TestCase = { timeline: caseTimeline, reporter: 'elastic', }; + +export const serviceNowConnector: Connector = { + connectorName: 'New connector', + URL: 'https://www.test.service-now.com', + username: 'Username Name', + password: 'password', +}; diff --git a/x-pack/plugins/siem/cypress/screens/all_cases.ts b/x-pack/plugins/siem/cypress/screens/all_cases.ts index b1e4c665153522..4fa6b69eea7c31 100644 --- a/x-pack/plugins/siem/cypress/screens/all_cases.ts +++ b/x-pack/plugins/siem/cypress/screens/all_cases.ts @@ -39,3 +39,5 @@ export const ALL_CASES_TAGS = (index: number) => { }; export const ALL_CASES_TAGS_COUNT = '[data-test-subj="options-filter-popover-button-Tags"]'; + +export const EDIT_EXTERNAL_CONNECTION = '[data-test-subj="configure-case-button"]'; diff --git a/x-pack/plugins/siem/cypress/screens/configure_cases.ts b/x-pack/plugins/siem/cypress/screens/configure_cases.ts new file mode 100644 index 00000000000000..5a1e897c43e27c --- /dev/null +++ b/x-pack/plugins/siem/cypress/screens/configure_cases.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const ADD_NEW_CONNECTOR_OPTION_LINK = + '[data-test-subj="case-configure-add-connector-button"]'; + +export const CONNECTOR = (id: string) => { + return `[data-test-subj='dropdown-connector-${id}']`; +}; + +export const CONNECTOR_NAME = '[data-test-subj="nameInput"]'; + +export const CONNECTORS_DROPDOWN = '[data-test-subj="dropdown-connectors"]'; + +export const PASSWORD = '[data-test-subj="connector-servicenow-password-form-input"]'; + +export const SAVE_BTN = '[data-test-subj="saveNewActionButton"]'; + +export const SAVE_CHANGES_BTN = '[data-test-subj="case-configure-action-bottom-bar-save-button"]'; + +export const SERVICE_NOW_CONNECTOR_CARD = '[data-test-subj=".servicenow-card"]'; + +export const TOASTER = '[data-test-subj="euiToastHeader"]'; + +export const URL = '[data-test-subj="apiUrlFromInput"]'; + +export const USERNAME = '[data-test-subj="connector-servicenow-username-form-input"]'; diff --git a/x-pack/plugins/siem/cypress/tasks/all_cases.ts b/x-pack/plugins/siem/cypress/tasks/all_cases.ts index f3745322013241..8ebe35e173e59b 100644 --- a/x-pack/plugins/siem/cypress/tasks/all_cases.ts +++ b/x-pack/plugins/siem/cypress/tasks/all_cases.ts @@ -4,7 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ALL_CASES_NAME, ALL_CASES_CREATE_NEW_CASE_BTN } from '../screens/all_cases'; +import { + ALL_CASES_NAME, + ALL_CASES_CREATE_NEW_CASE_BTN, + EDIT_EXTERNAL_CONNECTION, +} from '../screens/all_cases'; export const goToCreateNewCase = () => { cy.get(ALL_CASES_CREATE_NEW_CASE_BTN).click({ force: true }); @@ -13,3 +17,7 @@ export const goToCreateNewCase = () => { export const goToCaseDetails = () => { cy.get(ALL_CASES_NAME).click({ force: true }); }; + +export const goToEditExternalConnection = () => { + cy.get(EDIT_EXTERNAL_CONNECTION).click({ force: true }); +}; diff --git a/x-pack/plugins/siem/cypress/tasks/configure_cases.ts b/x-pack/plugins/siem/cypress/tasks/configure_cases.ts new file mode 100644 index 00000000000000..9172e02708ae76 --- /dev/null +++ b/x-pack/plugins/siem/cypress/tasks/configure_cases.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + ADD_NEW_CONNECTOR_OPTION_LINK, + CONNECTOR, + CONNECTOR_NAME, + CONNECTORS_DROPDOWN, + PASSWORD, + SAVE_BTN, + SAVE_CHANGES_BTN, + SERVICE_NOW_CONNECTOR_CARD, + URL, + USERNAME, +} from '../screens/configure_cases'; +import { MAIN_PAGE } from '../screens/siem_main'; + +import { Connector } from '../objects/case'; + +export const addServiceNowConnector = (connector: Connector) => { + cy.get(SERVICE_NOW_CONNECTOR_CARD).click(); + cy.get(CONNECTOR_NAME).type(connector.connectorName); + cy.get(URL).type(connector.URL); + cy.get(USERNAME).type(connector.username); + cy.get(PASSWORD).type(connector.password); + cy.get(SAVE_BTN).click({ force: true }); +}; + +export const openAddNewConnectorOption = () => { + cy.get(MAIN_PAGE).then($page => { + if ($page.find(SERVICE_NOW_CONNECTOR_CARD).length !== 1) { + cy.wait(1000); + cy.get(ADD_NEW_CONNECTOR_OPTION_LINK).click({ force: true }); + } + }); +}; + +export const saveChanges = () => { + cy.get(SAVE_CHANGES_BTN).click(); +}; + +export const selectLastConnectorCreated = () => { + cy.get(CONNECTORS_DROPDOWN).click({ force: true }); + cy.get('@createConnector') + .its('response') + .then(response => { + cy.get(CONNECTOR(response.body.id)).click(); + }); +}; diff --git a/x-pack/plugins/siem/public/containers/case/translations.ts b/x-pack/plugins/siem/public/containers/case/translations.ts index d5ea287fd2cdd0..79edcc56b0362f 100644 --- a/x-pack/plugins/siem/public/containers/case/translations.ts +++ b/x-pack/plugins/siem/public/containers/case/translations.ts @@ -50,19 +50,11 @@ export const REOPENED_CASES = ({ defaultMessage: 'Reopened {totalCases, plural, =1 {"{caseTitle}"} other {{totalCases} cases}}', }); -export const TAG_FETCH_FAILURE = i18n.translate( - 'xpack.siem.containers.case.tagFetchFailDescription', - { - defaultMessage: 'Failed to fetch Tags', - } -); - -export const SUCCESS_SEND_TO_EXTERNAL_SERVICE = i18n.translate( - 'xpack.siem.containers.case.pushToExterService', - { - defaultMessage: 'Successfully sent to ServiceNow', - } -); +export const SUCCESS_SEND_TO_EXTERNAL_SERVICE = (serviceName: string) => + i18n.translate('xpack.siem.containers.case.pushToExternalService', { + values: { serviceName }, + defaultMessage: 'Successfully sent to { serviceName }', + }); export const ERROR_PUSH_TO_SERVICE = i18n.translate( 'xpack.siem.case.configure.errorPushingToService', diff --git a/x-pack/plugins/siem/public/containers/case/use_get_case_user_actions.test.tsx b/x-pack/plugins/siem/public/containers/case/use_get_case_user_actions.test.tsx index 0848d12c8d308b..1603beddbb1dc8 100644 --- a/x-pack/plugins/siem/public/containers/case/use_get_case_user_actions.test.tsx +++ b/x-pack/plugins/siem/public/containers/case/use_get_case_user_actions.test.tsx @@ -122,13 +122,14 @@ describe('useGetCaseUserActions', () => { ...basicPush, firstPushIndex: 3, lastPushIndex: 3, + commentsToUpdate: [], hasDataToPush: false, }, }, }); }); - it('Correctly marks first/last index - hasDataToPush: true', () => { + it('Correctly marks first/last index and comment id - hasDataToPush: true', () => { const userActions = [ ...caseUserActions, getUserAction(['pushed'], 'push-to-service'), @@ -142,6 +143,83 @@ describe('useGetCaseUserActions', () => { ...basicPush, firstPushIndex: 3, lastPushIndex: 3, + commentsToUpdate: [userActions[userActions.length - 1].commentId], + hasDataToPush: true, + }, + }, + }); + }); + + it('Correctly marks first/last index and multiple comment ids, both needs push', () => { + const userActions = [ + ...caseUserActions, + getUserAction(['pushed'], 'push-to-service'), + getUserAction(['comment'], 'create'), + { ...getUserAction(['comment'], 'create'), commentId: 'muahaha' }, + ]; + const result = getPushedInfo(userActions, '123'); + expect(result).toEqual({ + hasDataToPush: true, + caseServices: { + '123': { + ...basicPush, + firstPushIndex: 3, + lastPushIndex: 3, + commentsToUpdate: [ + userActions[userActions.length - 2].commentId, + userActions[userActions.length - 1].commentId, + ], + hasDataToPush: true, + }, + }, + }); + }); + + it('Correctly marks first/last index and multiple comment ids, one needs push', () => { + const userActions = [ + ...caseUserActions, + getUserAction(['pushed'], 'push-to-service'), + getUserAction(['comment'], 'create'), + getUserAction(['pushed'], 'push-to-service'), + { ...getUserAction(['comment'], 'create'), commentId: 'muahaha' }, + ]; + const result = getPushedInfo(userActions, '123'); + expect(result).toEqual({ + hasDataToPush: true, + caseServices: { + '123': { + ...basicPush, + firstPushIndex: 3, + lastPushIndex: 5, + commentsToUpdate: [userActions[userActions.length - 1].commentId], + hasDataToPush: true, + }, + }, + }); + }); + + it('Correctly marks first/last index and multiple comment ids, one needs push and one needs update', () => { + const userActions = [ + ...caseUserActions, + getUserAction(['pushed'], 'push-to-service'), + getUserAction(['comment'], 'create'), + getUserAction(['pushed'], 'push-to-service'), + { ...getUserAction(['comment'], 'create'), commentId: 'muahaha' }, + getUserAction(['comment'], 'update'), + getUserAction(['comment'], 'update'), + ]; + const result = getPushedInfo(userActions, '123'); + expect(result).toEqual({ + hasDataToPush: true, + caseServices: { + '123': { + ...basicPush, + firstPushIndex: 3, + lastPushIndex: 5, + commentsToUpdate: [ + userActions[userActions.length - 3].commentId, + userActions[userActions.length - 1].commentId, + ], hasDataToPush: true, }, }, @@ -162,6 +240,7 @@ describe('useGetCaseUserActions', () => { ...basicPush, firstPushIndex: 3, lastPushIndex: 3, + commentsToUpdate: [], hasDataToPush: false, }, }, @@ -182,11 +261,34 @@ describe('useGetCaseUserActions', () => { ...basicPush, firstPushIndex: 3, lastPushIndex: 5, + commentsToUpdate: [], hasDataToPush: false, }, }, }); }); + it('Correctly handles comment update with multiple push actions', () => { + const userActions = [ + ...caseUserActions, + getUserAction(['pushed'], 'push-to-service'), + getUserAction(['comment'], 'create'), + getUserAction(['pushed'], 'push-to-service'), + getUserAction(['comment'], 'update'), + ]; + const result = getPushedInfo(userActions, '123'); + expect(result).toEqual({ + hasDataToPush: true, + caseServices: { + '123': { + ...basicPush, + firstPushIndex: 3, + lastPushIndex: 5, + commentsToUpdate: [userActions[userActions.length - 1].commentId], + hasDataToPush: true, + }, + }, + }); + }); it('Multiple connector tracking - hasDataToPush: true', () => { const pushAction123 = getUserAction(['pushed'], 'push-to-service'); @@ -215,6 +317,7 @@ describe('useGetCaseUserActions', () => { ...basicPush, firstPushIndex: 3, lastPushIndex: 3, + commentsToUpdate: [userActions[userActions.length - 2].commentId], hasDataToPush: true, }, '456': { @@ -224,6 +327,7 @@ describe('useGetCaseUserActions', () => { externalId: 'other_external_id', firstPushIndex: 5, lastPushIndex: 5, + commentsToUpdate: [], hasDataToPush: false, }, }, @@ -257,6 +361,7 @@ describe('useGetCaseUserActions', () => { ...basicPush, firstPushIndex: 3, lastPushIndex: 3, + commentsToUpdate: [userActions[userActions.length - 2].commentId], hasDataToPush: true, }, '456': { @@ -266,6 +371,7 @@ describe('useGetCaseUserActions', () => { externalId: 'other_external_id', firstPushIndex: 5, lastPushIndex: 5, + commentsToUpdate: [], hasDataToPush: false, }, }, diff --git a/x-pack/plugins/siem/public/containers/case/use_get_case_user_actions.tsx b/x-pack/plugins/siem/public/containers/case/use_get_case_user_actions.tsx index a2290f946be9bb..5afe06a9828e5f 100644 --- a/x-pack/plugins/siem/public/containers/case/use_get_case_user_actions.tsx +++ b/x-pack/plugins/siem/public/containers/case/use_get_case_user_actions.tsx @@ -14,9 +14,10 @@ import { CaseExternalService, CaseUserActions, ElasticUser } from './types'; import { convertToCamelCase, parseString } from './utils'; import { CaseFullExternalService } from '../../../../case/common/api/cases'; -interface CaseService extends CaseExternalService { +export interface CaseService extends CaseExternalService { firstPushIndex: number; lastPushIndex: number; + commentsToUpdate: string[]; hasDataToPush: boolean; } @@ -48,6 +49,10 @@ export interface UseGetCaseUserActions extends CaseUserActionsState { const getExternalService = (value: string): CaseExternalService | null => convertToCamelCase(parseString(`${value}`)); +interface CommentsAndIndex { + commentId: string; + commentIndex: number; +} export const getPushedInfo = ( caseUserActions: CaseUserActions[], @@ -69,11 +74,25 @@ export const getPushedInfo = ( .action !== 'push-to-service' ); }; + const commentsAndIndex = caseUserActions.reduce( + (bacc, mua, index) => + mua.actionField[0] === 'comment' && mua.commentId != null + ? [ + ...bacc, + { + commentId: mua.commentId, + commentIndex: index, + }, + ] + : bacc, + [] + ); - const caseServices = caseUserActions.reduce((acc, cua, i) => { + let caseServices = caseUserActions.reduce((acc, cua, i) => { if (cua.action !== 'push-to-service') { return acc; } + const externalService = getExternalService(`${cua.newValue}`); if (externalService === null) { return acc; @@ -87,6 +106,7 @@ export const getPushedInfo = ( ...acc[externalService.connectorId], ...externalService, lastPushIndex: i, + commentsToUpdate: [], }, } : { @@ -95,11 +115,31 @@ export const getPushedInfo = ( firstPushIndex: i, lastPushIndex: i, hasDataToPush: hasDataToPushForConnector(externalService.connectorId), + commentsToUpdate: [], }, }), }; }, {}); + caseServices = Object.keys(caseServices).reduce((acc, key) => { + return { + ...acc, + [key]: { + ...caseServices[key], + // if the comment happens after the lastUpdateToCaseIndex, it should be included in commentsToUpdate + commentsToUpdate: commentsAndIndex.reduce( + (bacc, currentComment) => + currentComment.commentIndex > caseServices[key].lastPushIndex + ? bacc.indexOf(currentComment.commentId) > -1 + ? [...bacc.filter(e => e !== currentComment.commentId), currentComment.commentId] + : [...bacc, currentComment.commentId] + : bacc, + [] + ), + }, + }; + }, {}); + const hasDataToPush = caseServices[caseConnectorId] != null ? caseServices[caseConnectorId].hasDataToPush : true; return { diff --git a/x-pack/plugins/siem/public/containers/case/use_post_push_to_service.test.tsx b/x-pack/plugins/siem/public/containers/case/use_post_push_to_service.test.tsx index 72609e15d1ec4f..96fa824c1cadd0 100644 --- a/x-pack/plugins/siem/public/containers/case/use_post_push_to_service.test.tsx +++ b/x-pack/plugins/siem/public/containers/case/use_post_push_to_service.test.tsx @@ -19,6 +19,7 @@ import { serviceConnectorUser, } from './mock'; import * as api from './api'; +import { CaseServices } from './use_get_case_user_actions'; jest.mock('./api'); @@ -32,6 +33,7 @@ describe('usePostPushToService', () => { ...basicPush, firstPushIndex: 1, lastPushIndex: 1, + commentsToUpdate: [basicComment.id], hasDataToPush: false, }, }, @@ -64,6 +66,7 @@ describe('usePostPushToService', () => { ...basicPush, firstPushIndex: 1, lastPushIndex: 1, + commentsToUpdate: [basicComment.id], hasDataToPush: true, }, '456': { @@ -71,6 +74,7 @@ describe('usePostPushToService', () => { connectorId: '456', externalId: 'other_external_id', firstPushIndex: 4, + commentsToUpdate: [basicComment.id], lastPushIndex: 6, hasDataToPush: false, }, @@ -127,6 +131,31 @@ describe('usePostPushToService', () => { await waitForNextUpdate(); expect(spyOnPushToService).toBeCalledWith( samplePush.connectorId, + formatServiceRequestData(basicCase, '123', sampleCaseServices as CaseServices), + abortCtrl.signal + ); + }); + }); + + it('calls pushToService with correct arguments when no push history', async () => { + const samplePush2 = { + caseId: pushedCase.id, + caseServices: {}, + connectorName: 'connector name', + connectorId: 'none', + updateCase, + }; + const spyOnPushToService = jest.spyOn(api, 'pushToService'); + + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePostPushToService() + ); + await waitForNextUpdate(); + result.current.postPushToService(samplePush2); + await waitForNextUpdate(); + expect(spyOnPushToService).toBeCalledWith( + samplePush2.connectorId, formatServiceRequestData(basicCase, 'none', {}), abortCtrl.signal ); diff --git a/x-pack/plugins/siem/public/containers/case/use_post_push_to_service.tsx b/x-pack/plugins/siem/public/containers/case/use_post_push_to_service.tsx index 3d0836cdc8adfe..7f4c4a42761721 100644 --- a/x-pack/plugins/siem/public/containers/case/use_post_push_to_service.tsx +++ b/x-pack/plugins/siem/public/containers/case/use_post_push_to_service.tsx @@ -122,7 +122,10 @@ export const usePostPushToService = (): UsePostPushToService => { dispatch({ type: 'FETCH_SUCCESS_PUSH_SERVICE', payload: responseService }); dispatch({ type: 'FETCH_SUCCESS_PUSH_CASE', payload: responseCase }); updateCase(responseCase); - displaySuccessToast(i18n.SUCCESS_SEND_TO_EXTERNAL_SERVICE, dispatchToaster); + displaySuccessToast( + i18n.SUCCESS_SEND_TO_EXTERNAL_SERVICE(connectorName), + dispatchToaster + ); } } catch (error) { if (!cancel) { @@ -156,25 +159,12 @@ export const formatServiceRequestData = ( createdBy, comments, description, - externalService, title, updatedAt, updatedBy, } = myCase; - let actualExternalService = externalService; - if ( - externalService != null && - externalService.connectorId !== connectorId && - caseServices[connectorId] - ) { - actualExternalService = caseServices[connectorId]; - } else if ( - externalService != null && - externalService.connectorId !== connectorId && - !caseServices[connectorId] - ) { - actualExternalService = null; - } + const actualExternalService = caseServices[connectorId] ?? null; + return { caseId, createdAt, @@ -183,17 +173,9 @@ export const formatServiceRequestData = ( username: createdBy?.username ?? '', }, comments: comments - .filter(c => { - const lastPush = c.pushedAt != null ? new Date(c.pushedAt) : null; - const lastUpdate = c.updatedAt != null ? new Date(c.updatedAt) : null; - if ( - lastPush === null || - (lastPush != null && lastUpdate != null && lastPush.getTime() < lastUpdate?.getTime()) - ) { - return true; - } - return false; - }) + .filter( + c => actualExternalService == null || actualExternalService.commentsToUpdate.includes(c.id) + ) .map(c => ({ commentId: c.id, comment: c.comment, diff --git a/x-pack/plugins/siem/public/pages/case/components/case_status/index.tsx b/x-pack/plugins/siem/public/pages/case/components/case_status/index.tsx index 718eb95767f2e3..f48d9a68ffaf0a 100644 --- a/x-pack/plugins/siem/public/pages/case/components/case_status/index.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/case_status/index.tsx @@ -20,6 +20,7 @@ import * as i18n from '../case_view/translations'; import { FormattedRelativePreferenceDate } from '../../../../components/formatted_date'; import { CaseViewActions } from '../case_view/actions'; import { Case } from '../../../../containers/case/types'; +import { CaseService } from '../../../../containers/case/use_get_case_user_actions'; const MyDescriptionList = styled(EuiDescriptionList)` ${({ theme }) => css` @@ -35,6 +36,7 @@ interface CaseStatusProps { badgeColor: string; buttonLabel: string; caseData: Case; + currentExternalIncident: CaseService | null; disabled?: boolean; icon: string; isLoading: boolean; @@ -50,6 +52,7 @@ const CaseStatusComp: React.FC = ({ badgeColor, buttonLabel, caseData, + currentExternalIncident, disabled = false, icon, isLoading, @@ -100,7 +103,11 @@ const CaseStatusComp: React.FC = ({ /> - + diff --git a/x-pack/plugins/siem/public/pages/case/components/case_view/actions.test.tsx b/x-pack/plugins/siem/public/pages/case/components/case_view/actions.test.tsx index 8b6ee76dd783db..24fbd59b3282b2 100644 --- a/x-pack/plugins/siem/public/pages/case/components/case_view/actions.test.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/case_view/actions.test.tsx @@ -9,8 +9,9 @@ import { mount } from 'enzyme'; import { useDeleteCases } from '../../../../containers/case/use_delete_cases'; import { TestProviders } from '../../../../mock'; -import { basicCase } from '../../../../containers/case/mock'; +import { basicCase, basicPush } from '../../../../containers/case/mock'; import { CaseViewActions } from './actions'; +import * as i18n from './translations'; jest.mock('../../../../containers/case/use_delete_cases'); const useDeleteCasesMock = useDeleteCases as jest.Mock; @@ -34,7 +35,7 @@ describe('CaseView actions', () => { it('clicking trash toggles modal', () => { const wrapper = mount( - + ); @@ -54,7 +55,7 @@ describe('CaseView actions', () => { })); const wrapper = mount( - + ); @@ -64,4 +65,33 @@ describe('CaseView actions', () => { { id: basicCase.id, title: basicCase.title }, ]); }); + it('displays active incident link', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="confirm-delete-case-modal"]').exists()).toBeFalsy(); + + wrapper + .find('button[data-test-subj="property-actions-ellipses"]') + .first() + .simulate('click'); + expect( + wrapper + .find('[data-test-subj="property-actions-popout"]') + .first() + .prop('aria-label') + ).toEqual(i18n.VIEW_INCIDENT(basicPush.externalTitle)); + }); }); diff --git a/x-pack/plugins/siem/public/pages/case/components/case_view/actions.tsx b/x-pack/plugins/siem/public/pages/case/components/case_view/actions.tsx index 216180eb2cf0a2..4acdaef6ca51f8 100644 --- a/x-pack/plugins/siem/public/pages/case/components/case_view/actions.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/case_view/actions.tsx @@ -13,13 +13,19 @@ import { ConfirmDeleteCaseModal } from '../confirm_delete_case'; import { SiemPageName } from '../../../home/types'; import { PropertyActions } from '../property_actions'; import { Case } from '../../../../containers/case/types'; +import { CaseService } from '../../../../containers/case/use_get_case_user_actions'; interface CaseViewActions { caseData: Case; + currentExternalIncident: CaseService | null; disabled?: boolean; } -const CaseViewActionsComponent: React.FC = ({ caseData, disabled = false }) => { +const CaseViewActionsComponent: React.FC = ({ + caseData, + currentExternalIncident, + disabled = false, +}) => { // Delete case const { handleToggleModal, @@ -48,17 +54,17 @@ const CaseViewActionsComponent: React.FC = ({ caseData, disable label: i18n.DELETE_CASE, onClick: handleToggleModal, }, - ...(caseData.externalService != null && !isEmpty(caseData.externalService?.externalUrl) + ...(currentExternalIncident != null && !isEmpty(currentExternalIncident?.externalUrl) ? [ { iconType: 'popout', - label: i18n.VIEW_INCIDENT(caseData.externalService?.externalTitle ?? ''), - onClick: () => window.open(caseData.externalService?.externalUrl, '_blank'), + label: i18n.VIEW_INCIDENT(currentExternalIncident?.externalTitle ?? ''), + onClick: () => window.open(currentExternalIncident?.externalUrl, '_blank'), }, ] : []), ], - [disabled, handleToggleModal, caseData] + [disabled, handleToggleModal, currentExternalIncident] ); if (isDeleted) { diff --git a/x-pack/plugins/siem/public/pages/case/components/case_view/index.test.tsx b/x-pack/plugins/siem/public/pages/case/components/case_view/index.test.tsx index 7ce9d7b8533e43..a6e6b19a071ce5 100644 --- a/x-pack/plugins/siem/public/pages/case/components/case_view/index.test.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/case_view/index.test.tsx @@ -70,6 +70,7 @@ describe('CaseView ', () => { const defaultUseGetCaseUserActions = { caseUserActions, + caseServices: {}, fetchCaseUserActions, firstIndexPushToService: -1, hasDataToPush: false, diff --git a/x-pack/plugins/siem/public/pages/case/components/case_view/index.tsx b/x-pack/plugins/siem/public/pages/case/components/case_view/index.tsx index 14039dc2cbc304..fed8ec8edbe8b5 100644 --- a/x-pack/plugins/siem/public/pages/case/components/case_view/index.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/case_view/index.tsx @@ -164,6 +164,15 @@ export const CaseComponent = React.memo( () => connectors.find(c => c.id === caseData.connectorId)?.name ?? 'none', [connectors, caseData.connectorId] ); + + const currentExternalIncident = useMemo( + () => + caseServices != null && caseServices[caseData.connectorId] != null + ? caseServices[caseData.connectorId] + : null, + [caseServices, caseData.connectorId] + ); + const { pushButton, pushCallouts } = usePushToService({ caseConnectorId: caseData.connectorId, caseConnectorName, @@ -254,6 +263,7 @@ export const CaseComponent = React.memo( title={caseData.title} > = ({ /> - - {connector.name} - + {connector.name} ), diff --git a/x-pack/plugins/siem/public/pages/case/components/use_push_to_service/helpers.tsx b/x-pack/plugins/siem/public/pages/case/components/use_push_to_service/helpers.tsx index 1e4fd92058e8d2..0613c40d1181d6 100644 --- a/x-pack/plugins/siem/public/pages/case/components/use_push_to_service/helpers.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/use_push_to_service/helpers.tsx @@ -32,7 +32,7 @@ export const getKibanaConfigError = () => ({ title: i18n.PUSH_DISABLE_BY_KIBANA_CONFIG_TITLE, description: ( { ...basicPush, firstPushIndex: 0, lastPushIndex: 0, + commentsToUpdate: [], hasDataToPush: true, }, }; diff --git a/x-pack/plugins/siem/public/pages/case/components/use_push_to_service/translations.ts b/x-pack/plugins/siem/public/pages/case/components/use_push_to_service/translations.ts index 2a36fcf8a6bc4b..bdd6ae98a5d01a 100644 --- a/x-pack/plugins/siem/public/pages/case/components/use_push_to_service/translations.ts +++ b/x-pack/plugins/siem/public/pages/case/components/use_push_to_service/translations.ts @@ -60,7 +60,7 @@ export const PUSH_DISABLE_BECAUSE_CASE_CLOSED_TITLE = i18n.translate( export const PUSH_DISABLE_BY_KIBANA_CONFIG_TITLE = i18n.translate( 'xpack.siem.case.caseView.pushToServiceDisableByConfigTitle', { - defaultMessage: 'Enable ServiceNow in Kibana configuration file', + defaultMessage: 'Enable external service in Kibana configuration file', } ); diff --git a/x-pack/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx b/x-pack/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx index 736974545a1df8..b9a94f83fded1a 100644 --- a/x-pack/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx +++ b/x-pack/plugins/siem/public/pages/case/components/user_action_tree/index.test.tsx @@ -86,6 +86,7 @@ describe('UserActionTree ', () => { ...basicPush, firstPushIndex: 0, lastPushIndex: 0, + commentsToUpdate: [`${ourActions[ourActions.length - 1].commentId}`], hasDataToPush: true, }, }, @@ -111,6 +112,7 @@ describe('UserActionTree ', () => { ...basicPush, firstPushIndex: 0, lastPushIndex: 0, + commentsToUpdate: [], hasDataToPush: false, }, }, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 67eec939701c37..1adc77267c44f0 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4386,7 +4386,6 @@ "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": "タイプ", @@ -13282,8 +13281,6 @@ "xpack.siem.containers.anomalies.stackByJobId": "ジョブ", "xpack.siem.containers.anomalies.title": "異常", "xpack.siem.containers.case.errorTitle": "データの取得中にエラーが発生", - "xpack.siem.containers.case.pushToExterService": "ServiceNow への送信が正常に完了しました", - "xpack.siem.containers.case.tagFetchFailDescription": "タグを取得できませんでした", "xpack.siem.containers.detectionEngine.addRuleFailDescription": "ルールを追加できませんでした", "xpack.siem.containers.detectionEngine.createPrePackagedRuleFailDescription": "Elasticから事前にパッケージ化されているルールをインストールすることができませんでした", "xpack.siem.containers.detectionEngine.createPrePackagedRuleSuccesDescription": "Elasticから事前にパッケージ化されているルールをインストールしました", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 486bb747a15e0f..a57b517123e77a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4387,7 +4387,6 @@ "xpack.apm.serviceMap.emptyBanner.title": "似乎仅有一个服务。", "xpack.apm.serviceMap.focusMapButtonText": "聚焦地图", "xpack.apm.serviceMap.invalidLicenseMessage": "要访问服务地图,必须订阅 Elastic 白金级许可证。使用该许可证,您将能够可视化整个应用程序堆栈以及 APM 数据。", - "xpack.apm.serviceMap.numInstancesMetric": "{numInstances} 个实例", "xpack.apm.serviceMap.serviceDetailsButtonText": "服务详情", "xpack.apm.serviceMap.subtypePopoverMetric": "子类型", "xpack.apm.serviceMap.typePopoverMetric": "类型", @@ -13289,8 +13288,6 @@ "xpack.siem.containers.anomalies.stackByJobId": "作业", "xpack.siem.containers.anomalies.title": "异常", "xpack.siem.containers.case.errorTitle": "提取数据时出错", - "xpack.siem.containers.case.pushToExterService": "已成功发送到 ServiceNow", - "xpack.siem.containers.case.tagFetchFailDescription": "无法提取标记", "xpack.siem.containers.detectionEngine.addRuleFailDescription": "无法添加规则", "xpack.siem.containers.detectionEngine.createPrePackagedRuleFailDescription": "无法安装 elastic 的预打包规则", "xpack.siem.containers.detectionEngine.createPrePackagedRuleSuccesDescription": "已安装 elastic 的预打包规则", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.test.tsx index 64a85efe9e194c..b0f21afeaa96c7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.test.tsx @@ -9,7 +9,6 @@ import { act } from 'react-dom/test-utils'; import { EsIndexActionConnector } from '../types'; import { coreMock } from '../../../../../../../../src/core/public/mocks'; import IndexActionConnectorFields from './es_index_connector'; -import { ActionsConnectorsContextProvider } from '../../../context/actions_connectors_context'; import { TypeRegistry } from '../../../type_registry'; import { DocLinksStart } from 'kibana/public'; @@ -82,25 +81,14 @@ describe('IndexActionConnectorFields renders', () => { }, } as EsIndexActionConnector; const wrapper = mountWithIntl( - { - return new Promise(() => {}); - }, - docLinks: deps!.docLinks, - }} - > - {}} - editActionSecrets={() => {}} - /> - + {}} + editActionSecrets={() => {}} + http={deps!.http} + docLinks={deps!.docLinks} + /> ); await act(async () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.tsx index b4712856e023bc..9cd3a185453450 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/es_index/es_index_connector.tsx @@ -25,14 +25,10 @@ import { getIndexOptions, getIndexPatterns, } from '../../../../common/index_controls'; -import { useActionsConnectorsContext } from '../../../context/actions_connectors_context'; -export const IndexActionConnectorFields = ({ - action, - editActionConfig, - errors, -}: ActionConnectorFieldsProps) => { - const { http } = useActionsConnectorsContext(); +const IndexActionConnectorFields: React.FunctionComponent> = ({ action, editActionConfig, errors, http }) => { const { index, refresh, executionTimeField } = action.config; const [hasTimeFieldCheckbox, setTimeFieldCheckboxState] = useState( executionTimeField != null diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.test.tsx index 7dfb9c9295548a..3f3fba1599bd2b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.test.tsx @@ -6,11 +6,8 @@ import React from 'react'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { act } from 'react-dom/test-utils'; -import { coreMock } from '../../../../../../../../src/core/public/mocks'; import { PagerDutyActionConnector } from '.././types'; import PagerDutyActionConnectorFields from './pagerduty_connectors'; -import { ActionsConnectorsContextProvider } from '../../../context/actions_connectors_context'; -import { TypeRegistry } from '../../../type_registry'; import { DocLinksStart } from 'kibana/public'; describe('PagerDutyActionConnectorFields renders', () => { @@ -26,47 +23,18 @@ describe('PagerDutyActionConnectorFields renders', () => { apiUrl: 'http:\\test', }, } as PagerDutyActionConnector; - const mocks = coreMock.createSetup(); - const [ - { - application: { capabilities }, - }, - ] = await mocks.getStartServices(); const deps = { - toastNotifications: mocks.notifications.toasts, - http: mocks.http, - capabilities: { - ...capabilities, - actions: { - delete: true, - save: true, - show: true, - }, - }, - actionTypeRegistry: {} as TypeRegistry, docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart, }; const wrapper = mountWithIntl( - { - return new Promise(() => {}); - }, - docLinks: deps!.docLinks, - }} - > - {}} - editActionSecrets={() => {}} - /> - + {}} + editActionSecrets={() => {}} + docLinks={deps!.docLinks} + /> ); await act(async () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.tsx index eb5eeb63b3572b..48da3f1778b488 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty/pagerduty_connectors.tsx @@ -9,12 +9,10 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { ActionConnectorFieldsProps } from '../../../../types'; import { PagerDutyActionConnector } from '.././types'; -import { useActionsConnectorsContext } from '../../../context/actions_connectors_context'; const PagerDutyActionConnectorFields: React.FunctionComponent> = ({ errors, action, editActionConfig, editActionSecrets }) => { - const { docLinks } = useActionsConnectorsContext(); +>> = ({ errors, action, editActionConfig, editActionSecrets, docLinks }) => { const { apiUrl } = action.config; const { routingKey } = action.secrets; return ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.test.tsx index 4905bf89936a5a..7d7f6fc0869283 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.test.tsx @@ -5,13 +5,10 @@ */ import React from 'react'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { DocLinksStart } from 'kibana/public'; import { act } from '@testing-library/react'; import { SlackActionConnector } from '../types'; import SlackActionFields from './slack_connectors'; -import { coreMock } from '../../../../../../../../src/core/public/mocks'; -import { TypeRegistry } from '../../../type_registry'; -import { ActionsConnectorsContextProvider } from '../../../context/actions_connectors_context'; +import { DocLinksStart } from 'kibana/public'; describe('SlackActionFields renders', () => { test('all connector fields is rendered', async () => { @@ -24,46 +21,17 @@ describe('SlackActionFields renders', () => { name: 'email', config: {}, } as SlackActionConnector; - const mocks = coreMock.createSetup(); - const [ - { - application: { capabilities }, - }, - ] = await mocks.getStartServices(); const deps = { - toastNotifications: mocks.notifications.toasts, - http: mocks.http, - capabilities: { - ...capabilities, - actions: { - delete: true, - save: true, - show: true, - }, - }, - actionTypeRegistry: {} as TypeRegistry, - docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, + docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' } as DocLinksStart, }; const wrapper = mountWithIntl( - { - return new Promise(() => {}); - }, - docLinks: deps!.docLinks as DocLinksStart, - }} - > - {}} - editActionSecrets={() => {}} - /> - + {}} + editActionSecrets={() => {}} + docLinks={deps!.docLinks} + /> ); await act(async () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx index e8be1f1678c48e..ad3e76ad8ae6cb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/slack/slack_connectors.tsx @@ -12,7 +12,7 @@ import { SlackActionConnector } from '../types'; const SlackActionFields: React.FunctionComponent> = ({ action, editActionSecrets, errors }) => { +>> = ({ action, editActionSecrets, errors, docLinks }) => { const { webhookUrl } = action.secrets; return ( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx index 09547f5c8ea667..95620a5be84745 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/context/alerts_context.tsx @@ -49,7 +49,7 @@ export const AlertsContextProvider = ({ export const useAlertsContext = () => { const ctx = useContext(AlertsContext); if (!ctx) { - throw new Error('ActionsConnectorsContext has not been set.'); + throw new Error('AlertsContext has not been set.'); } return ctx; }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx index 3b78096c4c644e..17a1d929a0def4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.test.tsx @@ -9,29 +9,14 @@ import { coreMock } from '../../../../../../../src/core/public/mocks'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ValidationResult, ActionConnector } from '../../../types'; import { ActionConnectorForm } from './action_connector_form'; -import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context'; const actionTypeRegistry = actionTypeRegistryMock.create(); describe('action_connector_form', () => { let deps: any; beforeAll(async () => { const mocks = coreMock.createSetup(); - const [ - { - application: { capabilities }, - }, - ] = await mocks.getStartServices(); deps = { - toastNotifications: mocks.notifications.toasts, http: mocks.http, - capabilities: { - ...capabilities, - actions: { - delete: true, - save: true, - show: true, - }, - }, actionTypeRegistry: actionTypeRegistry as any, docLinks: { ELASTIC_WEBSITE_URL: '', DOC_LINK_VERSION: '' }, }; @@ -63,25 +48,15 @@ describe('action_connector_form', () => { let wrapper; if (deps) { wrapper = mountWithIntl( - { - return new Promise(() => {}); - }, - docLinks: deps!.docLinks, - }} - > - {}} - errors={{ name: [] }} - /> - + {}} + errors={{ name: [] }} + http={deps!.http} + actionTypeRegistry={deps!.actionTypeRegistry} + docLinks={deps!.docLinks} + /> ); } const connectorNameField = wrapper?.find('[data-test-subj="nameInput"]'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx index 22fe188774b13d..7fb4d053b7f941 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx @@ -16,9 +16,10 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { HttpSetup, DocLinksStart } from 'kibana/public'; import { ReducerAction } from './connector_reducer'; -import { ActionConnector, IErrorObject } from '../../../types'; -import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; +import { ActionConnector, IErrorObject, ActionTypeModel } from '../../../types'; +import { TypeRegistry } from '../../type_registry'; export function validateBaseProperties(actionObject: ActionConnector) { const validationResult = { errors: {} }; @@ -47,6 +48,9 @@ interface ActionConnectorProps { body: { message: string; error: string }; }; errors: IErrorObject; + http: HttpSetup; + actionTypeRegistry: TypeRegistry; + docLinks: DocLinksStart; } export const ActionConnectorForm = ({ @@ -55,8 +59,10 @@ export const ActionConnectorForm = ({ actionTypeName, serverError, errors, + http, + actionTypeRegistry, + docLinks, }: ActionConnectorProps) => { - const { actionTypeRegistry, docLinks } = useActionsConnectorsContext(); const setActionProperty = (key: string, value: any) => { dispatch({ command: { type: 'setProperty' }, payload: { key, value } }); }; @@ -152,6 +158,8 @@ export const ActionConnectorForm = ({ errors={errors} editActionConfig={setActionConfigProperty} editActionSecrets={setActionSecretsProperty} + http={http} + docLinks={docLinks} /> ) : null} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx index 80294e8b73dc88..c9844f4e10864b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_flyout.tsx @@ -52,6 +52,7 @@ export const ConnectorAddFlyout = ({ capabilities, actionTypeRegistry, reloadConnectors, + docLinks, } = useActionsConnectorsContext(); const [actionType, setActionType] = useState(undefined); const [hasActionsUpgradeableByTrial, setHasActionsUpgradeableByTrial] = useState(false); @@ -114,6 +115,9 @@ export const ConnectorAddFlyout = ({ connector={connector} dispatch={dispatch} errors={errors} + actionTypeRegistry={actionTypeRegistry} + http={http} + docLinks={docLinks} /> ); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx index a31336f38bdcdd..8312f2b151082f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_modal.tsx @@ -25,7 +25,6 @@ import { createActionConnector } from '../../lib/action_connector_api'; import { TypeRegistry } from '../../type_registry'; import './connector_add_modal.scss'; import { PLUGIN } from '../../constants/plugin'; -import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context'; import { hasSaveActionsCapability } from '../../lib/capabilities'; interface ConnectorAddModalProps { @@ -156,23 +155,16 @@ export const ConnectorAddModal = ({ - - - + diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx index b86524efe19eaa..4a0effcbd68254 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_edit_flyout.tsx @@ -182,6 +182,9 @@ export const ConnectorEditFlyout = ({ errors={errors} actionTypeName={connector.actionType} dispatch={dispatch} + actionTypeRegistry={actionTypeRegistry} + http={http} + docLinks={docLinks} /> ) : ( diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index b65ed4927b03e3..cc511434267cc2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.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 { HttpSetup } from 'kibana/public'; +import { HttpSetup, DocLinksStart } from 'kibana/public'; import { ComponentType } from 'react'; import { ActionGroup } from '../../alerting/common'; import { ActionType } from '../../actions/common'; @@ -30,6 +30,7 @@ export interface ActionConnectorFieldsProps { editActionConfig: (property: string, value: any) => void; editActionSecrets: (property: string, value: any) => void; errors: IErrorObject; + docLinks: DocLinksStart; http?: HttpSetup; } diff --git a/x-pack/plugins/uptime/common/runtime_types/monitor/state.ts b/x-pack/plugins/uptime/common/runtime_types/monitor/state.ts index 90aa692f89a42f..b3c39e5180adf3 100644 --- a/x-pack/plugins/uptime/common/runtime_types/monitor/state.ts +++ b/x-pack/plugins/uptime/common/runtime_types/monitor/state.ts @@ -9,7 +9,7 @@ import * as t from 'io-ts'; export const CheckMonitorType = t.intersection([ t.partial({ name: t.string, - ip: t.union([t.array(t.string), t.string]), + ip: t.union([t.array(t.union([t.string, t.null])), t.string, t.null]), }), t.type({ status: t.string, diff --git a/x-pack/test/api_integration/apis/management/index_management/indices.js b/x-pack/test/api_integration/apis/management/index_management/indices.js index 9442beda3501d7..04121928087007 100644 --- a/x-pack/test/api_integration/apis/management/index_management/indices.js +++ b/x-pack/test/api_integration/apis/management/index_management/indices.js @@ -199,7 +199,11 @@ export default function({ getService }) { 'ilm', // data enricher 'isRollupIndex', // data enricher ]; - expect(Object.keys(body[0])).to.eql(expectedKeys); + // We need to sort the keys before comparing then, because race conditions + // can cause enrichers to register in non-deterministic order. + const sortedExpectedKeys = expectedKeys.sort(); + const sortedReceivedKeys = Object.keys(body[0]).sort(); + expect(sortedReceivedKeys).to.eql(sortedExpectedKeys); }); }); @@ -225,7 +229,11 @@ export default function({ getService }) { 'ilm', // data enricher 'isRollupIndex', // data enricher ]; - expect(Object.keys(body[0])).to.eql(expectedKeys); + // We need to sort the keys before comparing then, because race conditions + // can cause enrichers to register in non-deterministic order. + const sortedExpectedKeys = expectedKeys.sort(); + const sortedReceivedKeys = Object.keys(body[0]).sort(); + expect(sortedReceivedKeys).to.eql(sortedExpectedKeys); expect(body.length > 1).to.be(true); // to contrast it with the next test }); }); diff --git a/x-pack/test/functional/apps/index_management/home_page.ts b/x-pack/test/functional/apps/index_management/home_page.ts index 046b8ec44b9fa5..5ed6064314af8b 100644 --- a/x-pack/test/functional/apps/index_management/home_page.ts +++ b/x-pack/test/functional/apps/index_management/home_page.ts @@ -34,7 +34,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Index templates', () => { it('renders the index templates tab', async () => { // Navigate to the index templates tab - pageObjects.indexManagement.changeTabs('templatesTab'); + await pageObjects.indexManagement.changeTabs('templatesTab'); await pageObjects.header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test/functional/apps/lens/index.ts b/x-pack/test/functional/apps/lens/index.ts index 857cbe15463b96..53e800bae3f6d4 100644 --- a/x-pack/test/functional/apps/lens/index.ts +++ b/x-pack/test/functional/apps/lens/index.ts @@ -15,7 +15,7 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { describe('lens app', () => { before(async () => { log.debug('Starting lens before method'); - browser.setWindowSize(1280, 800); + await browser.setWindowSize(1280, 800); await esArchiver.loadIfNeeded('logstash_functional'); await esArchiver.loadIfNeeded('lens/basic'); }); diff --git a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/cloning.ts b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/cloning.ts index 93f225989592ed..d87d7d654f5c42 100644 --- a/x-pack/test/functional/apps/machine_learning/data_frame_analytics/cloning.ts +++ b/x-pack/test/functional/apps/machine_learning/data_frame_analytics/cloning.ts @@ -13,7 +13,8 @@ export default function({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - describe('jobs cloning supported by UI form', function() { + // TODO add fix for https://github.com/elastic/elasticsearch/pull/56118 + describe.skip('jobs cloning supported by UI form', function() { const testDataList: Array<{ suiteTitle: string; archive: string; diff --git a/x-pack/test/functional/apps/uptime/certificates.ts b/x-pack/test/functional/apps/uptime/certificates.ts index 05967e0f3acaf0..59e9dda7b184fa 100644 --- a/x-pack/test/functional/apps/uptime/certificates.ts +++ b/x-pack/test/functional/apps/uptime/certificates.ts @@ -25,7 +25,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('can navigate to cert page', async () => { - await uptimeService.navigation.refreshApp(); await uptimeService.cert.hasViewCertButton(); await uptimeService.navigation.goToCertificates(); }); diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index f6b80b1b9fc679..4c78758de448c9 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -146,9 +146,6 @@ export default async function({ readConfigFile }) { uptime: { pathname: '/app/uptime', }, - apm: { - pathname: '/app/apm', - }, ml: { pathname: '/app/ml', }, diff --git a/x-pack/test/functional/page_objects/index_management_page.ts b/x-pack/test/functional/page_objects/index_management_page.ts index 1ae23b24156d0b..453b283ab969d9 100644 --- a/x-pack/test/functional/page_objects/index_management_page.ts +++ b/x-pack/test/functional/page_objects/index_management_page.ts @@ -57,7 +57,7 @@ export function IndexManagementPageProvider({ getService }: FtrProviderContext) }); }, async changeTabs(tab: 'indicesTab' | 'templatesTab') { - return await testSubjects.click(tab); + await testSubjects.click(tab); }, }; } diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index c4dcf63941cd51..7425ed25728c25 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -150,7 +150,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont } await testSubjects.click('confirmSaveSavedObjectButton'); - retry.waitForWithTimeout('Save modal to disappear', 1000, () => + await retry.waitForWithTimeout('Save modal to disappear', 1000, () => testSubjects .missingOrFail('confirmSaveSavedObjectButton') .then(() => true) diff --git a/x-pack/test/functional/page_objects/security_page.js b/x-pack/test/functional/page_objects/security_page.js index 08895de815b396..ae26a831d41723 100644 --- a/x-pack/test/functional/page_objects/security_page.js +++ b/x-pack/test/functional/page_objects/security_page.js @@ -394,9 +394,9 @@ export function SecurityPageProvider({ getService, getPageObjects }) { }); } }) //clicking save button - .then(function() { + .then(async () => { log.debug('click save button'); - testSubjects.click('roleFormSaveButton'); + await testSubjects.click('roleFormSaveButton'); }) .then(function() { return PageObjects.common.sleep(5000); diff --git a/x-pack/test/functional/services/logs_ui/log_entry_categories.ts b/x-pack/test/functional/services/logs_ui/log_entry_categories.ts index b9a400b1556797..70d8622e620ef7 100644 --- a/x-pack/test/functional/services/logs_ui/log_entry_categories.ts +++ b/x-pack/test/functional/services/logs_ui/log_entry_categories.ts @@ -13,7 +13,7 @@ export function LogEntryCategoriesPageProvider({ getPageObjects, getService }: F return { async navigateTo() { - pageObjects.infraLogs.navigateToTab('log-categories'); + await pageObjects.infraLogs.navigateToTab('log-categories'); }, async getSetupScreen(): Promise { diff --git a/x-pack/test/functional/services/logs_ui/log_entry_rate.ts b/x-pack/test/functional/services/logs_ui/log_entry_rate.ts index 96c69e85aa0a48..ffaa6ce08a1dce 100644 --- a/x-pack/test/functional/services/logs_ui/log_entry_rate.ts +++ b/x-pack/test/functional/services/logs_ui/log_entry_rate.ts @@ -13,7 +13,7 @@ export function LogEntryRatePageProvider({ getPageObjects, getService }: FtrProv return { async navigateTo() { - pageObjects.infraLogs.navigateToTab('log-rate'); + await pageObjects.infraLogs.navigateToTab('log-rate'); }, async getSetupScreen(): Promise { diff --git a/x-pack/test/functional/services/logs_ui/log_stream.ts b/x-pack/test/functional/services/logs_ui/log_stream.ts index 75486534cf5ccc..5fa950a86e6964 100644 --- a/x-pack/test/functional/services/logs_ui/log_stream.ts +++ b/x-pack/test/functional/services/logs_ui/log_stream.ts @@ -15,7 +15,7 @@ export function LogStreamPageProvider({ getPageObjects, getService }: FtrProvide return { async navigateTo(params?: TabsParams['stream']) { - pageObjects.infraLogs.navigateToTab('stream', params); + await pageObjects.infraLogs.navigateToTab('stream', params); }, async getColumnHeaderLabels(): Promise { diff --git a/x-pack/test/functional/services/uptime/alerts.ts b/x-pack/test/functional/services/uptime/alerts.ts index dc10fcccaa6cef..c4f75b843d7816 100644 --- a/x-pack/test/functional/services/uptime/alerts.ts +++ b/x-pack/test/functional/services/uptime/alerts.ts @@ -11,10 +11,14 @@ export function UptimeAlertsProvider({ getService }: FtrProviderContext) { const browser = getService('browser'); return { - async openFlyout() { + async openFlyout(alertType: 'monitorStatus' | 'tls') { await testSubjects.click('xpack.uptime.alertsPopover.toggleButton', 5000); await testSubjects.click('xpack.uptime.openAlertContextPanel', 5000); - await testSubjects.click('xpack.uptime.toggleAlertFlyout', 5000); + if (alertType === 'monitorStatus') { + await testSubjects.click('xpack.uptime.toggleAlertFlyout', 5000); + } else if (alertType === 'tls') { + await testSubjects.click('xpack.uptime.toggleTlsAlertFlyout'); + } }, async openMonitorStatusAlertType(alertType: string) { return testSubjects.click(`xpack.uptime.alerts.${alertType}-SelectOption`, 5000); diff --git a/x-pack/test/functional/services/uptime/monitor.ts b/x-pack/test/functional/services/uptime/monitor.ts index a3e3d953e2eb76..b6689737e8618b 100644 --- a/x-pack/test/functional/services/uptime/monitor.ts +++ b/x-pack/test/functional/services/uptime/monitor.ts @@ -38,8 +38,9 @@ export function UptimeMonitorProvider({ getService }: FtrProviderContext) { async checkForPingListTimestamps(timestamps: string[]): Promise { return retry.tryForTime(10000, async () => { await Promise.all( - timestamps.map(timestamp => - testSubjects.existOrFail(`xpack.uptime.pingList.ping-${timestamp}`) + timestamps.map( + async timestamp => + await testSubjects.existOrFail(`xpack.uptime.pingList.ping-${timestamp}`) ) ); }); diff --git a/x-pack/test/functional/services/uptime/navigation.ts b/x-pack/test/functional/services/uptime/navigation.ts index 13d3cc62183bda..37cc71d6865b02 100644 --- a/x-pack/test/functional/services/uptime/navigation.ts +++ b/x-pack/test/functional/services/uptime/navigation.ts @@ -65,8 +65,8 @@ export function UptimeNavigationProvider({ getService, getPageObjects }: FtrProv }, goToCertificates: async () => { - await testSubjects.click('uptimeCertificatesLink'); - return retry.tryForTime(30 * 1000, async () => { + await testSubjects.click('uptimeCertificatesLink', 10000); + return retry.tryForTime(60 * 1000, async () => { await testSubjects.existOrFail('uptimeCertificatesPage'); }); }, diff --git a/x-pack/test/functional_endpoint/apps/endpoint/policy_list.ts b/x-pack/test/functional_endpoint/apps/endpoint/policy_list.ts index c54eafdd8b787d..28a7cbd2e3c309 100644 --- a/x-pack/test/functional_endpoint/apps/endpoint/policy_list.ts +++ b/x-pack/test/functional_endpoint/apps/endpoint/policy_list.ts @@ -5,45 +5,86 @@ */ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { PolicyTestResourceInfo } from '../../services/endpoint_policy'; export default function({ getPageObjects, getService }: FtrProviderContext) { const pageObjects = getPageObjects(['common', 'endpoint']); const testSubjects = getService('testSubjects'); + const policyTestResources = getService('policyTestResources'); - // FIXME: Skipped until we can figure out how to load data for Ingest - describe.skip('Endpoint Policy List', function() { + describe('When on the Endpoint Policy List', function() { this.tags(['ciGroup7']); before(async () => { await pageObjects.common.navigateToUrlWithBrowserHistory('endpoint', '/policy'); - await pageObjects.endpoint.waitForTableToHaveData('policyTable'); }); it('loads the Policy List Page', async () => { await testSubjects.existOrFail('policyListPage'); }); it('displays page title', async () => { - const policyTitle = await testSubjects.getVisibleText('policyViewTitle'); + const policyTitle = await testSubjects.getVisibleText('pageViewHeaderLeftTitle'); expect(policyTitle).to.equal('Policies'); }); it('shows policy count total', async () => { const policyTotal = await testSubjects.getVisibleText('policyTotalCount'); - expect(policyTotal).to.equal('100 Policies'); - }); - it('includes policy list table', async () => { - await testSubjects.existOrFail('policyTable'); + expect(policyTotal).to.equal('0 Policies'); }); it('has correct table headers', async () => { const allHeaderCells = await pageObjects.endpoint.tableHeaderVisibleText('policyTable'); expect(allHeaderCells).to.eql([ 'Policy Name', - 'Total', - 'Pending', - 'Failed', - 'Created By', - 'Created', - 'Last Updated By', - 'Last Updated', + 'Revision', + 'Version', + 'Description', + 'Agent Configuration', ]); }); + it('should show empty table results message', async () => { + const [, [noItemsFoundMessage]] = await pageObjects.endpoint.getEndpointAppTableData( + 'policyTable' + ); + expect(noItemsFoundMessage).to.equal('No items found'); + }); + + describe('and policies exists', () => { + let policyInfo: PolicyTestResourceInfo; + + before(async () => { + // load/create a policy and then navigate back to the policy view so that the list is refreshed + policyInfo = await policyTestResources.createPolicy(); + await pageObjects.common.navigateToUrlWithBrowserHistory('endpoint', '/policy'); + await pageObjects.endpoint.waitForTableToHaveData('policyTable'); + }); + after(async () => { + if (policyInfo) { + await policyInfo.cleanup(); + } + }); + + it('should show policy on the list', async () => { + const [, policyRow] = await pageObjects.endpoint.getEndpointAppTableData('policyTable'); + expect(policyRow).to.eql([ + 'Protect East Coast', + '1', + 'Elastic Endpoint v1.0.0', + 'Protect the worlds data - but in the East Coast', + policyInfo.agentConfig.id, + ]); + }); + it('should show policy name as link', async () => { + const policyNameLink = await testSubjects.find('policyNameLink'); + expect(await policyNameLink.getTagName()).to.equal('a'); + expect(await policyNameLink.getAttribute('href')).to.match( + new RegExp(`\/endpoint\/policy\/${policyInfo.datasource.id}$`) + ); + }); + it('should show agent configuration as link', async () => { + const agentConfigLink = await testSubjects.find('agentConfigLink'); + expect(await agentConfigLink.getTagName()).to.equal('a'); + expect(await agentConfigLink.getAttribute('href')).to.match( + new RegExp(`\/app\/ingestManager\#\/configs\/${policyInfo.datasource.config_id}$`) + ); + }); + }); }); } diff --git a/x-pack/test/functional_endpoint/config.ts b/x-pack/test/functional_endpoint/config.ts index d7f1cc21828d18..a371c548f3022a 100644 --- a/x-pack/test/functional_endpoint/config.ts +++ b/x-pack/test/functional_endpoint/config.ts @@ -7,6 +7,7 @@ import { resolve } from 'path'; import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; import { pageObjects } from './page_objects'; +import { services } from './services'; export default async function({ readConfigFile }: FtrConfigProviderContext) { const xpackFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js')); @@ -18,6 +19,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { junit: { reportName: 'X-Pack Endpoint Functional Tests', }, + services, apps: { ...xpackFunctionalConfig.get('apps'), endpoint: { diff --git a/x-pack/test/functional_endpoint/ftr_provider_context.d.ts b/x-pack/test/functional_endpoint/ftr_provider_context.d.ts index 21ab5d5a4e554b..bb257cdcbfe1b5 100644 --- a/x-pack/test/functional_endpoint/ftr_provider_context.d.ts +++ b/x-pack/test/functional_endpoint/ftr_provider_context.d.ts @@ -7,6 +7,6 @@ import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; import { pageObjects } from './page_objects'; -import { services } from '../functional/services'; +import { services } from './services'; export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/functional_endpoint/services/endpoint_policy.ts b/x-pack/test/functional_endpoint/services/endpoint_policy.ts new file mode 100644 index 00000000000000..e8e2d9957aa383 --- /dev/null +++ b/x-pack/test/functional_endpoint/services/endpoint_policy.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 { FtrProviderContext } from '../ftr_provider_context'; +import { + CreateAgentConfigResponse, + CreateDatasourceResponse, +} from '../../../plugins/ingest_manager/common'; +import { Immutable } from '../../../plugins/endpoint/common/types'; +import { factory as policyConfigFactory } from '../../../plugins/endpoint/common/models/policy_config'; + +const INGEST_API_ROOT = '/api/ingest_manager'; +const INGEST_API_AGENT_CONFIGS = `${INGEST_API_ROOT}/agent_configs`; +const INGEST_API_AGENT_CONFIGS_DELETE = `${INGEST_API_AGENT_CONFIGS}/delete`; +const INGEST_API_DATASOURCES = `${INGEST_API_ROOT}/datasources`; +const INGEST_API_DATASOURCES_DELETE = `${INGEST_API_DATASOURCES}/delete`; + +/** + * Holds information about the test resources created to support an Endpoint Policy + */ +export interface PolicyTestResourceInfo { + /** The Ingest agent configuration created */ + agentConfig: Immutable; + /** The Ingest datasource created and added to agent configuration. + * This is where Endpoint Policy is stored. + */ + datasource: Immutable; + /** will clean up (delete) the objects created (agent config + datasource) */ + cleanup: () => Promise; +} + +export function EndpointPolicyTestResourcesProvider({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + return { + /** + * Creates an Ingest Agent Configuration and adds to it the Endpoint Datasource that + * stores the Policy configuration data + */ + async createPolicy(): Promise { + // FIXME: Refactor after https://github.com/elastic/kibana/issues/64822 is fixed. `isInitialized` setup below should be deleted + // Due to an issue in Ingest API, we first create the Fleet user. This avoids the Agent Config api throwing a 500 + const isFleetSetupResponse = await supertest + .get('/api/ingest_manager/fleet/setup') + .set('kbn-xsrf', 'xxx') + .expect(200); + if (!isFleetSetupResponse.body.isInitialized) { + await supertest + .post('/api/ingest_manager/fleet/setup') + .set('kbn-xsrf', 'xxx') + .send() + .expect(200); + } + + // create agent config + const { + body: { item: agentConfig }, + }: { body: CreateAgentConfigResponse } = await supertest + .post(INGEST_API_AGENT_CONFIGS) + .set('kbn-xsrf', 'xxx') + .send({ name: 'East Coast', description: 'East Coast call center', namespace: '' }) + .expect(200); + + // create datasource and associated it to agent config + const { + body: { item: datasource }, + }: { body: CreateDatasourceResponse } = await supertest + .post(INGEST_API_DATASOURCES) + .set('kbn-xsrf', 'xxx') + .send({ + name: 'Protect East Coast', + description: 'Protect the worlds data - but in the East Coast', + config_id: agentConfig.id, + enabled: true, + output_id: '', + inputs: [ + // TODO: should we retrieve the latest Endpoint Package and build the input (policy) from that? + { + type: 'endpoint', + enabled: true, + streams: [], + config: { + policy: { + value: policyConfigFactory(), + }, + }, + }, + ], + namespace: '', + package: { + name: 'endpoint', + title: 'Elastic Endpoint', + version: '1.0.0', + }, + }) + .expect(200); + + return { + agentConfig, + datasource, + async cleanup() { + // Delete Datasource + await supertest + .post(INGEST_API_DATASOURCES_DELETE) + .set('kbn-xsrf', 'xxx') + .send({ datasourceIds: [datasource.id] }) + .expect(200); + + // Delete Agent config + await supertest + .post(INGEST_API_AGENT_CONFIGS_DELETE) + .set('kbn-xsrf', 'xxx') + .send({ agentConfigId: agentConfig.id }) + .expect(200); + }, + }; + }, + }; +} diff --git a/x-pack/test/functional_endpoint/services/index.ts b/x-pack/test/functional_endpoint/services/index.ts new file mode 100644 index 00000000000000..0247d9b00968a1 --- /dev/null +++ b/x-pack/test/functional_endpoint/services/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { services as xPackFunctionalServices } from '../../functional/services'; +import { EndpointPolicyTestResourcesProvider } from './endpoint_policy'; + +export const services = { + ...xPackFunctionalServices, + policyTestResources: EndpointPolicyTestResourcesProvider, +}; diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts index 2edab1b164a1bb..bd793883eed906 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts @@ -29,7 +29,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Connectors tab', () => { it('renders the connectors tab', async () => { // Navigate to the connectors tab - pageObjects.triggersActionsUI.changeTabs('connectorsTab'); + await pageObjects.triggersActionsUI.changeTabs('connectorsTab'); await pageObjects.header.waitUntilLoadingHasFinished(); @@ -45,7 +45,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Alerts tab', () => { it('renders the alerts tab', async () => { // Navigate to the alerts tab - pageObjects.triggersActionsUI.changeTabs('alertsTab'); + await pageObjects.triggersActionsUI.changeTabs('alertsTab'); await pageObjects.header.waitUntilLoadingHasFinished(); diff --git a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts index fb4f34d65f9b00..f1883733b02c9c 100644 --- a/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts +++ b/x-pack/test/functional_with_es_ssl/apps/uptime/alert_flyout.ts @@ -8,123 +8,208 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ getPageObjects, getService }: FtrProviderContext) => { - describe('overview page alert flyout controls', function() { - const DEFAULT_DATE_START = 'Sep 10, 2019 @ 12:40:08.078'; - const DEFAULT_DATE_END = 'Sep 11, 2019 @ 19:40:08.078'; + describe('uptime alerts', () => { const pageObjects = getPageObjects(['common', 'uptime']); const supertest = getService('supertest'); const retry = getService('retry'); - let alerts: any; - before(async () => { - alerts = getService('uptime').alerts; - }); + describe('overview page alert flyout controls', function() { + const DEFAULT_DATE_START = 'Sep 10, 2019 @ 12:40:08.078'; + const DEFAULT_DATE_END = 'Sep 11, 2019 @ 19:40:08.078'; + let alerts: any; - it('can open alert flyout', async () => { - await pageObjects.uptime.goToUptimeOverviewAndLoadData(DEFAULT_DATE_START, DEFAULT_DATE_END); - await alerts.openFlyout(); - }); + before(async () => { + alerts = getService('uptime').alerts; + }); - it('can set alert name', async () => { - await alerts.setAlertName('uptime-test'); - }); + it('can open alert flyout', async () => { + await pageObjects.uptime.goToUptimeOverviewAndLoadData( + DEFAULT_DATE_START, + DEFAULT_DATE_END + ); + await alerts.openFlyout('monitorStatus'); + }); - it('can set alert tags', async () => { - await alerts.setAlertTags(['uptime', 'another']); - }); + it('can set alert name', async () => { + await alerts.setAlertName('uptime-test'); + }); - it('can set alert interval', async () => { - await alerts.setAlertInterval('11'); - }); + it('can set alert tags', async () => { + await alerts.setAlertTags(['uptime', 'another']); + }); - it('can set alert throttle interval', async () => { - await alerts.setAlertThrottleInterval('30'); - }); + it('can set alert interval', async () => { + await alerts.setAlertInterval('11'); + }); - it('can set alert status number of time', async () => { - await alerts.setAlertStatusNumTimes('3'); - }); - it('can set alert time range', async () => { - await alerts.setAlertTimerangeSelection('1'); - }); - it('can set monitor hours', async () => { - await alerts.setMonitorStatusSelectableToHours(); - }); + it('can set alert throttle interval', async () => { + await alerts.setAlertThrottleInterval('30'); + }); - it('can set kuery bar filters', async () => { - await pageObjects.uptime.setAlertKueryBarText('monitor.id: "0001-up"'); - }); + it('can set alert status number of time', async () => { + await alerts.setAlertStatusNumTimes('3'); + }); - it('can select location filter', async () => { - await alerts.clickAddFilterLocation(); - await alerts.clickLocationExpression('mpls'); - }); + it('can set alert time range', async () => { + await alerts.setAlertTimerangeSelection('1'); + }); - it('can select port filter', async () => { - await alerts.clickAddFilterPort(); - await alerts.clickPortExpression('5678'); - }); + it('can set monitor hours', async () => { + await alerts.setMonitorStatusSelectableToHours(); + }); - it('can select type/scheme filter', async () => { - await alerts.clickAddFilterType(); - await alerts.clickTypeExpression('http'); - }); + it('can set kuery bar filters', async () => { + await pageObjects.uptime.setAlertKueryBarText('monitor.id: "0001-up"'); + }); + + it('can select location filter', async () => { + await alerts.clickAddFilterLocation(); + await alerts.clickLocationExpression('mpls'); + }); + + it('can select port filter', async () => { + await alerts.clickAddFilterPort(); + await alerts.clickPortExpression('5678'); + }); + + it('can select type/scheme filter', async () => { + await alerts.clickAddFilterType(); + await alerts.clickTypeExpression('http'); + }); + + it('can save alert', async () => { + await alerts.clickSaveAlertButton(); + }); - it('can save alert', async () => { - await alerts.clickSaveAlertButton(); + it('posts an alert, verifies its presence, and deletes the alert', async () => { + // The creation of the alert could take some time, so the first few times we query after + // the previous line resolves, the API may not be done creating the alert yet, so we + // put the fetch code in a retry block with a timeout. + let alert: any; + await retry.tryForTime(15000, async () => { + const apiResponse = await supertest.get('/api/alert/_find?search=uptime-test'); + const alertsFromThisTest = apiResponse.body.data.filter( + ({ name }: { name: string }) => name === 'uptime-test' + ); + expect(alertsFromThisTest).to.have.length(1); + alert = alertsFromThisTest[0]; + }); + + // Ensure the parameters and other stateful data + // on the alert match up with the values we provided + // for our test helper to input into the flyout. + const { + actions, + alertTypeId, + consumer, + id, + params: { numTimes, timerange, locations, filters }, + schedule: { interval }, + tags, + } = alert; + + try { + // we're not testing the flyout's ability to associate alerts with action connectors + expect(actions).to.eql([]); + + expect(alertTypeId).to.eql('xpack.uptime.alerts.monitorStatus'); + expect(consumer).to.eql('uptime'); + expect(interval).to.eql('11m'); + expect(tags).to.eql(['uptime', 'another']); + expect(numTimes).to.be(3); + expect(timerange.from).to.be('now-1h'); + expect(timerange.to).to.be('now'); + expect(locations).to.eql(['mpls']); + expect(filters).to.eql( + '{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"monitor.id":"0001-up"}}],' + + '"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"match":{"observer.geo.name":"mpls"}}],' + + '"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"match":{"url.port":5678}}],' + + '"minimum_should_match":1}},{"bool":{"should":[{"match":{"monitor.type":"http"}}],"minimum_should_match":1}}]}}]}}]}}' + ); + } finally { + await supertest + .delete(`/api/alert/${id}`) + .set('kbn-xsrf', 'true') + .expect(204); + } + }); }); - it('posts an alert, verifies its presence, and deletes the alert', async () => { - // The creation of the alert could take some time, so the first few times we query after - // the previous line resolves, the API may not be done creating the alert yet, so we - // put the fetch code in a retry block with a timeout. - let alert: any; - await retry.tryForTime(15000, async () => { - const apiResponse = await supertest.get('/api/alert/_find?search=uptime-test'); - const alertsFromThisTest = apiResponse.body.data.filter( - ({ name }: { name: string }) => name === 'uptime-test' - ); - expect(alertsFromThisTest).to.have.length(1); - alert = alertsFromThisTest[0]; - }); - - // Ensure the parameters and other stateful data - // on the alert match up with the values we provided - // for our test helper to input into the flyout. - const { - actions, - alertTypeId, - consumer, - id, - params: { numTimes, timerange, locations, filters }, - schedule: { interval }, - tags, - } = alert; - - try { - // we're not testing the flyout's ability to associate alerts with action connectors - expect(actions).to.eql([]); - - expect(alertTypeId).to.eql('xpack.uptime.alerts.monitorStatus'); - expect(consumer).to.eql('uptime'); - expect(interval).to.eql('11m'); - expect(tags).to.eql(['uptime', 'another']); - expect(numTimes).to.be(3); - expect(timerange.from).to.be('now-1h'); - expect(timerange.to).to.be('now'); - expect(locations).to.eql(['mpls']); - expect(filters).to.eql( - '{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"monitor.id":"0001-up"}}],' + - '"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"match":{"observer.geo.name":"mpls"}}],' + - '"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"match":{"url.port":5678}}],' + - '"minimum_should_match":1}},{"bool":{"should":[{"match":{"monitor.type":"http"}}],"minimum_should_match":1}}]}}]}}]}}' + describe('tls alert', function() { + const DEFAULT_DATE_START = 'Sep 10, 2019 @ 12:40:08.078'; + const DEFAULT_DATE_END = 'Sep 11, 2019 @ 19:40:08.078'; + let alerts: any; + const alertId = 'uptime-tls'; + + before(async () => { + alerts = getService('uptime').alerts; + }); + + it('can open alert flyout', async () => { + await pageObjects.uptime.goToUptimeOverviewAndLoadData( + DEFAULT_DATE_START, + DEFAULT_DATE_END ); - } finally { - await supertest - .delete(`/api/alert/${id}`) - .set('kbn-xsrf', 'true') - .expect(204); - } + await alerts.openFlyout('tls'); + }); + + it('can set alert name', async () => { + await alerts.setAlertName(alertId); + }); + + it('can set alert tags', async () => { + await alerts.setAlertTags(['uptime', 'certs']); + }); + + it('can set alert interval', async () => { + await alerts.setAlertInterval('11'); + }); + + it('can set alert throttle interval', async () => { + await alerts.setAlertThrottleInterval('30'); + }); + + it('can save alert', async () => { + await alerts.clickSaveAlertButton(); + }); + + it('has created a valid alert with expected parameters', async () => { + let alert: any; + await retry.tryForTime(15000, async () => { + const apiResponse = await supertest.get(`/api/alert/_find?search=${alertId}`); + const alertsFromThisTest = apiResponse.body.data.filter( + ({ name }: { name: string }) => name === alertId + ); + expect(alertsFromThisTest).to.have.length(1); + alert = alertsFromThisTest[0]; + }); + + // Ensure the parameters and other stateful data + // on the alert match up with the values we provided + // for our test helper to input into the flyout. + const { + actions, + alertTypeId, + consumer, + id, + params, + schedule: { interval }, + tags, + } = alert; + try { + expect(actions).to.eql([]); + expect(alertTypeId).to.eql('xpack.uptime.alerts.tls'); + expect(consumer).to.eql('uptime'); + expect(tags).to.eql(['uptime', 'certs']); + expect(params).to.eql({}); + expect(interval).to.eql('11m'); + } finally { + await supertest + .delete(`/api/alert/${id}`) + .set('kbn-xsrf', 'true') + .expect(204); + } + }); }); }); }; diff --git a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts index ca7f064e206900..2cd094f9045c59 100644 --- a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts +++ b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts @@ -120,7 +120,7 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) await find.clickDisplayedByCssSelector(`[data-test-subj="alertsList"] [title="${name}"]`); }, async changeTabs(tab: 'alertsTab' | 'connectorsTab') { - return await testSubjects.click(tab); + await testSubjects.click(tab); }, async toggleSwitch(testSubject: string) { const switchBtn = await testSubjects.find(testSubject); diff --git a/x-pack/test/kerberos_api_integration/anonymous_access.config.ts b/x-pack/test/kerberos_api_integration/anonymous_access.config.ts index 90d47ec61a4dc9..8b712afe6c4d65 100644 --- a/x-pack/test/kerberos_api_integration/anonymous_access.config.ts +++ b/x-pack/test/kerberos_api_integration/anonymous_access.config.ts @@ -21,7 +21,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { serverArgs: [ ...kerberosAPITestsConfig.get('esTestCluster.serverArgs'), 'xpack.security.authc.anonymous.username=anonymous_user', - 'xpack.security.authc.anonymous.roles=superuser', + 'xpack.security.authc.anonymous.roles=superuser_anonymous', ], }, }; diff --git a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts index fbf9a977e8b1f0..e81db7e2b02f39 100644 --- a/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts +++ b/x-pack/test/kerberos_api_integration/apis/security/kerberos_login.ts @@ -100,9 +100,7 @@ export default function({ getService }: FtrProviderContext) { }); }); - // Preventing ES Snapshot to be promoted - // https://github.com/elastic/kibana/issues/65114 - describe.skip('finishing SPNEGO', () => { + describe('finishing SPNEGO', () => { it('should properly set cookie and authenticate user', async () => { const response = await supertest .get('/internal/security/me') @@ -120,13 +118,22 @@ export default function({ getService }: FtrProviderContext) { const sessionCookie = request.cookie(cookies[0])!; checkCookieIsSet(sessionCookie); + const isAnonymousAccessEnabled = (config.get( + 'esTestCluster.serverArgs' + ) as string[]).some(setting => setting.startsWith('xpack.security.authc.anonymous')); + + // `superuser_anonymous` role is derived from the enabled anonymous access. + const expectedUserRoles = isAnonymousAccessEnabled + ? ['kibana_admin', 'superuser_anonymous'] + : ['kibana_admin']; + await supertest .get('/internal/security/me') .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(200, { username: 'tester@TEST.ELASTIC.CO', - roles: ['kibana_admin'], + roles: expectedUserRoles, full_name: null, email: null, metadata: { diff --git a/x-pack/test/licensing_plugin/config.public.ts b/x-pack/test/licensing_plugin/config.public.ts index 42209aa49bcb47..adde6320119d1d 100644 --- a/x-pack/test/licensing_plugin/config.public.ts +++ b/x-pack/test/licensing_plugin/config.public.ts @@ -14,9 +14,9 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { ...commonConfig.getAll(), testFiles: [require.resolve('./public')], kbnTestServer: { + ...commonConfig.get('kbnTestServer'), serverArgs: [ ...commonConfig.get('kbnTestServer.serverArgs'), - // Required to load new platform plugin provider via `--plugin-path` flag. '--env.name=development', `--plugin-path=${path.resolve( diff --git a/x-pack/test/saml_api_integration/apis/security/saml_login.ts b/x-pack/test/saml_api_integration/apis/security/saml_login.ts index 0b127288e79583..0684a5e572f555 100644 --- a/x-pack/test/saml_api_integration/apis/security/saml_login.ts +++ b/x-pack/test/saml_api_integration/apis/security/saml_login.ts @@ -515,7 +515,9 @@ export default function({ getService }: FtrProviderContext) { describe('API access with expired access token.', () => { let sessionCookie: Cookie; - beforeEach(async () => { + beforeEach(async function() { + this.timeout(40000); + const captureURLResponse = await supertest .get('/abc/xyz/handshake?one=two three') .expect(302); @@ -539,6 +541,10 @@ export default function({ getService }: FtrProviderContext) { .expect(302); sessionCookie = request.cookie(samlAuthenticationResponse.headers['set-cookie'][0])!; + + // Access token expiration is set to 15s for API integration tests. + // Let's wait for 20s to make sure token expires. + await delay(20000); }); const expectNewSessionCookie = (cookie: Cookie) => { @@ -549,13 +555,7 @@ export default function({ getService }: FtrProviderContext) { expect(cookie.value).to.not.be(sessionCookie.value); }; - it('expired access token should be automatically refreshed', async function() { - this.timeout(40000); - - // Access token expiration is set to 15s for API integration tests. - // Let's wait for 20s to make sure token expires. - await delay(20000); - + it('expired access token should be automatically refreshed', async () => { // This api call should succeed and automatically refresh token. Returned cookie will contain // the new access and refresh token pair. const firstResponse = await supertest @@ -600,6 +600,19 @@ export default function({ getService }: FtrProviderContext) { .set('Cookie', secondNewCookie.cookieString()) .expect(200); }); + + it('should refresh access token even if multiple concurrent requests try to refresh it', async () => { + // Send 5 concurrent requests with a cookie that contains an expired access token. + await Promise.all( + Array.from({ length: 5 }).map((value, index) => + supertest + .get(`/internal/security/me?a=${index}`) + .set('kbn-xsrf', 'xxx') + .set('Cookie', sessionCookie.cookieString()) + .expect(200) + ) + ); + }); }); describe('API access with missing access token document.', () => { @@ -629,9 +642,7 @@ export default function({ getService }: FtrProviderContext) { .expect(302); sessionCookie = request.cookie(samlAuthenticationResponse.headers['set-cookie'][0])!; - }); - it('should properly set cookie and start new SAML handshake', async function() { // Let's delete tokens from `.security` index directly to simulate the case when // Elasticsearch automatically removes access/refresh token document from the index // after some period of time. @@ -643,7 +654,9 @@ export default function({ getService }: FtrProviderContext) { expect(esResponse) .to.have.property('deleted') .greaterThan(0); + }); + it('should properly set cookie and start new SAML handshake', async () => { const handshakeResponse = await supertest .get('/abc/xyz/handshake?one=two three') .set('Cookie', sessionCookie.cookieString()) @@ -662,6 +675,19 @@ export default function({ getService }: FtrProviderContext) { '/internal/security/saml/capture-url-fragment' ); }); + + it('should start new SAML handshake even if multiple concurrent requests try to refresh access token', async () => { + // Issue 5 concurrent requests with a cookie that contains access/refresh token pair without + // a corresponding document in Elasticsearch. + await Promise.all( + Array.from({ length: 5 }).map((value, index) => + supertest + .get(`/abc/xyz/handshake?one=two three&a=${index}`) + .set('Cookie', sessionCookie.cookieString()) + .expect(302) + ) + ); + }); }); describe('IdP initiated login with active session', () => {