diff --git a/.backportrc.json b/.backportrc.json index 30b18b7561eaa..af064451595c8 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -1,5 +1,5 @@ { "upstream": "elastic/kibana", - "branches": [{ "name": "7.x", "checked": true }, "7.4", "7.3", "7.2", "7.1", "7.0", "6.8", "6.7", "6.6", "6.5", "6.4", "6.3", "6.2", "6.1", "6.0", "5.6"], + "branches": [{ "name": "7.x", "checked": true }, "7.5", "7.4", "7.3", "7.2", "7.1", "7.0", "6.8", "6.7", "6.6", "6.5", "6.4", "6.3", "6.2", "6.1", "6.0", "5.6"], "labels": ["backport"] } diff --git a/.eslintrc.js b/.eslintrc.js index 56950a70970d4..12bdd11fc8528 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -150,6 +150,9 @@ module.exports = { '!src/core/server/index.ts', '!src/core/server/mocks.ts', '!src/core/server/types.ts', + // for absolute imports until fixed in + // https://github.com/elastic/kibana/issues/36096 + '!src/core/server/types', '!src/core/server/*.test.mocks.ts', 'src/plugins/**/public/**/*', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4c06d2aee017b..012e49690fd15 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -29,6 +29,7 @@ # Logs & Metrics UI /x-pack/legacy/plugins/infra/ @elastic/logs-metrics-ui +/x-pack/legacy/plugins/integrations_manager/ @elastic/epm # Machine Learning /x-pack/legacy/plugins/ml/ @elastic/ml-ui diff --git a/docs/api/dashboard-api.asciidoc b/docs/api/dashboard-api.asciidoc new file mode 100644 index 0000000000000..50c2abc975763 --- /dev/null +++ b/docs/api/dashboard-api.asciidoc @@ -0,0 +1,17 @@ +[[dashboard-api]] +== Import and export dashboard APIs + +Import and export dashboards with the corresponding saved objects, such as visualizations, saved +searches, and index patterns. + +WARNING: Do not write documents directly to the `.kibana` index. When you write directly +to the `.kibana` index, the data becomes corrupted and permanently breaks future {kib} versions. + +The following import and export dashboard APIs are available: + +* <> to import dashboards and corresponding saved objects + +* <> to export dashboards and corresponding saved objects + +include::dashboard/import-dashboard.asciidoc[] +include::dashboard/export-dashboard.asciidoc[] \ No newline at end of file diff --git a/docs/api/dashboard-import.asciidoc b/docs/api/dashboard-import.asciidoc deleted file mode 100644 index 19eb6f2a446b5..0000000000000 --- a/docs/api/dashboard-import.asciidoc +++ /dev/null @@ -1,14 +0,0 @@ -[[dashboard-import-api]] -== Dashboard import and export APIs - -Import and export dashboards with the corresponding saved objects, such as visualizations, saved -searches, and index patterns. - -WARNING: Do not write documents directly to the `.kibana` index. When you write directly -to the `.kibana` index, the data becomes corrupted and permanently breaks future {kib} versions. - -* <> -* <> - -include::dashboard-import/import.asciidoc[] -include::dashboard-import/export.asciidoc[] diff --git a/docs/api/dashboard-import/export.asciidoc b/docs/api/dashboard/export-dashboard.asciidoc similarity index 85% rename from docs/api/dashboard-import/export.asciidoc rename to docs/api/dashboard/export-dashboard.asciidoc index 13a0ffe8febeb..7858b69d44c79 100644 --- a/docs/api/dashboard-import/export.asciidoc +++ b/docs/api/dashboard/export-dashboard.asciidoc @@ -1,7 +1,7 @@ [[dashboard-api-export]] === Export dashboard API ++++ -Dashboard export +Export dashboard ++++ experimental[] Export dashboards and corresponding saved objects. @@ -21,7 +21,7 @@ experimental[] Export dashboards and corresponding saved objects. ==== Response body `objects`:: - (array) A top level property that includes the saved objects. The order of the objects is not guaranteed. Use the exact response body as the request body for the corresponding <>. + (array) A top level property that includes the saved objects. The order of the objects is not guaranteed. Use the exact response body as the request body for the corresponding <>. [[dashboard-api-export-codes]] ==== Response code @@ -39,4 +39,4 @@ GET api/kibana/dashboards/export?dashboard=942dcef0-b2cd-11e8-ad8e-85441f0c2e5c -------------------------------------------------- // KIBANA -<1> In this example, `942dcef0-b2cd-11e8-ad8e-85441f0c2e5c` is the dashboard ID. +<1> The dashboard ID is `942dcef0-b2cd-11e8-ad8e-85441f0c2e5c`. \ No newline at end of file diff --git a/docs/api/dashboard-import/import.asciidoc b/docs/api/dashboard/import-dashboard.asciidoc similarity index 99% rename from docs/api/dashboard-import/import.asciidoc rename to docs/api/dashboard/import-dashboard.asciidoc index 9ff84abb51c8e..0c6ea2bcf5933 100644 --- a/docs/api/dashboard-import/import.asciidoc +++ b/docs/api/dashboard/import-dashboard.asciidoc @@ -1,4 +1,4 @@ -[[dashboard-import-api-import]] +[[dashboard-import-api]] === Import dashboard API ++++ Import dashboard diff --git a/docs/api/features.asciidoc b/docs/api/features.asciidoc index 798f2be3d7212..7928f7f34aad7 100644 --- a/docs/api/features.asciidoc +++ b/docs/api/features.asciidoc @@ -1,9 +1,217 @@ [role="xpack"] -[[features-api]] -== Features API +[[features-api-get]] +== Get features API -View information about the available features in {kib}. Features are used by spaces and security to refine and secure access to {kib}. +experimental[] Retrieves all {kib} features. Features are used by spaces and security to refine and secure access to {kib}. -* <> +[float] +[[features-api-get-request]] +=== Request -include::features/get.asciidoc[] +`GET /api/features` + +[float] +[[features-api-get-codes]] +=== Response code + +`200`:: + Indicates a successful call. + +[float] +[[features-api-get-example]] +=== Example + +The API returns the following: + +[source,js] +-------------------------------------------------- + { + "id": "discover", + "name": "Discover", + "icon": "discoverApp", + "navLinkId": "kibana:discover", + "app": [ + "kibana" + ], + "catalogue": [ + "discover" + ], + "privileges": { + "all": { + "savedObject": { + "all": [ + "search", + "url" + ], + "read": [ + "config", + "index-pattern" + ] + }, + "ui": [ + "show", + "createShortUrl", + "save" + ] + }, + "read": { + "savedObject": { + "all": [], + "read": [ + "config", + "index-pattern", + "search", + "url" + ] + }, + "ui": [ + "show" + ] + } + } + }, + { + "id": "visualize", + "name": "Visualize", + "icon": "visualizeApp", + "navLinkId": "kibana:visualize", + "app": [ + "kibana" + ], + "catalogue": [ + "visualize" + ], + "privileges": { + "all": { + "savedObject": { + "all": [ + "visualization", + "url" + ], + "read": [ + "config", + "index-pattern", + "search" + ] + }, + "ui": [ + "show", + "createShortUrl", + "delete", + "save" + ] + }, + "read": { + "savedObject": { + "all": [], + "read": [ + "config", + "index-pattern", + "search", + "visualization" + ] + }, + "ui": [ + "show" + ] + } + } + }, + { + "id": "dashboard", + "name": "Dashboard", + "icon": "dashboardApp", + "navLinkId": "kibana:dashboard", + "app": [ + "kibana" + ], + "catalogue": [ + "dashboard" + ], + "privileges": { + "all": { + "savedObject": { + "all": [ + "dashboard", + "url" + ], + "read": [ + "config", + "index-pattern", + "search", + "visualization", + "timelion-sheet", + "canvas-workpad" + ] + }, + "ui": [ + "createNew", + "show", + "showWriteControls" + ] + }, + "read": { + "savedObject": { + "all": [], + "read": [ + "config", + "index-pattern", + "search", + "visualization", + "timelion-sheet", + "canvas-workpad", + "dashboard" + ] + }, + "ui": [ + "show" + ] + } + } + }, + { + "id": "dev_tools", + "name": "Dev Tools", + "icon": "devToolsApp", + "navLinkId": "kibana:dev_tools", + "app": [ + "kibana" + ], + "catalogue": [ + "console", + "searchprofiler", + "grokdebugger" + ], + "privileges": { + "all": { + "api": [ + "console" + ], + "savedObject": { + "all": [], + "read": [ + "config" + ] + }, + "ui": [ + "show" + ] + }, + "read": { + "api": [ + "console" + ], + "savedObject": { + "all": [], + "read": [ + "config" + ] + }, + "ui": [ + "show" + ] + } + }, + "privilegesTooltip": "User should also be granted the appropriate Elasticsearch cluster and index privileges" + }, +-------------------------------------------------- diff --git a/docs/api/features/get.asciidoc b/docs/api/features/get.asciidoc deleted file mode 100644 index fc2c0446d82a8..0000000000000 --- a/docs/api/features/get.asciidoc +++ /dev/null @@ -1,216 +0,0 @@ -[[features-api-get]] -=== Get features API -++++ -Get features -++++ - -experimental[] Retrieves all {kib} features. - -[[features-api-get-request]] -==== Request - -`GET /api/features` - -[[features-api-get-codes]] -==== Response code - -`200`:: - Indicates a successful call. - -[[features-api-get-example]] -==== Example - -The API returns the following: - -[source,js] --------------------------------------------------- - { - "id": "discover", - "name": "Discover", - "icon": "discoverApp", - "navLinkId": "kibana:discover", - "app": [ - "kibana" - ], - "catalogue": [ - "discover" - ], - "privileges": { - "all": { - "savedObject": { - "all": [ - "search", - "url" - ], - "read": [ - "config", - "index-pattern" - ] - }, - "ui": [ - "show", - "createShortUrl", - "save" - ] - }, - "read": { - "savedObject": { - "all": [], - "read": [ - "config", - "index-pattern", - "search", - "url" - ] - }, - "ui": [ - "show" - ] - } - } - }, - { - "id": "visualize", - "name": "Visualize", - "icon": "visualizeApp", - "navLinkId": "kibana:visualize", - "app": [ - "kibana" - ], - "catalogue": [ - "visualize" - ], - "privileges": { - "all": { - "savedObject": { - "all": [ - "visualization", - "url" - ], - "read": [ - "config", - "index-pattern", - "search" - ] - }, - "ui": [ - "show", - "createShortUrl", - "delete", - "save" - ] - }, - "read": { - "savedObject": { - "all": [], - "read": [ - "config", - "index-pattern", - "search", - "visualization" - ] - }, - "ui": [ - "show" - ] - } - } - }, - { - "id": "dashboard", - "name": "Dashboard", - "icon": "dashboardApp", - "navLinkId": "kibana:dashboard", - "app": [ - "kibana" - ], - "catalogue": [ - "dashboard" - ], - "privileges": { - "all": { - "savedObject": { - "all": [ - "dashboard", - "url" - ], - "read": [ - "config", - "index-pattern", - "search", - "visualization", - "timelion-sheet", - "canvas-workpad" - ] - }, - "ui": [ - "createNew", - "show", - "showWriteControls" - ] - }, - "read": { - "savedObject": { - "all": [], - "read": [ - "config", - "index-pattern", - "search", - "visualization", - "timelion-sheet", - "canvas-workpad", - "dashboard" - ] - }, - "ui": [ - "show" - ] - } - } - }, - { - "id": "dev_tools", - "name": "Dev Tools", - "icon": "devToolsApp", - "navLinkId": "kibana:dev_tools", - "app": [ - "kibana" - ], - "catalogue": [ - "console", - "searchprofiler", - "grokdebugger" - ], - "privileges": { - "all": { - "api": [ - "console" - ], - "savedObject": { - "all": [], - "read": [ - "config" - ] - }, - "ui": [ - "show" - ] - }, - "read": { - "api": [ - "console" - ], - "savedObject": { - "all": [], - "read": [ - "config" - ] - }, - "ui": [ - "show" - ] - } - }, - "privilegesTooltip": "User should also be granted the appropriate Elasticsearch cluster and index privileges" - }, --------------------------------------------------- diff --git a/docs/api/logstash-configuration-management.asciidoc b/docs/api/logstash-configuration-management.asciidoc index e53218ec439aa..fbb45095c214b 100644 --- a/docs/api/logstash-configuration-management.asciidoc +++ b/docs/api/logstash-configuration-management.asciidoc @@ -6,12 +6,19 @@ Programmatically integrate with the Logstash configuration management feature. WARNING: Do not directly access the `.logstash` index. The structure of the `.logstash` index is subject to change, which could cause your integration to break. Instead, use the Logstash configuration management APIs. -* <> -* <> -* <> -* <> - -include::logstash-configuration-management/create.asciidoc[] -include::logstash-configuration-management/retrieve.asciidoc[] -include::logstash-configuration-management/delete.asciidoc[] -include::logstash-configuration-management/list.asciidoc[] \ No newline at end of file +The following Logstash configuration management APIs are available: + +* <> to delete a centrally-managed Logstash pipeline + +* <> to list all centrally-managed Logstash pipelines + +* <> to create a centrally-managed Logstash pipeline, or update an existing pipeline + +* <> to retrieve a centrally-managed Logstash pipeline + +include::logstash-configuration-management/delete-pipeline.asciidoc[] +include::logstash-configuration-management/list-pipeline.asciidoc[] +include::logstash-configuration-management/create-logstash.asciidoc[] +include::logstash-configuration-management/retrieve-pipeline.asciidoc[] + + diff --git a/docs/api/logstash-configuration-management/create.asciidoc b/docs/api/logstash-configuration-management/create-logstash.asciidoc similarity index 96% rename from docs/api/logstash-configuration-management/create.asciidoc rename to docs/api/logstash-configuration-management/create-logstash.asciidoc index d6fbfd4947bab..38e0ee12a0ebf 100644 --- a/docs/api/logstash-configuration-management/create.asciidoc +++ b/docs/api/logstash-configuration-management/create-logstash.asciidoc @@ -1,8 +1,7 @@ -[role="xpack"] [[logstash-configuration-management-api-create]] === Create Logstash pipeline API ++++ -Create pipeline +Create Logstash pipeline ++++ experimental[] Create a centrally-managed Logstash pipeline, or update an existing pipeline. diff --git a/docs/api/logstash-configuration-management/delete.asciidoc b/docs/api/logstash-configuration-management/delete-pipeline.asciidoc similarity index 97% rename from docs/api/logstash-configuration-management/delete.asciidoc rename to docs/api/logstash-configuration-management/delete-pipeline.asciidoc index c5aadb495ee15..15d44034b46fe 100644 --- a/docs/api/logstash-configuration-management/delete.asciidoc +++ b/docs/api/logstash-configuration-management/delete-pipeline.asciidoc @@ -1,4 +1,3 @@ -[role="xpack"] [[logstash-configuration-management-api-delete]] === Delete Logstash pipeline API ++++ @@ -31,4 +30,5 @@ experimental[] Delete a centrally-managed Logstash pipeline. -------------------------------------------------- DELETE api/logstash/pipeline/hello-world -------------------------------------------------- -// KIBANA \ No newline at end of file +// KIBANA + diff --git a/docs/api/logstash-configuration-management/list.asciidoc b/docs/api/logstash-configuration-management/list-pipeline.asciidoc similarity index 84% rename from docs/api/logstash-configuration-management/list.asciidoc rename to docs/api/logstash-configuration-management/list-pipeline.asciidoc index a11199d7b8dbc..7140c35d89853 100644 --- a/docs/api/logstash-configuration-management/list.asciidoc +++ b/docs/api/logstash-configuration-management/list-pipeline.asciidoc @@ -1,4 +1,3 @@ -[role="xpack"] [[logstash-configuration-management-api-list]] === List Logstash pipeline API ++++ @@ -36,4 +35,4 @@ The API returns the following: } -------------------------------------------------- -<1> The `username` property may or may not be present, depending on if security was enabled when the pipeline was created or last updated. +<1> The `username` property appears when security is enabled, and depends on when the pipeline was created or last updated. \ No newline at end of file diff --git a/docs/api/logstash-configuration-management/retrieve.asciidoc b/docs/api/logstash-configuration-management/retrieve-pipeline.asciidoc similarity index 85% rename from docs/api/logstash-configuration-management/retrieve.asciidoc rename to docs/api/logstash-configuration-management/retrieve-pipeline.asciidoc index 3bc5f13c56ac1..93a1ec3aa1da5 100644 --- a/docs/api/logstash-configuration-management/retrieve.asciidoc +++ b/docs/api/logstash-configuration-management/retrieve-pipeline.asciidoc @@ -1,11 +1,10 @@ -[role="xpack"] [[logstash-configuration-management-api-retrieve]] === Retrieve pipeline API ++++ Retrieve pipeline ++++ -experimental[] Retrieves a centrally-managed Logstash pipeline. +experimental[] Retrieve a centrally-managed Logstash pipeline. [[logstash-configuration-management-api-retrieve-request]] ==== Request @@ -34,4 +33,4 @@ The API returns the following: "queue.type": "persistent" } } --------------------------------------------------- +-------------------------------------------------- \ No newline at end of file diff --git a/docs/api/role-management.asciidoc b/docs/api/role-management.asciidoc index 622d26571cd6e..482d1a9b3cdd3 100644 --- a/docs/api/role-management.asciidoc +++ b/docs/api/role-management.asciidoc @@ -1,15 +1,20 @@ [role="xpack"] [[role-management-api]] -== Kibana role management APIs +== {kib} role management APIs Manage the roles that grant <>. WARNING: Do not use the {ref}/security-api.html#security-role-apis[{es} role management APIs] to manage {kib} roles. -* <> -* <> -* <> -* <> +The following {kib} role management APIs are available: + +* <> to create a new {kib} role, or update the attributes of an existing role + +* <> to retrieve all {kib} roles + +* <> to retrieve a specific role + +* <> to delete a {kib} role include::role-management/put.asciidoc[] include::role-management/get.asciidoc[] diff --git a/docs/api/role-management/delete.asciidoc b/docs/api/role-management/delete.asciidoc index eb8ab183576fd..acf2e4a3e3f1f 100644 --- a/docs/api/role-management/delete.asciidoc +++ b/docs/api/role-management/delete.asciidoc @@ -4,7 +4,7 @@ Delete role ++++ -Deletes a {kib} role. +Delete a {kib} role. experimental["The underlying mechanism of enforcing role-based access control is stable, but the APIs for managing the roles are experimental."] diff --git a/docs/api/role-management/get-all.asciidoc b/docs/api/role-management/get-all.asciidoc index b318fecb3b4e1..4a3dbd7734d3a 100644 --- a/docs/api/role-management/get-all.asciidoc +++ b/docs/api/role-management/get-all.asciidoc @@ -4,7 +4,7 @@ Get all roles ++++ -Retrieves all {kib} roles. +Retrieve all {kib} roles. experimental["The underlying mechanism of enforcing role-based access control is stable, but the APIs for managing the roles are experimental."] diff --git a/docs/api/role-management/get.asciidoc b/docs/api/role-management/get.asciidoc index 34d7c256bd564..44423b01abe5b 100644 --- a/docs/api/role-management/get.asciidoc +++ b/docs/api/role-management/get.asciidoc @@ -4,7 +4,7 @@ Get specific role ++++ -Retrieves a specific role. +Retrieve a specific role. experimental["The underlying mechanism of enforcing role-based access control is stable, but the APIs for managing the roles are experimental."] diff --git a/docs/api/role-management/put.asciidoc b/docs/api/role-management/put.asciidoc index af7544f8ecb46..67ec15892afe4 100644 --- a/docs/api/role-management/put.asciidoc +++ b/docs/api/role-management/put.asciidoc @@ -4,7 +4,7 @@ Create or update role ++++ -Creates a new {kib} role, or updates the attributes of an existing role. {kib} roles are stored in the +Create a new {kib} role, or update the attributes of an existing role. {kib} roles are stored in the {es} native realm. experimental["The underlying mechanism of enforcing role-based access control is stable, but the APIs for managing the roles are experimental."] @@ -40,7 +40,7 @@ To use the create or update role API, you must have the `manage_security` cluste `feature` ::: (object) Contains privileges for specific features. When the `feature` privileges are specified, you are unable to use the `base` section. - To retrieve a list of available features, use the <>. + To retrieve a list of available features, use the <>. `spaces` ::: (list) The spaces to apply the privileges to. diff --git a/docs/api/saved-objects.asciidoc b/docs/api/saved-objects.asciidoc index 6c7e015f9f81c..a4e9fa32f8a5c 100644 --- a/docs/api/saved-objects.asciidoc +++ b/docs/api/saved-objects.asciidoc @@ -6,16 +6,27 @@ Manage {kib} saved objects, including dashboards, visualizations, index patterns WARNING: Do not write documents directly to the `.kibana` index. When you write directly to the `.kibana` index, the data becomes corrupted and permanently breaks future {kib} versions. -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> +The following saved objects APIs are available: + +* <> to retrieve a single {kib} saved object by ID + +* <> to retrieve multiple {kib} saved objects by ID + +* <> to retrieve a paginated set of {kib} saved objects by various conditions + +* <> to create {kib} saved objects + +* <> to create multiple {kib} saved objects + +* <> to update the attributes for existing {kib} saved objects + +* <> to remove {kib} saved objects + +* <> to retrieve sets of saved objects that you want to import into {kib} + +* <> to create sets of {kib} saved objects from a file created by the export API + +* <> to resolve errors from the import API include::saved-objects/get.asciidoc[] include::saved-objects/bulk_get.asciidoc[] diff --git a/docs/api/saved-objects/bulk_create.asciidoc b/docs/api/saved-objects/bulk_create.asciidoc index de66af78f5bd2..ca8cc0f287015 100644 --- a/docs/api/saved-objects/bulk_create.asciidoc +++ b/docs/api/saved-objects/bulk_create.asciidoc @@ -1,7 +1,7 @@ [[saved-objects-api-bulk-create]] === Bulk create saved objects API ++++ -Bulk create objects +Bulk create saved objects ++++ experimental[] Create multiple {kib} saved objects. diff --git a/docs/api/saved-objects/bulk_get.asciidoc b/docs/api/saved-objects/bulk_get.asciidoc index 454572254e5c5..4f2cbcb980f82 100644 --- a/docs/api/saved-objects/bulk_get.asciidoc +++ b/docs/api/saved-objects/bulk_get.asciidoc @@ -4,7 +4,7 @@ Bulk get objects ++++ -experimental[] Retrieves multiple {kib} saved objects by ID. +experimental[] Retrieve multiple {kib} saved objects by ID. [[saved-objects-api-bulk-get-request]] ==== Request diff --git a/docs/api/saved-objects/create.asciidoc b/docs/api/saved-objects/create.asciidoc index 1969d9b9ffbd8..fecc3f3732f2a 100644 --- a/docs/api/saved-objects/create.asciidoc +++ b/docs/api/saved-objects/create.asciidoc @@ -1,10 +1,10 @@ [[saved-objects-api-create]] -=== Create saved object API +=== Create saved objects API ++++ -Create object +Create saved objects ++++ -experimental[] Create a {kib} saved object. +experimental[] Create {kib} saved objects. [[saved-objects-api-create-request]] ==== Request diff --git a/docs/api/saved-objects/delete.asciidoc b/docs/api/saved-objects/delete.asciidoc index 84381e3946adc..4a96cf554f784 100644 --- a/docs/api/saved-objects/delete.asciidoc +++ b/docs/api/saved-objects/delete.asciidoc @@ -4,7 +4,7 @@ Delete object ++++ -experimental[] Remove a {kib} saved object. +experimental[] Remove {kib} saved objects. WARNING: Once you delete a saved object, _it cannot be recovered_. diff --git a/docs/api/saved-objects/export.asciidoc b/docs/api/saved-objects/export.asciidoc index d06756b109a90..ee56e6bad75c8 100644 --- a/docs/api/saved-objects/export.asciidoc +++ b/docs/api/saved-objects/export.asciidoc @@ -4,7 +4,7 @@ Export objects ++++ -experimental[] Retrieve a set of saved objects that you want to import into {kib}. +experimental[] Retrieve sets of saved objects that you want to import into {kib}. [[saved-objects-api-export-request]] ==== Request @@ -23,12 +23,29 @@ experimental[] Retrieve a set of saved objects that you want to import into {kib `includeReferencesDeep`:: (Optional, boolean) Includes all of the referenced objects in the exported objects. +`excludeExportDetails`:: + (Optional, boolean) Do not add export details entry at the end of the stream. + TIP: You must include `type` or `objects` in the request body. [[saved-objects-api-export-request-response-body]] ==== Response body -The format of the response body includes newline delimited JSON. +The format of the response body is newline delimited JSON. Each exported object is exported as a valid JSON record and separated by the newline character '\n'. + +When `excludeExportDetails=false` (the default) we append an export result details record at the end of the file after all the saved object records. The export result details object has the following format: + +[source,json] +-------------------------------------------------- +{ + "exportedCount": 27, + "missingRefCount": 2, + "missingReferences": [ + { "id": "an-id", "type": "visualisation"}, + { "id": "another-id", "type": "index-pattern"} + ] +} +-------------------------------------------------- [[export-objects-api-create-request-codes]] ==== Response code @@ -50,6 +67,18 @@ POST api/saved_objects/_export -------------------------------------------------- // KIBANA +Export all index pattern saved objects and exclude the export summary from the stream: + +[source,js] +-------------------------------------------------- +POST api/saved_objects/_export +{ + "type": "index-pattern", + "excludeExportDetails": true +} +-------------------------------------------------- +// KIBANA + Export a specific saved object: [source,js] @@ -65,3 +94,20 @@ POST api/saved_objects/_export } -------------------------------------------------- // KIBANA + +Export a specific saved object and it's related objects : + +[source,js] +-------------------------------------------------- +POST api/saved_objects/_export +{ + "objects": [ + { + "type": "dashboard", + "id": "be3733a0-9efe-11e7-acb3-3dab96693fab" + } + ], + "includeReferencesDeep": true +} +-------------------------------------------------- +// KIBANA diff --git a/docs/api/saved-objects/import.asciidoc b/docs/api/saved-objects/import.asciidoc index af384718051c0..0331f23284352 100644 --- a/docs/api/saved-objects/import.asciidoc +++ b/docs/api/saved-objects/import.asciidoc @@ -4,7 +4,7 @@ Import objects ++++ -experimental[] Create a set of {kib} saved objects from a file created by the export API. +experimental[] Create sets of {kib} saved objects from a file created by the export API. [[saved-objects-api-import-request]] ==== Request diff --git a/docs/api/saved-objects/update.asciidoc b/docs/api/saved-objects/update.asciidoc index a1e7be37e1c4a..5c4bb98d09228 100644 --- a/docs/api/saved-objects/update.asciidoc +++ b/docs/api/saved-objects/update.asciidoc @@ -4,7 +4,7 @@ Update object ++++ -experimental[] Update the attributes for an existing {kib} saved object. +experimental[] Update the attributes for existing {kib} saved objects. [[saved-objects-api-update-request]] ==== Request diff --git a/docs/api/spaces-management.asciidoc b/docs/api/spaces-management.asciidoc index bb949667963b7..2e3b9abec9120 100644 --- a/docs/api/spaces-management.asciidoc +++ b/docs/api/spaces-management.asciidoc @@ -1,16 +1,24 @@ [role="xpack"] [[spaces-api]] -== Kibana Spaces APIs +== {kib} spaces APIs Manage your {kib} spaces. -* <> -* <> -* <> -* <> -* <> -* <> -* <> +The following {kib} spaces APIs are available: + +* <> to create a {kib} space + +* <> to update an existing {kib} space + +* <> to retrieve a specified {kib} space + +* <> to retrieve all {kib} spaces + +* <> to delete a {kib} space + +* <> to copy saved objects between spaces + +* <> to overwrite saved objects returned as errors from the copy saved objects to space API include::spaces-management/post.asciidoc[] include::spaces-management/put.asciidoc[] diff --git a/docs/api/spaces-management/delete.asciidoc b/docs/api/spaces-management/delete.asciidoc index 64b90f3d49dad..c66307ea3070f 100644 --- a/docs/api/spaces-management/delete.asciidoc +++ b/docs/api/spaces-management/delete.asciidoc @@ -4,7 +4,7 @@ Delete space ++++ -Deletes a {kib} space. +Delete a {kib} space. experimental["The underlying Spaces concepts are stable, but the APIs for managing Spaces are experimental."] diff --git a/docs/api/spaces-management/get.asciidoc b/docs/api/spaces-management/get.asciidoc index 569117e866d2a..49119d7602b20 100644 --- a/docs/api/spaces-management/get.asciidoc +++ b/docs/api/spaces-management/get.asciidoc @@ -4,7 +4,7 @@ Get space ++++ -Retrieves a specified {kib} space. +Retrieve a specified {kib} space. experimental["The underlying Spaces concepts are stable, but the APIs for managing Spaces are experimental."] diff --git a/docs/api/spaces-management/get_all.asciidoc b/docs/api/spaces-management/get_all.asciidoc index 6f683b864e145..4ae67a0dcca8b 100644 --- a/docs/api/spaces-management/get_all.asciidoc +++ b/docs/api/spaces-management/get_all.asciidoc @@ -4,7 +4,7 @@ Get all spaces ++++ -Retrieves all {kib} spaces. +Retrieve all {kib} spaces. experimental["The underlying Spaces concepts are stable, but the APIs for managing Spaces are experimental."] diff --git a/docs/api/spaces-management/post.asciidoc b/docs/api/spaces-management/post.asciidoc index ea8311ea8213f..d8c194d8dc2b5 100644 --- a/docs/api/spaces-management/post.asciidoc +++ b/docs/api/spaces-management/post.asciidoc @@ -4,7 +4,7 @@ Create space ++++ -Creates a {kib} space. +Create a {kib} space. experimental["The underlying Spaces concepts are stable, but the APIs for managing Spaces are experimental."] diff --git a/docs/api/spaces-management/put.asciidoc b/docs/api/spaces-management/put.asciidoc index 113fdbce6a90a..586818707c76f 100644 --- a/docs/api/spaces-management/put.asciidoc +++ b/docs/api/spaces-management/put.asciidoc @@ -4,7 +4,7 @@ Update space ++++ -Updates an existing {kib} space. +Update an existing {kib} space. experimental["The underlying Spaces concepts are stable, but the APIs for managing Spaces are experimental."] diff --git a/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc b/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc index bb82c07e765cb..7b52125599c05 100644 --- a/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc +++ b/docs/api/spaces-management/resolve_copy_saved_objects_conflicts.asciidoc @@ -5,7 +5,7 @@ Resolve copy to space conflicts ++++ -Overwrites specific saved objects that were returned as errors from the <>. +Overwrite specific saved objects that were returned as errors from the <>. experimental["The underlying Spaces concepts are stable, but the APIs for managing Spaces are experimental."] diff --git a/docs/api/upgrade-assistant.asciidoc b/docs/api/upgrade-assistant.asciidoc index 3cb256c337175..b524307c0f273 100644 --- a/docs/api/upgrade-assistant.asciidoc +++ b/docs/api/upgrade-assistant.asciidoc @@ -4,10 +4,15 @@ Check the upgrade status of your Elasticsearch cluster and reindex indices that were created in the previous major version. The assistant helps you prepare for the next major version of Elasticsearch. -* <> -* <> -* <> -* <> +The following upgrade assistant APIs are available: + +* <> to check the status of your cluster + +* <> to start a new reindex or resume a paused reindex + +* <> to check the status of the reindex operation + +* <> to cancel reindexes that are waiting for the Elasticsearch reindex task to complete include::upgrade-assistant/status.asciidoc[] include::upgrade-assistant/reindexing.asciidoc[] diff --git a/docs/api/url-shortening.asciidoc b/docs/api/url-shortening.asciidoc index e98210e92d94a..8bc701a3d5d12 100644 --- a/docs/api/url-shortening.asciidoc +++ b/docs/api/url-shortening.asciidoc @@ -1,11 +1,57 @@ [[url-shortening-api]] -== URL shortening API +=== Shorten URL API +++++ +Shorten URL +++++ -{kib} URLs contain the state of the application, which makes them long and cumbersome. +experimental[] Convert a {kib} URL into a token. {kib} URLs contain the state of the application, which makes them long and cumbersome. Internet Explorer has URL length restrictions, and some wiki and markup parsers don't do well with the full-length version of the {kib} URL. Short URLs are designed to make sharing {kib} URLs easier. -* <> +[[url-shortening-api-request]] +==== Request -include::url_shortening/shorten_url.asciidoc[] +`POST /api/shorten_url` + +[[url-shortening-api-request-body]] +==== Request body + +`url`:: + (Required, string) The {kib} URL that you want to shorten, Relative to `/app/kibana`. + +[[url-shortening-api-response-body]] +==== Response body + +urlId:: A top level property that contains the shortened URL token for the provided request body. + +[[url-shortening-api-codes]] +==== Response code + +`200`:: + Indicates a successful call. + +[[url-shortening-api-example]] +==== Example + +[source,js] +-------------------------------------------------- +POST api/shorten_url +{ + "url": "/app/kibana#/dashboard?_g=()&_a=(description:'',filters:!(),fullScreenMode:!f,options:(hidePanelTitles:!f,useMargins:!t),panels:!((embeddableConfig:(),gridData:(h:15,i:'1',w:24,x:0,y:0),id:'8f4d0c00-4c86-11e8-b3d7-01146121b73d',panelIndex:'1',type:visualization,version:'7.0.0-alpha1')),query:(language:lucene,query:''),timeRestore:!f,title:'New%20Dashboard',viewMode:edit)" +} +-------------------------------------------------- +// KIBANA + +The API returns the following result: + +[source,js] +-------------------------------------------------- +{ + "urlId": "f73b295ff92718b26bc94edac766d8e3" +} +-------------------------------------------------- + +For easy sharing, construct the shortened {kib} URL: + +`http://localhost:5601/goto/f73b295ff92718b26bc94edac766d8e3` diff --git a/docs/api/url_shortening/shorten_url.asciidoc b/docs/api/url_shortening/shorten_url.asciidoc deleted file mode 100644 index 0e04a32efe35e..0000000000000 --- a/docs/api/url_shortening/shorten_url.asciidoc +++ /dev/null @@ -1,55 +0,0 @@ -[[shorten-url-api]] -=== Shorten URL API -++++ -Shorten URL -++++ - -experimental[] Convert a {kib} URL into a token. - -[[url-shortening-api-request]] -==== Request - -`POST /api/shorten_url` - -[[url-shortening-api-request-body]] -==== Request body - -`url`:: - (Required, string) The {kib} URL that you want to shorten, Relative to `/app/kibana`. - -[[url-shortening-api-response-body]] -==== Response body - -urlId:: A top level property that contains the shortened URL token for the provided request body. - -[[url-shortening-api-codes]] -==== Response code - -`200`:: - Indicates a successful call. - -[[url-shortening-api-example]] -==== Example - -[source,js] --------------------------------------------------- -POST api/shorten_url -{ - "url": "/app/kibana#/dashboard?_g=()&_a=(description:'',filters:!(),fullScreenMode:!f,options:(hidePanelTitles:!f,useMargins:!t),panels:!((embeddableConfig:(),gridData:(h:15,i:'1',w:24,x:0,y:0),id:'8f4d0c00-4c86-11e8-b3d7-01146121b73d',panelIndex:'1',type:visualization,version:'7.0.0-alpha1')),query:(language:lucene,query:''),timeRestore:!f,title:'New%20Dashboard',viewMode:edit)" -} --------------------------------------------------- -// KIBANA - -The API returns the following result: - -[source,js] --------------------------------------------------- -{ - "urlId": "f73b295ff92718b26bc94edac766d8e3" -} --------------------------------------------------- - -For easy sharing, construct the shortened {kib} URL: - -`http://localhost:5601/goto/f73b295ff92718b26bc94edac766d8e3` - diff --git a/docs/api/using-api.asciidoc b/docs/api/using-api.asciidoc new file mode 100644 index 0000000000000..82071af463636 --- /dev/null +++ b/docs/api/using-api.asciidoc @@ -0,0 +1,46 @@ +[[using-api]] +== Using the APIs + +Interact with the {kib} APIs through the `curl` command and HTTP and HTTPs protocols. + +It is recommended that you use HTTPs on port 5601 because it is more secure. + +NOTE: The {kib} Console supports only Elasticsearch APIs. You are unable to interact with the {kib} APIs with the Console and must use `curl` or another HTTP tool instead. For more information, refer to <>. + +[float] +[[api-authentication]] +=== Authentication +{kib} supports token-based authentication with the same username and password that you use to log into the {kib} Console. + +[float] +[[api-calls]] +=== API calls +API calls are stateless. Each request that you make happens in isolation from other calls and must include all of the necessary information for {kib} to fulfill the request. API requests return JSON output, which is a format that is machine-readable and works well for automation. + +Calls to the API endpoints require different operations. To interact with the {kib} APIs, use the following operations: + +* *GET* - Fetches the information. + +* *POST* - Adds new information. + +* *PUT* - Updates the existing information. + +* *DELETE* - Removes the information. + +For example, the following `curl` command exports a dashboard: + +[source,sh] +-- +curl -X POST -u $USER:$PASSWORD "localhost:5601/api/kibana/dashboards/export?dashboard=942dcef0-b2cd-11e8-ad8e-85441f0c2e5c" +-- + +The following {kib} APIs are available: + +* <> +* <> +* <> +* <> +* <> +* <> +* <> +* <> diff --git a/docs/dev-tools/console/console.asciidoc b/docs/dev-tools/console/console.asciidoc index a678e7189abec..26620688499af 100644 --- a/docs/dev-tools/console/console.asciidoc +++ b/docs/dev-tools/console/console.asciidoc @@ -12,6 +12,8 @@ To get started, go to *Dev Tools > Console*. [role="screenshot"] image::dev-tools/console/images/console.png["Console"] +NOTE: You are unable to interact with the REST API of {kib} with the Console. + [float] [[console-api]] === Write requests diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.get.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.get.md new file mode 100644 index 0000000000000..0ec3ac45c6cb5 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.get.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [get](./kibana-plugin-server.iuisettingsclient.get.md) + +## IUiSettingsClient.get property + +Retrieves uiSettings values set by the user with fallbacks to default values if not specified. + +Signature: + +```typescript +get: (key: string) => Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getall.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getall.md new file mode 100644 index 0000000000000..d6765a5e5407e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getall.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [getAll](./kibana-plugin-server.iuisettingsclient.getall.md) + +## IUiSettingsClient.getAll property + +Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified. + +Signature: + +```typescript +getAll: () => Promise>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getdefaults.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getdefaults.md new file mode 100644 index 0000000000000..29faa6d945b43 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getdefaults.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [getDefaults](./kibana-plugin-server.iuisettingsclient.getdefaults.md) + +## IUiSettingsClient.getDefaults property + +Returns uiSettings default values [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) + +Signature: + +```typescript +getDefaults: () => Record; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md new file mode 100644 index 0000000000000..9a449b64ed5d0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.getuserprovided.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [getUserProvided](./kibana-plugin-server.iuisettingsclient.getuserprovided.md) + +## IUiSettingsClient.getUserProvided property + +Retrieves a set of all uiSettings values set by the user. + +Signature: + +```typescript +getUserProvided: () => Promise>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.isoverridden.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.isoverridden.md new file mode 100644 index 0000000000000..a53655763a79b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.isoverridden.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [isOverridden](./kibana-plugin-server.iuisettingsclient.isoverridden.md) + +## IUiSettingsClient.isOverridden property + +Shows whether the uiSettings value set by the user. + +Signature: + +```typescript +isOverridden: (key: string) => boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md new file mode 100644 index 0000000000000..142f33d27c385 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) + +## IUiSettingsClient interface + +Service that provides access to the UiSettings stored in elasticsearch. + +Signature: + +```typescript +export interface IUiSettingsClient +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [get](./kibana-plugin-server.iuisettingsclient.get.md) | <T extends SavedObjectAttribute = any>(key: string) => Promise<T> | Retrieves uiSettings values set by the user with fallbacks to default values if not specified. | +| [getAll](./kibana-plugin-server.iuisettingsclient.getall.md) | <T extends SavedObjectAttribute = any>() => Promise<Record<string, T>> | Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified. | +| [getDefaults](./kibana-plugin-server.iuisettingsclient.getdefaults.md) | () => Record<string, UiSettingsParams> | Returns uiSettings default values [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | +| [getUserProvided](./kibana-plugin-server.iuisettingsclient.getuserprovided.md) | <T extends SavedObjectAttribute = any>() => Promise<Record<string, {
userValue?: T;
isOverridden?: boolean;
}>> | Retrieves a set of all uiSettings values set by the user. | +| [isOverridden](./kibana-plugin-server.iuisettingsclient.isoverridden.md) | (key: string) => boolean | Shows whether the uiSettings value set by the user. | +| [remove](./kibana-plugin-server.iuisettingsclient.remove.md) | (key: string) => Promise<void> | Removes uiSettings value by key. | +| [removeMany](./kibana-plugin-server.iuisettingsclient.removemany.md) | (keys: string[]) => Promise<void> | Removes multiple uiSettings values by keys. | +| [set](./kibana-plugin-server.iuisettingsclient.set.md) | <T extends SavedObjectAttribute = any>(key: string, value: T) => Promise<void> | Writes uiSettings value and marks it as set by the user. | +| [setMany](./kibana-plugin-server.iuisettingsclient.setmany.md) | <T extends SavedObjectAttribute = any>(changes: Record<string, T>) => Promise<void> | Writes multiple uiSettings values and marks them as set by the user. | + diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.remove.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.remove.md new file mode 100644 index 0000000000000..fa15b11fd76e4 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.remove.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [remove](./kibana-plugin-server.iuisettingsclient.remove.md) + +## IUiSettingsClient.remove property + +Removes uiSettings value by key. + +Signature: + +```typescript +remove: (key: string) => Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.removemany.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.removemany.md new file mode 100644 index 0000000000000..ef5f994707aae --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.removemany.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [removeMany](./kibana-plugin-server.iuisettingsclient.removemany.md) + +## IUiSettingsClient.removeMany property + +Removes multiple uiSettings values by keys. + +Signature: + +```typescript +removeMany: (keys: string[]) => Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.set.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.set.md new file mode 100644 index 0000000000000..bc67d05b3f0ee --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.set.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [set](./kibana-plugin-server.iuisettingsclient.set.md) + +## IUiSettingsClient.set property + +Writes uiSettings value and marks it as set by the user. + +Signature: + +```typescript +set: (key: string, value: T) => Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.iuisettingsclient.setmany.md b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.setmany.md new file mode 100644 index 0000000000000..ec2c24951f0ec --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.iuisettingsclient.setmany.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) > [setMany](./kibana-plugin-server.iuisettingsclient.setmany.md) + +## IUiSettingsClient.setMany property + +Writes multiple uiSettings values and marks them as set by the user. + +Signature: + +```typescript +setMany: (changes: Record) => Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index c085d4cdf0d42..7b302838995c1 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -63,6 +63,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IKibanaSocket](./kibana-plugin-server.ikibanasocket.md) | A tiny abstraction for TCP socket. | | [IndexSettingsDeprecationInfo](./kibana-plugin-server.indexsettingsdeprecationinfo.md) | | | [IRouter](./kibana-plugin-server.irouter.md) | Registers route handlers for specified resource path and method. See [RouteConfig](./kibana-plugin-server.routeconfig.md) and [RequestHandler](./kibana-plugin-server.requesthandler.md) for more information about arguments to route registrations. | +| [IUiSettingsClient](./kibana-plugin-server.iuisettingsclient.md) | Service that provides access to the UiSettings stored in elasticsearch. | | [KibanaRequestRoute](./kibana-plugin-server.kibanarequestroute.md) | Request specific route information exposed to a handler. | | [LegacyRequest](./kibana-plugin-server.legacyrequest.md) | | | [LegacyServiceSetupDeps](./kibana-plugin-server.legacyservicesetupdeps.md) | | @@ -92,6 +93,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) | Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance. | | [SavedObjectsCreateOptions](./kibana-plugin-server.savedobjectscreateoptions.md) | | | [SavedObjectsExportOptions](./kibana-plugin-server.savedobjectsexportoptions.md) | Options controlling the export operation. | +| [SavedObjectsExportResultDetails](./kibana-plugin-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry | | [SavedObjectsFindOptions](./kibana-plugin-server.savedobjectsfindoptions.md) | | | [SavedObjectsFindResponse](./kibana-plugin-server.savedobjectsfindresponse.md) | Return type of the Saved Objects find() method.\*Note\*: this type is different between the Public and Server Saved Objects clients. | | [SavedObjectsImportConflictError](./kibana-plugin-server.savedobjectsimportconflicterror.md) | Represents a failure to import due to a conflict. | @@ -111,6 +113,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SessionStorage](./kibana-plugin-server.sessionstorage.md) | Provides an interface to store and retrieve data across requests. | | [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) | Configuration used to create HTTP session storage based on top of cookie mechanism. | | [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request | +| [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) | UiSettings parameters defined by the plugins. | ## Variables @@ -162,4 +165,5 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | | [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | +| [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | UI element type to represent the settings. | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.excludeexportdetails.md b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.excludeexportdetails.md new file mode 100644 index 0000000000000..bffc809689834 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.excludeexportdetails.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsExportOptions](./kibana-plugin-server.savedobjectsexportoptions.md) > [excludeExportDetails](./kibana-plugin-server.savedobjectsexportoptions.excludeexportdetails.md) + +## SavedObjectsExportOptions.excludeExportDetails property + +flag to not append [export details](./kibana-plugin-server.savedobjectsexportresultdetails.md) to the end of the export stream. + +Signature: + +```typescript +excludeExportDetails?: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.includereferencesdeep.md b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.includereferencesdeep.md index d721fc260eaf8..0cd7688e04184 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.includereferencesdeep.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.includereferencesdeep.md @@ -4,7 +4,7 @@ ## SavedObjectsExportOptions.includeReferencesDeep property -flag to also include all related saved objects in the export response. +flag to also include all related saved objects in the export stream. Signature: diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.md index 0f1bd94d01552..d312d7d4b3499 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsexportoptions.md @@ -16,8 +16,9 @@ export interface SavedObjectsExportOptions | Property | Type | Description | | --- | --- | --- | +| [excludeExportDetails](./kibana-plugin-server.savedobjectsexportoptions.excludeexportdetails.md) | boolean | flag to not append [export details](./kibana-plugin-server.savedobjectsexportresultdetails.md) to the end of the export stream. | | [exportSizeLimit](./kibana-plugin-server.savedobjectsexportoptions.exportsizelimit.md) | number | the maximum number of objects to export. | -| [includeReferencesDeep](./kibana-plugin-server.savedobjectsexportoptions.includereferencesdeep.md) | boolean | flag to also include all related saved objects in the export response. | +| [includeReferencesDeep](./kibana-plugin-server.savedobjectsexportoptions.includereferencesdeep.md) | boolean | flag to also include all related saved objects in the export stream. | | [namespace](./kibana-plugin-server.savedobjectsexportoptions.namespace.md) | string | optional namespace to override the namespace used by the savedObjectsClient. | | [objects](./kibana-plugin-server.savedobjectsexportoptions.objects.md) | Array<{
id: string;
type: string;
}> | optional array of objects to export. | | [savedObjectsClient](./kibana-plugin-server.savedobjectsexportoptions.savedobjectsclient.md) | SavedObjectsClientContract | an instance of the SavedObjectsClient. | diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsexportresultdetails.exportedcount.md b/docs/development/core/server/kibana-plugin-server.savedobjectsexportresultdetails.exportedcount.md new file mode 100644 index 0000000000000..c2e588dd3c121 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsexportresultdetails.exportedcount.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsExportResultDetails](./kibana-plugin-server.savedobjectsexportresultdetails.md) > [exportedCount](./kibana-plugin-server.savedobjectsexportresultdetails.exportedcount.md) + +## SavedObjectsExportResultDetails.exportedCount property + +number of successfully exported objects + +Signature: + +```typescript +exportedCount: number; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsexportresultdetails.md b/docs/development/core/server/kibana-plugin-server.savedobjectsexportresultdetails.md new file mode 100644 index 0000000000000..fb3af350d21ea --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsexportresultdetails.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsExportResultDetails](./kibana-plugin-server.savedobjectsexportresultdetails.md) + +## SavedObjectsExportResultDetails interface + +Structure of the export result details entry + +Signature: + +```typescript +export interface SavedObjectsExportResultDetails +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [exportedCount](./kibana-plugin-server.savedobjectsexportresultdetails.exportedcount.md) | number | number of successfully exported objects | +| [missingRefCount](./kibana-plugin-server.savedobjectsexportresultdetails.missingrefcount.md) | number | number of missing references | +| [missingReferences](./kibana-plugin-server.savedobjectsexportresultdetails.missingreferences.md) | Array<{
id: string;
type: string;
}> | missing references details | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsexportresultdetails.missingrefcount.md b/docs/development/core/server/kibana-plugin-server.savedobjectsexportresultdetails.missingrefcount.md new file mode 100644 index 0000000000000..5b51199ea4780 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsexportresultdetails.missingrefcount.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsExportResultDetails](./kibana-plugin-server.savedobjectsexportresultdetails.md) > [missingRefCount](./kibana-plugin-server.savedobjectsexportresultdetails.missingrefcount.md) + +## SavedObjectsExportResultDetails.missingRefCount property + +number of missing references + +Signature: + +```typescript +missingRefCount: number; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsexportresultdetails.missingreferences.md b/docs/development/core/server/kibana-plugin-server.savedobjectsexportresultdetails.missingreferences.md new file mode 100644 index 0000000000000..1602bfb6e6cb6 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsexportresultdetails.missingreferences.md @@ -0,0 +1,16 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsExportResultDetails](./kibana-plugin-server.savedobjectsexportresultdetails.md) > [missingReferences](./kibana-plugin-server.savedobjectsexportresultdetails.missingreferences.md) + +## SavedObjectsExportResultDetails.missingReferences property + +missing references details + +Signature: + +```typescript +missingReferences: Array<{ + id: string; + type: string; + }>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.category.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.category.md new file mode 100644 index 0000000000000..47aedbfbf2810 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.category.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [category](./kibana-plugin-server.uisettingsparams.category.md) + +## UiSettingsParams.category property + +used to group the configured setting in the UI + +Signature: + +```typescript +category: string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.description.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.description.md new file mode 100644 index 0000000000000..8d8887285ae2e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.description.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [description](./kibana-plugin-server.uisettingsparams.description.md) + +## UiSettingsParams.description property + +description provided to a user in UI + +Signature: + +```typescript +description: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.md new file mode 100644 index 0000000000000..275111c05eff9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) + +## UiSettingsParams interface + +UiSettings parameters defined by the plugins. + +Signature: + +```typescript +export interface UiSettingsParams +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [category](./kibana-plugin-server.uisettingsparams.category.md) | string[] | used to group the configured setting in the UI | +| [description](./kibana-plugin-server.uisettingsparams.description.md) | string | description provided to a user in UI | +| [name](./kibana-plugin-server.uisettingsparams.name.md) | string | title in the UI | +| [optionLabels](./kibana-plugin-server.uisettingsparams.optionlabels.md) | Record<string, string> | text labels for 'select' type UI element | +| [options](./kibana-plugin-server.uisettingsparams.options.md) | string[] | a range of valid values | +| [readonly](./kibana-plugin-server.uisettingsparams.readonly.md) | boolean | a flag indicating that value cannot be changed | +| [requiresPageReload](./kibana-plugin-server.uisettingsparams.requirespagereload.md) | boolean | a flag indicating whether new value applying requires page reloading | +| [type](./kibana-plugin-server.uisettingsparams.type.md) | UiSettingsType | defines a type of UI element [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | +| [value](./kibana-plugin-server.uisettingsparams.value.md) | SavedObjectAttribute | default value to fall back to if a user doesn't provide any | + diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.name.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.name.md new file mode 100644 index 0000000000000..2b414eefffed2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.name.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [name](./kibana-plugin-server.uisettingsparams.name.md) + +## UiSettingsParams.name property + +title in the UI + +Signature: + +```typescript +name: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.optionlabels.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.optionlabels.md new file mode 100644 index 0000000000000..cb0e196fdcacc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.optionlabels.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [optionLabels](./kibana-plugin-server.uisettingsparams.optionlabels.md) + +## UiSettingsParams.optionLabels property + +text labels for 'select' type UI element + +Signature: + +```typescript +optionLabels?: Record; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.options.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.options.md new file mode 100644 index 0000000000000..71eecdfabc4a0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.options.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [options](./kibana-plugin-server.uisettingsparams.options.md) + +## UiSettingsParams.options property + +a range of valid values + +Signature: + +```typescript +options?: string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.readonly.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.readonly.md new file mode 100644 index 0000000000000..faec4d6eadbcc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.readonly.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [readonly](./kibana-plugin-server.uisettingsparams.readonly.md) + +## UiSettingsParams.readonly property + +a flag indicating that value cannot be changed + +Signature: + +```typescript +readonly?: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.requirespagereload.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.requirespagereload.md new file mode 100644 index 0000000000000..224b3695224b9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.requirespagereload.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [requiresPageReload](./kibana-plugin-server.uisettingsparams.requirespagereload.md) + +## UiSettingsParams.requiresPageReload property + +a flag indicating whether new value applying requires page reloading + +Signature: + +```typescript +requiresPageReload?: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.type.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.type.md new file mode 100644 index 0000000000000..ccf2d67b2dffb --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.type.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [type](./kibana-plugin-server.uisettingsparams.type.md) + +## UiSettingsParams.type property + +defines a type of UI element [UiSettingsType](./kibana-plugin-server.uisettingstype.md) + +Signature: + +```typescript +type?: UiSettingsType; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.value.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.value.md new file mode 100644 index 0000000000000..455756899ecfc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.value.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [value](./kibana-plugin-server.uisettingsparams.value.md) + +## UiSettingsParams.value property + +default value to fall back to if a user doesn't provide any + +Signature: + +```typescript +value: SavedObjectAttribute; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingstype.md b/docs/development/core/server/kibana-plugin-server.uisettingstype.md new file mode 100644 index 0000000000000..789d4d5788468 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingstype.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsType](./kibana-plugin-server.uisettingstype.md) + +## UiSettingsType type + +UI element type to represent the settings. + +Signature: + +```typescript +export declare type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; +``` diff --git a/docs/user/api.asciidoc b/docs/user/api.asciidoc index 8ad8b71c789f4..20f1fc89367f2 100644 --- a/docs/user/api.asciidoc +++ b/docs/user/api.asciidoc @@ -8,7 +8,7 @@ integration with {kib}, or automating certain aspects of configuring and deploying {kib}. Each API is experimental and can include breaking changes in any version of -{kib}, or might have been entirely removed from {kib}. +{kib}, or might be entirely removed from {kib}. //// Each API has one of the following labels: @@ -29,25 +29,14 @@ entirely. If a label is missing from an API, it is considered `experimental`. //// -NOTE: You cannot access the APIs via the Console in {kib}. - -[float] -== APIs -* <> -* <> -* <> -* <> -* <> -* <> -* <> -* <> -- +include::{kib-repo-dir}/api/using-api.asciidoc[] include::{kib-repo-dir}/api/features.asciidoc[] include::{kib-repo-dir}/api/spaces-management.asciidoc[] include::{kib-repo-dir}/api/role-management.asciidoc[] include::{kib-repo-dir}/api/saved-objects.asciidoc[] -include::{kib-repo-dir}/api/dashboard-import.asciidoc[] +include::{kib-repo-dir}/api/dashboard-api.asciidoc[] include::{kib-repo-dir}/api/logstash-configuration-management.asciidoc[] include::{kib-repo-dir}/api/url-shortening.asciidoc[] include::{kib-repo-dir}/api/upgrade-assistant.asciidoc[] diff --git a/package.json b/package.json index 85ca5e2909b24..31abf1bce6141 100644 --- a/package.json +++ b/package.json @@ -390,7 +390,7 @@ "exit-hook": "^2.2.0", "faker": "1.1.0", "fetch-mock": "^7.3.9", - "geckodriver": "^1.18.0", + "geckodriver": "^1.19.0", "getopts": "^2.2.4", "grunt": "1.0.4", "grunt-available-tasks": "^0.6.3", diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 01bcf1aa6f215..09158295fde07 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -25,6 +25,7 @@ * [How is "common" code shared on both the client and server?](#how-is-common-code-shared-on-both-the-client-and-server) * [When does code go into a plugin, core, or packages?](#when-does-code-go-into-a-plugin-core-or-packages) * [How do I build my shim for New Platform services?](#how-do-i-build-my-shim-for-new-platform-services) + * aka, where did everything move to? * [How to](#how-to) * [Configure plugin](#configure-plugin) * [Mock new platform services in tests](#mock-new-platform-services-in-tests) @@ -296,6 +297,14 @@ Once those things are finished for any given plugin, it can officially be switch Legacy server-side plugins access functionality from core and other plugins at runtime via function arguments, which is similar to how they must be architected to use the new plugin system. This greatly simplifies the plan of action for migrating server-side plugins. +Here is the high-level for migrating a server-side plugin: +- De-couple from hapi.js server and request objects +- Introduce a new plugin definition shim +- Replace legacy services in shim with new platform services +- Finally, move to the new plugin system + +These steps (except for the last one) do not have to be completed strictly in order, and some can be done in parallel or as part of the same change. In general, we recommend that larger plugins approach this more methodically, doing each step in a separate change. This makes each individual change less risk and more focused. This approach may not make sense for smaller plugins. For instance, it may be simpler to switch to New Platform services when you introduce your Plugin class, rather than shimming it with the legacy service. + ### De-couple from hapi.js server and request objects Most integrations with core and other plugins occur through the hapi.js `server` and `request` objects, and neither of these things are exposed through the new platform, so tackle this problem first. @@ -340,6 +349,8 @@ export default (kibana) => { This example legacy plugin uses hapi's `server` object directly inside of its `init` function, which is something we can address in a later step. What we need to address in this step is when we pass the raw `server` and `request` objects into our custom `search` function. +Our goal in this step is to make sure we're not integrating with other plugins via functions on `server.plugins.*` or on the `request` object. You should begin by finding all of the integration points where you make these calls, and put them behind a "facade" abstraction that can hide the details of where these APIs come from. This allows you to easily switch out how you access these APIs without having to change all of the code that may use them. + Instead, we identify which functionality we actually need from those objects and craft custom new interfaces for them, taking care not to leak hapi.js implementation details into their design. ```ts @@ -440,7 +451,7 @@ We now move this logic into a new plugin definition, which is based off of the c import { ElasticsearchPlugin } from '../elasticsearch'; interface CoreSetup { - elasticsearch: ElasticsearchPlugin // note: we know elasticsearch will move to core + elasticsearch: ElasticsearchPlugin // note: Elasticsearch is in Core in NP, rather than a plugin } interface FooSetup { @@ -457,11 +468,14 @@ export class Plugin { public setup(core: CoreSetup, plugins: PluginsSetup) { const serverFacade: ServerFacade = { plugins: { + // We're still using the legacy Elasticsearch here, but we're now accessing it + // the same way a NP plugin would, via core. Later, we'll swap this out for the + // actual New Platform service. elasticsearch: core.elasticsearch } } - // HTTP functionality from core + // HTTP functionality from legacy platform, accessed in the NP convention, just like Elasticsearch above. core.http.route({ // note: we know routes will be created on core.http path: '/api/demo_plugin/search', method: 'POST', @@ -518,6 +532,8 @@ export default (kibana) => { This introduces a layer between the legacy plugin system with hapi.js and the logic you want to move to the new plugin system. The functionality exposed through that layer is still provided from the legacy world and in some cases is still technically powered directly by hapi, but building this layer forced you to identify the remaining touch points into the legacy world and it provides you with control when you start migrating to new platform-backed services. +> Need help constructing your shim? There are some common APIs that are already present in the New Platform. In these cases, it may make more sense to simply use the New Platform service rather than crafting your own shim. Refer to the _[How do I build my shim for New Platform services?](#how-do-i-build-my-shim-for-new-platform-services)_ section for a table of legacy to new platform service translations to identify these. Note that while some APIs have simply _moved_ others are completely different. Take care when choosing how much refactoring to do in a single change. + ### Switch to new platform services At this point, your legacy server-side plugin is described in the shape and conventions of the new plugin system, and all of the touch points with the legacy world and hapi.js have been isolated to the shims in the legacy plugin definition. @@ -594,10 +610,16 @@ At this point, your legacy server-side plugin logic is no longer coupled to lega With both shims converted, you are now ready to complete your migration to the new platform. -Many plugins will copy and paste all of their plugin code into a new plugin directory and then delete their legacy shims. +Many plugins will copy and paste all of their plugin code into a new plugin directory in either `src/plugins` for OSS or `x-pack/plugins` for commerical code and then delete their legacy shims. It's at this point that you'll want to make sure to create your `kibana.json` file if it does not already exist. With the previous steps resolved, this final step should be easy, but the exact process may vary plugin by plugin, so when you're at this point talk to the platform team to figure out the exact changes you need. +Other plugins may want to move subsystems over individually. For instance, you can move routes over to the New Platform in groups rather than all at once. Other examples that could be broken up: +- Configuration schema ([see example](./MIGRATION_EXAMPLES.md#declaring-config-schema)) +- HTTP route registration +- Polling mechanisms (eg. job worker) + +In general, we recommend moving all at once by ensuring you're not depending on any legacy code before you move over. ## Browser-side plan of action @@ -841,6 +863,11 @@ Many plugins at this point will copy over their plugin definition class & the co With the previous steps resolved, this final step should be easy, but the exact process may vary plugin by plugin, so when you're at this point talk to the platform team to figure out the exact changes you need. +Other plugins may want to move subsystems over individually. Examples of pieces that could be broken up: +- Registration logic (eg. viz types, embeddables, chrome nav controls) +- Application mounting +- Polling mechanisms (eg. job worker) + #### Bonus: Tips for complex migration scenarios For a few plugins, some of these steps (such as angular removal) could be a months-long process. In those cases, it may be helpful from an organizational perspective to maintain a clear separation of code that is and isn't "ready" for the new platform. @@ -1021,6 +1048,8 @@ Many of the utilities you're using to build your plugins are available in the Ne #### Client-side +TODO: add links to API docs on items in "New Platform" column. + ##### Core services In client code, `core` can be imported in legacy plugins via the `ui/new_platform` module. @@ -1028,21 +1057,22 @@ In client code, `core` can be imported in legacy plugins via the `ui/new_platfor import { npStart: { core } } from 'ui/new_platform'; ``` -| Legacy Platform | New Platform | Notes | -|-------------------------------------------------------|--------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| -| `chrome.addBasePath` | `core.http.basePath.prepend` | | -| `chrome.breadcrumbs.set` | `core.chrome.setBreadcrumbs` | | -| `chrome.getUiSettingsClient` | `core.uiSettings` | | -| `chrome.helpExtension.set` | `core.chrome.setHelpExtension` | | -| `chrome.setVisible` | `core.chrome.setVisible` | | -| `chrome.getInjected` | -- | Not available, we'd like to hear about your usecase. | -| `chrome.setRootTemplate` / `chrome.setRootController` | -- | Use application mounting via `core.application.register` (coming soon). | -| `import { recentlyAccessed } from 'ui/persisted_log'` | `core.chrome.recentlyAccessed` | | -| `ui/documentation_links` | `core.docLinks` | | -| `ui/kfetch` | `core.http` | API is nearly identical | -| `ui/metadata` | `core.injectedMetadata` | May be removed in the future. If you have a necessary usecase, please let us know. | -| `ui/notify` | `core.notifications` | Currently only supports toast messages. Banners coming soon. | -| `ui/routes` | -- | There is no global routing mechanism. Each app [configures its own routing](/rfcs/text/0004_application_service_mounting.md#complete-example). | +| Legacy Platform | New Platform | Notes | +|-------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| +| `chrome.addBasePath` | [`core.http.basePath.prepend`](/docs/development/core/public/kibana-plugin-public.httpservicebase.basepath.md) | | +| `chrome.breadcrumbs.set` | [`core.chrome.setBreadcrumbs`](/docs/development/core/public/kibana-plugin-public.chromestart.setbreadcrumbs.md) | | +| `chrome.getUiSettingsClient` | [`core.uiSettings`](/docs/development/core/public/kibana-plugin-public.uisettingsclient.md) | | +| `chrome.helpExtension.set` | [`core.chrome.setHelpExtension`](/docs/development/core/public/kibana-plugin-public.chromestart.sethelpextension.md) | | +| `chrome.setVisible` | [`core.chrome.setIsVisible`](/docs/development/core/public/kibana-plugin-public.chromestart.setisvisible.md) | | +| `chrome.getInjected` | -- | Not implemented yet, see [#41990](https://github.com/elastic/kibana/issues/41990) | +| `chrome.setRootTemplate` / `chrome.setRootController` | -- | Use application mounting via `core.application.register` (not available to legacy plugins at this time). | +| `import { recentlyAccessed } from 'ui/persisted_log'` | [`core.chrome.recentlyAccessed`](/docs/development/core/public/kibana-plugin-public.chromerecentlyaccessed.md) | | +| `ui/capabilities` | [`core.application.capabilities`](/docs/development/core/public/kibana-plugin-public.capabilities.md) | | +| `ui/documentation_links` | [`core.docLinks`](/docs/development/core/public/kibana-plugin-public.doclinksstart.md) | | +| `ui/kfetch` | [`core.http`](/docs/development/core/public/kibana-plugin-public.httpservicebase.md) | API is nearly identical | +| `ui/notify` | [`core.notifications`](/docs/development/core/public/kibana-plugin-public.notificationsstart.md) and [`core.overlays`](/docs/development/core/public/kibana-plugin-public.overlaystart.md) | Toast messages are in `notifications`, banners are in `overlays`. May be combined later. | +| `ui/routes` | -- | There is no global routing mechanism. Each app [configures its own routing](/rfcs/text/0004_application_service_mounting.md#complete-example). | +| `ui/saved_objects` | [`core.savedObjects`](/docs/development/core/public/kibana-plugin-public.savedobjectsstart.md) | Client API is the same | _See also: [Public's CoreStart API Docs](/docs/development/core/public/kibana-plugin-public.corestart.md)_ @@ -1081,15 +1111,61 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy'; ##### Core services In server code, `core` can be accessed from either `server.newPlatform` or `kbnServer.newPlatform`. There are not currently very many services available on the server-side: -| Legacy Platform | New Platform | Notes | -|----------------------------------------------------|-----------------------------------|----------------------------------------------------| -| `request.getBasePath()` | `core.http.basePath.get` | | -| `server.plugins.elasticsearch.getCluster('data')` | `core.elasticsearch.dataClient$` | Handlers will also include a pre-configured client | -| `server.plugins.elasticsearch.getCluster('admin')` | `core.elasticsearch.adminClient$` | Handlers will also include a pre-configured client | -| `request.getBasePath()` | `core.http.basePath` | | +| Legacy Platform | New Platform | Notes | +|----------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| +| `server.config()` | [`initializerContext.config.create()`](/docs/development/core/server/kibana-plugin-server.plugininitializercontext.config.md) | Must also define schema. See _[how to configure plugin](#configure-plugin)_ | +| `server.route` | [`core.http.createRouter`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.createrouter.md) | | +| `request.getBasePath()` | [`core.http.basePath.get`](/docs/development/core/server/kibana-plugin-server.httpservicesetup.basepath.md) | | +| `server.plugins.elasticsearch.getCluster('data')` | [`core.elasticsearch.dataClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.dataclient_.md) | Handlers will also include a pre-configured client | +| `server.plugins.elasticsearch.getCluster('admin')` | [`core.elasticsearch.adminClient$`](/docs/development/core/server/kibana-plugin-server.elasticsearchservicesetup.adminclient_.md) | Handlers will also include a pre-configured client | _See also: [Server's CoreSetup API Docs](/docs/development/core/server/kibana-plugin-server.coresetup.md)_ +#### UI Exports + +The legacy platform uses a set of "uiExports" to inject modules from one plugin into other plugins. This mechansim is not necessary in the New Platform because all plugins are executed on the page at once (though only one application) is rendered at a time. + +This table shows where these uiExports have moved to in the New Platform. In most cases, if a uiExport you need is not yet available in the New Platform, you may leave in your legacy plugin for the time being and continue to migrate the rest of your app to the New Platform. + +| Legacy Platform | New Platform | Notes | +|------------------------------|---------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------| +| `aliases` | | | +| `app` | [`core.application.register`](/docs/development/core/public/kibana-plugin-public.applicationsetup.register.md) | | +| `canvas` | | Should be an API on the canvas plugin. | +| `chromeNavControls` | [`core.chrome.navControls.register{Left,Right}`](/docs/development/core/public/kibana-plugin-public.chromenavcontrols.md) | | +| `contextMenuActions` | | Should be an API on the devTools plugin. | +| `devTools` | | | +| `docViews` | | | +| `embeddableActions` | | Should be an API on the embeddables plugin. | +| `embeddableFactories` | | Should be an API on the embeddables plugin. | +| `fieldFormatEditors` | | | +| `fieldFormats` | | | +| `hacks` | n/a | Just run the code in your plugin's `start` method. | +| `home` | | Should be an API on the home plugin. | +| `indexManagement` | | Should be an API on the indexManagement plugin. | +| `injectDefaultVars` | n/a | Plugins will only be able to "whitelist" config values for the frontend. See [#41990](https://github.com/elastic/kibana/issues/41990) | +| `inspectorViews` | | Should be an API on the data (?) plugin. | +| `interpreter` | | Should be an API on the interpreter plugin. | +| `links` | n/a | Not necessary, just register your app via `core.application.register` | +| `managementSections` | [`plugins.management.sections.register`](/rfcs/text/0006_management_section_service.md) | API finalized, implementation in progress. | +| `mappings` | | Part of SavedObjects, see [#33587](https://github.com/elastic/kibana/issues/33587) | +| `migrations` | | Part of SavedObjects, see [#33587](https://github.com/elastic/kibana/issues/33587) | +| `navbarExtensions` | n/a | Deprecated | +| `savedObjectSchemas` | | Part of SavedObjects, see [#33587](https://github.com/elastic/kibana/issues/33587) | +| `savedObjectsManagement` | | Part of SavedObjects, see [#33587](https://github.com/elastic/kibana/issues/33587) | +| `savedObjectTypes` | | Part of SavedObjects, see [#33587](https://github.com/elastic/kibana/issues/33587) | +| `search` | | | +| `shareContextMenuExtensions` | | | +| `styleSheetPaths` | | | +| `taskDefinitions` | | Should be an API on the taskManager plugin. | +| `uiCapabilities` | [`core.application.register`](/docs/development/core/public/kibana-plugin-public.applicationsetup.register.md) | | +| `uiSettingDefaults` | | Most likely part of server-side UiSettingsService. | +| `validations` | | Part of SavedObjects, see [#33587](https://github.com/elastic/kibana/issues/33587) | +| `visEditorTypes` | | | +| `visTypeEnhancers` | | | +| `visTypes` | | | +| `visualize` | | | + ## How to ### Configure plugin diff --git a/src/core/MIGRATION_EXAMPLES.md b/src/core/MIGRATION_EXAMPLES.md new file mode 100644 index 0000000000000..79ae8953df0ea --- /dev/null +++ b/src/core/MIGRATION_EXAMPLES.md @@ -0,0 +1,267 @@ +# Migration Examples + +This document is a list of examples of how to migrate plugin code from legacy +APIs to their New Platform equivalents. + +## Configuration + +### Declaring config schema + +Declaring the schema of your configuration fields is similar to the Legacy Platform but uses the `@kbn/config-schema` package instead of Joi. This package has full TypeScript support, but may be missing some features you need. Let the Platform team know by opening an issue and we'll add what you're missing. + +```ts +// Legacy config schema +import Joi from 'joi'; + +new kibana.Plugin({ + config() { + return Joi.object({ + enabled: Joi.boolean().default(true), + defaultAppId: Joi.string().default('home'), + index: Joi.string().default('.kibana'), + disableWelcomeScreen: Joi.boolean().default(false), + autocompleteTerminateAfter: Joi.number().integer().min(1).default(100000), + }) + } +}); + +// New Platform equivalent +import { schema, TypeOf } from '@kbn/config-schema'; + +export const config = { + schema: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + defaultAppId: schema.string({ defaultValue: true }), + index: schema.string({ defaultValue: '.kibana' }), + disableWelcomeScreen: schema.boolean({ defaultValue: false }), + autocompleteTerminateAfter: schema.duration({ min: 1, defaultValue: 100000 }), + }) +}; + +// @kbn/config-schema is written in TypeScript, so you can use your schema +// definition to create a type to use in your plugin code. +export type MyPluginConfig = TypeOf; +``` + +### Using New Platform config from a Legacy plugin + +During the migration process, you'll want to migrate your schema to the new +format. However, legacy plugins cannot directly get access to New Platform's +config service due to the way that config is tied to the `kibana.json` file +(which does not exist for legacy plugins). + +There is a workaround though: +- Create a New Platform plugin that contains your plugin's config schema in the new format +- Expose the config from the New Platform plugin in its setup contract +- Read the config from the setup contract in your legacy plugin + +#### Create a New Platform plugin + +For example, if wanted to move the legacy `timelion` plugin's configuration to +the New Platform, we could create a NP plugin with the same name in +`src/plugins/timelion` with the following files: + +```json5 +// src/plugins/timelion/kibana.json +{ + "id": "timelion", + "server": true +} +``` + +```ts +// src/plugins/timelion/server/index.ts +import { schema, TypeOf } from '@kbn/config-schema'; +import { PluginInitializerContext } from 'src/core/server'; +import { TimelionPlugin } from './plugin'; + +export const config = { + schema: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + }); +} + +export const plugin = (initContext: PluginInitializerContext) => new TimelionPlugin(initContext); + +export type TimelionConfig = TypeOf; +export { TimelionSetup } from './plugin'; +``` + +```ts +// src/plugins/timelion/server/plugin.ts +import { PluginInitializerContext, Plugin, CoreSetup } from '../../core/server'; +import { TimelionConfig } from '.'; + +export class TimelionPlugin implements Plugin { + constructor(private readonly initContext: PluginInitializerContext) {} + + public setup(core: CoreSetup) { + return { + __legacy: { + config$: this.initContext.config.create(), + }, + }; + } + + public start() {} + public stop() {} +} + +export interface TimelionSetup { + /** @deprecated */ + __legacy: { + config$: Observable; + }; +} +``` + +With the New Platform plugin in place, you can then read this `config$` +Observable from your legacy plugin: + +```ts +import { take } from 'rxjs/operators'; + +new kibana.Plugin({ + async init(server) { + const { config$ } = server.newPlatform.setup.plugins.timelion; + const currentConfig = await config$.pipe(take(1)).toPromise(); + } +}); +``` + +## HTTP Routes + +In the legacy platform, plugins have direct access to the Hapi `server` object +which gives full access to all of Hapi's API. In the New Platform, plugins have +access to the +[HttpServiceSetup](/docs/development/core/server/kibana-plugin-server.httpservicesetup.md) +interface, which is exposed via the +[CoreSetup](/docs/development/core/server/kibana-plugin-server.coresetup.md) +object injected into the `setup` method of server-side plugins. + +This interface has a different API with slightly different behaviors. +- All input (body, query parameters, and URL parameters) must be validated using + the `@kbn/config-schema` package. If no validation schema is provided, these + values will be empty objects. +- All exceptions thrown by handlers result in 500 errors. If you need a specific + HTTP error code, catch any exceptions in your handler and construct the + appropriate response using the provided response factory. While you can + continue using the `boom` module internally in your plugin, the framework does + not have native support for converting Boom exceptions into HTTP responses. + +### Route Registration + +```ts +// Legacy route registration +import Joi from 'joi'; + +new kibana.Plugin({ + init(server) { + server.route({ + path: '/api/my-plugin/my-route', + method: 'POST', + options: { + validate: { + payload: Joi.object({ + field1: Joi.string().required(), + }), + } + }, + handler(req, h) { + return { message: `Received field1: ${req.payload.field1}` }; + } + }); + } +}); + + +// New Platform equivalent +import { schema } from '@kbn/config-schema'; + +class Plugin { + public setup(core) { + const router = core.http.createRouter(); + router.post( + { + path: '/api/my-plugin/my-route', + validate: { + body: schema.object({ + field1: schema.string(), + }), + } + }, + (context, req, res) => { + return res.ok({ + body: { + message: `Received field1: ${req.body.field1}` + } + }); + } + ) + } +} +``` + +### Accessing Services + +Services in the Legacy Platform were typically available via methods on either +`server.plugins.*`, `server.*`, or `req.*`. In the New Platform, all services +are available via the `context` argument to the route handler. The type of this +argument is the +[RequestHandlerContext](/docs/development/core/server/kibana-plugin-server.requesthandlercontext.md). +The APIs available here will include all Core services and any services +registered by plugins this plugin depends on. + +```ts +new kibana.Plugin({ + init(server) { + const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); + + server.route({ + path: '/api/my-plugin/my-route', + method: 'POST', + async handler(req, h) { + const results = await callWithRequest(req, 'search', query); + return { results }; + } + }); + } +}); + +class Plugin { + public setup(core) { + const router = core.http.createRouter(); + router.post( + { + path: '/api/my-plugin/my-route', + }, + async (context, req, res) => { + const results = await context.elasticsearch.dataClient.callAsCurrentUser('search', query); + return res.ok({ + body: { results } + }); + } + ) + } +} +``` + +## Chrome + +In the Legacy Platform, the `ui/chrome` import contained APIs for a very wide +range of features. In the New Platform, some of these APIs have changed or moved +elsewhere. + +| Legacy Platform | New Platform | Notes | +|-------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `chrome.addBasePath` | [`core.http.basePath.prepend`](/docs/development/core/public/kibana-plugin-public.httpservicebase.basepath.md) | | +| `chrome.breadcrumbs.set` | [`core.chrome.setBreadcrumbs`](/docs/development/core/public/kibana-plugin-public.chromestart.setbreadcrumbs.md) | | +| `chrome.getUiSettingsClient` | [`core.uiSettings`](/docs/development/core/public/kibana-plugin-public.uisettingsclient.md) | | +| `chrome.helpExtension.set` | [`core.chrome.setHelpExtension`](/docs/development/core/public/kibana-plugin-public.chromestart.sethelpextension.md) | | +| `chrome.setVisible` | [`core.chrome.setIsVisible`](/docs/development/core/public/kibana-plugin-public.chromestart.setisvisible.md) | | +| `chrome.getInjected` | [`core.injectedMetadata.getInjected`](/docs/development/core/public/kibana-plugin-public.coresetup.injectedmetadata.md) (temporary) | A temporary API is available to read injected vars provided by legacy plugins. This will be removed after [#41990](https://github.com/elastic/kibana/issues/41990) is completed. | +| `chrome.setRootTemplate` / `chrome.setRootController` | -- | Use application mounting via `core.application.register` (not currently avaiable to legacy plugins). | + +In most cases, the most convenient way to access these APIs will be via the +[AppMountContext](/docs/development/core/public/kibana-plugin-public.appmountcontext.md) +object passed to your application when your app is mounted on the page. diff --git a/src/core/public/core_system.test.ts b/src/core/public/core_system.test.ts index 460f2b3839aec..ec6f020a3490b 100644 --- a/src/core/public/core_system.test.ts +++ b/src/core/public/core_system.test.ts @@ -168,6 +168,22 @@ describe('#setup()', () => { expect(MockContextService.setup).toHaveBeenCalledTimes(1); }); + it('injects legacy dependency to context#setup()', async () => { + const pluginA = Symbol(); + const pluginB = Symbol(); + const pluginDependencies = new Map([[pluginA, []], [pluginB, [pluginA]]]); + MockPluginsService.getOpaqueIds.mockReturnValue(pluginDependencies); + await setupCore(); + + expect(MockContextService.setup).toHaveBeenCalledWith({ + pluginDependencies: new Map([ + [pluginA, []], + [pluginB, [pluginA]], + [MockLegacyPlatformService.legacyId, [pluginA, pluginB]], + ]), + }); + }); + it('calls injectedMetadata#setup()', async () => { await setupCore(); expect(MockInjectedMetadataService.setup).toHaveBeenCalledTimes(1); diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 7104b9e10c741..71e9c39df7aae 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -161,9 +161,14 @@ export class CoreSystem { const pluginDependencies = this.plugins.getOpaqueIds(); const context = this.context.setup({ - // We inject a fake "legacy plugin" with no dependencies so that legacy plugins can register context providers - // that will only be available to other legacy plugins and will not leak into New Platform plugins. - pluginDependencies: new Map([...pluginDependencies, [this.legacy.legacyId, []]]), + // We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins: + // 1) Can access context from any NP plugin + // 2) Can register context providers that will only be available to other legacy plugins and will not leak into + // New Platform plugins. + pluginDependencies: new Map([ + ...pluginDependencies, + [this.legacy.legacyId, [...pluginDependencies.keys()]], + ]), }); const application = this.application.setup({ context }); diff --git a/src/core/public/legacy/legacy_service.mock.ts b/src/core/public/legacy/legacy_service.mock.ts index bf5d4f67059cb..0c8d9682185d5 100644 --- a/src/core/public/legacy/legacy_service.mock.ts +++ b/src/core/public/legacy/legacy_service.mock.ts @@ -18,9 +18,11 @@ */ import { LegacyPlatformService } from './legacy_service'; -type LegacyPlatformServiceContract = PublicMethodsOf; +// Use Required to get only public properties +type LegacyPlatformServiceContract = Required; const createMock = () => { const mocked: jest.Mocked = { + legacyId: Symbol(), setup: jest.fn(), start: jest.fn(), stop: jest.fn(), diff --git a/src/core/server/core_context.mock.ts b/src/core/server/core_context.mock.ts index e8c0a0a4830bf..d287348e19079 100644 --- a/src/core/server/core_context.mock.ts +++ b/src/core/server/core_context.mock.ts @@ -32,7 +32,7 @@ function create({ env?: Env; logger?: jest.Mocked; configService?: jest.Mocked; -} = {}): CoreContext { +} = {}): DeeplyMockedKeys { return { coreId: Symbol(), env, logger, configService }; } diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index a42d38fd4cb70..c4a61aaf83ac7 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -38,6 +38,7 @@ export const config = { validate: match(validBasePathRegex, "must start with a slash, don't end with one"), }) ), + defaultRoute: schema.maybe(schema.string()), cors: schema.conditional( schema.contextRef('dev'), true, @@ -106,6 +107,7 @@ export class HttpConfig { public basePath?: string; public rewriteBasePath: boolean; public publicDir: string; + public defaultRoute?: string; public ssl: SslConfig; /** @@ -123,5 +125,6 @@ export class HttpConfig { this.rewriteBasePath = rawConfig.rewriteBasePath; this.publicDir = env.staticFilesDir; this.ssl = new SslConfig(rawConfig.ssl || {}); + this.defaultRoute = rawConfig.defaultRoute; } } diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 40a140f154a6e..00c9aedc42cfb 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -68,6 +68,7 @@ const createSetupContractMock = () => { getAuthHeaders: jest.fn(), }, isTlsEnabled: false, + config: {}, }; setupContract.createCookieSessionStorageFactory.mockResolvedValue( sessionStorageMock.createFactory() diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index c720d2fea4fc6..caebd768c70e5 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -106,6 +106,10 @@ export class HttpService implements CoreService ) => this.requestHandlerContext!.registerContext(pluginOpaqueId, contextName, provider), + + config: { + defaultRoute: config.defaultRoute, + }, }; return contract; diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index d75028ca12d66..6f5cb02fd8cba 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -231,6 +231,16 @@ export interface InternalHttpServiceSetup contextName: T, provider: RequestHandlerContextProvider ) => RequestHandlerContextContainer; + config: { + /** + * @internalRemarks + * Deprecated part of the server config, provided until + * https://github.com/elastic/kibana/issues/40255 + * + * @deprecated + * */ + defaultRoute?: string; + }; } /** @public */ diff --git a/src/core/server/index.ts b/src/core/server/index.ts index d92c92841bb48..90e5746b2766d 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -48,6 +48,8 @@ import { InternalHttpServiceSetup, HttpServiceSetup } from './http'; import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plugins'; import { ContextSetup } from './context'; import { SavedObjectsServiceStart } from './saved_objects'; + +import { InternalUiSettingsServiceSetup } from './ui_settings'; import { SavedObjectsClientContract } from './saved_objects/types'; export { bootstrap } from './bootstrap'; @@ -144,6 +146,7 @@ export { SavedObjectsCreateOptions, SavedObjectsErrorHelpers, SavedObjectsExportOptions, + SavedObjectsExportResultDetails, SavedObjectsFindResponse, SavedObjectsImportConflictError, SavedObjectsImportError, @@ -163,6 +166,13 @@ export { SavedObjectsUpdateResponse, } from './saved_objects'; +export { + IUiSettingsClient, + UiSettingsParams, + InternalUiSettingsServiceSetup, + UiSettingsType, +} from './ui_settings'; + export { RecursiveReadonly } from '../utils'; export { @@ -230,6 +240,7 @@ export interface InternalCoreSetup { context: ContextSetup; http: InternalHttpServiceSetup; elasticsearch: InternalElasticsearchServiceSetup; + uiSettings: InternalUiSettingsServiceSetup; } /** diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index bdb1499e065ca..590bd192e3ded 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -30,52 +30,33 @@ jest.mock('./plugins/find_legacy_plugin_specs.ts', () => ({ }), })); -import { LegacyService } from '.'; +import { LegacyService, LegacyServiceSetupDeps, LegacyServiceStartDeps } from '.'; // @ts-ignore: implicit any for JS file import MockClusterManager from '../../../cli/cluster/cluster_manager'; import KbnServer from '../../../legacy/server/kbn_server'; import { Config, Env, ObjectToConfigAdapter } from '../config'; -import { ContextSetup } from '../context'; import { contextServiceMock } from '../context/context_service.mock'; import { getEnvOptions } from '../config/__mocks__/env'; import { configServiceMock } from '../config/config_service.mock'; -import { InternalElasticsearchServiceSetup } from '../elasticsearch'; -import { HttpServiceStart, BasePathProxyServer } from '../http'; + +import { BasePathProxyServer } from '../http'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { DiscoveredPlugin, DiscoveredPluginInternal } from '../plugins'; -import { PluginsServiceSetup, PluginsServiceStart } from '../plugins/plugins_service'; -import { - SavedObjectsServiceStart, - SavedObjectsServiceSetup, -} from 'src/core/server/saved_objects/saved_objects_service'; + import { KibanaMigrator } from '../saved_objects/migrations'; import { ISavedObjectsClientProvider } from '../saved_objects'; import { httpServiceMock } from '../http/http_service.mock'; +import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; const MockKbnServer: jest.Mock = KbnServer as any; let coreId: symbol; let env: Env; let config$: BehaviorSubject; -let setupDeps: { - core: { - context: ContextSetup; - elasticsearch: InternalElasticsearchServiceSetup; - http: any; - plugins: PluginsServiceSetup; - savedObjects: SavedObjectsServiceSetup; - }; - plugins: Record; -}; - -let startDeps: { - core: { - http: HttpServiceStart; - savedObjects: SavedObjectsServiceStart; - plugins: PluginsServiceStart; - }; - plugins: Record; -}; + +let setupDeps: LegacyServiceSetupDeps; + +let startDeps: LegacyServiceStartDeps; const logger = loggingServiceMock.create(); let configService: ReturnType; @@ -91,12 +72,14 @@ beforeEach(() => { core: { context: contextServiceMock.createSetupContract(), elasticsearch: { legacy: {} } as any, + uiSettings: uiSettingsServiceMock.createSetup(), http: { ...httpServiceMock.createSetupContract(), auth: { getAuthHeaders: () => undefined, - }, + } as any, }, + plugins: { contracts: new Map([['plugin-id', 'plugin-value']]), uiPlugins: { @@ -104,18 +87,12 @@ beforeEach(() => { internal: new Map([['plugin-id', {} as DiscoveredPluginInternal]]), }, }, - savedObjects: { - clientProvider: {} as ISavedObjectsClientProvider, - }, }, plugins: { 'plugin-id': 'plugin-value' }, }; startDeps = { core: { - http: { - isListening: () => true, - }, savedObjects: { migrator: {} as KibanaMigrator, clientProvider: {} as ISavedObjectsClientProvider, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index edd913fe16a63..dd3ee3db89153 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -275,6 +275,7 @@ export class LegacyService implements CoreService { kibanaMigrator: startDeps.core.savedObjects.migrator, uiPlugins: setupDeps.core.plugins.uiPlugins, elasticsearch: setupDeps.core.elasticsearch, + uiSettings: setupDeps.core.uiSettings, savedObjectsClientProvider: startDeps.core.savedObjects.clientProvider, }, logger: this.coreContext.logger, diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index c3d524c77402c..fb703c6c35008 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -30,6 +30,7 @@ export { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service. export { httpServiceMock } from './http/http_service.mock'; export { loggingServiceMock } from './logging/logging_service.mock'; export { SavedObjectsClientMock } from './saved_objects/service/saved_objects_client.mock'; +export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; export function pluginInitializerContextConfigMock(config: T) { const mock: jest.Mocked['config']> = { diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index 100d043a762f8..7599ff0378caf 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -43,7 +43,8 @@ function createPlugin( required = [], optional = [], server = true, - }: { required?: string[]; optional?: string[]; server?: boolean } = {} + ui = true, + }: { required?: string[]; optional?: string[]; server?: boolean; ui?: boolean } = {} ) { return new PluginWrapper({ path: 'some-path', @@ -55,7 +56,7 @@ function createPlugin( requiredPlugins: required, optionalPlugins: optional, server, - ui: true, + ui, }, opaqueId: Symbol(id), initializerContext: { logger } as any, @@ -147,13 +148,13 @@ test('`setupPlugins` ignores missing optional dependency', async () => { pluginsSystem.addPlugin(plugin); expect([...(await pluginsSystem.setupPlugins(setupDeps))]).toMatchInlineSnapshot(` -Array [ - Array [ - "some-id", - "test", - ], -] -`); + Array [ + Array [ + "some-id", + "test", + ], + ] + `); }); test('correctly orders plugins and returns exposed values for "setup" and "start"', async () => { @@ -221,29 +222,29 @@ test('correctly orders plugins and returns exposed values for "setup" and "start ); expect([...(await pluginsSystem.setupPlugins(setupDeps))]).toMatchInlineSnapshot(` -Array [ - Array [ - "order-0", - "added-as-1", - ], - Array [ - "order-1", - "added-as-3", - ], - Array [ - "order-2", - "added-as-2", - ], - Array [ - "order-3", - "added-as-4", - ], - Array [ - "order-4", - "added-as-0", - ], -] -`); + Array [ + Array [ + "order-0", + "added-as-1", + ], + Array [ + "order-1", + "added-as-3", + ], + Array [ + "order-2", + "added-as-2", + ], + Array [ + "order-3", + "added-as-4", + ], + Array [ + "order-4", + "added-as-0", + ], + ] + `); for (const [plugin, deps] of plugins) { expect(mockCreatePluginSetupContext).toHaveBeenCalledWith(coreContext, setupDeps, plugin); @@ -253,29 +254,29 @@ Array [ const startDeps = {}; expect([...(await pluginsSystem.startPlugins(startDeps))]).toMatchInlineSnapshot(` -Array [ - Array [ - "order-0", - "started-as-1", - ], - Array [ - "order-1", - "started-as-3", - ], - Array [ - "order-2", - "started-as-2", - ], - Array [ - "order-3", - "started-as-4", - ], - Array [ - "order-4", - "started-as-0", - ], -] -`); + Array [ + Array [ + "order-0", + "started-as-1", + ], + Array [ + "order-1", + "started-as-3", + ], + Array [ + "order-2", + "started-as-2", + ], + Array [ + "order-3", + "started-as-4", + ], + Array [ + "order-4", + "started-as-0", + ], + ] + `); for (const [plugin, deps] of plugins) { expect(mockCreatePluginStartContext).toHaveBeenCalledWith(coreContext, startDeps, plugin); @@ -296,17 +297,17 @@ test('`setupPlugins` only setups plugins that have server side', async () => { }); expect([...(await pluginsSystem.setupPlugins(setupDeps))]).toMatchInlineSnapshot(` -Array [ - Array [ - "order-1", - "added-as-2", - ], - Array [ - "order-0", - "added-as-0", - ], -] -`); + Array [ + Array [ + "order-1", + "added-as-2", + ], + Array [ + "order-0", + "added-as-0", + ], + ] + `); expect(mockCreatePluginSetupContext).toHaveBeenCalledWith( coreContext, @@ -327,11 +328,11 @@ Array [ test('`uiPlugins` returns empty Maps before plugins are added', async () => { expect(pluginsSystem.uiPlugins()).toMatchInlineSnapshot(` -Object { - "internal": Map {}, - "public": Map {}, -} -`); + Object { + "internal": Map {}, + "public": Map {}, + } + `); }); test('`uiPlugins` returns ordered Maps of all plugin manifests', async () => { @@ -354,14 +355,37 @@ test('`uiPlugins` returns ordered Maps of all plugin manifests', async () => { }); expect([...pluginsSystem.uiPlugins().internal.keys()]).toMatchInlineSnapshot(` -Array [ - "order-0", - "order-1", - "order-2", - "order-3", - "order-4", -] -`); + Array [ + "order-0", + "order-1", + "order-2", + "order-3", + "order-4", + ] + `); +}); + +test('`uiPlugins` returns only ui plugin dependencies', async () => { + const plugins = [ + createPlugin('ui-plugin', { + required: ['req-ui', 'req-no-ui'], + optional: ['opt-ui', 'opt-no-ui'], + ui: true, + server: false, + }), + createPlugin('req-ui', { ui: true, server: false }), + createPlugin('req-no-ui', { ui: false, server: true }), + createPlugin('opt-ui', { ui: true, server: false }), + createPlugin('opt-no-ui', { ui: false, server: true }), + ]; + + plugins.forEach(plugin => { + pluginsSystem.addPlugin(plugin); + }); + + const plugin = pluginsSystem.uiPlugins().internal.get('ui-plugin')!; + expect(plugin.requiredPlugins).toEqual(['req-ui']); + expect(plugin.optionalPlugins).toEqual(['opt-ui']); }); test('can start without plugins', async () => { @@ -386,15 +410,15 @@ test('`startPlugins` only starts plugins that were setup', async () => { await pluginsSystem.setupPlugins(setupDeps); const result = await pluginsSystem.startPlugins({}); expect([...result]).toMatchInlineSnapshot(` -Array [ - Array [ - "order-1", - "started-as-2", - ], - Array [ - "order-0", - "started-as-0", - ], -] -`); + Array [ + Array [ + "order-1", + "started-as-2", + ], + Array [ + "order-0", + "started-as-0", + ], + ] + `); }); diff --git a/src/core/server/plugins/plugins_system.ts b/src/core/server/plugins/plugins_system.ts index 1f797525ba14f..266a68b32703e 100644 --- a/src/core/server/plugins/plugins_system.ts +++ b/src/core/server/plugins/plugins_system.ts @@ -161,22 +161,23 @@ export class PluginsSystem { * Get a Map of all discovered UI plugins in topological order. */ public uiPlugins() { + const uiPluginNames = [...this.getTopologicallySortedPluginNames().keys()].filter( + pluginName => this.plugins.get(pluginName)!.includesUiPlugin + ); const internal = new Map( - [...this.getTopologicallySortedPluginNames().keys()] - .filter(pluginName => this.plugins.get(pluginName)!.includesUiPlugin) - .map(pluginName => { - const plugin = this.plugins.get(pluginName)!; - return [ - pluginName, - { - id: pluginName, - path: plugin.path, - configPath: plugin.manifest.configPath, - requiredPlugins: plugin.manifest.requiredPlugins, - optionalPlugins: plugin.manifest.optionalPlugins, - }, - ] as [PluginName, DiscoveredPluginInternal]; - }) + uiPluginNames.map(pluginName => { + const plugin = this.plugins.get(pluginName)!; + return [ + pluginName, + { + id: pluginName, + path: plugin.path, + configPath: plugin.manifest.configPath, + requiredPlugins: plugin.manifest.requiredPlugins.filter(p => uiPluginNames.includes(p)), + optionalPlugins: plugin.manifest.optionalPlugins.filter(p => uiPluginNames.includes(p)), + }, + ] as [PluginName, DiscoveredPluginInternal]; + }) ); const publicPlugins = new Map( diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts index df3bbe7c455e4..1a2a843ebb2b8 100644 --- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts +++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.test.ts @@ -74,27 +74,32 @@ describe('getSortedObjectsForExport()', () => { const response = await readStreamToCompletion(exportStream); expect(response).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "name", - "type": "index-pattern", - }, - ], - "type": "search", - }, - ] - `); + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "name", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "exportedCount": 2, + "missingRefCount": 0, + "missingReferences": Array [], + }, + ] + `); expect(savedObjectsClient.find).toMatchInlineSnapshot(` [MockFunction] { "calls": Array [ @@ -122,6 +127,65 @@ describe('getSortedObjectsForExport()', () => { `); }); + test('exclude export details if option is specified', async () => { + savedObjectsClient.find.mockResolvedValueOnce({ + total: 2, + saved_objects: [ + { + id: '2', + type: 'search', + attributes: {}, + references: [ + { + name: 'name', + type: 'index-pattern', + id: '1', + }, + ], + }, + { + id: '1', + type: 'index-pattern', + attributes: {}, + references: [], + }, + ], + per_page: 1, + page: 0, + }); + const exportStream = await getSortedObjectsForExport({ + savedObjectsClient, + exportSizeLimit: 500, + types: ['index-pattern', 'search'], + excludeExportDetails: true, + }); + + const response = await readStreamToCompletion(exportStream); + + expect(response).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "name", + "type": "index-pattern", + }, + ], + "type": "search", + }, + ] + `); + }); + test('exports selected types with search string when present', async () => { savedObjectsClient.find.mockResolvedValueOnce({ total: 2, @@ -158,27 +222,32 @@ describe('getSortedObjectsForExport()', () => { const response = await readStreamToCompletion(exportStream); expect(response).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "name", - "type": "index-pattern", - }, - ], - "type": "search", - }, - ] - `); + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "name", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "exportedCount": 2, + "missingRefCount": 0, + "missingReferences": Array [], + }, + ] + `); expect(savedObjectsClient.find).toMatchInlineSnapshot(` [MockFunction] { "calls": Array [ @@ -242,27 +311,32 @@ describe('getSortedObjectsForExport()', () => { const response = await readStreamToCompletion(exportStream); expect(response).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "name", - "type": "index-pattern", - }, - ], - "type": "search", - }, - ] - `); + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "name", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "exportedCount": 2, + "missingRefCount": 0, + "missingReferences": Array [], + }, + ] + `); expect(savedObjectsClient.find).toMatchInlineSnapshot(` [MockFunction] { "calls": Array [ @@ -365,27 +439,32 @@ describe('getSortedObjectsForExport()', () => { }); const response = await readStreamToCompletion(exportStream); expect(response).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "name", - "type": "index-pattern", - }, - ], - "type": "search", - }, - ] - `); + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "name", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "exportedCount": 2, + "missingRefCount": 0, + "missingReferences": Array [], + }, + ] + `); expect(savedObjectsClient.bulkGet).toMatchInlineSnapshot(` [MockFunction] { "calls": Array [ @@ -456,27 +535,32 @@ describe('getSortedObjectsForExport()', () => { }); const response = await readStreamToCompletion(exportStream); expect(response).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "name", - "type": "index-pattern", - }, - ], - "type": "search", - }, - ] - `); + Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "name", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "exportedCount": 2, + "missingRefCount": 0, + "missingReferences": Array [], + }, + ] + `); expect(savedObjectsClient.bulkGet).toMatchInlineSnapshot(` [MockFunction] { "calls": Array [ diff --git a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts index eca8fc0405300..e1a705a36db75 100644 --- a/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts +++ b/src/core/server/saved_objects/export/get_sorted_objects_for_export.ts @@ -20,7 +20,7 @@ import Boom from 'boom'; import { createListStream } from '../../../../legacy/utils/streams'; import { SavedObjectsClientContract } from '../types'; -import { injectNestedDependencies } from './inject_nested_depdendencies'; +import { fetchNestedDependencies } from './inject_nested_depdendencies'; import { sortObjects } from './sort_objects'; /** @@ -43,12 +43,32 @@ export interface SavedObjectsExportOptions { savedObjectsClient: SavedObjectsClientContract; /** the maximum number of objects to export. */ exportSizeLimit: number; - /** flag to also include all related saved objects in the export response. */ + /** flag to also include all related saved objects in the export stream. */ includeReferencesDeep?: boolean; + /** flag to not append {@link SavedObjectsExportResultDetails | export details} to the end of the export stream. */ + excludeExportDetails?: boolean; /** optional namespace to override the namespace used by the savedObjectsClient. */ namespace?: string; } +/** + * Structure of the export result details entry + * @public + */ +export interface SavedObjectsExportResultDetails { + /** number of successfully exported objects */ + exportedCount: number; + /** number of missing references */ + missingRefCount: number; + /** missing references details */ + missingReferences: Array<{ + /** the missing reference id. */ + id: string; + /** the missing reference type. */ + type: string; + }>; +} + async function fetchObjectsToExport({ objects, types, @@ -106,9 +126,10 @@ export async function getSortedObjectsForExport({ savedObjectsClient, exportSizeLimit, includeReferencesDeep = false, + excludeExportDetails = false, namespace, }: SavedObjectsExportOptions) { - const objectsToExport = await fetchObjectsToExport({ + const rootObjects = await fetchObjectsToExport({ types, objects, search, @@ -116,12 +137,18 @@ export async function getSortedObjectsForExport({ exportSizeLimit, namespace, }); - - const exportedObjects = sortObjects( - includeReferencesDeep - ? await injectNestedDependencies(objectsToExport, savedObjectsClient, namespace) - : objectsToExport - ); - - return createListStream(exportedObjects); + let exportedObjects = [...rootObjects]; + let missingReferences: SavedObjectsExportResultDetails['missingReferences'] = []; + if (includeReferencesDeep) { + const fetchResult = await fetchNestedDependencies(rootObjects, savedObjectsClient, namespace); + exportedObjects = fetchResult.objects; + missingReferences = fetchResult.missingRefs; + } + exportedObjects = sortObjects(exportedObjects); + const exportDetails: SavedObjectsExportResultDetails = { + exportedCount: exportedObjects.length, + missingRefCount: missingReferences.length, + missingReferences, + }; + return createListStream([...exportedObjects, ...(excludeExportDetails ? [] : [exportDetails])]); } diff --git a/src/core/server/saved_objects/export/index.ts b/src/core/server/saved_objects/export/index.ts index d994df2af627c..7533b8e500039 100644 --- a/src/core/server/saved_objects/export/index.ts +++ b/src/core/server/saved_objects/export/index.ts @@ -20,4 +20,5 @@ export { getSortedObjectsForExport, SavedObjectsExportOptions, + SavedObjectsExportResultDetails, } from './get_sorted_objects_for_export'; diff --git a/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts b/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts index 4613553fbd301..89d555e06a634 100644 --- a/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts +++ b/src/core/server/saved_objects/export/inject_nested_depdendencies.test.ts @@ -18,10 +18,7 @@ */ import { SavedObject } from '../types'; -import { - getObjectReferencesToFetch, - injectNestedDependencies, -} from './inject_nested_depdendencies'; +import { getObjectReferencesToFetch, fetchNestedDependencies } from './inject_nested_depdendencies'; describe('getObjectReferencesToFetch()', () => { test('works with no saved objects', () => { @@ -110,7 +107,7 @@ describe('getObjectReferencesToFetch()', () => { }); }); -describe('injectNestedDependencies', () => { +describe('fetchNestedDependencies', () => { const savedObjectsClient = { errors: {} as any, find: jest.fn(), @@ -135,16 +132,19 @@ describe('injectNestedDependencies', () => { references: [], }, ]; - const result = await injectNestedDependencies(savedObjects, savedObjectsClient); + const result = await fetchNestedDependencies(savedObjects, savedObjectsClient); expect(result).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - ] + Object { + "missingRefs": Array [], + "objects": Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + ], + } `); }); @@ -169,28 +169,31 @@ describe('injectNestedDependencies', () => { ], }, ]; - const result = await injectNestedDependencies(savedObjects, savedObjectsClient); + const result = await fetchNestedDependencies(savedObjects, savedObjectsClient); expect(result).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "ref_0", - "type": "index-pattern", - }, - ], - "type": "search", - }, - ] + Object { + "missingRefs": Array [], + "objects": Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "ref_0", + "type": "index-pattern", + }, + ], + "type": "search", + }, + ], + } `); }); @@ -219,28 +222,31 @@ describe('injectNestedDependencies', () => { }, ], }); - const result = await injectNestedDependencies(savedObjects, savedObjectsClient); + const result = await fetchNestedDependencies(savedObjects, savedObjectsClient); expect(result).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "ref_0", - "type": "index-pattern", - }, - ], - "type": "search", - }, - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - ] + Object { + "missingRefs": Array [], + "objects": Array [ + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "ref_0", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + ], + } `); expect(savedObjectsClient.bulkGet).toMatchInlineSnapshot(` [MockFunction] { @@ -337,69 +343,72 @@ describe('injectNestedDependencies', () => { }, ], }); - const result = await injectNestedDependencies(savedObjects, savedObjectsClient); + const result = await fetchNestedDependencies(savedObjects, savedObjectsClient); expect(result).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "5", - "references": Array [ - Object { - "id": "4", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "3", - "name": "panel_1", - "type": "visualization", - }, - ], - "type": "dashboard", - }, - Object { - "attributes": Object {}, - "id": "4", - "references": Array [ - Object { - "id": "2", - "name": "ref_0", - "type": "search", - }, - ], - "type": "visualization", - }, - Object { - "attributes": Object {}, - "id": "3", - "references": Array [ - Object { - "id": "1", - "name": "ref_0", - "type": "index-pattern", - }, - ], - "type": "visualization", - }, - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "ref_0", - "type": "index-pattern", - }, - ], - "type": "search", - }, - Object { - "attributes": Object {}, - "id": "1", - "references": Array [], - "type": "index-pattern", - }, - ] + Object { + "missingRefs": Array [], + "objects": Array [ + Object { + "attributes": Object {}, + "id": "5", + "references": Array [ + Object { + "id": "4", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "3", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", + }, + Object { + "attributes": Object {}, + "id": "4", + "references": Array [ + Object { + "id": "2", + "name": "ref_0", + "type": "search", + }, + ], + "type": "visualization", + }, + Object { + "attributes": Object {}, + "id": "3", + "references": Array [ + Object { + "id": "1", + "name": "ref_0", + "type": "index-pattern", + }, + ], + "type": "visualization", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "ref_0", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "attributes": Object {}, + "id": "1", + "references": Array [], + "type": "index-pattern", + }, + ], + } `); expect(savedObjectsClient.bulkGet).toMatchInlineSnapshot(` [MockFunction] { @@ -449,10 +458,10 @@ describe('injectNestedDependencies', () => { `); }); - test('throws error when bulkGet returns an error', async () => { + test('returns list of missing references', async () => { const savedObjects = [ { - id: '2', + id: '1', type: 'search', attributes: {}, references: [ @@ -461,6 +470,11 @@ describe('injectNestedDependencies', () => { type: 'index-pattern', id: '1', }, + { + name: 'ref_1', + type: 'index-pattern', + id: '2', + }, ], }, ]; @@ -474,11 +488,50 @@ describe('injectNestedDependencies', () => { message: 'Not found', }, }, + { + id: '2', + type: 'index-pattern', + attributes: {}, + references: [], + }, ], }); - await expect( - injectNestedDependencies(savedObjects, savedObjectsClient) - ).rejects.toThrowErrorMatchingInlineSnapshot(`"Bad Request"`); + const result = await fetchNestedDependencies(savedObjects, savedObjectsClient); + expect(result).toMatchInlineSnapshot(` + Object { + "missingRefs": Array [ + Object { + "id": "1", + "type": "index-pattern", + }, + ], + "objects": Array [ + Object { + "attributes": Object {}, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "ref_0", + "type": "index-pattern", + }, + Object { + "id": "2", + "name": "ref_1", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "attributes": Object {}, + "id": "2", + "references": Array [], + "type": "index-pattern", + }, + ], + } + `); }); test(`doesn't deal with circular dependencies`, async () => { @@ -512,34 +565,37 @@ describe('injectNestedDependencies', () => { }, ], }); - const result = await injectNestedDependencies(savedObjects, savedObjectsClient); + const result = await fetchNestedDependencies(savedObjects, savedObjectsClient); expect(result).toMatchInlineSnapshot(` - Array [ - Object { - "attributes": Object {}, - "id": "2", - "references": Array [ - Object { - "id": "1", - "name": "ref_0", - "type": "index-pattern", - }, - ], - "type": "search", - }, - Object { - "attributes": Object {}, - "id": "1", - "references": Array [ - Object { - "id": "2", - "name": "ref_0", - "type": "search", - }, - ], - "type": "index-pattern", - }, - ] + Object { + "missingRefs": Array [], + "objects": Array [ + Object { + "attributes": Object {}, + "id": "2", + "references": Array [ + Object { + "id": "1", + "name": "ref_0", + "type": "index-pattern", + }, + ], + "type": "search", + }, + Object { + "attributes": Object {}, + "id": "1", + "references": Array [ + Object { + "id": "2", + "name": "ref_0", + "type": "search", + }, + ], + "type": "index-pattern", + }, + ], + } `); expect(savedObjectsClient.bulkGet).toMatchInlineSnapshot(` [MockFunction] { diff --git a/src/core/server/saved_objects/export/inject_nested_depdendencies.ts b/src/core/server/saved_objects/export/inject_nested_depdendencies.ts index 279b06f955571..d00650926e57a 100644 --- a/src/core/server/saved_objects/export/inject_nested_depdendencies.ts +++ b/src/core/server/saved_objects/export/inject_nested_depdendencies.ts @@ -17,47 +17,43 @@ * under the License. */ -import Boom from 'boom'; import { SavedObject, SavedObjectsClientContract } from '../types'; export function getObjectReferencesToFetch(savedObjectsMap: Map) { const objectsToFetch = new Map(); for (const savedObject of savedObjectsMap.values()) { - for (const { type, id } of savedObject.references || []) { - if (!savedObjectsMap.has(`${type}:${id}`)) { - objectsToFetch.set(`${type}:${id}`, { type, id }); + for (const ref of savedObject.references || []) { + if (!savedObjectsMap.has(objKey(ref))) { + objectsToFetch.set(objKey(ref), { type: ref.type, id: ref.id }); } } } return [...objectsToFetch.values()]; } -export async function injectNestedDependencies( +export async function fetchNestedDependencies( savedObjects: SavedObject[], savedObjectsClient: SavedObjectsClientContract, namespace?: string ) { const savedObjectsMap = new Map(); for (const savedObject of savedObjects) { - savedObjectsMap.set(`${savedObject.type}:${savedObject.id}`, savedObject); + savedObjectsMap.set(objKey(savedObject), savedObject); } let objectsToFetch = getObjectReferencesToFetch(savedObjectsMap); while (objectsToFetch.length > 0) { const bulkGetResponse = await savedObjectsClient.bulkGet(objectsToFetch, { namespace }); - // Check for errors - const erroredObjects = bulkGetResponse.saved_objects.filter(obj => !!obj.error); - if (erroredObjects.length) { - const err = Boom.badRequest(); - err.output.payload.attributes = { - objects: erroredObjects, - }; - throw err; - } // Push to array result for (const savedObject of bulkGetResponse.saved_objects) { - savedObjectsMap.set(`${savedObject.type}:${savedObject.id}`, savedObject); + savedObjectsMap.set(objKey(savedObject), savedObject); } objectsToFetch = getObjectReferencesToFetch(savedObjectsMap); } - return [...savedObjectsMap.values()]; + const allObjects = [...savedObjectsMap.values()]; + return { + objects: allObjects.filter(obj => !obj.error), + missingRefs: allObjects.filter(obj => !!obj.error).map(obj => ({ type: obj.type, id: obj.id })), + }; } + +const objKey = (obj: { type: string; id: string }) => `${obj.type}:${obj.id}`; diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index 674f8df33ee37..76c62e0841bff 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -25,7 +25,11 @@ export { SavedObjectsManagement } from './management'; export * from './import'; -export { getSortedObjectsForExport, SavedObjectsExportOptions } from './export'; +export { + getSortedObjectsForExport, + SavedObjectsExportOptions, + SavedObjectsExportResultDetails, +} from './export'; export { SavedObjectsSerializer, RawDoc as SavedObjectsRawDoc } from './serialization'; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 46e5d2b6ab6c6..a329fda5d8593 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -722,6 +722,8 @@ export interface InternalCoreSetup { // // (undocumented) http: InternalHttpServiceSetup; + // (undocumented) + uiSettings: InternalUiSettingsServiceSetup; } // @internal (undocumented) @@ -732,6 +734,12 @@ export interface InternalCoreStart { savedObjects: SavedObjectsServiceStart; } +// @internal (undocumented) +export interface InternalUiSettingsServiceSetup { + asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; + setDefaults(values: Record): void; +} + // @public export interface IRouter { delete:

(route: RouteConfig, handler: RequestHandler) => void; @@ -751,6 +759,22 @@ export type IsAuthenticated = (request: KibanaRequest | LegacyRequest) => boolea // @public export type IScopedClusterClient = Pick; +// @public +export interface IUiSettingsClient { + get: (key: string) => Promise; + getAll: () => Promise>; + getDefaults: () => Record; + getUserProvided: () => Promise>; + isOverridden: (key: string) => boolean; + remove: (key: string) => Promise; + removeMany: (keys: string[]) => Promise; + set: (key: string, value: T) => Promise; + setMany: (changes: Record) => Promise; +} + // @public export class KibanaRequest { // @internal (undocumented) @@ -1263,6 +1287,7 @@ export class SavedObjectsErrorHelpers { // @public export interface SavedObjectsExportOptions { + excludeExportDetails?: boolean; exportSizeLimit: number; includeReferencesDeep?: boolean; namespace?: string; @@ -1275,6 +1300,16 @@ export interface SavedObjectsExportOptions { types?: string[]; } +// @public +export interface SavedObjectsExportResultDetails { + exportedCount: number; + missingRefCount: number; + missingReferences: Array<{ + id: string; + type: string; + }>; +} + // @public (undocumented) export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions { // (undocumented) @@ -1545,6 +1580,22 @@ export interface SessionStorageFactory { asScoped: (request: KibanaRequest) => SessionStorage; } +// @public +export interface UiSettingsParams { + category: string[]; + description: string; + name: string; + optionLabels?: Record; + options?: string[]; + readonly?: boolean; + requiresPageReload?: boolean; + type?: UiSettingsType; + value: SavedObjectAttribute; +} + +// @public +export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; + // Warnings were encountered during analysis: // diff --git a/src/core/server/index.test.mocks.ts b/src/core/server/server.test.mocks.ts similarity index 89% rename from src/core/server/index.test.mocks.ts rename to src/core/server/server.test.mocks.ts index 12cba7b29fc78..f8eb5e32f4c5a 100644 --- a/src/core/server/index.test.mocks.ts +++ b/src/core/server/server.test.mocks.ts @@ -36,6 +36,7 @@ jest.doMock('./elasticsearch/elasticsearch_service', () => ({ })); export const mockLegacyService = { + legacyId: Symbol(), setup: jest.fn().mockReturnValue({ uiExports: {} }), start: jest.fn(), stop: jest.fn(), @@ -55,3 +56,9 @@ export const mockSavedObjectsService = savedObjectsServiceMock.create(); jest.doMock('./saved_objects/saved_objects_service', () => ({ SavedObjectsService: jest.fn(() => mockSavedObjectsService), })); + +import { contextServiceMock } from './context/context_service.mock'; +export const mockContextService = contextServiceMock.create(); +jest.doMock('./context/context_service', () => ({ + ContextService: jest.fn(() => mockContextService), +})); diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts index 7319e4126fc07..aee6461580654 100644 --- a/src/core/server/server.test.ts +++ b/src/core/server/server.test.ts @@ -24,7 +24,8 @@ import { mockPluginsService, mockConfigService, mockSavedObjectsService, -} from './index.test.mocks'; + mockContextService, +} from './server.test.mocks'; import { BehaviorSubject } from 'rxjs'; import { Env, Config, ObjectToConfigAdapter } from './config'; @@ -64,6 +65,25 @@ test('sets up services on "setup"', async () => { expect(mockSavedObjectsService.setup).toHaveBeenCalledTimes(1); }); +test('injects legacy dependency to context#setup()', async () => { + const server = new Server(config$, env, logger); + + const pluginA = Symbol(); + const pluginB = Symbol(); + const pluginDependencies = new Map([[pluginA, []], [pluginB, [pluginA]]]); + mockPluginsService.discover.mockResolvedValue(pluginDependencies); + + await server.setup(); + + expect(mockContextService.setup).toHaveBeenCalledWith({ + pluginDependencies: new Map([ + [pluginA, []], + [pluginB, [pluginA]], + [mockLegacyService.legacyId, [pluginA, pluginB]], + ]), + }); +}); + test('runs services on "start"', async () => { const server = new Server(config$, env, logger); diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 455bb97d47b57..ffe910a87385a 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -25,6 +25,7 @@ import { ElasticsearchService, ElasticsearchServiceSetup } from './elasticsearch import { HttpService, InternalHttpServiceSetup } from './http'; import { LegacyService } from './legacy'; import { Logger, LoggerFactory } from './logging'; +import { UiSettingsService } from './ui_settings'; import { PluginsService, config as pluginsConfig } from './plugins'; import { SavedObjectsService } from '../server/saved_objects'; @@ -34,6 +35,7 @@ import { config as loggingConfig } from './logging'; import { config as devConfig } from './dev'; import { config as kibanaConfig } from './kibana_config'; import { config as savedObjectsConfig } from './saved_objects'; +import { config as uiSettingsConfig } from './ui_settings'; import { mapToObject } from '../utils/'; import { ContextService } from './context'; import { SavedObjectsServiceSetup } from './saved_objects/saved_objects_service'; @@ -50,6 +52,7 @@ export class Server { private readonly log: Logger; private readonly plugins: PluginsService; private readonly savedObjects: SavedObjectsService; + private readonly uiSettings: UiSettingsService; constructor( readonly config$: Observable, @@ -66,6 +69,7 @@ export class Server { this.legacy = new LegacyService(core); this.elasticsearch = new ElasticsearchService(core); this.savedObjects = new SavedObjectsService(core); + this.uiSettings = new UiSettingsService(core); } public async setup() { @@ -74,9 +78,14 @@ export class Server { // Discover any plugins before continuing. This allows other systems to utilize the plugin dependency graph. const pluginDependencies = await this.plugins.discover(); const contextServiceSetup = this.context.setup({ - // We inject a fake "legacy plugin" with no dependencies so that legacy plugins can register context providers - // that will only be available to other legacy plugins and will not leak into New Platform plugins. - pluginDependencies: new Map([...pluginDependencies, [this.legacy.legacyId, []]]), + // We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins: + // 1) Can access context from any NP plugin + // 2) Can register context providers that will only be available to other legacy plugins and will not leak into + // New Platform plugins. + pluginDependencies: new Map([ + ...pluginDependencies, + [this.legacy.legacyId, [...pluginDependencies.keys()]], + ]), }); const httpSetup = await this.http.setup({ @@ -89,10 +98,15 @@ export class Server { http: httpSetup, }); + const uiSettingsSetup = await this.uiSettings.setup({ + http: httpSetup, + }); + const coreSetup = { context: contextServiceSetup, elasticsearch: elasticsearchServiceSetup, http: httpSetup, + uiSettings: uiSettingsSetup, }; const pluginsSetup = await this.plugins.setup(coreSetup); @@ -184,6 +198,7 @@ export class Server { [devConfig.path, devConfig.schema], [kibanaConfig.path, kibanaConfig.schema], [savedObjectsConfig.path, savedObjectsConfig.schema], + [uiSettingsConfig.path, uiSettingsConfig.schema], ]; for (const [path, schema] of schemas) { diff --git a/src/legacy/ui/ui_settings/create_objects_client_stub.ts b/src/core/server/ui_settings/create_objects_client_stub.ts similarity index 96% rename from src/legacy/ui/ui_settings/create_objects_client_stub.ts rename to src/core/server/ui_settings/create_objects_client_stub.ts index ad19b5c8bc7cf..d52ec58fa7e37 100644 --- a/src/legacy/ui/ui_settings/create_objects_client_stub.ts +++ b/src/core/server/ui_settings/create_objects_client_stub.ts @@ -18,7 +18,7 @@ */ import sinon from 'sinon'; -import { SavedObjectsClient } from '../../../../src/core/server'; +import { SavedObjectsClient } from '../saved_objects'; export const savedObjectsClientErrors = SavedObjectsClient.errors; diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts similarity index 87% rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts rename to src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts index 654c0fbb66c8b..5f7e915365873 100644 --- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.test.ts @@ -18,14 +18,13 @@ */ import sinon from 'sinon'; -import expect from '@kbn/expect'; import Chance from 'chance'; +import { loggingServiceMock } from '../../logging/logging_service.mock'; import * as getUpgradeableConfigNS from './get_upgradeable_config'; import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; const chance = new Chance(); - describe('uiSettings/createOrUpgradeSavedConfig', function() { const sandbox = sinon.createSandbox(); afterEach(() => sandbox.restore()); @@ -35,7 +34,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { const buildNum = chance.integer({ min: 1000, max: 5000 }); function setup() { - const logWithMetadata = sinon.stub(); + const logger = loggingServiceMock.create(); const getUpgradeableConfig = sandbox.stub(getUpgradeableConfigNS, 'getUpgradeableConfig'); const savedObjectsClient = { create: sinon.stub().callsFake(async (type, attributes, options = {}) => ({ @@ -50,7 +49,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { savedObjectsClient, version, buildNum, - logWithMetadata, + log: logger.get(), ...options, }); @@ -62,7 +61,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { return { buildNum, - logWithMetadata, + logger, run, version, savedObjectsClient, @@ -126,7 +125,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { }); it('should log a message for upgrades', async () => { - const { getUpgradeableConfig, logWithMetadata, run } = setup(); + const { getUpgradeableConfig, logger, run } = setup(); getUpgradeableConfig.resolves({ id: prevVersion, @@ -136,20 +135,21 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { }); await run(); - sinon.assert.calledOnce(logWithMetadata); - sinon.assert.calledWithExactly( - logWithMetadata, - ['plugin', 'elasticsearch'], - sinon.match('Upgrade'), - sinon.match({ - prevVersion, - newVersion: version, - }) - ); + expect(loggingServiceMock.collect(logger).debug).toMatchInlineSnapshot(` + Array [ + Array [ + "Upgrade config from 4.0.0 to 4.0.1", + Object { + "newVersion": "4.0.1", + "prevVersion": "4.0.0", + }, + ], + ] + `); }); it('does not log when upgrade fails', async () => { - const { getUpgradeableConfig, logWithMetadata, run, savedObjectsClient } = setup(); + const { getUpgradeableConfig, logger, run, savedObjectsClient } = setup(); getUpgradeableConfig.resolves({ id: prevVersion, @@ -166,10 +166,10 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { await run(); throw new Error('Expected run() to throw an error'); } catch (error) { - expect(error.message).to.be('foo'); + expect(error.message).toBe('foo'); } - sinon.assert.notCalled(logWithMetadata); + expect(loggingServiceMock.collect(logger).debug).toHaveLength(0); }); }); @@ -198,7 +198,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { }); const result = await run({ onWriteError: () => 123 }); - expect(result).to.be(123); + expect(result).toBe(123); }); it('rejects with the error from onWriteError() if it rejects', async () => { @@ -214,7 +214,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { }); throw new Error('expected run() to reject'); } catch (error) { - expect(error.message).to.be('foo bar'); + expect(error.message).toBe('foo bar'); } }); @@ -233,7 +233,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { }); throw new Error('expected run() to reject'); } catch (error) { - expect(error.message).to.be('foo bar'); + expect(error.message).toBe('foo bar'); } }); @@ -250,7 +250,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function() { }); throw new Error('expected run() to reject'); } catch (error) { - expect(error.message).to.be('foo'); + expect(error.message).toBe('foo'); } }); }); diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts similarity index 82% rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts rename to src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts index 0dc3d5f50e97e..1655297adb6c9 100644 --- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/create_or_upgrade_saved_config.ts @@ -18,8 +18,9 @@ */ import { defaults } from 'lodash'; -import { SavedObjectsClientContract, SavedObjectAttribute } from 'src/core/server'; -import { Legacy } from 'kibana'; + +import { SavedObjectsClientContract, SavedObjectAttribute } from '../../saved_objects/types'; +import { Logger } from '../../logging'; import { getUpgradeableConfig } from './get_upgradeable_config'; @@ -27,7 +28,7 @@ interface Options { savedObjectsClient: SavedObjectsClientContract; version: string; buildNum: number; - logWithMetadata: Legacy.Server['logWithMetadata']; + log: Logger; onWriteError?: ( error: Error, attributes: Record @@ -36,7 +37,7 @@ interface Options { export async function createOrUpgradeSavedConfig( options: Options ): Promise | undefined> { - const { savedObjectsClient, version, buildNum, logWithMetadata, onWriteError } = options; + const { savedObjectsClient, version, buildNum, log, onWriteError } = options; // try to find an older config we can upgrade const upgradeableConfig = await getUpgradeableConfig({ @@ -59,13 +60,9 @@ export async function createOrUpgradeSavedConfig { let savedObjectsClient: SavedObjectsClientContract; let kbnServer: KbnServer; @@ -88,7 +89,7 @@ describe('createOrUpgradeSavedConfig()', () => { savedObjectsClient, version: '5.4.0', buildNum: 54099, - logWithMetadata: sinon.stub(), + log: logger, }); const config540 = await savedObjectsClient.get('config', '5.4.0'); @@ -114,7 +115,7 @@ describe('createOrUpgradeSavedConfig()', () => { savedObjectsClient, version: '5.4.1', buildNum: 54199, - logWithMetadata: sinon.stub(), + log: logger, }); const config541 = await savedObjectsClient.get('config', '5.4.1'); @@ -140,7 +141,7 @@ describe('createOrUpgradeSavedConfig()', () => { savedObjectsClient, version: '7.0.0-rc1', buildNum: 70010, - logWithMetadata: sinon.stub(), + log: logger, }); const config700rc1 = await savedObjectsClient.get('config', '7.0.0-rc1'); @@ -167,7 +168,7 @@ describe('createOrUpgradeSavedConfig()', () => { savedObjectsClient, version: '7.0.0', buildNum: 70099, - logWithMetadata: sinon.stub(), + log: logger, }); const config700 = await savedObjectsClient.get('config', '7.0.0'); @@ -195,7 +196,7 @@ describe('createOrUpgradeSavedConfig()', () => { savedObjectsClient, version: '6.2.3-rc1', buildNum: 62310, - logWithMetadata: sinon.stub(), + log: logger, }); const config623rc1 = await savedObjectsClient.get('config', '6.2.3-rc1'); diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts similarity index 86% rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts rename to src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts index 6bb2cb3b87850..073a6961fdec4 100644 --- a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts +++ b/src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.test.ts @@ -20,8 +20,6 @@ import expect from '@kbn/expect'; import { isConfigVersionUpgradeable } from './is_config_version_upgradeable'; -// @ts-ignore -import { pkg } from '../../../utils'; describe('savedObjects/health_check/isConfigVersionUpgradeable', function() { function isUpgradeableTest(savedVersion: string, kibanaVersion: string, expected: boolean) { @@ -30,10 +28,10 @@ describe('savedObjects/health_check/isConfigVersionUpgradeable', function() { }); } - isUpgradeableTest('1.0.0-beta1', pkg.version, false); - isUpgradeableTest('1.0.0-beta256', pkg.version, false); + isUpgradeableTest('1.0.0-beta1', '7.4.0', false); + isUpgradeableTest('1.0.0-beta256', '7.4.0', false); isUpgradeableTest('10.100.1000-beta256', '10.100.1000-beta257', false); - isUpgradeableTest(pkg.version, pkg.version, false); + isUpgradeableTest('7.4.0', '7.4.0', false); isUpgradeableTest('4.0.0-RC1', '4.0.0-RC2', true); isUpgradeableTest('10.100.1000-rc256', '10.100.1000-RC257', true); isUpgradeableTest('4.0.0-rc2', '4.0.0-rc1', false); @@ -48,6 +46,6 @@ describe('savedObjects/health_check/isConfigVersionUpgradeable', function() { isUpgradeableTest('4.1.0-rc1-SNAPSHOT', '4.1.0-rc1', false); isUpgradeableTest('5.0.0-alpha11', '5.0.0', false); isUpgradeableTest('50.0.10-rc150-SNAPSHOT', '50.0.9', false); - isUpgradeableTest(undefined as any, pkg.version, false); - isUpgradeableTest('@@version', pkg.version, false); + isUpgradeableTest(undefined as any, '7.4.0', false); + isUpgradeableTest('@@version', '7.4.0', false); }); diff --git a/src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.ts b/src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.ts similarity index 100% rename from src/legacy/ui/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.ts rename to src/core/server/ui_settings/create_or_upgrade_saved_config/is_config_version_upgradeable.ts diff --git a/src/core/server/ui_settings/index.ts b/src/core/server/ui_settings/index.ts new file mode 100644 index 0000000000000..edd0bfc4f3a89 --- /dev/null +++ b/src/core/server/ui_settings/index.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { + IUiSettingsClient, + UiSettingsClient, + UiSettingsServiceOptions, +} from './ui_settings_client'; + +export { config } from './ui_settings_config'; +export { + UiSettingsParams, + UiSettingsService, + InternalUiSettingsServiceSetup, + UiSettingsType, +} from './ui_settings_service'; diff --git a/src/legacy/ui/ui_settings/ui_settings_service.test.ts b/src/core/server/ui_settings/ui_settings_client.test.ts similarity index 94% rename from src/legacy/ui/ui_settings/ui_settings_service.test.ts rename to src/core/server/ui_settings/ui_settings_client.test.ts index f37076b27ad6f..59c13fbebee70 100644 --- a/src/legacy/ui/ui_settings/ui_settings_service.test.ts +++ b/src/core/server/ui_settings/ui_settings_client.test.ts @@ -21,17 +21,20 @@ import expect from '@kbn/expect'; import Chance from 'chance'; import sinon from 'sinon'; -import { UiSettingsService } from './ui_settings_service'; +import { loggingServiceMock } from '../logging/logging_service.mock'; + +import { UiSettingsClient } from './ui_settings_client'; import * as createOrUpgradeSavedConfigNS from './create_or_upgrade_saved_config/create_or_upgrade_saved_config'; import { createObjectsClientStub, savedObjectsClientErrors } from './create_objects_client_stub'; +const logger = loggingServiceMock.create().get(); + const TYPE = 'config'; const ID = 'kibana-version'; const BUILD_NUM = 1234; const chance = new Chance(); interface SetupOptions { - getDefaults?: () => Record; defaults?: Record; esDocSource?: Record; overrides?: Record; @@ -41,17 +44,18 @@ describe('ui settings', () => { const sandbox = sinon.createSandbox(); function setup(options: SetupOptions = {}) { - const { getDefaults, defaults = {}, overrides = {}, esDocSource = {} } = options; + const { defaults = {}, overrides = {}, esDocSource = {} } = options; const savedObjectsClient = createObjectsClientStub(esDocSource); - const uiSettings = new UiSettingsService({ + const uiSettings = new UiSettingsClient({ type: TYPE, id: ID, buildNum: BUILD_NUM, - getDefaults: getDefaults || (() => defaults), + defaults, savedObjectsClient, overrides, + log: logger, }); const createOrUpgradeSavedConfig = sandbox.stub( @@ -239,25 +243,10 @@ describe('ui settings', () => { }); describe('#getDefaults()', () => { - it('returns a promise for the defaults', async () => { - const { uiSettings } = setup(); - const promise = uiSettings.getDefaults(); - expect(promise).to.be.a(Promise); - expect(await promise).to.eql({}); - }); - }); - - describe('getDefaults() argument', () => { - it('casts sync `getDefaults()` to promise', () => { - const getDefaults = () => ({ key: { value: chance.word() } }); - const { uiSettings } = setup({ getDefaults }); - expect(uiSettings.getDefaults()).to.be.a(Promise); - }); - - it('returns the defaults returned by getDefaults() argument', async () => { + it('returns the defaults passed to the constructor', () => { const value = chance.word(); const { uiSettings } = setup({ defaults: { key: { value } } }); - expect(await uiSettings.getDefaults()).to.eql({ + expect(uiSettings.getDefaults()).to.eql({ key: { value }, }); }); diff --git a/src/legacy/ui/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_client.ts similarity index 63% rename from src/legacy/ui/ui_settings/ui_settings_service.ts rename to src/core/server/ui_settings/ui_settings_client.ts index 57312140b16b3..c495d1b4c4567 100644 --- a/src/legacy/ui/ui_settings/ui_settings_service.ts +++ b/src/core/server/ui_settings/ui_settings_client.ts @@ -16,12 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import { Legacy } from 'kibana'; import { defaultsDeep } from 'lodash'; import Boom from 'boom'; -import { SavedObjectsClientContract, SavedObjectAttribute } from 'src/core/server'; +import { SavedObjectsClientContract, SavedObjectAttribute } from '../saved_objects/types'; +import { Logger } from '../logging'; import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config'; +import { UiSettingsParams } from './ui_settings_service'; export interface UiSettingsServiceOptions { type: string; @@ -29,8 +30,8 @@ export interface UiSettingsServiceOptions { buildNum: number; savedObjectsClient: SavedObjectsClientContract; overrides?: Record; - getDefaults?: () => Record; - logWithMetadata?: Legacy.Server['logWithMetadata']; + defaults?: Record; + log: Logger; } interface ReadOptions { @@ -38,82 +39,87 @@ interface ReadOptions { autoCreateOrUpgradeIfMissing?: boolean; } -interface UserProvidedValue { - userValue?: SavedObjectAttribute; +interface UserProvidedValue { + userValue?: T; isOverridden?: boolean; } type UiSettingsRawValue = UiSettingsParams & UserProvidedValue; -type UserProvided = Record; +type UserProvided = Record>; type UiSettingsRaw = Record; -type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; - -interface UiSettingsParams { - name: string; - value: SavedObjectAttribute; - description: string; - category: string[]; - options?: string[]; - optionLabels?: Record; - requiresPageReload?: boolean; - readonly?: boolean; - type?: UiSettingsType; -} - +/** + * Service that provides access to the UiSettings stored in elasticsearch. + * + * @public + */ export interface IUiSettingsClient { - getDefaults: () => Promise>; + /** + * Returns uiSettings default values {@link UiSettingsParams} + */ + getDefaults: () => Record; + /** + * Retrieves uiSettings values set by the user with fallbacks to default values if not specified. + */ get: (key: string) => Promise; + /** + * Retrieves a set of all uiSettings values set by the user with fallbacks to default values if not specified. + */ getAll: () => Promise>; - getUserProvided: () => Promise; + /** + * Retrieves a set of all uiSettings values set by the user. + */ + getUserProvided: () => Promise< + Record + >; + /** + * Writes multiple uiSettings values and marks them as set by the user. + */ setMany: (changes: Record) => Promise; + /** + * Writes uiSettings value and marks it as set by the user. + */ set: (key: string, value: T) => Promise; + /** + * Removes uiSettings value by key. + */ remove: (key: string) => Promise; + /** + * Removes multiple uiSettings values by keys. + */ removeMany: (keys: string[]) => Promise; + /** + * Shows whether the uiSettings value set by the user. + */ isOverridden: (key: string) => boolean; } -/** - * Service that provides access to the UiSettings stored in elasticsearch. - * @class UiSettingsService - */ -export class UiSettingsService implements IUiSettingsClient { - private readonly _type: UiSettingsServiceOptions['type']; - private readonly _id: UiSettingsServiceOptions['id']; - private readonly _buildNum: UiSettingsServiceOptions['buildNum']; - private readonly _savedObjectsClient: UiSettingsServiceOptions['savedObjectsClient']; - private readonly _overrides: NonNullable; - private readonly _getDefaults: NonNullable; - private readonly _logWithMetadata: NonNullable; + +export class UiSettingsClient implements IUiSettingsClient { + private readonly type: UiSettingsServiceOptions['type']; + private readonly id: UiSettingsServiceOptions['id']; + private readonly buildNum: UiSettingsServiceOptions['buildNum']; + private readonly savedObjectsClient: UiSettingsServiceOptions['savedObjectsClient']; + private readonly overrides: NonNullable; + private readonly defaults: NonNullable; + private readonly log: Logger; constructor(options: UiSettingsServiceOptions) { - const { - type, - id, - buildNum, - savedObjectsClient, - // we use a function for getDefaults() so that defaults can be different in - // different scenarios, and so they can change over time - getDefaults = () => ({}), - // function that accepts log messages in the same format as server.logWithMetadata - logWithMetadata = () => {}, - overrides = {}, - } = options; - - this._type = type; - this._id = id; - this._buildNum = buildNum; - this._savedObjectsClient = savedObjectsClient; - this._getDefaults = getDefaults; - this._overrides = overrides; - this._logWithMetadata = logWithMetadata; + const { type, id, buildNum, savedObjectsClient, log, defaults = {}, overrides = {} } = options; + + this.type = type; + this.id = id; + this.buildNum = buildNum; + this.savedObjectsClient = savedObjectsClient; + this.defaults = defaults; + this.overrides = overrides; + this.log = log; } - async getDefaults() { - return await this._getDefaults(); + getDefaults() { + return this.defaults; } - // returns a Promise for the value of the requested setting async get(key: string): Promise { const all = await this.getAll(); return all[key]; @@ -135,14 +141,16 @@ export class UiSettingsService implements IUiSettingsClient { // NOTE: should be a private method async getRaw(): Promise { const userProvided = await this.getUserProvided(); - return defaultsDeep(userProvided, await this.getDefaults()); + return defaultsDeep(userProvided, this.defaults); } - async getUserProvided(options: ReadOptions = {}): Promise { + async getUserProvided( + options: ReadOptions = {} + ): Promise> { const userProvided: UserProvided = {}; // write the userValue for each key stored in the saved object that is not overridden - for (const [key, userValue] of Object.entries(await this._read(options))) { + for (const [key, userValue] of Object.entries(await this.read(options))) { if (userValue !== null && !this.isOverridden(key)) { userProvided[key] = { userValue, @@ -152,7 +160,7 @@ export class UiSettingsService implements IUiSettingsClient { // write all overridden keys, dropping the userValue is override is null and // adding keys for overrides that are not in saved object - for (const [key, userValue] of Object.entries(this._overrides)) { + for (const [key, userValue] of Object.entries(this.overrides)) { userProvided[key] = userValue === null ? { isOverridden: true } : { isOverridden: true, userValue }; } @@ -161,7 +169,7 @@ export class UiSettingsService implements IUiSettingsClient { } async setMany(changes: Record) { - await this._write({ changes }); + await this.write({ changes }); } async set(key: string, value: T) { @@ -181,7 +189,7 @@ export class UiSettingsService implements IUiSettingsClient { } isOverridden(key: string) { - return this._overrides.hasOwnProperty(key); + return this.overrides.hasOwnProperty(key); } // NOTE: should be private method @@ -191,7 +199,7 @@ export class UiSettingsService implements IUiSettingsClient { } } - private async _write({ + private async write({ changes, autoCreateOrUpgradeIfMissing = true, }: { @@ -203,28 +211,28 @@ export class UiSettingsService implements IUiSettingsClient { } try { - await this._savedObjectsClient.update(this._type, this._id, changes); + await this.savedObjectsClient.update(this.type, this.id, changes); } catch (error) { - const { isNotFoundError } = this._savedObjectsClient.errors; + const { isNotFoundError } = this.savedObjectsClient.errors; if (!isNotFoundError(error) || !autoCreateOrUpgradeIfMissing) { throw error; } await createOrUpgradeSavedConfig({ - savedObjectsClient: this._savedObjectsClient, - version: this._id, - buildNum: this._buildNum, - logWithMetadata: this._logWithMetadata, + savedObjectsClient: this.savedObjectsClient, + version: this.id, + buildNum: this.buildNum, + log: this.log, }); - await this._write({ + await this.write({ changes, autoCreateOrUpgradeIfMissing: false, }); } } - private async _read({ + private async read({ ignore401Errors = false, autoCreateOrUpgradeIfMissing = true, }: ReadOptions = {}): Promise> { @@ -233,18 +241,18 @@ export class UiSettingsService implements IUiSettingsClient { isNotFoundError, isForbiddenError, isNotAuthorizedError, - } = this._savedObjectsClient.errors; + } = this.savedObjectsClient.errors; try { - const resp = await this._savedObjectsClient.get(this._type, this._id); + const resp = await this.savedObjectsClient.get(this.type, this.id); return resp.attributes; } catch (error) { if (isNotFoundError(error) && autoCreateOrUpgradeIfMissing) { const failedUpgradeAttributes = await createOrUpgradeSavedConfig({ - savedObjectsClient: this._savedObjectsClient, - version: this._id, - buildNum: this._buildNum, - logWithMetadata: this._logWithMetadata, + savedObjectsClient: this.savedObjectsClient, + version: this.id, + buildNum: this.buildNum, + log: this.log, onWriteError(writeError, attributes) { if (isConflictError(writeError)) { // trigger `!failedUpgradeAttributes` check below, since another @@ -262,7 +270,7 @@ export class UiSettingsService implements IUiSettingsClient { }); if (!failedUpgradeAttributes) { - return await this._read({ + return await this.read({ ignore401Errors, autoCreateOrUpgradeIfMissing: false, }); @@ -284,7 +292,7 @@ export class UiSettingsService implements IUiSettingsClient { isForbiddenError, isEsUnavailableError, isNotAuthorizedError, - } = this._savedObjectsClient.errors; + } = this.savedObjectsClient.errors; return ( isForbiddenError(error) || diff --git a/src/core/server/ui_settings/ui_settings_config.ts b/src/core/server/ui_settings/ui_settings_config.ts new file mode 100644 index 0000000000000..702286f953ef1 --- /dev/null +++ b/src/core/server/ui_settings/ui_settings_config.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 type UiSettingsConfigType = TypeOf; + +export const config = { + path: 'uiSettings', + schema: schema.object({ + overrides: schema.object({}, { allowUnknowns: true }), + // Deprecation is implemented in LP. + // We define schema here not to fail on the validation step. + enabled: schema.maybe(schema.boolean()), + }), +}; diff --git a/src/legacy/ui/ui_settings/ui_settings_service.mock.ts b/src/core/server/ui_settings/ui_settings_service.mock.ts similarity index 71% rename from src/legacy/ui/ui_settings/ui_settings_service.mock.ts rename to src/core/server/ui_settings/ui_settings_service.mock.ts index 7c1a17ebd447c..2127faf0d2029 100644 --- a/src/legacy/ui/ui_settings/ui_settings_service.mock.ts +++ b/src/core/server/ui_settings/ui_settings_service.mock.ts @@ -17,9 +17,10 @@ * under the License. */ -import { IUiSettingsClient } from './ui_settings_service'; +import { IUiSettingsClient } from './ui_settings_client'; +import { InternalUiSettingsServiceSetup } from './ui_settings_service'; -const createServiceMock = () => { +const createClientMock = () => { const mocked: jest.Mocked = { getDefaults: jest.fn(), get: jest.fn(), @@ -35,6 +36,18 @@ const createServiceMock = () => { return mocked; }; +const createSetupMock = () => { + const mocked: jest.Mocked = { + setDefaults: jest.fn(), + asScopedToClient: jest.fn(), + }; + + mocked.asScopedToClient.mockReturnValue(createClientMock()); + + return mocked; +}; + export const uiSettingsServiceMock = { - create: createServiceMock, + createSetup: createSetupMock, + createClient: createClientMock, }; diff --git a/src/core/server/ui_settings/ui_settings_service.test.mock.ts b/src/core/server/ui_settings/ui_settings_service.test.mock.ts new file mode 100644 index 0000000000000..586ad3049ed6a --- /dev/null +++ b/src/core/server/ui_settings/ui_settings_service.test.mock.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const MockUiSettingsClientConstructor = jest.fn(); + +jest.doMock('./ui_settings_client', () => ({ + UiSettingsClient: MockUiSettingsClientConstructor, +})); diff --git a/src/core/server/ui_settings/ui_settings_service.test.ts b/src/core/server/ui_settings/ui_settings_service.test.ts new file mode 100644 index 0000000000000..832d61bdb4137 --- /dev/null +++ b/src/core/server/ui_settings/ui_settings_service.test.ts @@ -0,0 +1,109 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { BehaviorSubject } from 'rxjs'; + +import { MockUiSettingsClientConstructor } from './ui_settings_service.test.mock'; + +import { UiSettingsService } from './ui_settings_service'; +import { httpServiceMock } from '../http/http_service.mock'; +import { loggingServiceMock } from '../logging/logging_service.mock'; +import { SavedObjectsClientMock } from '../mocks'; +import { mockCoreContext } from '../core_context.mock'; + +const overrides = { + overrideBaz: 'baz', +}; + +const defaults = { + foo: { + name: 'foo', + value: 'bar', + category: [], + description: '', + }, +}; + +const coreContext = mockCoreContext.create(); +coreContext.configService.atPath.mockReturnValue(new BehaviorSubject({ overrides })); +const httpSetup = httpServiceMock.createSetupContract(); +const setupDeps = { http: httpSetup }; +const savedObjectsClient = SavedObjectsClientMock.create(); + +afterEach(() => { + MockUiSettingsClientConstructor.mockClear(); +}); + +describe('uiSettings', () => { + describe('#setup', () => { + describe('#asScopedToClient', () => { + it('passes overrides to UiSettingsClient', async () => { + const service = new UiSettingsService(coreContext); + const setup = await service.setup(setupDeps); + setup.asScopedToClient(savedObjectsClient); + expect(MockUiSettingsClientConstructor).toBeCalledTimes(1); + expect(MockUiSettingsClientConstructor.mock.calls[0][0].overrides).toBe(overrides); + expect(MockUiSettingsClientConstructor.mock.calls[0][0].overrides).toEqual(overrides); + }); + + it('passes overrides with deprecated "server.defaultRoute"', async () => { + const service = new UiSettingsService(coreContext); + const httpSetupWithDefaultRoute = httpServiceMock.createSetupContract(); + httpSetupWithDefaultRoute.config.defaultRoute = '/defaultRoute'; + const setup = await service.setup({ http: httpSetupWithDefaultRoute }); + setup.asScopedToClient(savedObjectsClient); + + expect(MockUiSettingsClientConstructor.mock.calls[0][0].overrides).toEqual({ + ...overrides, + defaultRoute: '/defaultRoute', + }); + + expect(loggingServiceMock.collect(coreContext.logger).warn).toMatchInlineSnapshot(` + Array [ + Array [ + "Config key \\"server.defaultRoute\\" is deprecated. It has been replaced with \\"uiSettings.overrides.defaultRoute\\"", + ], + ] + `); + }); + + it('passes a copy of set defaults to UiSettingsClient', async () => { + const service = new UiSettingsService(coreContext); + const setup = await service.setup(setupDeps); + + setup.setDefaults(defaults); + setup.asScopedToClient(savedObjectsClient); + expect(MockUiSettingsClientConstructor).toBeCalledTimes(1); + + expect(MockUiSettingsClientConstructor.mock.calls[0][0].defaults).toEqual(defaults); + expect(MockUiSettingsClientConstructor.mock.calls[0][0].defaults).not.toBe(defaults); + }); + }); + + describe('#setDefaults', () => { + it('throws if set defaults for the same key twice', async () => { + const service = new UiSettingsService(coreContext); + const setup = await service.setup(setupDeps); + setup.setDefaults(defaults); + expect(() => setup.setDefaults(defaults)).toThrowErrorMatchingInlineSnapshot( + `"uiSettings defaults for key [foo] has been already set"` + ); + }); + }); + }); +}); diff --git a/src/core/server/ui_settings/ui_settings_service.ts b/src/core/server/ui_settings/ui_settings_service.ts new file mode 100644 index 0000000000000..746fa514c5d4b --- /dev/null +++ b/src/core/server/ui_settings/ui_settings_service.ts @@ -0,0 +1,140 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { CoreService } from '../../types'; +import { CoreContext } from '../core_context'; +import { Logger } from '../logging'; + +import { SavedObjectsClientContract, SavedObjectAttribute } from '../saved_objects/types'; +import { InternalHttpServiceSetup } from '../http'; +import { UiSettingsConfigType } from './ui_settings_config'; +import { IUiSettingsClient, UiSettingsClient } from './ui_settings_client'; +import { mapToObject } from '../../utils/'; + +interface SetupDeps { + http: InternalHttpServiceSetup; +} + +/** + * UI element type to represent the settings. + * @public + * */ +export type UiSettingsType = 'json' | 'markdown' | 'number' | 'select' | 'boolean' | 'string'; + +/** + * UiSettings parameters defined by the plugins. + * @public + * */ +export interface UiSettingsParams { + /** title in the UI */ + name: string; + /** default value to fall back to if a user doesn't provide any */ + value: SavedObjectAttribute; + /** description provided to a user in UI */ + description: string; + /** used to group the configured setting in the UI */ + category: string[]; + /** a range of valid values */ + options?: string[]; + /** text labels for 'select' type UI element */ + optionLabels?: Record; + /** a flag indicating whether new value applying requires page reloading */ + requiresPageReload?: boolean; + /** a flag indicating that value cannot be changed */ + readonly?: boolean; + /** defines a type of UI element {@link UiSettingsType} */ + type?: UiSettingsType; +} + +/** @internal */ +export interface InternalUiSettingsServiceSetup { + /** + * Sets the parameters with default values for the uiSettings. + * @param values + */ + setDefaults(values: Record): void; + /** + * Creates uiSettings client with provided *scoped* saved objects client {@link IUiSettingsClient} + * @param values + */ + asScopedToClient(savedObjectsClient: SavedObjectsClientContract): IUiSettingsClient; +} + +/** @internal */ +export class UiSettingsService implements CoreService { + private readonly log: Logger; + private readonly config$: Observable; + private readonly uiSettingsDefaults = new Map(); + + constructor(private readonly coreContext: CoreContext) { + this.log = coreContext.logger.get('ui-settings-service'); + this.config$ = coreContext.configService.atPath('uiSettings'); + } + + public async setup(deps: SetupDeps): Promise { + this.log.debug('Setting up ui settings service'); + const overrides = await this.getOverrides(deps); + const { version, buildNum } = this.coreContext.env.packageInfo; + + return { + setDefaults: this.setDefaults.bind(this), + asScopedToClient: (savedObjectsClient: SavedObjectsClientContract) => { + return new UiSettingsClient({ + type: 'config', + id: version, + buildNum, + savedObjectsClient, + defaults: mapToObject(this.uiSettingsDefaults), + overrides, + log: this.log, + }); + }, + }; + } + + public async start() {} + + public async stop() {} + + private setDefaults(values: Record = {}) { + Object.entries(values).forEach(([key, value]) => { + if (this.uiSettingsDefaults.has(key)) { + throw new Error(`uiSettings defaults for key [${key}] has been already set`); + } + this.uiSettingsDefaults.set(key, value); + }); + } + + private async getOverrides(deps: SetupDeps) { + const config = await this.config$.pipe(first()).toPromise(); + const overrides: Record = config.overrides; + // manually implemented deprecation until New platform Config service + // supports them https://github.com/elastic/kibana/issues/40255 + if (typeof deps.http.config.defaultRoute !== 'undefined') { + overrides.defaultRoute = deps.http.config.defaultRoute; + this.log.warn( + 'Config key "server.defaultRoute" is deprecated. It has been replaced with "uiSettings.overrides.defaultRoute"' + ); + } + + return overrides; + } +} diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts index 479feabd07943..d8bd9623f5c08 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.test.ts @@ -34,15 +34,20 @@ jest.mock('ui/registry/field_formats', () => ({ }, })); -jest.mock('ui/utils/mapping_setup', () => ({ - expandShorthand: jest.fn().mockImplementation(() => ({ - id: true, - title: true, - fieldFormatMap: { - _deserialize: jest.fn().mockImplementation(() => []), - }, - })), -})); +jest.mock('../../../../../../plugins/kibana_utils/public', () => { + const originalModule = jest.requireActual('../../../../../../plugins/kibana_utils/public'); + + return { + ...originalModule, + expandShorthand: jest.fn(() => ({ + id: true, + title: true, + fieldFormatMap: { + _deserialize: jest.fn().mockImplementation(() => []), + }, + })), + }; +}); jest.mock('ui/notify', () => ({ toastNotifications: { diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts index 35ec4bad68e14..0a1eb4c36ae7b 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -21,32 +21,27 @@ import _, { each, reject } from 'lodash'; import { i18n } from '@kbn/i18n'; // @ts-ignore import { fieldFormats } from 'ui/registry/field_formats'; -// @ts-ignore -import { expandShorthand } from 'ui/utils/mapping_setup'; - import { NotificationsSetup, SavedObjectsClientContract } from 'src/core/public'; -import { SavedObjectNotFound, DuplicateField } from '../../../../../../plugins/kibana_utils/public'; -import { findIndexPatternByTitle } from '../utils'; +import { + DuplicateField, + SavedObjectNotFound, + expandShorthand, + FieldMappingSpec, + MappingObject, +} from '../../../../../../plugins/kibana_utils/public'; + +import { findIndexPatternByTitle, getRoutes } from '../utils'; import { IndexPatternMissingIndices } from '../errors'; -import { Field, FieldList, FieldType, FieldListInterface } from '../fields'; +import { Field, FieldList, FieldListInterface, FieldType } from '../fields'; import { createFieldsFetcher } from './_fields_fetcher'; -import { getRoutes } from '../utils'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; import { IIndexPatternsApiClient } from './index_patterns_api_client'; +import { ES_FIELD_TYPES } from '../../../../../../plugins/data/common'; const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; const type = 'index-pattern'; -interface FieldMappingSpec { - _serialize: (mapping: any) => string; - _deserialize: (mapping: string) => any; -} - -interface MappingObject { - [key: string]: FieldMappingSpec; -} - export interface StaticIndexPattern { fields: FieldType[]; title: string; @@ -81,13 +76,13 @@ export class IndexPattern implements StaticIndexPattern { private shortDotsEnable: boolean = false; private mapping: MappingObject = expandShorthand({ - title: 'text', - timeFieldName: 'keyword', - intervalName: 'keyword', + title: ES_FIELD_TYPES.TEXT, + timeFieldName: ES_FIELD_TYPES.KEYWORD, + intervalName: ES_FIELD_TYPES.KEYWORD, fields: 'json', sourceFilters: 'json', fieldFormatMap: { - type: 'text', + type: ES_FIELD_TYPES.TEXT, _serialize: (map = {}) => { const serialized = _.transform(map, this.serializeFieldFormatMap); return _.isEmpty(serialized) ? undefined : JSON.stringify(serialized); @@ -98,7 +93,7 @@ export class IndexPattern implements StaticIndexPattern { }); }, }, - type: 'keyword', + type: ES_FIELD_TYPES.KEYWORD, typeMeta: 'json', }); @@ -181,6 +176,7 @@ export class IndexPattern implements StaticIndexPattern { if (!fieldMapping._deserialize || !name) { return; } + response._source[name] = fieldMapping._deserialize(response._source[name]); }); diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx index deb47b1668655..7ab191062e32d 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx @@ -47,9 +47,14 @@ startMock.uiSettings.get.mockImplementation((key: string) => { }, ]; case 'dateFormat': - return 'YY'; + return 'MMM D, YYYY @ HH:mm:ss.SSS'; case 'history:limit': return 10; + case 'timepicker:timeDefaults': + return { + from: 'now-15m', + to: 'now', + }; default: throw new Error(`Unexpected config key: ${key}`); } @@ -122,6 +127,7 @@ function wrapQueryBarTopRowInContext(testProps: any) { describe('QueryBarTopRowTopRow', () => { const QUERY_INPUT_SELECTOR = 'QueryBarInputUI'; const TIMEPICKER_SELECTOR = 'EuiSuperDatePicker'; + const TIMEPICKER_DURATION = '[data-shared-timefilter-duration]'; beforeEach(() => { jest.clearAllMocks(); @@ -198,6 +204,24 @@ describe('QueryBarTopRowTopRow', () => { expect(component.find(TIMEPICKER_SELECTOR).length).toBe(1); }); + it('Should render the timefilter duration container for sharing', () => { + const component = mount( + wrapQueryBarTopRowInContext({ + isDirty: false, + screenTitle: 'Another Screen', + showDatePicker: true, + dateRangeFrom: 'now-7d', + dateRangeTo: 'now', + timeHistory: timefilterSetupMock.history, + }) + ); + + // match the data attribute rendered in the in the ReactHTML object + expect(component.find(TIMEPICKER_DURATION)).toMatchObject( + / { const component = mount( wrapQueryBarTopRowInContext({ diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx index 2dc6ef7847cd2..9a846ab82f47c 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx @@ -17,12 +17,20 @@ * under the License. */ +import dateMath from '@elastic/datemath'; import { doesKueryExpressionHaveLuceneSyntaxError } from '@kbn/es-query'; import classNames from 'classnames'; import React, { useState, useEffect } from 'react'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiLink, EuiSuperDatePicker } from '@elastic/eui'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiSuperDatePicker, + prettyDuration, +} from '@elastic/eui'; // @ts-ignore import { EuiSuperUpdateButton, OnRefreshProps } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; @@ -156,6 +164,14 @@ function QueryBarTopRowUI(props: Props) { }); } + function toAbsoluteString(value: string, roundUp = false) { + const valueAsMoment = dateMath.parse(value, { roundUp }); + if (!valueAsMoment) { + return value; + } + return valueAsMoment.toISOString(); + } + function renderQueryInput() { if (!shouldRenderQueryInput()) return; return ( @@ -174,6 +190,22 @@ function QueryBarTopRowUI(props: Props) { ); } + function renderSharingMetaFields() { + const { from, to } = getDateRange(); + const dateRangePretty = prettyDuration( + toAbsoluteString(from), + toAbsoluteString(to), + [], + uiSettings.get('dateFormat') + ); + return ( +

+ ); + } + function shouldRenderDatePicker(): boolean { return Boolean(props.showDatePicker || props.showAutoRefreshOnly); } @@ -322,6 +354,7 @@ function QueryBarTopRowUI(props: Props) { justifyContent="flexEnd" > {renderQueryInput()} + {renderSharingMetaFields()} {renderUpdateButton()} ); diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 8b12f71660844..7a47b5d54316f 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -329,9 +329,9 @@ export default function (kibana) { } }, - init: function (server) { + init: async function (server) { // uuid - manageUuid(server); + await manageUuid(server); // routes searchApi(server); scriptsApi(server); diff --git a/src/legacy/core_plugins/kibana/public/context/components/action_bar/_index.scss b/src/legacy/core_plugins/kibana/public/context/components/action_bar/_index.scss deleted file mode 100644 index 1f54ecea5e1cb..0000000000000 --- a/src/legacy/core_plugins/kibana/public/context/components/action_bar/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './action_bar'; diff --git a/src/legacy/core_plugins/kibana/public/discover/_index.scss b/src/legacy/core_plugins/kibana/public/discover/_index.scss index 57a6b89c37722..0b0bd12cb268b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/_index.scss +++ b/src/legacy/core_plugins/kibana/public/discover/_index.scss @@ -10,7 +10,7 @@ @import 'components/fetch_error/index'; @import 'components/field_chooser/index'; -@import 'directives/index'; +@import 'angular/directives/index'; @import 'doc_table/index'; @import 'hacks'; @@ -18,3 +18,9 @@ @import 'discover'; @import 'embeddable/index'; + +// Doc Viewer +@import 'doc_viewer/index'; + +// Context styles +@import 'context/index'; diff --git a/src/legacy/core_plugins/kibana/public/discover/directives/__snapshots__/no_results.test.js.snap b/src/legacy/core_plugins/kibana/public/discover/angular/directives/__snapshots__/no_results.test.js.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/directives/__snapshots__/no_results.test.js.snap rename to src/legacy/core_plugins/kibana/public/discover/angular/directives/__snapshots__/no_results.test.js.snap diff --git a/src/legacy/core_plugins/kibana/public/discover/directives/_histogram.scss b/src/legacy/core_plugins/kibana/public/discover/angular/directives/_histogram.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/directives/_histogram.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/directives/_histogram.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/directives/_index.scss b/src/legacy/core_plugins/kibana/public/discover/angular/directives/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/directives/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/directives/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/directives/_no_results.scss b/src/legacy/core_plugins/kibana/public/discover/angular/directives/_no_results.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/directives/_no_results.scss rename to src/legacy/core_plugins/kibana/public/discover/angular/directives/_no_results.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/directives/histogram.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/directives/histogram.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/directives/histogram.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/directives/index.js b/src/legacy/core_plugins/kibana/public/discover/angular/directives/index.js similarity index 96% rename from src/legacy/core_plugins/kibana/public/discover/directives/index.js rename to src/legacy/core_plugins/kibana/public/discover/angular/directives/index.js index dc834c02759e0..27918bd704f5a 100644 --- a/src/legacy/core_plugins/kibana/public/discover/directives/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/directives/index.js @@ -20,7 +20,7 @@ import 'ngreact'; import { wrapInI18nContext } from 'ui/i18n'; import { uiModules } from 'ui/modules'; -import '../../../../../ui/public/render_complete/directive'; +import '../../../../../../ui/public/render_complete/directive'; import { DiscoverNoResults } from './no_results'; import { DiscoverUninitialized } from './uninitialized'; diff --git a/src/legacy/core_plugins/kibana/public/discover/directives/no_results.js b/src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/directives/no_results.js rename to src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.js diff --git a/src/legacy/core_plugins/kibana/public/discover/directives/no_results.test.js b/src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.test.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/directives/no_results.test.js rename to src/legacy/core_plugins/kibana/public/discover/angular/directives/no_results.test.js diff --git a/src/legacy/core_plugins/kibana/public/discover/directives/uninitialized.tsx b/src/legacy/core_plugins/kibana/public/discover/angular/directives/uninitialized.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/directives/uninitialized.tsx rename to src/legacy/core_plugins/kibana/public/discover/angular/directives/uninitialized.tsx diff --git a/src/legacy/core_plugins/kibana/public/discover/directives/unsupported_index_pattern.js b/src/legacy/core_plugins/kibana/public/discover/angular/directives/unsupported_index_pattern.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/directives/unsupported_index_pattern.js rename to src/legacy/core_plugins/kibana/public/discover/angular/directives/unsupported_index_pattern.js diff --git a/src/legacy/core_plugins/kibana/public/discover/controllers/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/controllers/discover.js rename to src/legacy/core_plugins/kibana/public/discover/angular/discover.js diff --git a/src/legacy/core_plugins/kibana/public/discover/controllers/get_painless_error.ts b/src/legacy/core_plugins/kibana/public/discover/angular/get_painless_error.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/discover/controllers/get_painless_error.ts rename to src/legacy/core_plugins/kibana/public/discover/angular/get_painless_error.ts diff --git a/src/legacy/core_plugins/kibana/public/context/NOTES.md b/src/legacy/core_plugins/kibana/public/discover/context/NOTES.md similarity index 97% rename from src/legacy/core_plugins/kibana/public/context/NOTES.md rename to src/legacy/core_plugins/kibana/public/discover/context/NOTES.md index 445080c215998..7aaa251348961 100644 --- a/src/legacy/core_plugins/kibana/public/context/NOTES.md +++ b/src/legacy/core_plugins/kibana/public/discover/context/NOTES.md @@ -29,11 +29,11 @@ employed in some cases, e.g. when dealing with the isolate scope bindings in **Loose Coupling**: An attempt was made to couple the parts that make up this app as loosely as possible. This means using pure functions whenever possible and isolating the angular directives diligently. To that end, the app has been -implemented as the independent `ContextApp` directive in [app.js](./app.js). It +implemented as the independent `ContextApp` directive in [app.js](app.js). It does not access the Kibana `AppState` directly but communicates only via its directive properties. The binding of these attributes to the state and thereby to the route is performed by the `CreateAppRouteController`in -[index.js](./index.js). Similarly, the `SizePicker` directive only communicates +[index.js](index.js). Similarly, the `SizePicker` directive only communicates with its parent via the passed properties. diff --git a/src/legacy/core_plugins/kibana/public/context/_index.scss b/src/legacy/core_plugins/kibana/public/discover/context/_index.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/_index.scss rename to src/legacy/core_plugins/kibana/public/discover/context/_index.scss diff --git a/src/legacy/core_plugins/kibana/public/context/api/__tests__/_stubs.js b/src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/_stubs.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/api/__tests__/_stubs.js rename to src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/_stubs.js diff --git a/src/legacy/core_plugins/kibana/public/context/api/__tests__/anchor.js b/src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/anchor.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/api/__tests__/anchor.js rename to src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/anchor.js diff --git a/src/legacy/core_plugins/kibana/public/context/api/__tests__/predecessors.js b/src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/predecessors.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/api/__tests__/predecessors.js rename to src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/predecessors.js diff --git a/src/legacy/core_plugins/kibana/public/context/api/__tests__/successors.js b/src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/successors.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/api/__tests__/successors.js rename to src/legacy/core_plugins/kibana/public/discover/context/api/__tests__/successors.js diff --git a/src/legacy/core_plugins/kibana/public/context/api/anchor.js b/src/legacy/core_plugins/kibana/public/discover/context/api/anchor.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/api/anchor.js rename to src/legacy/core_plugins/kibana/public/discover/context/api/anchor.js diff --git a/src/legacy/core_plugins/kibana/public/context/api/context.ts b/src/legacy/core_plugins/kibana/public/discover/context/api/context.ts similarity index 98% rename from src/legacy/core_plugins/kibana/public/context/api/context.ts rename to src/legacy/core_plugins/kibana/public/discover/context/api/context.ts index baecf8a673521..39c7421d3b912 100644 --- a/src/legacy/core_plugins/kibana/public/context/api/context.ts +++ b/src/legacy/core_plugins/kibana/public/discover/context/api/context.ts @@ -18,7 +18,7 @@ */ // @ts-ignore -import { SearchSourceProvider, SearchSource } from 'ui/courier'; +import { SearchSourceProvider } from 'ui/courier'; import { IPrivate } from 'ui/private'; import { Filter } from '@kbn/es-query'; import { IndexPatterns, IndexPattern } from 'ui/index_patterns'; diff --git a/src/legacy/core_plugins/kibana/public/context/api/utils/__tests__/date_conversion.test.ts b/src/legacy/core_plugins/kibana/public/discover/context/api/utils/__tests__/date_conversion.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/api/utils/__tests__/date_conversion.test.ts rename to src/legacy/core_plugins/kibana/public/discover/context/api/utils/__tests__/date_conversion.test.ts diff --git a/src/legacy/core_plugins/kibana/public/context/api/utils/__tests__/sorting.test.ts b/src/legacy/core_plugins/kibana/public/discover/context/api/utils/__tests__/sorting.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/api/utils/__tests__/sorting.test.ts rename to src/legacy/core_plugins/kibana/public/discover/context/api/utils/__tests__/sorting.test.ts diff --git a/src/legacy/core_plugins/kibana/public/context/api/utils/date_conversion.ts b/src/legacy/core_plugins/kibana/public/discover/context/api/utils/date_conversion.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/api/utils/date_conversion.ts rename to src/legacy/core_plugins/kibana/public/discover/context/api/utils/date_conversion.ts diff --git a/src/legacy/core_plugins/kibana/public/context/api/utils/fetch_hits_in_interval.ts b/src/legacy/core_plugins/kibana/public/discover/context/api/utils/fetch_hits_in_interval.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/api/utils/fetch_hits_in_interval.ts rename to src/legacy/core_plugins/kibana/public/discover/context/api/utils/fetch_hits_in_interval.ts diff --git a/src/legacy/core_plugins/kibana/public/context/api/utils/generate_intervals.ts b/src/legacy/core_plugins/kibana/public/discover/context/api/utils/generate_intervals.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/api/utils/generate_intervals.ts rename to src/legacy/core_plugins/kibana/public/discover/context/api/utils/generate_intervals.ts diff --git a/src/legacy/core_plugins/kibana/public/context/api/utils/get_es_query_search_after.ts b/src/legacy/core_plugins/kibana/public/discover/context/api/utils/get_es_query_search_after.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/api/utils/get_es_query_search_after.ts rename to src/legacy/core_plugins/kibana/public/discover/context/api/utils/get_es_query_search_after.ts diff --git a/src/legacy/core_plugins/kibana/public/context/api/utils/get_es_query_sort.ts b/src/legacy/core_plugins/kibana/public/discover/context/api/utils/get_es_query_sort.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/api/utils/get_es_query_sort.ts rename to src/legacy/core_plugins/kibana/public/discover/context/api/utils/get_es_query_sort.ts diff --git a/src/legacy/core_plugins/kibana/public/context/api/utils/sorting.ts b/src/legacy/core_plugins/kibana/public/discover/context/api/utils/sorting.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/api/utils/sorting.ts rename to src/legacy/core_plugins/kibana/public/discover/context/api/utils/sorting.ts diff --git a/src/legacy/core_plugins/kibana/public/context/app.html b/src/legacy/core_plugins/kibana/public/discover/context/app.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/app.html rename to src/legacy/core_plugins/kibana/public/discover/context/app.html diff --git a/src/legacy/core_plugins/kibana/public/context/app.js b/src/legacy/core_plugins/kibana/public/discover/context/app.js similarity index 99% rename from src/legacy/core_plugins/kibana/public/context/app.js rename to src/legacy/core_plugins/kibana/public/discover/context/app.js index 6b10d80078f80..7754f743632cb 100644 --- a/src/legacy/core_plugins/kibana/public/context/app.js +++ b/src/legacy/core_plugins/kibana/public/discover/context/app.js @@ -38,7 +38,7 @@ import { import { timefilter } from 'ui/timefilter'; // load directives -import '../../../data/public/legacy'; +import '../../../../data/public/legacy'; const module = uiModules.get('apps/context', [ 'elasticsearch', diff --git a/src/legacy/core_plugins/kibana/public/context/components/action_bar/_action_bar.scss b/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/_action_bar.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/components/action_bar/_action_bar.scss rename to src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/_action_bar.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/_index.scss b/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/_index.scss new file mode 100644 index 0000000000000..d54e2caffc122 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/_index.scss @@ -0,0 +1 @@ +@import 'action_bar'; diff --git a/src/legacy/core_plugins/kibana/public/context/components/action_bar/action_bar.test.tsx b/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/components/action_bar/action_bar.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/context/components/action_bar/action_bar.tsx b/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/components/action_bar/action_bar.tsx rename to src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar.tsx diff --git a/src/legacy/core_plugins/kibana/public/context/components/action_bar/action_bar_directive.ts b/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar_directive.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/components/action_bar/action_bar_directive.ts rename to src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar_directive.ts diff --git a/src/legacy/core_plugins/kibana/public/context/components/action_bar/action_bar_warning.tsx b/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar_warning.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/components/action_bar/action_bar_warning.tsx rename to src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/action_bar_warning.tsx diff --git a/src/legacy/core_plugins/kibana/public/context/components/action_bar/index.ts b/src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/components/action_bar/index.ts rename to src/legacy/core_plugins/kibana/public/discover/context/components/action_bar/index.ts diff --git a/src/legacy/core_plugins/kibana/public/context/index.html b/src/legacy/core_plugins/kibana/public/discover/context/index.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/index.html rename to src/legacy/core_plugins/kibana/public/discover/context/index.html diff --git a/src/legacy/core_plugins/kibana/public/context/index.js b/src/legacy/core_plugins/kibana/public/discover/context/index.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/context/index.js rename to src/legacy/core_plugins/kibana/public/discover/context/index.js index 71044d36d1aa5..902bee2badb7c 100644 --- a/src/legacy/core_plugins/kibana/public/context/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/context/index.js @@ -25,7 +25,7 @@ import { i18n } from '@kbn/i18n'; import './app'; import contextAppRouteTemplate from './index.html'; -import { getRootBreadcrumbs } from '../discover/breadcrumbs'; +import { getRootBreadcrumbs } from '../breadcrumbs'; import { npStart } from 'ui/new_platform'; import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; diff --git a/src/legacy/core_plugins/kibana/public/context/query/actions.js b/src/legacy/core_plugins/kibana/public/discover/context/query/actions.js similarity index 98% rename from src/legacy/core_plugins/kibana/public/context/query/actions.js rename to src/legacy/core_plugins/kibana/public/discover/context/query/actions.js index 72624210fcb49..c55dcc374fa5a 100644 --- a/src/legacy/core_plugins/kibana/public/context/query/actions.js +++ b/src/legacy/core_plugins/kibana/public/discover/context/query/actions.js @@ -26,7 +26,7 @@ import { fetchAnchorProvider } from '../api/anchor'; import { fetchContextProvider } from '../api/context'; import { QueryParameterActionsProvider } from '../query_parameters'; import { FAILURE_REASONS, LOADING_STATUS } from './constants'; -import { MarkdownSimple } from '../../../../kibana_react/public'; +import { MarkdownSimple } from '../../../../../kibana_react/public'; export function QueryActionsProvider(Private, Promise) { const fetchAnchor = Private(fetchAnchorProvider); diff --git a/src/legacy/core_plugins/kibana/public/context/query/constants.js b/src/legacy/core_plugins/kibana/public/discover/context/query/constants.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/query/constants.js rename to src/legacy/core_plugins/kibana/public/discover/context/query/constants.js diff --git a/src/legacy/core_plugins/kibana/public/context/query/index.js b/src/legacy/core_plugins/kibana/public/discover/context/query/index.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/query/index.js rename to src/legacy/core_plugins/kibana/public/discover/context/query/index.js diff --git a/src/legacy/core_plugins/kibana/public/context/query/state.js b/src/legacy/core_plugins/kibana/public/discover/context/query/state.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/query/state.js rename to src/legacy/core_plugins/kibana/public/discover/context/query/state.js diff --git a/src/legacy/core_plugins/kibana/public/context/query_parameters/__tests__/_utils.js b/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/_utils.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/query_parameters/__tests__/_utils.js rename to src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/_utils.js diff --git a/src/legacy/core_plugins/kibana/public/context/query_parameters/__tests__/action_add_filter.js b/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_add_filter.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/query_parameters/__tests__/action_add_filter.js rename to src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_add_filter.js diff --git a/src/legacy/core_plugins/kibana/public/context/query_parameters/__tests__/action_set_predecessor_count.js b/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_predecessor_count.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/query_parameters/__tests__/action_set_predecessor_count.js rename to src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_predecessor_count.js diff --git a/src/legacy/core_plugins/kibana/public/context/query_parameters/__tests__/action_set_query_parameters.js b/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_query_parameters.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/query_parameters/__tests__/action_set_query_parameters.js rename to src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_query_parameters.js diff --git a/src/legacy/core_plugins/kibana/public/context/query_parameters/__tests__/action_set_successor_count.js b/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_successor_count.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/query_parameters/__tests__/action_set_successor_count.js rename to src/legacy/core_plugins/kibana/public/discover/context/query_parameters/__tests__/action_set_successor_count.js diff --git a/src/legacy/core_plugins/kibana/public/context/query_parameters/actions.js b/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/actions.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/query_parameters/actions.js rename to src/legacy/core_plugins/kibana/public/discover/context/query_parameters/actions.js diff --git a/src/legacy/core_plugins/kibana/public/context/query_parameters/constants.ts b/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/constants.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/query_parameters/constants.ts rename to src/legacy/core_plugins/kibana/public/discover/context/query_parameters/constants.ts diff --git a/src/legacy/core_plugins/kibana/public/context/query_parameters/index.js b/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/index.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/query_parameters/index.js rename to src/legacy/core_plugins/kibana/public/discover/context/query_parameters/index.js diff --git a/src/legacy/core_plugins/kibana/public/context/query_parameters/state.ts b/src/legacy/core_plugins/kibana/public/discover/context/query_parameters/state.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/context/query_parameters/state.ts rename to src/legacy/core_plugins/kibana/public/discover/context/query_parameters/state.ts diff --git a/src/legacy/core_plugins/kibana/public/doc/doc.test.tsx b/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/doc/doc.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/doc/doc.tsx b/src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx similarity index 98% rename from src/legacy/core_plugins/kibana/public/doc/doc.tsx rename to src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx index 3b972d88d329f..1f2d2fc532b57 100644 --- a/src/legacy/core_plugins/kibana/public/doc/doc.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc/doc.tsx @@ -22,7 +22,7 @@ import { EuiCallOut, EuiLink, EuiLoadingSpinner, EuiPageContent } from '@elastic import { IndexPatterns } from 'ui/index_patterns'; import { metadata } from 'ui/metadata'; import { ElasticSearchHit } from 'ui/registry/doc_views_types'; -import { DocViewer } from '../doc_viewer/doc_viewer'; +import { DocViewer } from '../doc_viewer'; import { ElasticRequestState, useEsDocSearch } from './use_es_doc_search'; export interface ElasticSearchResult { diff --git a/src/legacy/core_plugins/kibana/public/doc/doc_directive.ts b/src/legacy/core_plugins/kibana/public/discover/doc/doc_directive.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/doc/doc_directive.ts rename to src/legacy/core_plugins/kibana/public/discover/doc/doc_directive.ts diff --git a/src/legacy/core_plugins/kibana/public/doc/index.html b/src/legacy/core_plugins/kibana/public/discover/doc/index.html similarity index 100% rename from src/legacy/core_plugins/kibana/public/doc/index.html rename to src/legacy/core_plugins/kibana/public/discover/doc/index.html diff --git a/src/legacy/core_plugins/kibana/public/doc/index.ts b/src/legacy/core_plugins/kibana/public/discover/doc/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/doc/index.ts rename to src/legacy/core_plugins/kibana/public/discover/doc/index.ts diff --git a/src/legacy/core_plugins/kibana/public/doc/use_es_doc_search.test.tsx b/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/doc/use_es_doc_search.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/doc/use_es_doc_search.ts b/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/doc/use_es_doc_search.ts rename to src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.ts index acbfbf5f7aedd..d1a01dadb72be 100644 --- a/src/legacy/core_plugins/kibana/public/doc/use_es_doc_search.ts +++ b/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.ts @@ -19,7 +19,7 @@ import { useEffect, useState } from 'react'; import { ElasticSearchHit } from 'ui/registry/doc_views_types'; import { DocProps } from './doc'; -import { IndexPattern } from '../../../data/public/index_patterns'; +import { IndexPattern } from '../../../../data/public/index_patterns'; export enum ElasticRequestState { Loading, diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_row.js b/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_row.js index a0a69969b49ff..8e4105bac827e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_row.js +++ b/src/legacy/core_plugins/kibana/public/discover/doc_table/components/table_row.js @@ -20,7 +20,7 @@ import _ from 'lodash'; import $ from 'jquery'; import rison from 'rison-node'; -import 'plugins/kibana/doc_viewer'; +import '../../doc_viewer'; import { noWhiteSpace } from '../../../../common/utils/no_white_space'; import openRowHtml from './table_row/open.html'; import detailsHtml from './table_row/details.html'; diff --git a/src/legacy/core_plugins/kibana/public/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/doc_viewer/__snapshots__/doc_viewer.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/doc_viewer/__snapshots__/doc_viewer_render_tab.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/doc_viewer/_doc_viewer.scss b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/_doc_viewer.scss similarity index 100% rename from src/legacy/core_plugins/kibana/public/doc_viewer/_doc_viewer.scss rename to src/legacy/core_plugins/kibana/public/discover/doc_viewer/_doc_viewer.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/doc_viewer/_index.scss b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/_index.scss new file mode 100644 index 0000000000000..aaf925f435d81 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/_index.scss @@ -0,0 +1 @@ +@import 'doc_viewer'; diff --git a/src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer.test.tsx b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer.tsx b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer.tsx rename to src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer.tsx diff --git a/src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer_directive.ts b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_directive.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer_directive.ts rename to src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_directive.ts diff --git a/src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer_render_error.tsx b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_error.tsx similarity index 94% rename from src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer_render_error.tsx rename to src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_error.tsx index b81610c5569a4..80b9cb5110db7 100644 --- a/src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer_render_error.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_error.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { EuiCallOut, EuiCodeBlock } from '@elastic/eui'; // @ts-ignore -import { formatMsg, formatStack } from '../../../../ui/public/notify/lib'; +import { formatMsg, formatStack } from 'ui/notify/lib/index'; interface Props { error: Error | string | null; diff --git a/src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer_render_tab.test.tsx b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer_render_tab.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer_render_tab.tsx b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer_render_tab.tsx rename to src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_tab.tsx diff --git a/src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer_tab.tsx b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_tab.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/doc_viewer/doc_viewer_tab.tsx rename to src/legacy/core_plugins/kibana/public/discover/doc_viewer/doc_viewer_tab.tsx diff --git a/src/legacy/core_plugins/kibana/public/doc_viewer/index.js b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/index.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/doc_viewer/index.js rename to src/legacy/core_plugins/kibana/public/discover/doc_viewer/index.ts index 0771de0f2d599..8ce94f24128df 100644 --- a/src/legacy/core_plugins/kibana/public/doc_viewer/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/doc_viewer/index.ts @@ -18,3 +18,5 @@ */ import './doc_viewer_directive'; + +export * from './doc_viewer'; diff --git a/src/legacy/core_plugins/kibana/public/discover/index.js b/src/legacy/core_plugins/kibana/public/discover/index.js index def832107322d..e509936306275 100644 --- a/src/legacy/core_plugins/kibana/public/discover/index.js +++ b/src/legacy/core_plugins/kibana/public/discover/index.js @@ -19,12 +19,15 @@ import './saved_searches/saved_searches'; import { i18n } from '@kbn/i18n'; -import './directives'; + +import './angular/directives'; import 'ui/collapsible_sidebar'; import './components/field_chooser/field_chooser'; -import './controllers/discover'; +import './angular/discover'; import './doc_table/components/table_row'; import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; +import './doc'; +import './context'; FeatureCatalogueRegistryProvider.register(() => { return { diff --git a/src/legacy/core_plugins/kibana/public/doc_viewer/_index.scss b/src/legacy/core_plugins/kibana/public/doc_viewer/_index.scss deleted file mode 100644 index c8fe67fd3bae4..0000000000000 --- a/src/legacy/core_plugins/kibana/public/doc_viewer/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './doc_viewer'; diff --git a/src/legacy/core_plugins/kibana/public/index.scss b/src/legacy/core_plugins/kibana/public/index.scss index 7a47ca5e8eb1c..7a6a3ca1d01d0 100644 --- a/src/legacy/core_plugins/kibana/public/index.scss +++ b/src/legacy/core_plugins/kibana/public/index.scss @@ -9,9 +9,6 @@ // Public UI styles @import 'src/legacy/ui/public/index'; -// Context styles -@import './context/index'; - // Dev tools styles @import './dev_tools/index'; @@ -30,9 +27,6 @@ // Management styles @import './management/index'; -// Doc Viewer -@import './doc_viewer/index'; - // Dashboard styles // MUST STAY AT THE BOTTOM BECAUSE OF DARK THEME IMPORTS @import './dashboard/index'; diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 9a96bf26aede6..6c809e84c8c84 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -52,9 +52,7 @@ import './discover'; import './visualize'; import './dashboard'; import './management'; -import './doc'; import './dev_tools'; -import './context'; import 'ui/vislib'; import 'ui/agg_response'; import 'ui/agg_types'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js index 7cf3f935ec4b5..39a9f7cd98a57 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/objects_table.test.js @@ -25,6 +25,7 @@ import { ObjectsTable, POSSIBLE_TYPES } from '../objects_table'; import { Flyout } from '../components/flyout/'; import { Relationships } from '../components/relationships/'; import { findObjects } from '../../../lib'; +import { extractExportDetails } from '../../../lib/extract_export_details'; jest.mock('ui/kfetch', () => ({ kfetch: jest.fn() })); @@ -49,6 +50,10 @@ jest.mock('../../../lib/fetch_export_by_type_and_search', () => ({ fetchExportByTypeAndSearch: jest.fn(), })); +jest.mock('../../../lib/extract_export_details', () => ({ + extractExportDetails: jest.fn(), +})); + jest.mock('../../../lib/get_saved_object_counts', () => ({ getSavedObjectCounts: jest.fn().mockImplementation(() => { return { @@ -190,12 +195,14 @@ beforeEach(() => { let addDangerMock; let addSuccessMock; +let addWarningMock; describe('ObjectsTable', () => { beforeEach(() => { defaultProps.savedObjectsClient.find.mockClear(); + extractExportDetails.mockReset(); // mock _.debounce to fire immediately with no internal timer - require('lodash').debounce = function (func) { + require('lodash').debounce = func => { function debounced(...args) { return func.apply(this, args); } @@ -203,9 +210,11 @@ describe('ObjectsTable', () => { }; addDangerMock = jest.fn(); addSuccessMock = jest.fn(); + addWarningMock = jest.fn(); require('ui/notify').toastNotifications = { addDanger: addDangerMock, addSuccess: addSuccessMock, + addWarning: addWarningMock, }; }); @@ -280,6 +289,55 @@ describe('ObjectsTable', () => { }); }); + it('should display a warning is export contains missing references', async () => { + const mockSelectedSavedObjects = [ + { id: '1', type: 'index-pattern' }, + { id: '3', type: 'dashboard' }, + ]; + + const mockSavedObjects = mockSelectedSavedObjects.map(obj => ({ + _id: obj.id, + _type: obj._type, + _source: {}, + })); + + const mockSavedObjectsClient = { + ...defaultProps.savedObjectsClient, + bulkGet: jest.fn().mockImplementation(() => ({ + savedObjects: mockSavedObjects, + })), + }; + + const { fetchExportObjects } = require('../../../lib/fetch_export_objects'); + extractExportDetails.mockImplementation(() => ({ + exportedCount: 2, + missingRefCount: 1, + missingReferences: [{ id: '7', type: 'visualisation' }], + })); + + const component = shallowWithI18nProvider( + + ); + + // Ensure all promises resolve + await new Promise(resolve => process.nextTick(resolve)); + // Ensure the state changes are reflected + component.update(); + + // Set some as selected + component.instance().onSelectionChanged(mockSelectedSavedObjects); + + await component.instance().onExport(true); + + expect(fetchExportObjects).toHaveBeenCalledWith(mockSelectedSavedObjects, true); + expect(addWarningMock).toHaveBeenCalledWith({ + title: + 'Your file is downloading in the background. ' + + 'Some related objects could not be found. ' + + 'Please see the last line in the exported file for a list of missing objects.', + }); + }); + it('should allow the user to choose when exporting all', async () => { const component = shallowWithI18nProvider(); @@ -295,7 +353,9 @@ describe('ObjectsTable', () => { }); it('should export all', async () => { - const { fetchExportByTypeAndSearch } = require('../../../lib/fetch_export_by_type_and_search'); + const { + fetchExportByTypeAndSearch, + } = require('../../../lib/fetch_export_by_type_and_search'); const { saveAs } = require('@elastic/filesaver'); const component = shallowWithI18nProvider(); @@ -312,20 +372,20 @@ describe('ObjectsTable', () => { expect(fetchExportByTypeAndSearch).toHaveBeenCalledWith(POSSIBLE_TYPES, undefined, true); expect(saveAs).toHaveBeenCalledWith(blob, 'export.ndjson'); - expect(addSuccessMock).toHaveBeenCalledWith({ title: 'Your file is downloading in the background' }); + expect(addSuccessMock).toHaveBeenCalledWith({ + title: 'Your file is downloading in the background', + }); }); it('should export all, accounting for the current search criteria', async () => { - const { fetchExportByTypeAndSearch } = require('../../../lib/fetch_export_by_type_and_search'); + const { + fetchExportByTypeAndSearch, + } = require('../../../lib/fetch_export_by_type_and_search'); const { saveAs } = require('@elastic/filesaver'); - const component = shallowWithI18nProvider( - - ); + const component = shallowWithI18nProvider(); component.instance().onQueryChange({ - query: Query.parse('test') + query: Query.parse('test'), }); // Ensure all promises resolve diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/objects_table.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/objects_table.js index 52871c360309c..188762f165b24 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/objects_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/objects_table.js @@ -64,6 +64,7 @@ import { fetchExportByTypeAndSearch, findObjects, } from '../../lib'; +import { extractExportDetails } from '../../lib/extract_export_details'; export const POSSIBLE_TYPES = chrome.getInjected('importAndExportableTypes'); @@ -296,32 +297,31 @@ export class ObjectsTable extends Component { } saveAs(blob, 'export.ndjson'); - toastNotifications.addSuccess({ - title: i18n.translate('kbn.management.objects.objectsTable.export.successNotification', { - defaultMessage: 'Your file is downloading in the background', - }), - }); + + const exportDetails = await extractExportDetails(blob); + this.showExportSuccessMessage(exportDetails); }; onExportAll = async () => { const { exportAllSelectedOptions, isIncludeReferencesDeepChecked, activeQuery } = this.state; const { queryText } = parseQuery(activeQuery); - const exportTypes = Object.entries(exportAllSelectedOptions).reduce( - (accum, [id, selected]) => { - if (selected) { - accum.push(id); - } - return accum; - }, - [] - ); + const exportTypes = Object.entries(exportAllSelectedOptions).reduce((accum, [id, selected]) => { + if (selected) { + accum.push(id); + } + return accum; + }, []); let blob; try { - blob = await fetchExportByTypeAndSearch(exportTypes, queryText ? `${queryText}*` : undefined, isIncludeReferencesDeepChecked); + blob = await fetchExportByTypeAndSearch( + exportTypes, + queryText ? `${queryText}*` : undefined, + isIncludeReferencesDeepChecked + ); } catch (e) { toastNotifications.addDanger({ - title: i18n.translate('kbn.management.objects.objectsTable.exportAll.dangerNotification', { + title: i18n.translate('kbn.management.objects.objectsTable.export.dangerNotification', { defaultMessage: 'Unable to generate export', }), }); @@ -329,14 +329,34 @@ export class ObjectsTable extends Component { } saveAs(blob, 'export.ndjson'); - toastNotifications.addSuccess({ - title: i18n.translate('kbn.management.objects.objectsTable.exportAll.successNotification', { - defaultMessage: 'Your file is downloading in the background', - }), - }); + + const exportDetails = await extractExportDetails(blob); + this.showExportSuccessMessage(exportDetails); this.setState({ isShowingExportAllOptionsModal: false }); }; + showExportSuccessMessage = exportDetails => { + if (exportDetails && exportDetails.missingReferences.length > 0) { + toastNotifications.addWarning({ + title: i18n.translate( + 'kbn.management.objects.objectsTable.export.successWithMissingRefsNotification', + { + defaultMessage: + 'Your file is downloading in the background. ' + + 'Some related objects could not be found. ' + + 'Please see the last line in the exported file for a list of missing objects.', + } + ), + }); + } else { + toastNotifications.addSuccess({ + title: i18n.translate('kbn.management.objects.objectsTable.export.successNotification', { + defaultMessage: 'Your file is downloading in the background', + }), + }); + } + }; + finishImport = () => { this.hideImportFlyout(); this.fetchSavedObjects(); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/__jest__/extract_export_details.test.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/__jest__/extract_export_details.test.ts new file mode 100644 index 0000000000000..a6ed2e36839f4 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/__jest__/extract_export_details.test.ts @@ -0,0 +1,96 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { extractExportDetails, SavedObjectsExportResultDetails } from '../extract_export_details'; + +describe('extractExportDetails', () => { + const objLine = (id: string, type: string) => { + return JSON.stringify({ attributes: {}, id, references: [], type }) + '\n'; + }; + const detailsLine = ( + exported: number, + missingRefs: SavedObjectsExportResultDetails['missingReferences'] = [] + ) => { + return ( + JSON.stringify({ + exportedCount: exported, + missingRefCount: missingRefs.length, + missingReferences: missingRefs, + }) + '\n' + ); + }; + + it('should extract the export details from the export blob', async () => { + const exportData = new Blob( + [ + [ + objLine('1', 'index-pattern'), + objLine('2', 'index-pattern'), + objLine('3', 'index-pattern'), + detailsLine(3), + ].join(''), + ], + { type: 'application/ndjson', endings: 'transparent' } + ); + const result = await extractExportDetails(exportData); + expect(result).not.toBeUndefined(); + expect(result).toEqual({ + exportedCount: 3, + missingRefCount: 0, + missingReferences: [], + }); + }); + + it('should properly extract the missing references', async () => { + const exportData = new Blob( + [ + [ + objLine('1', 'index-pattern'), + detailsLine(1, [{ id: '2', type: 'index-pattern' }, { id: '3', type: 'index-pattern' }]), + ].join(''), + ], + { + type: 'application/ndjson', + endings: 'transparent', + } + ); + const result = await extractExportDetails(exportData); + expect(result).not.toBeUndefined(); + expect(result).toEqual({ + exportedCount: 1, + missingRefCount: 2, + missingReferences: [{ id: '2', type: 'index-pattern' }, { id: '3', type: 'index-pattern' }], + }); + }); + + it('should return undefined when the export does not contain details', async () => { + const exportData = new Blob( + [ + [ + objLine('1', 'index-pattern'), + objLine('2', 'index-pattern'), + objLine('3', 'index-pattern'), + ].join(''), + ], + { type: 'application/ndjson', endings: 'transparent' } + ); + const result = await extractExportDetails(exportData); + expect(result).toBeUndefined(); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/extract_export_details.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/extract_export_details.ts new file mode 100644 index 0000000000000..fdd72aece06bc --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/extract_export_details.ts @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 async function extractExportDetails( + blob: Blob +): Promise { + const reader = new FileReader(); + const content = await new Promise((resolve, reject) => { + reader.addEventListener('loadend', e => { + resolve((e as any).target.result); + }); + reader.addEventListener('error', e => { + reject(e); + }); + reader.readAsText(blob, 'utf-8'); + }); + const lines = content.split('\n').filter(l => l.length > 0); + const maybeDetails = JSON.parse(lines[lines.length - 1]); + if (isExportDetails(maybeDetails)) { + return maybeDetails; + } +} + +export interface SavedObjectsExportResultDetails { + exportedCount: number; + missingRefCount: number; + missingReferences: Array<{ + id: string; + type: string; + }>; +} + +function isExportDetails(object: any): object is SavedObjectsExportResultDetails { + return 'exportedCount' in object && 'missingRefCount' in object && 'missingReferences' in object; +} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/index.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/index.js index 245812867f1de..b6c8d25568446 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/index.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/index.js @@ -32,3 +32,4 @@ export * from './log_legacy_import'; export * from './process_import_response'; export * from './get_default_title'; export * from './find_objects'; +export * from './extract_export_details'; diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index 1d74fc63bcc0f..3f9b897730f51 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -108,9 +108,7 @@ export default () => Joi.object({ ssl: HANDLED_IN_NEW_PLATFORM, }).default(), - uiSettings: Joi.object().keys({ - overrides: Joi.object().unknown(true).default() - }).default(), + uiSettings: HANDLED_IN_NEW_PLATFORM, logging: Joi.object().keys({ silent: Joi.boolean().default(false), diff --git a/src/legacy/server/config/transform_deprecations.js b/src/legacy/server/config/transform_deprecations.js index 8be880074f9fd..7cac17a88fe64 100644 --- a/src/legacy/server/config/transform_deprecations.js +++ b/src/legacy/server/config/transform_deprecations.js @@ -95,7 +95,6 @@ const cspRules = (settings, log) => { const deprecations = [ //server - rename('server.defaultRoute', 'uiSettings.overrides.defaultRoute'), unused('server.xsrf.token'), unused('uiSettings.enabled'), rename('optimize.lazy', 'optimize.watch'), diff --git a/src/legacy/server/config/transform_deprecations.test.js b/src/legacy/server/config/transform_deprecations.test.js index 4094443ac0006..f8cf38efc8bd8 100644 --- a/src/legacy/server/config/transform_deprecations.test.js +++ b/src/legacy/server/config/transform_deprecations.test.js @@ -62,24 +62,6 @@ describe('server/config', function () { }); }); - describe('server.defaultRoute', () => { - it('renames to uiSettings.overrides.defaultRoute when specified', () => { - const settings = { - server: { - defaultRoute: '/app/foo', - }, - }; - - expect(transformDeprecations(settings)).toEqual({ - uiSettings: { - overrides: { - defaultRoute: '/app/foo' - } - } - }); - }); - }); - describe('csp.rules', () => { describe('with nonce source', () => { it('logs a warning', () => { diff --git a/src/legacy/server/http/integration_tests/default_route_provider.test.ts b/src/legacy/server/http/integration_tests/default_route_provider.test.ts index fe8c464965132..d74be851a3535 100644 --- a/src/legacy/server/http/integration_tests/default_route_provider.test.ts +++ b/src/legacy/server/http/integration_tests/default_route_provider.test.ts @@ -45,11 +45,11 @@ describe('default route provider', () => { throw Error(`unsupported ui setting: ${key}`); }, getDefaults: () => { - return Promise.resolve({ + return { defaultRoute: { value: '/app/kibana', }, - }); + }; }, }; }); diff --git a/src/legacy/server/http/setup_default_route_provider.ts b/src/legacy/server/http/setup_default_route_provider.ts index 07ff61015a187..f627cf30a3cff 100644 --- a/src/legacy/server/http/setup_default_route_provider.ts +++ b/src/legacy/server/http/setup_default_route_provider.ts @@ -40,7 +40,7 @@ export function setupDefaultRouteProvider(server: Legacy.Server) { `Ignoring configured default route of '${defaultRoute}', as it is malformed.` ); - const fallbackRoute = (await uiSettings.getDefaults()).defaultRoute.value; + const fallbackRoute = uiSettings.getDefaults().defaultRoute.value; const qualifiedFallbackRoute = `${request.getBasePath()}${fallbackRoute}`; return qualifiedFallbackRoute; diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index f5771c6b86d9a..e7f2f4c85435f 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -28,6 +28,7 @@ import { LoggerFactory, SavedObjectsClientContract, SavedObjectsLegacyService, + IUiSettingsClient, PackageInfo, } from '../../core/server'; @@ -41,7 +42,6 @@ import { CallClusterWithRequest, ElasticsearchPlugin } from '../core_plugins/ela import { CapabilitiesModifier } from './capabilities'; import { IndexPatternsServiceFactory } from './index_patterns'; import { Capabilities } from '../../core/public'; -import { IUiSettingsClient } from '../../legacy/ui/ui_settings/ui_settings_service'; import { UiSettingsServiceFactoryOptions } from '../../legacy/ui/ui_settings/ui_settings_service_factory'; export interface KibanaConfig { @@ -104,6 +104,13 @@ type KbnMixinFunc = (kbnServer: KbnServer, server: Server, config: any) => Promi // eslint-disable-next-line import/no-default-export export default class KbnServer { public readonly newPlatform: { + __internals: { + hapiServer: LegacyServiceSetupDeps['core']['http']['server']; + uiPlugins: LegacyServiceSetupDeps['core']['plugins']['uiPlugins']; + elasticsearch: LegacyServiceSetupDeps['core']['elasticsearch']; + uiSettings: LegacyServiceSetupDeps['core']['uiSettings']; + kibanaMigrator: LegacyServiceStartDeps['core']['savedObjects']['migrator']; + }; env: { mode: Readonly; packageInfo: Readonly; diff --git a/src/legacy/server/saved_objects/lib/create_saved_objects_stream_from_ndjson.test.ts b/src/legacy/server/saved_objects/lib/create_saved_objects_stream_from_ndjson.test.ts index 2eab73137fff0..342063fefaec6 100644 --- a/src/legacy/server/saved_objects/lib/create_saved_objects_stream_from_ndjson.test.ts +++ b/src/legacy/server/saved_objects/lib/create_saved_objects_stream_from_ndjson.test.ts @@ -77,4 +77,30 @@ describe('createSavedObjectsStreamFromNdJson', () => { }, ]); }); + + it('filters the export details entry from the stream', async () => { + const savedObjectsStream = createSavedObjectsStreamFromNdJson( + new Readable({ + read() { + this.push('{"id": "foo", "type": "foo-type"}\n'); + this.push('{"id": "bar", "type": "bar-type"}\n'); + this.push('{"exportedCount": 2, "missingRefCount": 0, "missingReferences": []}\n'); + this.push(null); + }, + }) + ); + + const result = await readStreamToCompletion(savedObjectsStream); + + expect(result).toEqual([ + { + id: 'foo', + type: 'foo-type', + }, + { + id: 'bar', + type: 'bar-type', + }, + ]); + }); }); diff --git a/src/legacy/server/saved_objects/lib/create_saved_objects_stream_from_ndjson.ts b/src/legacy/server/saved_objects/lib/create_saved_objects_stream_from_ndjson.ts index 10047284f5c96..b96514054db56 100644 --- a/src/legacy/server/saved_objects/lib/create_saved_objects_stream_from_ndjson.ts +++ b/src/legacy/server/saved_objects/lib/create_saved_objects_stream_from_ndjson.ts @@ -17,7 +17,7 @@ * under the License. */ import { Readable } from 'stream'; -import { SavedObject } from 'src/core/server'; +import { SavedObject, SavedObjectsExportResultDetails } from 'src/core/server'; import { createSplitStream, createMapStream, createFilterStream } from '../../../utils/streams'; export function createSavedObjectsStreamFromNdJson(ndJsonStream: Readable) { @@ -30,5 +30,9 @@ export function createSavedObjectsStreamFromNdJson(ndJsonStream: Readable) { } }) ) - .pipe(createFilterStream(obj => !!obj)); + .pipe( + createFilterStream( + obj => !!obj && !(obj as SavedObjectsExportResultDetails).exportedCount + ) + ); } diff --git a/src/legacy/server/saved_objects/routes/export.test.ts b/src/legacy/server/saved_objects/routes/export.test.ts index 491e3a9067611..1b7e0dfa65db5 100644 --- a/src/legacy/server/saved_objects/routes/export.test.ts +++ b/src/legacy/server/saved_objects/routes/export.test.ts @@ -157,6 +157,7 @@ describe('POST /api/saved_objects/_export', () => { "calls": Array [ Array [ Object { + "excludeExportDetails": false, "exportSizeLimit": 10000, "includeReferencesDeep": true, "objects": undefined, diff --git a/src/legacy/server/saved_objects/routes/export.ts b/src/legacy/server/saved_objects/routes/export.ts index fc120030a873c..ce4aed4b78c2a 100644 --- a/src/legacy/server/saved_objects/routes/export.ts +++ b/src/legacy/server/saved_objects/routes/export.ts @@ -28,7 +28,7 @@ import { } from '../../../utils/streams'; // Disable lint errors for imports from src/core/server/saved_objects until SavedObjects migration is complete // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { getSortedObjectsForExport } from '../../../../core/server/saved_objects/export'; +import { getSortedObjectsForExport } from '../../../../core/server/saved_objects'; import { Prerequisites } from './types'; interface ExportRequest extends Hapi.Request { @@ -43,6 +43,7 @@ interface ExportRequest extends Hapi.Request { }>; search?: string; includeReferencesDeep: boolean; + excludeExportDetails: boolean; }; } @@ -73,6 +74,7 @@ export const createExportRoute = ( .optional(), search: Joi.string().optional(), includeReferencesDeep: Joi.boolean().default(false), + excludeExportDetails: Joi.boolean().default(false), }) .xor('type', 'objects') .nand('search', 'objects') @@ -87,6 +89,7 @@ export const createExportRoute = ( objects: request.payload.objects, exportSizeLimit: server.config().get('savedObjects.maxImportExportSize'), includeReferencesDeep: request.payload.includeReferencesDeep, + excludeExportDetails: request.payload.excludeExportDetails, }); const docsToExport: string[] = await createPromiseFromStreams([ diff --git a/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts b/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts index 3720677ec17d2..f9a5c25878322 100644 --- a/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts +++ b/src/legacy/ui/field_formats/mixin/field_formats_mixin.ts @@ -28,7 +28,7 @@ export function fieldFormatsMixin(kbnServer: any, server: Legacy.Server) { // for use outside of the request context, for special cases server.decorate('server', 'fieldFormatServiceFactory', async function(uiSettings) { const uiConfigs = await uiSettings.getAll(); - const uiSettingDefaults = await uiSettings.getDefaults(); + const uiSettingDefaults = uiSettings.getDefaults(); Object.keys(uiSettingDefaults).forEach(key => { if (has(uiConfigs, key) && uiSettingDefaults[key].type === 'json') { uiConfigs[key] = JSON.parse(uiConfigs[key]); diff --git a/src/legacy/ui/public/agg_types/__tests__/agg_params.js b/src/legacy/ui/public/agg_types/__tests__/agg_params.js deleted file mode 100644 index d4fe2a663543a..0000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/agg_params.js +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { initParams } from '../agg_params'; -import { BaseParamType } from '../param_types/base'; -import { FieldParamType } from '../param_types/field'; -import { OptionedParamType } from '../param_types/optioned'; - -describe('AggParams class', function () { - - describe('constructor args', function () { - it('accepts an array of param defs', function () { - const params = [ - { name: 'one' }, - { name: 'two' } - ]; - const aggParams = initParams(params); - - expect(aggParams).to.have.length(params.length); - expect(aggParams).to.be.an(Array); - }); - }); - - describe('AggParam creation', function () { - it('Uses the FieldParamType class for params with the name "field"', function () { - const params = [ - { name: 'field', type: 'field' } - ]; - const aggParams = initParams(params); - - expect(aggParams).to.have.length(params.length); - expect(aggParams[0]).to.be.a(FieldParamType); - }); - - it('Uses the OptionedParamType class for params of type "optioned"', function () { - const params = [ - { - name: 'interval', - type: 'optioned' - } - ]; - const aggParams = initParams(params); - - expect(aggParams).to.have.length(params.length); - expect(aggParams[0]).to.be.a(OptionedParamType); - }); - - it('Uses the OptionedParamType class for params of type "optioned"', function () { - const params = [ - { - name: 'order', - type: 'optioned' - } - ]; - const aggParams = initParams(params); - - expect(aggParams).to.have.length(params.length); - expect(aggParams[0]).to.be.a(OptionedParamType); - }); - - it('Always converts the params to a BaseParamType', function () { - const params = [ - { - name: 'height', - editor: 'high' - }, - { - name: 'weight', - editor: 'big' - }, - { - name: 'waist', - editor: 'small' - } - ]; - const aggParams = initParams(params); - - expect(aggParams).to.have.length(params.length); - aggParams.forEach(function (aggParam) { - expect(aggParam).to.be.a(BaseParamType); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/agg_type.js b/src/legacy/ui/public/agg_types/__tests__/agg_type.js deleted file mode 100644 index 81daa9b54fa43..0000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/agg_type.js +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import '../../private'; -import { VisProvider } from '../../vis'; -import { fieldFormats } from '../../registry/field_formats'; -import { AggType } from '../agg_type'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('AggType Class', function () { - let indexPattern; - let Vis; - - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - - Vis = Private(VisProvider); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - describe('constructor', function () { - - it('requires a config object as it\'s first param', function () { - expect(function () { - new AggType(null); - }).to.throwError(); - }); - - describe('application of config properties', function () { - const copiedConfigProps = [ - 'name', - 'title', - 'makeLabel', - 'ordered' - ]; - - describe('"' + copiedConfigProps.join('", "') + '"', function () { - it('assigns the config value to itself', function () { - const config = _.transform(copiedConfigProps, function (config, prop) { - config[prop] = {}; - }, {}); - - const aggType = new AggType(config); - - copiedConfigProps.forEach(function (prop) { - expect(aggType[prop]).to.be(config[prop]); - }); - }); - }); - - describe('makeLabel', function () { - it('makes a function when the makeLabel config is not specified', function () { - const someGetter = function () {}; - - let aggType = new AggType({ - makeLabel: someGetter - }); - - expect(aggType.makeLabel).to.be(someGetter); - - aggType = new AggType({ - name: 'pizza' - }); - - expect(aggType.makeLabel).to.be.a('function'); - expect(aggType.makeLabel()).to.be('pizza'); - }); - }); - - describe('getFormat', function () { - it('returns the formatter for the aggConfig', function () { - const aggType = new AggType({}); - - let vis = new Vis(indexPattern, { - aggs: [ - { - type: 'date_histogram', - schema: 'segment' - } - ] - }); - let aggConfig = vis.aggs.byName('date_histogram')[0]; - - expect(aggType.getFormat(aggConfig)).to.be(fieldFormats.getDefaultInstance('date')); - - vis = new Vis(indexPattern, { - aggs: [ - { - type: 'count', - schema: 'metric' - } - ] - }); - aggConfig = vis.aggs.byName('count')[0]; - - expect(aggType.getFormat(aggConfig)).to.be(fieldFormats.getDefaultInstance('string')); - }); - - it('can be overridden via config', function () { - const someGetter = function () {}; - - const aggType = new AggType({ - getFormat: someGetter - }); - - expect(aggType.getFormat).to.be(someGetter); - }); - }); - - describe('params', function () { - - it('defaults to AggParams object with JSON param', function () { - const aggType = new AggType({ - name: 'smart agg' - }); - - expect(aggType.params).to.be.an(Array); - expect(aggType.params.length).to.be(2); - expect(aggType.params[0].name).to.be('json'); - expect(aggType.params[1].name).to.be('customLabel'); - }); - - it('can disable customLabel', function () { - const aggType = new AggType({ - name: 'smart agg', - customLabels: false - }); - - expect(aggType.params.length).to.be(1); - expect(aggType.params[0].name).to.be('json'); - }); - - it('passes the params arg directly to the AggParams constructor', function () { - const params = [ - { name: 'one' }, - { name: 'two' } - ]; - const paramLength = params.length + 2; // json and custom label are always appended - - const aggType = new AggType({ - name: 'bucketeer', - params: params - }); - - expect(aggType.params).to.be.an(Array); - expect(aggType.params.length).to.be(paramLength); - }); - }); - - describe('getResponseAggs', function () { - it('copies the value', function () { - const football = {}; - const aggType = new AggType({ - getResponseAggs: football - }); - - expect(aggType.getResponseAggs).to.be(football); - }); - - it('defaults to noop', function () { - const aggType = new AggType({}); - const responseAggs = aggType.getRequestAggs(); - expect(responseAggs).to.be(undefined); - }); - }); - }); - - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/index.js b/src/legacy/ui/public/agg_types/__tests__/index.js deleted file mode 100644 index c977eb6eeb610..0000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/index.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import './agg_type'; -import './agg_params'; -import './buckets/_histogram'; -import './buckets/_geo_hash'; -import './buckets/_range'; -import './buckets/_terms_other_bucket_helper'; -import './buckets/date_histogram/_editor'; -import './buckets/date_histogram/_params'; -import { aggTypes } from '..'; -import { BucketAggType } from '../buckets/_bucket_agg_type'; -import { MetricAggType } from '../metrics/metric_agg_type'; - -const bucketAggs = aggTypes.buckets; -const metricAggs = aggTypes.metrics; - -describe('AggTypesComponent', function () { - - describe('bucket aggs', function () { - it('all extend BucketAggType', function () { - bucketAggs.forEach(function (bucketAgg) { - expect(bucketAgg).to.be.a(BucketAggType); - }); - }); - }); - - describe('metric aggs', function () { - it('all extend MetricAggType', function () { - metricAggs.forEach(function (metricAgg) { - expect(metricAgg).to.be.a(MetricAggType); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/param_types/_json.js b/src/legacy/ui/public/agg_types/__tests__/param_types/_json.js deleted file mode 100644 index 1876593f52956..0000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/param_types/_json.js +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import expect from '@kbn/expect'; -import { BaseParamType } from '../../param_types/base'; -import { JsonParamType } from '../../param_types/json'; - -// eslint-disable-next-line import/no-default-export -export default describe('JSON', function () { - const paramName = 'json_test'; - let aggParam; - let aggConfig; - let output; - - function initParamType(config) { - config = config || {}; - const defaults = { - name: paramName, - type: 'json' - }; - - aggParam = new JsonParamType(_.defaults(config, defaults)); - } - - // fetch out deps - beforeEach(function () { - aggConfig = { params: {} }; - output = { params: {} }; - - - initParamType(); - }); - - describe('constructor', function () { - it('it is an instance of BaseParamType', function () { - expect(aggParam).to.be.a(BaseParamType); - }); - }); - - describe('write', function () { - it('should do nothing when param is not defined', function () { - expect(aggConfig.params).not.to.have.property(paramName); - - aggParam.write(aggConfig, output); - expect(output).not.to.have.property(paramName); - }); - - it('should not append param when invalid JSON', function () { - aggConfig.params[paramName] = 'i am not json'; - - aggParam.write(aggConfig, output); - expect(aggConfig.params).to.have.property(paramName); - expect(output).not.to.have.property(paramName); - }); - - it('should append param when valid JSON', function () { - const jsonData = JSON.stringify({ - new_param: 'should exist in output' - }); - - output.params.existing = 'true'; - aggConfig.params[paramName] = jsonData; - - aggParam.write(aggConfig, output); - expect(aggConfig.params).to.have.property(paramName); - expect(output.params).to.eql({ - existing: 'true', - new_param: 'should exist in output' - }); - }); - - it('should not overwrite existing params', function () { - const jsonData = JSON.stringify({ - new_param: 'should exist in output', - existing: 'should be used' - }); - - output.params.existing = 'true'; - aggConfig.params[paramName] = jsonData; - - aggParam.write(aggConfig, output); - expect(output.params).to.eql(JSON.parse(jsonData)); - }); - - it('should drop nulled params', function () { - const jsonData = JSON.stringify({ - new_param: 'should exist in output', - field: null - }); - - output.params.field = 'extensions'; - aggConfig.params[paramName] = jsonData; - - aggParam.write(aggConfig, output); - expect(Object.keys(output.params)).to.contain('new_param'); - expect(Object.keys(output.params)).to.not.contain('field'); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/__tests__/param_types/_string.js b/src/legacy/ui/public/agg_types/__tests__/param_types/_string.js deleted file mode 100644 index 10c965a53bab5..0000000000000 --- a/src/legacy/ui/public/agg_types/__tests__/param_types/_string.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import expect from '@kbn/expect'; -import { BaseParamType } from '../../param_types/base'; -import { StringParamType } from '../../param_types/string'; - -// eslint-disable-next-line import/no-default-export -export default describe('String', function () { - const paramName = 'json_test'; - let aggParam; - let aggConfig; - let output; - - function initAggParam(config) { - config = config || {}; - const defaults = { - name: paramName, - type: 'string' - }; - - aggParam = new StringParamType(_.defaults(config, defaults)); - } - - - // fetch our deps - beforeEach(function () { - - aggConfig = { params: {} }; - output = { params: {} }; - }); - - describe('constructor', function () { - it('it is an instance of BaseParamType', function () { - initAggParam(); - expect(aggParam).to.be.a(BaseParamType); - }); - }); - - describe('write', function () { - it('should append param by name', function () { - const paramName = 'testing'; - const params = {}; - params[paramName] = 'some input'; - - initAggParam({ name: paramName }); - - aggConfig.params = params; - aggParam.write(aggConfig, output); - - expect(output.params).to.eql(params); - }); - - it('should not be in output with empty input', function () { - const paramName = 'more_testing'; - const params = {}; - params[paramName] = ''; - - initAggParam({ name: paramName }); - - aggConfig.params = params; - aggParam.write(aggConfig, output); - - expect(output.params).to.eql({}); - }); - }); -}); diff --git a/src/legacy/ui/public/agg_types/agg_params.test.ts b/src/legacy/ui/public/agg_types/agg_params.test.ts new file mode 100644 index 0000000000000..28d852c7f2567 --- /dev/null +++ b/src/legacy/ui/public/agg_types/agg_params.test.ts @@ -0,0 +1,83 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AggParam, initParams } from './agg_params'; +import { BaseParamType } from './param_types/base'; +import { FieldParamType } from './param_types/field'; +import { OptionedParamType } from './param_types/optioned'; + +jest.mock('ui/new_platform'); + +describe('AggParams class', () => { + describe('constructor args', () => { + it('accepts an array of param defs', () => { + const params = [{ name: 'one' }, { name: 'two' }] as AggParam[]; + const aggParams = initParams(params); + + expect(aggParams).toHaveLength(params.length); + expect(Array.isArray(aggParams)).toBeTruthy(); + }); + }); + + describe('AggParam creation', () => { + it('Uses the FieldParamType class for params with the name "field"', () => { + const params = [{ name: 'field', type: 'field' }] as AggParam[]; + const aggParams = initParams(params); + + expect(aggParams).toHaveLength(params.length); + expect(aggParams[0] instanceof FieldParamType).toBeTruthy(); + }); + + it('Uses the OptionedParamType class for params of type "optioned"', () => { + const params = [ + { + name: 'order', + type: 'optioned', + }, + ]; + const aggParams = initParams(params); + + expect(aggParams).toHaveLength(params.length); + expect(aggParams[0] instanceof OptionedParamType).toBeTruthy(); + }); + + it('Always converts the params to a BaseParamType', function() { + const params = [ + { + name: 'height', + displayName: 'height', + }, + { + name: 'weight', + displayName: 'weight', + }, + { + name: 'waist', + displayName: 'waist', + }, + ] as AggParam[]; + + const aggParams = initParams(params); + + expect(aggParams).toHaveLength(params.length); + + aggParams.forEach(aggParam => expect(aggParam instanceof BaseParamType).toBeTruthy()); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/agg_type.test.ts b/src/legacy/ui/public/agg_types/agg_type.test.ts new file mode 100644 index 0000000000000..1c1453b74fe98 --- /dev/null +++ b/src/legacy/ui/public/agg_types/agg_type.test.ts @@ -0,0 +1,172 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { AggType, AggTypeConfig } from './agg_type'; +import { AggConfig } from './agg_config'; + +jest.mock('ui/new_platform'); + +jest.mock('ui/registry/field_formats', () => ({ + fieldFormats: { + getDefaultInstance: jest.fn(() => 'default'), + }, +})); + +describe('AggType Class', () => { + describe('constructor', () => { + it("requires a valid config object as it's first param", () => { + expect(() => { + const aggConfig: AggTypeConfig = (undefined as unknown) as AggTypeConfig; + new AggType(aggConfig); + }).toThrowError(); + }); + + describe('application of config properties', () => { + it('assigns the config value to itself', () => { + const config: AggTypeConfig = { + name: 'name', + title: 'title', + }; + + const aggType = new AggType(config); + + expect(aggType.name).toBe('name'); + expect(aggType.title).toBe('title'); + }); + + describe('makeLabel', () => { + it('makes a function when the makeLabel config is not specified', () => { + const makeLabel = () => 'label'; + const aggConfig = {} as AggConfig; + const config: AggTypeConfig = { + name: 'name', + title: 'title', + makeLabel, + }; + + const aggType = new AggType(config); + + expect(aggType.makeLabel).toBe(makeLabel); + expect(aggType.makeLabel(aggConfig)).toBe('label'); + }); + }); + + describe('getResponseAggs/getRequestAggs', () => { + it('copies the value', () => { + const testConfig = (aggConfig: AggConfig) => [aggConfig]; + + const aggType = new AggType({ + name: 'name', + title: 'title', + getResponseAggs: testConfig, + getRequestAggs: testConfig, + }); + + expect(aggType.getResponseAggs).toBe(testConfig); + expect(aggType.getResponseAggs).toBe(testConfig); + }); + + it('defaults to noop', () => { + const aggConfig = {} as AggConfig; + const aggType = new AggType({ + name: 'name', + title: 'title', + }); + const responseAggs = aggType.getRequestAggs(aggConfig); + + expect(responseAggs).toBe(undefined); + }); + }); + + describe('params', () => { + it('defaults to AggParams object with JSON param', () => { + const aggType = new AggType({ + name: 'smart agg', + title: 'title', + }); + + expect(Array.isArray(aggType.params)).toBeTruthy(); + expect(aggType.params.length).toBe(2); + expect(aggType.params[0].name).toBe('json'); + expect(aggType.params[1].name).toBe('customLabel'); + }); + + it('can disable customLabel', () => { + const aggType = new AggType({ + name: 'smart agg', + title: 'title', + customLabels: false, + }); + + expect(aggType.params.length).toBe(1); + expect(aggType.params[0].name).toBe('json'); + }); + + it('passes the params arg directly to the AggParams constructor', () => { + const params = [{ name: 'one' }, { name: 'two' }]; + const paramLength = params.length + 2; // json and custom label are always appended + + const aggType = new AggType({ + name: 'bucketeer', + title: 'title', + params, + }); + + expect(Array.isArray(aggType.params)).toBeTruthy(); + expect(aggType.params.length).toBe(paramLength); + }); + }); + }); + + describe('getFormat', function() { + let aggConfig: AggConfig; + let field: any; + + beforeEach(() => { + aggConfig = ({ + getField: jest.fn(() => field), + } as unknown) as AggConfig; + }); + + it('returns the formatter for the aggConfig', () => { + const aggType = new AggType({ + name: 'name', + title: 'title', + }); + + field = { + format: 'format', + }; + + expect(aggType.getFormat(aggConfig)).toBe('format'); + }); + + it('returns default formatter', () => { + const aggType = new AggType({ + name: 'name', + title: 'title', + }); + + field = undefined; + + expect(aggType.getFormat(aggConfig)).toBe('default'); + }); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/agg_utils.ts b/src/legacy/ui/public/agg_types/index.test.ts similarity index 53% rename from src/legacy/ui/public/agg_types/agg_utils.ts rename to src/legacy/ui/public/agg_types/index.test.ts index d7f4ec961ded6..a867769a77fc1 100644 --- a/src/legacy/ui/public/agg_types/agg_utils.ts +++ b/src/legacy/ui/public/agg_types/index.test.ts @@ -17,23 +17,30 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -import { AggConfig } from './agg_config'; +import { aggTypes } from './index'; -function safeMakeLabel(agg: AggConfig) { - try { - return agg.makeLabel(); - } catch (e) { - return i18n.translate('common.ui.aggTypes.aggNotValidLabel', { - defaultMessage: '- agg not valid -', - }); - } -} +import { isBucketAggType } from './buckets/_bucket_agg_type'; +import { isMetricAggType } from './metrics/metric_agg_type'; + +const bucketAggs = aggTypes.buckets; +const metricAggs = aggTypes.metrics; -function isCompatibleAggregation(aggFilter: string[]) { - return (agg: AggConfig) => { - return !aggFilter.includes(`!${agg.type.name}`); - }; -} +jest.mock('ui/new_platform'); -export { safeMakeLabel, isCompatibleAggregation }; +describe('AggTypesComponent', () => { + describe('bucket aggs', () => { + it('all extend BucketAggType', () => { + bucketAggs.forEach(bucketAgg => { + expect(isBucketAggType(bucketAgg)).toBeTruthy(); + }); + }); + }); + + describe('metric aggs', () => { + it('all extend MetricAggType', () => { + metricAggs.forEach(metricAgg => { + expect(isMetricAggType(metricAgg)).toBeTruthy(); + }); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/__tests__/param_types/_field.js b/src/legacy/ui/public/agg_types/param_types/field.test.ts similarity index 51% rename from src/legacy/ui/public/agg_types/__tests__/param_types/_field.js rename to src/legacy/ui/public/agg_types/param_types/field.test.ts index 94a976f98e984..2434f95056b78 100644 --- a/src/legacy/ui/public/agg_types/__tests__/param_types/_field.js +++ b/src/legacy/ui/public/agg_types/param_types/field.test.ts @@ -17,56 +17,74 @@ * under the License. */ -import expect from '@kbn/expect'; -import { reject } from 'lodash'; -import ngMock from 'ng_mock'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { BaseParamType } from '../../param_types/base'; -import { FieldParamType } from '../../param_types/field'; +import { BaseParamType } from './base'; +import { FieldParamType } from './field'; +import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../../../../../plugins/data/common'; -describe('Field', function () { +jest.mock('ui/new_platform'); - let indexPattern; +describe('Field', () => { + const indexPattern = { + id: '1234', + title: 'logstash-*', + fields: [ + { + name: 'field1', + type: KBN_FIELD_TYPES.NUMBER, + esTypes: [ES_FIELD_TYPES.INTEGER], + aggregatable: true, + filterable: true, + searchable: true, + }, + { + name: 'field2', + type: KBN_FIELD_TYPES.STRING, + esTypes: [ES_FIELD_TYPES.TEXT], + aggregatable: false, + filterable: false, + searchable: true, + }, + ], + } as any; - beforeEach(ngMock.module('kibana')); - // fetch out deps - beforeEach(ngMock.inject(function (Private) { - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - describe('constructor', function () { - it('it is an instance of BaseParamType', function () { + describe('constructor', () => { + it('it is an instance of BaseParamType', () => { const aggParam = new FieldParamType({ - name: 'field', type: 'field' + name: 'field', + type: 'field', }); - expect(aggParam).to.be.a(BaseParamType); + expect(aggParam instanceof BaseParamType).toBeTruthy(); }); }); - describe('getAvailableFields', function () { - it('should return only aggregatable fields by default', function () { + describe('getAvailableFields', () => { + it('should return only aggregatable fields by default', () => { const aggParam = new FieldParamType({ - name: 'field', type: 'field' + name: 'field', + type: 'field', }); const fields = aggParam.getAvailableFields(indexPattern.fields); - expect(fields).to.not.have.length(0); + + expect(fields.length).toBe(1); + for (const field of fields) { - expect(field.aggregatable).to.be(true); + expect(field.aggregatable).toBe(true); } }); - it('should return all fields if onlyAggregatable is false', function () { + it('should return all fields if onlyAggregatable is false', () => { const aggParam = new FieldParamType({ - name: 'field', type: 'field' + name: 'field', + type: 'field', }); aggParam.onlyAggregatable = false; const fields = aggParam.getAvailableFields(indexPattern.fields); - const nonAggregatableFields = reject(fields, 'aggregatable'); - expect(nonAggregatableFields).to.not.be.empty(); + + expect(fields.length).toBe(2); }); }); }); diff --git a/src/legacy/ui/public/agg_types/param_types/json.test.ts b/src/legacy/ui/public/agg_types/param_types/json.test.ts new file mode 100644 index 0000000000000..fb31385505a76 --- /dev/null +++ b/src/legacy/ui/public/agg_types/param_types/json.test.ts @@ -0,0 +1,119 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { BaseParamType } from './base'; +import { JsonParamType } from './json'; +import { AggConfig } from 'ui/agg_types'; + +jest.mock('ui/new_platform'); + +describe('JSON', function() { + const paramName = 'json_test'; + let aggConfig: AggConfig; + let output: Record; + + function initAggParam(config: Record = {}) { + return new JsonParamType({ + ...config, + type: 'json', + name: paramName, + }); + } + + beforeEach(function() { + aggConfig = { params: {} } as AggConfig; + output = { params: {} }; + }); + + describe('constructor', () => { + it('it is an instance of BaseParamType', () => { + const aggParam = initAggParam(); + + expect(aggParam instanceof BaseParamType).toBeTruthy(); + }); + }); + + describe('write', () => { + it('should do nothing when param is not defined', () => { + const aggParam = initAggParam(); + + expect(aggConfig.params).not.toHaveProperty(paramName); + + aggParam.write(aggConfig, output); + expect(output).not.toHaveProperty(paramName); + }); + + it('should not append param when invalid JSON', () => { + const aggParam = initAggParam(); + + aggConfig.params[paramName] = 'i am not json'; + + aggParam.write(aggConfig, output); + expect(aggConfig.params).toHaveProperty(paramName); + expect(output).not.toHaveProperty(paramName); + }); + + it('should append param when valid JSON', () => { + const aggParam = initAggParam(); + const jsonData = JSON.stringify({ + new_param: 'should exist in output', + }); + + output.params.existing = 'true'; + aggConfig.params[paramName] = jsonData; + + aggParam.write(aggConfig, output); + expect(aggConfig.params).toHaveProperty(paramName); + + expect(output.params).toEqual({ + existing: 'true', + new_param: 'should exist in output', + }); + }); + + it('should not overwrite existing params', () => { + const aggParam = initAggParam(); + const jsonData = JSON.stringify({ + new_param: 'should exist in output', + existing: 'should be used', + }); + + output.params.existing = 'true'; + aggConfig.params[paramName] = jsonData; + + aggParam.write(aggConfig, output); + expect(output.params).toEqual(JSON.parse(jsonData)); + }); + + it('should drop nulled params', () => { + const aggParam = initAggParam(); + const jsonData = JSON.stringify({ + new_param: 'should exist in output', + field: null, + }); + + output.params.field = 'extensions'; + aggConfig.params[paramName] = jsonData; + + aggParam.write(aggConfig, output); + expect(Object.keys(output.params)).toContain('new_param'); + expect(Object.keys(output.params)).not.toContain('field'); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/__tests__/param_types/_optioned.js b/src/legacy/ui/public/agg_types/param_types/optioned.test.ts similarity index 71% rename from src/legacy/ui/public/agg_types/__tests__/param_types/_optioned.js rename to src/legacy/ui/public/agg_types/param_types/optioned.test.ts index 4e66f6cfbd41b..6b58d81914097 100644 --- a/src/legacy/ui/public/agg_types/__tests__/param_types/_optioned.js +++ b/src/legacy/ui/public/agg_types/param_types/optioned.test.ts @@ -17,20 +17,20 @@ * under the License. */ -import expect from '@kbn/expect'; -import { BaseParamType } from '../../param_types/base'; -import { OptionedParamType } from '../../param_types/optioned'; +import { BaseParamType } from './base'; +import { OptionedParamType } from './optioned'; -describe('Optioned', function () { +jest.mock('ui/new_platform'); - describe('constructor', function () { - it('it is an instance of BaseParamType', function () { +describe('Optioned', () => { + describe('constructor', () => { + it('it is an instance of BaseParamType', () => { const aggParam = new OptionedParamType({ name: 'some_param', - type: 'optioned' + type: 'optioned', }); - expect(aggParam).to.be.a(BaseParamType); + expect(aggParam instanceof BaseParamType).toBeTruthy(); }); }); }); diff --git a/src/legacy/ui/public/agg_types/param_types/string.test.ts b/src/legacy/ui/public/agg_types/param_types/string.test.ts new file mode 100644 index 0000000000000..3d496ecf898e4 --- /dev/null +++ b/src/legacy/ui/public/agg_types/param_types/string.test.ts @@ -0,0 +1,81 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { BaseParamType } from './base'; +import { StringParamType } from './string'; +import { AggConfig } from 'ui/agg_types'; + +jest.mock('ui/new_platform'); + +describe('String', function() { + let paramName = 'json_test'; + let aggConfig: AggConfig; + let output: Record; + + function initAggParam(config: Record = {}) { + return new StringParamType({ + ...config, + type: 'string', + name: paramName, + }); + } + + beforeEach(() => { + aggConfig = { params: {} } as AggConfig; + output = { params: {} }; + }); + + describe('constructor', () => { + it('it is an instance of BaseParamType', () => { + const aggParam = initAggParam(); + + expect(aggParam instanceof BaseParamType).toBeTruthy(); + }); + }); + + describe('write', () => { + it('should append param by name', () => { + const params = { + [paramName]: 'some input', + }; + + const aggParam = initAggParam({ name: paramName }); + + aggConfig.params = params; + aggParam.write(aggConfig, output); + + expect(output.params).toEqual(params); + }); + + it('should not be in output with empty input', () => { + paramName = 'more_testing'; + + const params = { + [paramName]: '', + }; + + const aggParam = initAggParam({ name: paramName }); + + aggConfig.params = params; + aggParam.write(aggConfig, output); + + expect(output.params).toEqual({}); + }); + }); +}); diff --git a/src/legacy/ui/public/agg_types/__tests__/utils.test.tsx b/src/legacy/ui/public/agg_types/utils.test.tsx similarity index 81% rename from src/legacy/ui/public/agg_types/__tests__/utils.test.tsx rename to src/legacy/ui/public/agg_types/utils.test.tsx index 655b606bb46b0..a3c7f24f3927d 100644 --- a/src/legacy/ui/public/agg_types/__tests__/utils.test.tsx +++ b/src/legacy/ui/public/agg_types/utils.test.tsx @@ -17,7 +17,7 @@ * under the License. */ -import { isValidJson } from '../utils'; +import { isValidJson } from './utils'; jest.mock('ui/new_platform'); @@ -29,23 +29,23 @@ const input = { describe('AggType utils', () => { describe('isValidJson', () => { it('should return true when empty string', () => { - expect(isValidJson('')).toBe(true); + expect(isValidJson('')).toBeTruthy(); }); it('should return true when undefine', () => { - expect(isValidJson(undefined as any)).toBe(true); + expect(isValidJson(undefined as any)).toBeTruthy(); }); it('should return false when invalid string', () => { - expect(isValidJson(input.invalid)).toBe(false); + expect(isValidJson(input.invalid)).toBeFalsy(); }); it('should return true when valid string', () => { - expect(isValidJson(input.valid)).toBe(true); + expect(isValidJson(input.valid)).toBeTruthy(); }); it('should return false if a number', () => { - expect(isValidJson('0')).toBe(false); + expect(isValidJson('0')).toBeFalsy(); }); }); }); diff --git a/src/legacy/ui/public/registry/doc_views_helpers.tsx b/src/legacy/ui/public/registry/doc_views_helpers.tsx index 02f276c48124b..1ff00713b10ef 100644 --- a/src/legacy/ui/public/registry/doc_views_helpers.tsx +++ b/src/legacy/ui/public/registry/doc_views_helpers.tsx @@ -26,7 +26,7 @@ import { AngularController, AngularDirective, } from './doc_views_types'; -import { DocViewerError } from '../../../core_plugins/kibana/public/doc_viewer/doc_viewer_render_error'; +import { DocViewerError } from '../../../core_plugins/kibana/public/discover/doc_viewer/doc_viewer_render_error'; /** * Compiles and injects the give angular template into the given dom node diff --git a/src/legacy/ui/public/saved_objects/saved_object.js b/src/legacy/ui/public/saved_objects/saved_object.js index cf83e6ef057f8..04505552dfc3f 100644 --- a/src/legacy/ui/public/saved_objects/saved_object.js +++ b/src/legacy/ui/public/saved_objects/saved_object.js @@ -32,8 +32,7 @@ import angular from 'angular'; import _ from 'lodash'; -import { InvalidJSONProperty, SavedObjectNotFound } from '../../../../plugins/kibana_utils/public'; -import { expandShorthand } from '../utils/mapping_setup'; +import { InvalidJSONProperty, SavedObjectNotFound, expandShorthand } from '../../../../plugins/kibana_utils/public'; import { SearchSourceProvider } from '../courier/search_source'; import { findObjectByTitle } from './find_object_by_title'; diff --git a/src/legacy/ui/public/utils/__tests__/mapping_setup.js b/src/legacy/ui/public/utils/__tests__/mapping_setup.js deleted file mode 100644 index acd8f82df280c..0000000000000 --- a/src/legacy/ui/public/utils/__tests__/mapping_setup.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; -import { expandShorthand } from '../mapping_setup'; - -describe('ui/utils/mapping_setup', function () { - beforeEach(ngMock.module('kibana')); - - describe('#expandShorthand()', function () { - it('allows shortcuts for field types by just setting the value to the type name', function () { - const mapping = expandShorthand({ foo: 'boolean' }); - expect(mapping.foo.type).to.be('boolean'); - }); - - it('can set type as an option', function () { - const mapping = expandShorthand({ foo: { type: 'integer' } }); - expect(mapping.foo.type).to.be('integer'); - }); - - describe('when type is json', function () { - it('returned object is type text', function () { - const mapping = expandShorthand({ foo: 'json' }); - expect(mapping.foo.type).to.be('text'); - }); - - it('returned object has _serialize function', function () { - const mapping = expandShorthand({ foo: 'json' }); - expect(_.isFunction(mapping.foo._serialize)).to.be(true); - }); - - it('returned object has _deserialize function', function () { - const mapping = expandShorthand({ foo: 'json' }); - expect(_.isFunction(mapping.foo._serialize)).to.be(true); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx index b87eb3f5fb303..5dfb14156deee 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx @@ -26,7 +26,6 @@ import { act } from 'react-dom/test-utils'; import { DefaultEditorAggParams } from './agg_params'; import { IndexPattern } from 'ui/index_patterns'; import { AggType } from 'ui/agg_types'; -import { Schema } from 'ui/vis/editors/default/schemas'; jest.mock('./agg_params', () => ({ DefaultEditorAggParams: () => null, @@ -158,7 +157,7 @@ describe('DefaultEditorAgg component', () => { it('should add schema component', () => { defaultProps.agg.schema = { editorComponent: () =>
, - } as Schema; + } as any; const comp = mount(); expect(comp.find('.schemaComponent').exists()).toBeTruthy(); diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts b/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts index 1a057f0c21702..232eaba76f3a1 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts +++ b/src/legacy/ui/public/vis/editors/default/components/agg_common_props.ts @@ -22,10 +22,13 @@ import { AggConfig, VisState, VisParams } from '../../..'; import { AggParams } from '../agg_params'; import { AggGroupNames } from '../agg_groups'; -export type OnAggParamsChange = ( - params: AggParams | VisParams, - paramName: string, - value: unknown +export type OnAggParamsChange = < + Params extends AggParams | VisParams, + ParamName extends keyof Params +>( + params: Params, + paramName: ParamName, + value: Params[ParamName] ) => void; export interface DefaultEditorAggCommonProps { diff --git a/src/legacy/ui/public/vis/editors/default/controls/__snapshots__/extended_bounds.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/controls/__snapshots__/extended_bounds.test.tsx.snap new file mode 100644 index 0000000000000..37783dc01492d --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/__snapshots__/extended_bounds.test.tsx.snap @@ -0,0 +1,44 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ExtendedBoundsParamEditor should be rendered with default set of props 1`] = ` + + + + + + + + + + +`; diff --git a/src/legacy/ui/public/vis/editors/default/controls/__snapshots__/metric_agg.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/controls/__snapshots__/metric_agg.test.tsx.snap new file mode 100644 index 0000000000000..a176295260c44 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/__snapshots__/metric_agg.test.tsx.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MetricAggParamEditor should be rendered with default set of props 1`] = ` + + + +`; diff --git a/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx b/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx index 91294dffe71de..f215cf755886d 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/agg_control_props.tsx @@ -19,9 +19,10 @@ import { VisParams } from '../../..'; import { AggParams } from '../agg_params'; +import { OnAggParamsChange } from '../components/agg_common_props'; -export interface AggControlProps { +export interface AggControlProps { aggParams: AggParams; editorStateParams: VisParams; - setValue(params: AggParams, paramName: string, value: T): void; + setValue: OnAggParamsChange; } diff --git a/src/legacy/ui/public/vis/editors/default/controls/agg_utils.test.tsx b/src/legacy/ui/public/vis/editors/default/controls/agg_utils.test.tsx new file mode 100644 index 0000000000000..9a96cc2221bd4 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/agg_utils.test.tsx @@ -0,0 +1,230 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { FunctionComponent } from 'react'; +import { mount, ReactWrapper } from 'enzyme'; + +import { AggConfig } from 'ui/agg_types'; +import { + safeMakeLabel, + useAvailableOptions, + useFallbackMetric, + useValidation, + CUSTOM_METRIC, +} from './agg_utils'; + +type Callback = () => void; + +let testComp: ReactWrapper; + +const TestHook: FunctionComponent<{ callback: Callback }> = ({ callback }) => { + callback(); + return null; +}; + +const testHook = (callback: Callback) => { + testComp = mount(); +}; + +const metricAggs = [ + { + id: '2', + type: { name: 'count' }, + makeLabel() { + return 'count'; + }, + }, + { + id: '3', + type: { name: 'avg' }, + makeLabel() { + return 'avg'; + }, + }, +] as AggConfig[]; + +const incompatibleAggs = [ + { + id: '2', + type: { name: 'top_hits' }, + makeLabel() { + return 'top_hits'; + }, + }, + { + id: '3', + type: { name: 'percentiles' }, + makeLabel() { + return 'percentiles'; + }, + }, +] as AggConfig[]; +const aggFilter = ['!top_hits', '!percentiles']; + +describe('Aggregations utils', () => { + describe('useFallbackMetric', () => { + let setValue: jest.Mock; + beforeEach(() => { + setValue = jest.fn(); + }); + + describe('should not call setValue', () => { + test('if there are no metricAggs', () => { + testHook(() => { + useFallbackMetric(setValue, aggFilter); + }); + + expect(setValue).not.toBeCalled(); + }); + + test('if there is no value', () => { + testHook(() => { + useFallbackMetric(setValue, aggFilter, metricAggs); + }); + + expect(setValue).not.toBeCalled(); + }); + + test('if value is "custom" metric', () => { + testHook(() => { + useFallbackMetric(setValue, aggFilter, metricAggs, 'custom'); + }); + + expect(setValue).not.toBeCalled(); + }); + + test('if value is selected metric is still available', () => { + testHook(() => { + useFallbackMetric(setValue, aggFilter, metricAggs, '2'); + }); + + expect(setValue).not.toBeCalled(); + }); + }); + + describe('should set up a new value if selected metric was removed', () => { + test('called with undefined', () => { + testHook(() => { + useFallbackMetric(setValue, aggFilter, metricAggs, '7'); + }); + + expect(setValue).toBeCalledWith(undefined); + }); + + test('called with fallback value', () => { + testHook(() => { + useFallbackMetric(setValue, aggFilter, metricAggs, '7', '_key'); + }); + + expect(setValue).toBeCalledWith('_key'); + }); + }); + }); + + describe('useAvailableOptions', () => { + test('should create an array with the only custom metric', () => { + let options; + + testHook(() => { + options = useAvailableOptions(aggFilter); + }); + + expect(options).toEqual([CUSTOM_METRIC]); + }); + + test('should include default options', () => { + const DEFAULT_OPTIONS = [{ text: '', value: '', hidden: true }]; + let options; + + testHook(() => { + options = useAvailableOptions(aggFilter, [], DEFAULT_OPTIONS); + }); + + expect(options).toEqual([CUSTOM_METRIC, ...DEFAULT_OPTIONS]); + }); + + test('should create an array with enabled metrics in appropriate format', () => { + let options; + + testHook(() => { + options = useAvailableOptions(aggFilter, metricAggs); + }); + + expect(options).toEqual([ + { text: expect.any(String), value: '2', disabled: false }, + { text: expect.any(String), value: '3', disabled: false }, + CUSTOM_METRIC, + ]); + }); + + test('should create an array with disabled metrics in appropriate format', () => { + let options; + + testHook(() => { + options = useAvailableOptions(aggFilter, incompatibleAggs); + }); + + expect(options).toEqual([ + { text: expect.any(String), value: '2', disabled: true }, + { text: expect.any(String), value: '3', disabled: true }, + CUSTOM_METRIC, + ]); + }); + }); + + describe('useValidation', () => { + let setValidity: jest.Mock; + beforeEach(() => { + setValidity = jest.fn(); + }); + + test('should call setValidity', () => { + testHook(() => { + useValidation(setValidity, false); + }); + + expect(setValidity).toBeCalledWith(false); + }); + + test('should call setValidity with true on component unmount', () => { + testHook(() => { + useValidation(setValidity, false); + }); + + testComp.unmount(); + + expect(setValidity).lastCalledWith(true); + expect(setValidity).toBeCalledTimes(2); + }); + }); + + describe('safeMakeLabel', () => { + test('should make agg label', () => { + const label = safeMakeLabel(metricAggs[0]); + + expect(label).toBe('count'); + }); + + test('should not fail and return a safety string if makeLabel func is not exist', () => { + const label = safeMakeLabel({} as AggConfig); + + expect(label).toEqual(expect.any(String)); + }); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/controls/agg_utils.ts b/src/legacy/ui/public/vis/editors/default/controls/agg_utils.ts new file mode 100644 index 0000000000000..6491ef2e46054 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/agg_utils.ts @@ -0,0 +1,132 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useEffect, useCallback, useMemo } from 'react'; +import { i18n } from '@kbn/i18n'; +import { AggConfig } from 'ui/agg_types'; + +type AggFilter = string[]; + +const DEFAULT_METRIC = 'custom'; +const CUSTOM_METRIC = { + text: i18n.translate('common.ui.aggTypes.customMetricLabel', { + defaultMessage: 'Custom metric', + }), + value: DEFAULT_METRIC, +}; + +function useCompatibleAggCallback(aggFilter: AggFilter) { + return useCallback(isCompatibleAggregation(aggFilter), [aggFilter]); +} + +/** + * the effect is used to set up a default metric aggregation in case, + * when previously selected metric has been removed + */ +function useFallbackMetric( + setValue: (value?: string) => void, + aggFilter: AggFilter, + metricAggs?: AggConfig[], + value?: string, + fallbackValue?: string +) { + const isCompatibleAgg = useCompatibleAggCallback(aggFilter); + + useEffect(() => { + if (metricAggs && value && value !== DEFAULT_METRIC) { + // ensure that metric is set to a valid agg + const respAgg = metricAggs + .filter(isCompatibleAgg) + .find(aggregation => aggregation.id === value); + + if (!respAgg) { + setValue(fallbackValue); + } + } + }, [setValue, isCompatibleAgg, metricAggs, value, fallbackValue]); +} + +/** + * this makes an array of available options in appropriate format for EuiSelect, + * calculates if an option is disabled + */ +function useAvailableOptions( + aggFilter: AggFilter, + metricAggs: AggConfig[] = [], + defaultOptions: Array<{ text: string; value: string }> = [] +) { + const isCompatibleAgg = useCompatibleAggCallback(aggFilter); + + const options = useMemo( + () => [ + ...metricAggs.map(respAgg => ({ + text: i18n.translate('common.ui.aggTypes.definiteMetricLabel', { + defaultMessage: 'Metric: {metric}', + values: { + metric: safeMakeLabel(respAgg), + }, + }), + value: respAgg.id, + disabled: !isCompatibleAgg(respAgg), + })), + CUSTOM_METRIC, + ...defaultOptions, + ], + [metricAggs, defaultOptions, isCompatibleAgg] + ); + + return options; +} + +/** + * the effect is used to set up the editor form validity + * and reset it if a param has been removed + */ +function useValidation(setValidity: (isValid: boolean) => void, isValid: boolean) { + useEffect(() => { + setValidity(isValid); + + return () => setValidity(true); + }, [isValid]); +} + +function safeMakeLabel(agg: AggConfig): string { + try { + return agg.makeLabel(); + } catch (e) { + return i18n.translate('common.ui.aggTypes.aggNotValidLabel', { + defaultMessage: '- agg not valid -', + }); + } +} + +function isCompatibleAggregation(aggFilter: string[]) { + return (agg: AggConfig) => { + return !aggFilter.includes(`!${agg.type.name}`); + }; +} + +export { + CUSTOM_METRIC, + safeMakeLabel, + isCompatibleAggregation, + useAvailableOptions, + useFallbackMetric, + useValidation, +}; diff --git a/src/legacy/ui/public/vis/editors/default/controls/extended_bounds.test.tsx b/src/legacy/ui/public/vis/editors/default/controls/extended_bounds.test.tsx new file mode 100644 index 0000000000000..9ceab07f76158 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/extended_bounds.test.tsx @@ -0,0 +1,139 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { mount, shallow } from 'enzyme'; + +import { ExtendedBoundsParamEditor, Bounds } from './extended_bounds'; +import { AggParamEditorProps } from '..'; + +describe('ExtendedBoundsParamEditor', () => { + let defaultProps: Partial>; + + beforeEach(() => { + defaultProps = { + setValue: jest.fn(), + setValidity: jest.fn(), + }; + }); + + test('should be rendered with default set of props', () => { + const comp = shallow( + )} /> + ); + + expect(comp).toMatchSnapshot(); + }); + + describe('validation', () => { + test('should not show validation if "showValidation" param is falsy', () => { + const comp = mount( + )} + showValidation={false} + /> + ); + + expect(comp.children().props().isInvalid).toBeFalsy(); + expect(defaultProps.setValidity).toBeCalledWith(false); + }); + + test('should change its validity due to passed props and show error if it is invalid', () => { + const comp = mount( + )} + showValidation={true} + /> + ); + + expect(comp.children().props().error).toEqual(expect.any(String)); + expect(comp.children().props().isInvalid).toBeTruthy(); + expect(defaultProps.setValidity).toBeCalledWith(false); + + // set valid bounds + comp.setProps({ value: { min: 0, max: 10 } }); + + expect(comp.props().error).toBeUndefined(); + expect(comp.children().props().isInvalid).toBeFalsy(); + expect(defaultProps.setValidity).toBeCalledWith(true); + + // set invalid bounds - min > max + comp.setProps({ value: { min: 10, max: 2 } }); + + expect(comp.children().props().error).toEqual(expect.any(String)); + expect(comp.children().props().isInvalid).toBeTruthy(); + expect(defaultProps.setValidity).toBeCalledWith(false); + }); + + test('should set valid state after removing from the DOM tree', () => { + const comp = mount( + )} + showValidation={true} + /> + ); + + expect(defaultProps.setValidity).toBeCalledWith(false); + + comp.unmount(); + + expect(defaultProps.setValidity).lastCalledWith(true); + expect(defaultProps.setValidity).toBeCalledTimes(2); + }); + }); + + describe('handle changes', () => { + test('should set numeric "min" or an empty string on change event', () => { + const comp = mount( + )} + showValidation={true} + /> + ); + + const minBound = comp.find('input').first(); + minBound.simulate('change', { target: { value: '2' } }); + + expect(defaultProps.setValue).lastCalledWith({ min: 2 }); + + minBound.simulate('change', { target: { value: '' } }); + + expect(defaultProps.setValue).lastCalledWith({ min: '' }); + }); + + test('should set numeric "max" or an empty string on change event', () => { + const comp = mount( + )} + value={{ min: 10, max: '' }} + showValidation={true} + /> + ); + + const maxBound = comp.find('input').last(); + maxBound.simulate('change', { target: { value: '30' } }); + + expect(defaultProps.setValue).toBeCalledWith({ min: 10, max: 30 }); + + maxBound.simulate('change', { target: { value: '' } }); + + expect(defaultProps.setValue).lastCalledWith({ min: 10, max: '' }); + }); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/controls/extended_bounds.tsx b/src/legacy/ui/public/vis/editors/default/controls/extended_bounds.tsx index dba8c2f871352..c933261cb45e6 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/extended_bounds.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/extended_bounds.tsx @@ -17,14 +17,15 @@ * under the License. */ -import React, { useEffect, ChangeEvent } from 'react'; +import React, { ChangeEvent } from 'react'; import { EuiFieldNumber, EuiFlexGroup, EuiFlexItem, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { isUndefined } from 'lodash'; +import { useValidation } from './agg_utils'; import { AggParamEditorProps } from '..'; -interface Bounds { +export interface Bounds { min: number | ''; max: number | ''; } @@ -61,11 +62,7 @@ function ExtendedBoundsParamEditor({ }); } - useEffect(() => { - setValidity(isValid); - - return () => setValidity(true); - }, [isValid]); + useValidation(setValidity, isValid); const handleChange = (ev: ChangeEvent, name: string) => { setValue({ diff --git a/src/legacy/ui/public/vis/editors/default/controls/metric_agg.test.tsx b/src/legacy/ui/public/vis/editors/default/controls/metric_agg.test.tsx new file mode 100644 index 0000000000000..24c506ca31738 --- /dev/null +++ b/src/legacy/ui/public/vis/editors/default/controls/metric_agg.test.tsx @@ -0,0 +1,148 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { mount, shallow } from 'enzyme'; + +import { AggConfig } from 'ui/agg_types'; +import { DEFAULT_OPTIONS, aggFilter, MetricAggParamEditor } from './metric_agg'; +import { AggParamEditorProps } from '..'; + +jest.mock('./agg_utils', () => ({ + useAvailableOptions: jest.fn((aggFilterArray, filteredMetrics, defaultOptions) => [ + ...filteredMetrics.map(({ id, type }: { id: string; type: { name: string } }) => ({ + text: type.name, + value: id, + })), + ...defaultOptions, + ]), + useFallbackMetric: jest.fn(), + useValidation: jest.fn(), +})); + +import { useAvailableOptions, useFallbackMetric, useValidation } from './agg_utils'; + +const agg = { + id: '1', + type: { name: 'cumulative_sum' }, + makeLabel() { + return 'cumulative_sum'; + }, +} as AggConfig; + +const metricAggs = [ + agg, + { + id: '2', + type: { name: 'count' }, + makeLabel() { + return 'count'; + }, + }, + { + id: '3', + type: { name: 'avg' }, + makeLabel() { + return 'avg'; + }, + }, + { + id: '4', + type: { name: 'max' }, + makeLabel() { + return 'max'; + }, + }, +] as AggConfig[]; + +describe('MetricAggParamEditor', () => { + let defaultProps: Partial>; + + beforeEach(() => { + defaultProps = { + agg, + showValidation: false, + setValue: jest.fn(), + setValidity: jest.fn(), + }; + }); + + test('should be rendered with default set of props', () => { + const comp = shallow( + )} /> + ); + + expect(comp).toMatchSnapshot(); + }); + + test('should call custom hooks', () => { + shallow( + )} value="custom" /> + ); + + expect(useFallbackMetric).toHaveBeenCalledWith(defaultProps.setValue, aggFilter, [], 'custom'); + expect(useValidation).toHaveBeenCalledWith(defaultProps.setValidity, true); + expect(useAvailableOptions).toHaveBeenCalledWith(aggFilter, [], DEFAULT_OPTIONS); + }); + + test('should filter self aggregation from available options', () => { + const comp = shallow( + )} + value="custom" + metricAggs={[agg]} + /> + ); + + expect(comp.find('EuiSelect').props()).toHaveProperty('options', [...DEFAULT_OPTIONS]); + expect(useFallbackMetric).toHaveBeenCalledWith( + defaultProps.setValue, + aggFilter, + [agg], + 'custom' + ); + }); + + test('should be valid/invalid if value is defined/undefined', () => { + const comp = mount( + )} value="custom" /> + ); + + expect(comp.children().props()).toHaveProperty('isInvalid', false); + expect(useValidation).lastCalledWith(defaultProps.setValidity, true); + + comp.setProps({ value: undefined, showValidation: true }); + + expect(comp.children().props()).toHaveProperty('isInvalid', true); + expect(useValidation).lastCalledWith(defaultProps.setValidity, false); + }); + + test('should set new value into the model on change', () => { + const comp = mount( + )} + value="custom" + metricAggs={metricAggs} + /> + ); + + comp.find('select').simulate('change', { target: { value: '2' } }); + expect(defaultProps.setValue).lastCalledWith('2'); + }); +}); diff --git a/src/legacy/ui/public/vis/editors/default/controls/metric_agg.tsx b/src/legacy/ui/public/vis/editors/default/controls/metric_agg.tsx index 82cf9af097805..9d25a02606ed2 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/metric_agg.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/metric_agg.tsx @@ -17,15 +17,15 @@ * under the License. */ -import React, { useEffect } from 'react'; +import React, { useMemo, useCallback } from 'react'; import { EuiFormRow, EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { safeMakeLabel, isCompatibleAggregation } from '../../../../agg_types/agg_utils'; +import { useAvailableOptions, useFallbackMetric, useValidation } from './agg_utils'; import { AggParamEditorProps } from '..'; const aggFilter = ['!top_hits', '!percentiles', '!percentile_ranks', '!median', '!std_dev']; -const isCompatibleAgg = isCompatibleAggregation(aggFilter); const EMPTY_VALUE = 'EMPTY_VALUE'; +const DEFAULT_OPTIONS = [{ text: '', value: EMPTY_VALUE, hidden: true }]; function MetricAggParamEditor({ agg, @@ -34,71 +34,32 @@ function MetricAggParamEditor({ setValue, setValidity, setTouched, - metricAggs, + metricAggs = [], }: AggParamEditorProps) { const label = i18n.translate('common.ui.aggTypes.metricLabel', { defaultMessage: 'Metric', }); const isValid = !!value; - useEffect(() => { - setValidity(isValid); - }, [isValid]); + useValidation(setValidity, isValid); + useFallbackMetric(setValue, aggFilter, metricAggs, value); - useEffect(() => { - if (metricAggs && value && value !== 'custom') { - // ensure that metricAgg is set to a valid agg - const respAgg = metricAggs - .filter(isCompatibleAgg) - .find(aggregation => aggregation.id === value); - - if (!respAgg) { - setValue(); - } - } - }, [metricAggs]); - - const options = metricAggs - ? metricAggs - .filter(respAgg => respAgg.type.name !== agg.type.name) - .map(respAgg => ({ - text: i18n.translate('common.ui.aggTypes.definiteMetricLabel', { - defaultMessage: 'Metric: {safeMakeLabel}', - values: { - safeMakeLabel: safeMakeLabel(respAgg), - }, - }), - value: respAgg.id, - disabled: !isCompatibleAgg(respAgg), - })) - : []; - - options.push({ - text: i18n.translate('common.ui.aggTypes.customMetricLabel', { - defaultMessage: 'Custom metric', - }), - value: 'custom', - disabled: false, - }); - - if (!value) { - options.unshift({ text: '', value: EMPTY_VALUE, disabled: false }); - } + const filteredMetrics = useMemo( + () => metricAggs.filter(respAgg => respAgg.type.name !== agg.type.name), + [metricAggs, agg.type.name] + ); + const options = useAvailableOptions(aggFilter, filteredMetrics, DEFAULT_OPTIONS); + const onChange = useCallback(ev => setValue(ev.target.value), [setValue]); return ( - + setValue(ev.target.value)} - fullWidth={true} - compressed - isInvalid={showValidation ? !isValid : false} + onChange={onChange} + isInvalid={showValidation && !isValid} onBlur={setTouched} data-test-subj={`visEditorSubAggMetric${agg.id}`} /> @@ -106,4 +67,4 @@ function MetricAggParamEditor({ ); } -export { MetricAggParamEditor }; +export { DEFAULT_OPTIONS, aggFilter, MetricAggParamEditor }; diff --git a/src/legacy/ui/public/vis/editors/default/controls/number_interval.tsx b/src/legacy/ui/public/vis/editors/default/controls/number_interval.tsx index 33f92c337648c..1084d6e8212e2 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/number_interval.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/number_interval.tsx @@ -18,7 +18,7 @@ */ import { get } from 'lodash'; -import React, { useEffect } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { EuiFieldNumber, EuiFormRow, EuiIconTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; @@ -62,17 +62,18 @@ function NumberIntervalParamEditor({ setValidity(isValid); }, [isValid]); - const onChange = (event: React.ChangeEvent) => { - const numberValue = parseFloat(event.target.value); - setValue(isNaN(numberValue) ? undefined : numberValue); - }; + const onChange = useCallback( + ({ target }: React.ChangeEvent) => + setValue(isNaN(target.valueAsNumber) ? undefined : target.valueAsNumber), + [setValue] + ); return ( { - setValidity(isValid); - }, [isValid]); + useValidation(setValidity, isValid); useEffect(() => { // setup the initial value of orderBy if (!value) { - let respAgg = { id: '_key' }; + let respAgg = { id: DEFAULT_VALUE }; if (metricAggs) { respAgg = metricAggs.filter(isCompatibleAgg)[0] || respAgg; @@ -70,61 +82,19 @@ function OrderByParamEditor({ } }, []); - useEffect(() => { - if (metricAggs && value && value !== 'custom') { - // ensure that orderBy is set to a valid agg - const respAgg = metricAggs - .filter(isCompatibleAgg) - .find(aggregation => aggregation.id === value); - - if (!respAgg) { - setValue('_key'); - } - } - }, [metricAggs]); - - const defaultOptions = [ - { - text: i18n.translate('common.ui.aggTypes.orderAgg.customMetricLabel', { - defaultMessage: 'Custom metric', - }), - value: 'custom', - }, - { - text: i18n.translate('common.ui.aggTypes.orderAgg.alphabeticalLabel', { - defaultMessage: 'Alphabetical', - }), - value: '_key', - }, - ]; + useFallbackMetric(setValue, aggFilter, metricAggs, value, DEFAULT_VALUE); - const options = metricAggs - ? metricAggs.map(respAgg => ({ - text: i18n.translate('common.ui.aggTypes.orderAgg.metricLabel', { - defaultMessage: 'Metric: {metric}', - values: { - metric: safeMakeLabel(respAgg), - }, - }), - value: respAgg.id, - disabled: !isCompatibleAgg(respAgg), - })) - : []; + const options = useAvailableOptions(aggFilter, metricAggs, DEFAULT_OPTIONS); return ( - + setValue(ev.target.value)} fullWidth={true} compressed - isInvalid={showValidation ? !isValid : false} + isInvalid={showValidation && !isValid} onBlur={setTouched} data-test-subj={`visEditorOrderBy${agg.id}`} /> diff --git a/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx b/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx index 595ab6e0faf46..d9e0abc0cce59 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/radius_ratio_option.tsx @@ -26,7 +26,7 @@ import { AggControlProps } from './agg_control_props'; const DEFAULT_VALUE = 50; const PARAM_NAME = 'radiusRatio'; -function RadiusRatioOptionControl({ editorStateParams, setValue }: AggControlProps) { +function RadiusRatioOptionControl({ editorStateParams, setValue }: AggControlProps) { const label = ( <> ) { +function RowsOrColumnsControl({ aggParams, setValue }: AggControlProps) { const idSelected = `visEditorSplitBy__${aggParams.row}`; const options = [ { diff --git a/src/legacy/ui/public/vis/editors/default/schemas.d.ts b/src/legacy/ui/public/vis/editors/default/schemas.d.ts index 905a7e8bc11b6..236421f30fb09 100644 --- a/src/legacy/ui/public/vis/editors/default/schemas.d.ts +++ b/src/legacy/ui/public/vis/editors/default/schemas.d.ts @@ -19,6 +19,7 @@ import { AggParam } from '../../../agg_types'; import { AggGroupNames } from './agg_groups'; +import { AggControlProps } from './controls/agg_control_props'; export interface Schema { aggFilter: string | string[]; @@ -32,5 +33,5 @@ export interface Schema { defaults: unknown; hideCustomLabel?: boolean; mustBeFirst?: boolean; - editorComponent?: any; + editorComponent?: React.ComponentType; } diff --git a/src/legacy/ui/public/vis/editors/default/vis_options.js b/src/legacy/ui/public/vis/editors/default/vis_options.js index 0562a3ed0ca29..6425a03ed9a74 100644 --- a/src/legacy/ui/public/vis/editors/default/vis_options.js +++ b/src/legacy/ui/public/vis/editors/default/vis_options.js @@ -20,7 +20,7 @@ import { wrapInI18nContext } from 'ui/i18n'; import { uiModules } from '../../../modules'; import { VisOptionsReactWrapper } from './vis_options_react_wrapper'; -import { safeMakeLabel } from 'ui/agg_types/agg_utils'; +import { safeMakeLabel } from './controls/agg_utils'; /** * This directive sort of "transcludes" in whatever template you pass in via the `editor` attribute. diff --git a/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts b/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts index f43c6436d1c33..9f553b37935d7 100644 --- a/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts +++ b/src/legacy/ui/ui_settings/integration_tests/ui_settings_mixin.test.ts @@ -20,9 +20,7 @@ import sinon from 'sinon'; import expect from '@kbn/expect'; -// @ts-ignore -import { Config } from '../../../server/config'; - +import { SavedObjectsClientMock } from '../../../../core/server/mocks'; import * as uiSettingsServiceFactoryNS from '../ui_settings_service_factory'; import * as getUiSettingsServiceForRequestNS from '../ui_settings_service_for_request'; // @ts-ignore @@ -33,18 +31,16 @@ interface Decorators { request: { [name: string]: any }; } +const uiSettingDefaults = { + application: { + defaultProperty1: 'value1', + }, +}; + describe('uiSettingsMixin()', () => { const sandbox = sinon.createSandbox(); function setup() { - const config = Config.withDefaultSchema({ - uiSettings: { - overrides: { - foo: 'bar', - }, - }, - }); - // maps of decorations passed to `server.decorate()` const decorations: Decorators = { server: {}, @@ -55,7 +51,6 @@ describe('uiSettingsMixin()', () => { const server = { log: sinon.stub(), route: sinon.stub(), - config: () => config, addMemoizedFactoryToRequest(name: string, factory: (...args: any[]) => any) { this.decorate('request', name, function(this: typeof server) { return factory(this); @@ -73,12 +68,18 @@ describe('uiSettingsMixin()', () => { const kbnServer = { server, - config, - uiExports: { addConsumer: sinon.stub() }, + uiExports: { uiSettingDefaults }, ready: sinon.stub().returns(readyPromise), + newPlatform: { + __internals: { + uiSettings: { + setDefaults: sinon.stub(), + }, + }, + }, }; - uiSettingsMixin(kbnServer, server, config); + uiSettingsMixin(kbnServer, server); return { kbnServer, @@ -90,6 +91,15 @@ describe('uiSettingsMixin()', () => { afterEach(() => sandbox.restore()); + it('passes uiSettingsDefaults to the new platform', () => { + const { kbnServer } = setup(); + sinon.assert.calledOnce(kbnServer.newPlatform.__internals.uiSettings.setDefaults); + sinon.assert.calledWithExactly( + kbnServer.newPlatform.__internals.uiSettings.setDefaults, + uiSettingDefaults + ); + }); + describe('server.uiSettingsServiceFactory()', () => { it('decorates server with "uiSettingsServiceFactory"', () => { const { decorations } = setup(); @@ -116,18 +126,16 @@ describe('uiSettingsMixin()', () => { uiSettingsServiceFactoryNS, 'uiSettingsServiceFactory' ); + sinon.assert.notCalled(uiSettingsServiceFactoryStub); + + const savedObjectsClient = SavedObjectsClientMock.create(); decorations.server.uiSettingsServiceFactory({ - foo: 'bar', + savedObjectsClient, }); sinon.assert.calledOnce(uiSettingsServiceFactoryStub); sinon.assert.calledWithExactly(uiSettingsServiceFactoryStub, server as any, { - // @ts-ignore foo doesn't exist on Hapi.Server - foo: 'bar', - overrides: { - foo: 'bar', - }, - getDefaults: sinon.match.func, + savedObjectsClient, }); }); }); @@ -161,12 +169,7 @@ describe('uiSettingsMixin()', () => { sinon.assert.notCalled(getUiSettingsServiceForRequestStub); const request = {}; decorations.request.getUiSettingsService.call(request); - sinon.assert.calledWith(getUiSettingsServiceForRequestStub, server as any, request as any, { - overrides: { - foo: 'bar', - }, - getDefaults: sinon.match.func, - }); + sinon.assert.calledWith(getUiSettingsServiceForRequestStub, server as any, request as any); }); }); diff --git a/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts b/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts index b076a2a86e166..ae0ef1c91411e 100644 --- a/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts +++ b/src/legacy/ui/ui_settings/routes/integration_tests/lib/servers.ts @@ -18,12 +18,11 @@ */ import { UnwrapPromise } from '@kbn/utility-types'; -import { SavedObjectsClientContract } from 'src/core/server'; +import { SavedObjectsClientContract, IUiSettingsClient } from 'src/core/server'; import KbnServer from '../../../../../server/kbn_server'; import { createTestServers } from '../../../../../../test_utils/kbn_server'; import { CallCluster } from '../../../../../../legacy/core_plugins/elasticsearch'; -import { IUiSettingsClient } from '../../../ui_settings_service'; let kbnServer: KbnServer; let servers: ReturnType; diff --git a/src/legacy/ui/ui_settings/ui_settings_mixin.js b/src/legacy/ui/ui_settings/ui_settings_mixin.js index 3683ba5dd265e..8c7ef25c6f8d7 100644 --- a/src/legacy/ui/ui_settings/ui_settings_mixin.js +++ b/src/legacy/ui/ui_settings/ui_settings_mixin.js @@ -42,22 +42,15 @@ export function uiSettingsMixin(kbnServer, server) { acc[currentKey] = updatedDefaultSetting; return acc; }, {}); - const getDefaults = () => mergedUiSettingDefaults; - const overrides = kbnServer.config.get('uiSettings.overrides'); + + kbnServer.newPlatform.__internals.uiSettings.setDefaults(mergedUiSettingDefaults); server.decorate('server', 'uiSettingsServiceFactory', (options = {}) => { - return uiSettingsServiceFactory(server, { - getDefaults, - overrides, - ...options - }); + return uiSettingsServiceFactory(server, options); }); server.addMemoizedFactoryToRequest('getUiSettingsService', request => { - return getUiSettingsServiceForRequest(server, request, { - getDefaults, - overrides, - }); + return getUiSettingsServiceForRequest(server, request); }); server.decorate('server', 'uiSettings', () => { diff --git a/src/legacy/ui/ui_settings/ui_settings_service_factory.ts b/src/legacy/ui/ui_settings/ui_settings_service_factory.ts index 9e1384494161c..ab4eb75e4b703 100644 --- a/src/legacy/ui/ui_settings/ui_settings_service_factory.ts +++ b/src/legacy/ui/ui_settings/ui_settings_service_factory.ts @@ -17,18 +17,13 @@ * under the License. */ import { Legacy } from 'kibana'; -import { - IUiSettingsClient, - UiSettingsService, - UiSettingsServiceOptions, -} from './ui_settings_service'; +import { IUiSettingsClient, SavedObjectsClientContract } from 'src/core/server'; -export type UiSettingsServiceFactoryOptions = Pick< - UiSettingsServiceOptions, - 'savedObjectsClient' | 'getDefaults' | 'overrides' ->; +export interface UiSettingsServiceFactoryOptions { + savedObjectsClient: SavedObjectsClientContract; +} /** - * Create an instance of UiSettingsService that will use the + * Create an instance of UiSettingsClient that will use the * passed `savedObjectsClient` to communicate with elasticsearch * * @return {IUiSettingsClient} @@ -37,17 +32,5 @@ export function uiSettingsServiceFactory( server: Legacy.Server, options: UiSettingsServiceFactoryOptions ): IUiSettingsClient { - const config = server.config(); - - const { savedObjectsClient, getDefaults, overrides } = options; - - return new UiSettingsService({ - type: 'config', - id: config.get('pkg.version'), - buildNum: config.get('pkg.buildNum'), - savedObjectsClient, - getDefaults, - overrides, - logWithMetadata: server.logWithMetadata, - }); + return server.newPlatform.__internals.uiSettings.asScopedToClient(options.savedObjectsClient); } diff --git a/src/legacy/ui/ui_settings/ui_settings_service_for_request.ts b/src/legacy/ui/ui_settings/ui_settings_service_for_request.ts index e265ad5f1e115..057fc64c9ebd7 100644 --- a/src/legacy/ui/ui_settings/ui_settings_service_for_request.ts +++ b/src/legacy/ui/ui_settings/ui_settings_service_for_request.ts @@ -18,10 +18,9 @@ */ import { Legacy } from 'kibana'; +import { IUiSettingsClient } from 'src/core/server'; import { uiSettingsServiceFactory } from './ui_settings_service_factory'; -import { IUiSettingsClient, UiSettingsServiceOptions } from './ui_settings_service'; -type Options = Pick; /** * Get/create an instance of UiSettingsService bound to a specific request. * Each call is cached (keyed on the request object itself) and subsequent @@ -36,16 +35,8 @@ type Options = Pick; */ export function getUiSettingsServiceForRequest( server: Legacy.Server, - request: Legacy.Request, - options: Options + request: Legacy.Request ): IUiSettingsClient { - const { getDefaults, overrides } = options; - - const uiSettingsService = uiSettingsServiceFactory(server, { - getDefaults, - overrides, - savedObjectsClient: request.getSavedObjectsClient(), - }); - - return uiSettingsService; + const savedObjectsClient = request.getSavedObjectsClient(); + return uiSettingsServiceFactory(server, { savedObjectsClient }); } diff --git a/src/legacy/ui/public/agg_types/__tests__/param_types/index.js b/src/plugins/kibana_utils/public/field_mapping/index.ts similarity index 86% rename from src/legacy/ui/public/agg_types/__tests__/param_types/index.js rename to src/plugins/kibana_utils/public/field_mapping/index.ts index 507df89960c6f..060d5a95790f7 100644 --- a/src/legacy/ui/public/agg_types/__tests__/param_types/index.js +++ b/src/plugins/kibana_utils/public/field_mapping/index.ts @@ -17,9 +17,5 @@ * under the License. */ -import './_field'; -import './_optioned'; -import './_string'; -import './_json'; -describe('ParamTypes', function () { -}); +export { FieldMappingSpec, MappingObject } from './types'; +export { expandShorthand } from './mapping_setup'; diff --git a/src/plugins/kibana_utils/public/field_mapping/mapping_setup.test.ts b/src/plugins/kibana_utils/public/field_mapping/mapping_setup.test.ts new file mode 100644 index 0000000000000..e57699e879a87 --- /dev/null +++ b/src/plugins/kibana_utils/public/field_mapping/mapping_setup.test.ts @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { expandShorthand } from './mapping_setup'; +import { ES_FIELD_TYPES } from '../../../data/common'; + +describe('mapping_setup', () => { + it('allows shortcuts for field types by just setting the value to the type name', () => { + const mapping = expandShorthand({ foo: ES_FIELD_TYPES.BOOLEAN }); + + expect(mapping.foo.type).toBe('boolean'); + }); + + it('can set type as an option', () => { + const mapping = expandShorthand({ foo: { type: ES_FIELD_TYPES.INTEGER } }); + + expect(mapping.foo.type).toBe('integer'); + }); + + describe('when type is json', () => { + it('returned object is type text', () => { + const mapping = expandShorthand({ foo: 'json' }); + + expect(mapping.foo.type).toBe('text'); + }); + + it('returned object has _serialize function', () => { + const mapping = expandShorthand({ foo: 'json' }); + + expect(mapping.foo._serialize).toBeInstanceOf(Function); + }); + + it('returned object has _deserialize function', () => { + const mapping = expandShorthand({ foo: 'json' }); + + expect(mapping.foo._serialize).toBeInstanceOf(Function); + }); + }); +}); diff --git a/src/legacy/ui/public/utils/mapping_setup.js b/src/plugins/kibana_utils/public/field_mapping/mapping_setup.ts similarity index 50% rename from src/legacy/ui/public/utils/mapping_setup.js rename to src/plugins/kibana_utils/public/field_mapping/mapping_setup.ts index 6154cc7eeb344..495338735337c 100644 --- a/src/legacy/ui/public/utils/mapping_setup.js +++ b/src/plugins/kibana_utils/public/field_mapping/mapping_setup.ts @@ -17,29 +17,28 @@ * under the License. */ -import { mapValues } from 'lodash'; +import { mapValues, isString } from 'lodash'; +import { ES_FIELD_TYPES } from '../../../../plugins/data/common'; +import { FieldMappingSpec, MappingObject } from './types'; -const json = { - _serialize: function (val) { - if (val != null) return JSON.stringify(val); +/** @private */ +type ShorthandFieldMapObject = FieldMappingSpec | ES_FIELD_TYPES | 'json'; + +const json: FieldMappingSpec = { + type: ES_FIELD_TYPES.TEXT, + _serialize(v) { + if (v) return JSON.stringify(v); + }, + _deserialize(v) { + if (v) return JSON.parse(v); }, - _deserialize: function (val) { - if (val != null) return JSON.parse(val); - } }; -export const expandShorthand = function (sh) { - return mapValues(sh || {}, function (val) { - // allow shortcuts for the field types, by just setting the value - // to the type name - if (typeof val === 'string') val = { type: val }; - - if (val.type === 'json') { - val.type = 'text'; - val._serialize = json._serialize; - val._deserialize = json._deserialize; - } +/** @public */ +export const expandShorthand = (sh: Record): MappingObject => { + return mapValues>(sh, (val: ShorthandFieldMapObject) => { + const fieldMap = isString(val) ? { type: val } : val; - return val; - }); + return fieldMap.type === 'json' ? json : fieldMap; + }) as MappingObject; }; diff --git a/test/plugin_functional/plugins/core_legacy_compat/index.ts b/src/plugins/kibana_utils/public/field_mapping/types.ts similarity index 66% rename from test/plugin_functional/plugins/core_legacy_compat/index.ts rename to src/plugins/kibana_utils/public/field_mapping/types.ts index dbec480816882..973a58d3baec4 100644 --- a/test/plugin_functional/plugins/core_legacy_compat/index.ts +++ b/src/plugins/kibana_utils/public/field_mapping/types.ts @@ -17,19 +17,14 @@ * under the License. */ -import { Server } from 'hapi'; +import { ES_FIELD_TYPES } from '../../../data/common'; -// eslint-disable-next-line import/no-default-export -export default function(kibana: any) { - return new kibana.Plugin({ - uiExports: { - app: { - title: 'Core Legacy Compat', - description: 'This is a sample plugin to test core to legacy compatibility', - main: 'plugins/core_legacy_compat/index', - }, - }, - - init(server: Server) {}, - }); +/** @public */ +export interface FieldMappingSpec { + type: ES_FIELD_TYPES; + _serialize?: (mapping: any) => string | undefined; + _deserialize?: (mapping: string) => any | undefined; } + +/** @public */ +export type MappingObject = Record; diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index ab609583f84ee..bac0ef629789a 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -21,3 +21,4 @@ export * from './store'; export * from './parse'; export * from './render_complete'; export * from './errors'; +export * from './field_mapping'; diff --git a/test/api_integration/apis/saved_objects/export.js b/test/api_integration/apis/saved_objects/export.js index e39749aa48159..9ab7a09309952 100644 --- a/test/api_integration/apis/saved_objects/export.js +++ b/test/api_integration/apis/saved_objects/export.js @@ -37,7 +37,30 @@ export default function ({ getService }) { type: ['index-pattern', 'search', 'visualization', 'dashboard'], }) .expect(200) - .then((resp) => { + .then(resp => { + const objects = resp.text.split('\n').map(JSON.parse); + expect(objects).to.have.length(4); + expect(objects[0]).to.have.property('id', '91200a00-9efd-11e7-acb3-3dab96693fab'); + expect(objects[0]).to.have.property('type', 'index-pattern'); + expect(objects[1]).to.have.property('id', 'dd7caf20-9efd-11e7-acb3-3dab96693fab'); + expect(objects[1]).to.have.property('type', 'visualization'); + expect(objects[2]).to.have.property('id', 'be3733a0-9efe-11e7-acb3-3dab96693fab'); + expect(objects[2]).to.have.property('type', 'dashboard'); + expect(objects[3]).to.have.property('exportedCount', 3); + expect(objects[3]).to.have.property('missingRefCount', 0); + expect(objects[3].missingReferences).to.have.length(0); + }); + }); + + it('should exclude the export details if asked', async () => { + await supertest + .post('/api/saved_objects/_export') + .send({ + type: ['index-pattern', 'search', 'visualization', 'dashboard'], + excludeExportDetails: true, + }) + .expect(200) + .then(resp => { const objects = resp.text.split('\n').map(JSON.parse); expect(objects).to.have.length(3); expect(objects[0]).to.have.property('id', '91200a00-9efd-11e7-acb3-3dab96693fab'); @@ -62,15 +85,18 @@ export default function ({ getService }) { ], }) .expect(200) - .then((resp) => { + .then(resp => { const objects = resp.text.split('\n').map(JSON.parse); - expect(objects).to.have.length(3); + expect(objects).to.have.length(4); expect(objects[0]).to.have.property('id', '91200a00-9efd-11e7-acb3-3dab96693fab'); expect(objects[0]).to.have.property('type', 'index-pattern'); expect(objects[1]).to.have.property('id', 'dd7caf20-9efd-11e7-acb3-3dab96693fab'); expect(objects[1]).to.have.property('type', 'visualization'); expect(objects[2]).to.have.property('id', 'be3733a0-9efe-11e7-acb3-3dab96693fab'); expect(objects[2]).to.have.property('type', 'dashboard'); + expect(objects[3]).to.have.property('exportedCount', 3); + expect(objects[3]).to.have.property('missingRefCount', 0); + expect(objects[3].missingReferences).to.have.length(0); }); }); @@ -82,15 +108,18 @@ export default function ({ getService }) { type: ['dashboard'], }) .expect(200) - .then((resp) => { + .then(resp => { const objects = resp.text.split('\n').map(JSON.parse); - expect(objects).to.have.length(3); + expect(objects).to.have.length(4); expect(objects[0]).to.have.property('id', '91200a00-9efd-11e7-acb3-3dab96693fab'); expect(objects[0]).to.have.property('type', 'index-pattern'); expect(objects[1]).to.have.property('id', 'dd7caf20-9efd-11e7-acb3-3dab96693fab'); expect(objects[1]).to.have.property('type', 'visualization'); expect(objects[2]).to.have.property('id', 'be3733a0-9efe-11e7-acb3-3dab96693fab'); expect(objects[2]).to.have.property('type', 'dashboard'); + expect(objects[3]).to.have.property('exportedCount', 3); + expect(objects[3]).to.have.property('missingRefCount', 0); + expect(objects[3].missingReferences).to.have.length(0); }); }); @@ -100,18 +129,21 @@ export default function ({ getService }) { .send({ includeReferencesDeep: true, type: ['dashboard'], - search: 'Requests*' + search: 'Requests*', }) .expect(200) - .then((resp) => { + .then(resp => { const objects = resp.text.split('\n').map(JSON.parse); - expect(objects).to.have.length(3); + expect(objects).to.have.length(4); expect(objects[0]).to.have.property('id', '91200a00-9efd-11e7-acb3-3dab96693fab'); expect(objects[0]).to.have.property('type', 'index-pattern'); expect(objects[1]).to.have.property('id', 'dd7caf20-9efd-11e7-acb3-3dab96693fab'); expect(objects[1]).to.have.property('type', 'visualization'); expect(objects[2]).to.have.property('id', 'be3733a0-9efe-11e7-acb3-3dab96693fab'); expect(objects[2]).to.have.property('type', 'dashboard'); + expect(objects[3]).to.have.property('exportedCount', 3); + expect(objects[3]).to.have.property('missingRefCount', 0); + expect(objects[3].missingReferences).to.have.length(0); }); }); @@ -127,7 +159,7 @@ export default function ({ getService }) { ], }) .expect(400) - .then((resp) => { + .then(resp => { expect(resp.body).to.eql({ statusCode: 400, error: 'Bad Request', @@ -159,12 +191,13 @@ export default function ({ getService }) { expect(resp.body).to.eql({ statusCode: 400, error: 'Bad Request', - message: 'child "type" fails because ["type" at position 0 fails because ' + + message: + 'child "type" fails because ["type" at position 0 fails because ' + '["0" must be one of [config, dashboard, index-pattern, query, search, url, visualization]]]', validation: { source: 'payload', keys: ['type.0'], - } + }, }); }); }); @@ -178,12 +211,12 @@ export default function ({ getService }) { await supertest .post('/api/saved_objects/_export') .expect(400) - .then((resp) => { + .then(resp => { expect(resp.body).to.eql({ statusCode: 400, error: 'Bad Request', message: '"value" must be an object', - validation: { source: 'payload', keys: [ 'value' ] }, + validation: { source: 'payload', keys: ['value'] }, }); }); }); @@ -193,47 +226,55 @@ export default function ({ getService }) { .post('/api/saved_objects/_export') .send({ type: 'dashboard', + excludeExportDetails: true, }) .expect(200) - .then((resp) => { - expect(resp.headers['content-disposition']).to.eql('attachment; filename="export.ndjson"'); + .then(resp => { + expect(resp.headers['content-disposition']).to.eql( + 'attachment; filename="export.ndjson"' + ); expect(resp.headers['content-type']).to.eql('application/ndjson'); const objects = resp.text.split('\n').map(JSON.parse); - expect(objects).to.eql([{ - attributes: { - description: '', - hits: 0, - kibanaSavedObjectMeta: { - searchSourceJSON: objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON, - }, - optionsJSON: objects[0].attributes.optionsJSON, - panelsJSON: objects[0].attributes.panelsJSON, - refreshInterval: { - display: 'Off', - pause: false, - value: 0, + expect(objects).to.eql([ + { + attributes: { + description: '', + hits: 0, + kibanaSavedObjectMeta: { + searchSourceJSON: + objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON, + }, + optionsJSON: objects[0].attributes.optionsJSON, + panelsJSON: objects[0].attributes.panelsJSON, + refreshInterval: { + display: 'Off', + pause: false, + value: 0, + }, + timeFrom: 'Wed Sep 16 2015 22:52:17 GMT-0700', + timeRestore: true, + timeTo: 'Fri Sep 18 2015 12:24:38 GMT-0700', + title: 'Requests', + version: 1, }, - timeFrom: 'Wed Sep 16 2015 22:52:17 GMT-0700', - timeRestore: true, - timeTo: 'Fri Sep 18 2015 12:24:38 GMT-0700', - title: 'Requests', - version: 1, + id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', + migrationVersion: objects[0].migrationVersion, + references: [ + { + id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + name: 'panel_0', + type: 'visualization', + }, + ], + type: 'dashboard', + updated_at: '2017-09-21T18:57:40.826Z', + version: objects[0].version, }, - id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', - migrationVersion: objects[0].migrationVersion, - references: [ - { - id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - name: 'panel_0', - type: 'visualization', - }, - ], - type: 'dashboard', - updated_at: '2017-09-21T18:57:40.826Z', - version: objects[0].version, - }]); + ]); expect(objects[0].migrationVersion).to.be.ok(); - expect(() => JSON.parse(objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON)).not.to.throwError(); + expect(() => + JSON.parse(objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON) + ).not.to.throwError(); expect(() => JSON.parse(objects[0].attributes.optionsJSON)).not.to.throwError(); expect(() => JSON.parse(objects[0].attributes.panelsJSON)).not.to.throwError(); }); @@ -244,47 +285,55 @@ export default function ({ getService }) { .post('/api/saved_objects/_export') .send({ type: ['dashboard'], + excludeExportDetails: true, }) .expect(200) - .then((resp) => { - expect(resp.headers['content-disposition']).to.eql('attachment; filename="export.ndjson"'); + .then(resp => { + expect(resp.headers['content-disposition']).to.eql( + 'attachment; filename="export.ndjson"' + ); expect(resp.headers['content-type']).to.eql('application/ndjson'); const objects = resp.text.split('\n').map(JSON.parse); - expect(objects).to.eql([{ - attributes: { - description: '', - hits: 0, - kibanaSavedObjectMeta: { - searchSourceJSON: objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON, - }, - optionsJSON: objects[0].attributes.optionsJSON, - panelsJSON: objects[0].attributes.panelsJSON, - refreshInterval: { - display: 'Off', - pause: false, - value: 0, + expect(objects).to.eql([ + { + attributes: { + description: '', + hits: 0, + kibanaSavedObjectMeta: { + searchSourceJSON: + objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON, + }, + optionsJSON: objects[0].attributes.optionsJSON, + panelsJSON: objects[0].attributes.panelsJSON, + refreshInterval: { + display: 'Off', + pause: false, + value: 0, + }, + timeFrom: 'Wed Sep 16 2015 22:52:17 GMT-0700', + timeRestore: true, + timeTo: 'Fri Sep 18 2015 12:24:38 GMT-0700', + title: 'Requests', + version: 1, }, - timeFrom: 'Wed Sep 16 2015 22:52:17 GMT-0700', - timeRestore: true, - timeTo: 'Fri Sep 18 2015 12:24:38 GMT-0700', - title: 'Requests', - version: 1, + id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', + migrationVersion: objects[0].migrationVersion, + references: [ + { + id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + name: 'panel_0', + type: 'visualization', + }, + ], + type: 'dashboard', + updated_at: '2017-09-21T18:57:40.826Z', + version: objects[0].version, }, - id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', - migrationVersion: objects[0].migrationVersion, - references: [ - { - id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - name: 'panel_0', - type: 'visualization', - }, - ], - type: 'dashboard', - updated_at: '2017-09-21T18:57:40.826Z', - version: objects[0].version, - }]); + ]); expect(objects[0].migrationVersion).to.be.ok(); - expect(() => JSON.parse(objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON)).not.to.throwError(); + expect(() => + JSON.parse(objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON) + ).not.to.throwError(); expect(() => JSON.parse(objects[0].attributes.optionsJSON)).not.to.throwError(); expect(() => JSON.parse(objects[0].attributes.panelsJSON)).not.to.throwError(); }); @@ -300,47 +349,55 @@ export default function ({ getService }) { id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', }, ], + excludeExportDetails: true, }) .expect(200) - .then((resp) => { - expect(resp.headers['content-disposition']).to.eql('attachment; filename="export.ndjson"'); + .then(resp => { + expect(resp.headers['content-disposition']).to.eql( + 'attachment; filename="export.ndjson"' + ); expect(resp.headers['content-type']).to.eql('application/ndjson'); const objects = resp.text.split('\n').map(JSON.parse); - expect(objects).to.eql([{ - attributes: { - description: '', - hits: 0, - kibanaSavedObjectMeta: { - searchSourceJSON: objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON, - }, - optionsJSON: objects[0].attributes.optionsJSON, - panelsJSON: objects[0].attributes.panelsJSON, - refreshInterval: { - display: 'Off', - pause: false, - value: 0, + expect(objects).to.eql([ + { + attributes: { + description: '', + hits: 0, + kibanaSavedObjectMeta: { + searchSourceJSON: + objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON, + }, + optionsJSON: objects[0].attributes.optionsJSON, + panelsJSON: objects[0].attributes.panelsJSON, + refreshInterval: { + display: 'Off', + pause: false, + value: 0, + }, + timeFrom: 'Wed Sep 16 2015 22:52:17 GMT-0700', + timeRestore: true, + timeTo: 'Fri Sep 18 2015 12:24:38 GMT-0700', + title: 'Requests', + version: 1, }, - timeFrom: 'Wed Sep 16 2015 22:52:17 GMT-0700', - timeRestore: true, - timeTo: 'Fri Sep 18 2015 12:24:38 GMT-0700', - title: 'Requests', - version: 1, + id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', + migrationVersion: objects[0].migrationVersion, + references: [ + { + id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + name: 'panel_0', + type: 'visualization', + }, + ], + type: 'dashboard', + updated_at: '2017-09-21T18:57:40.826Z', + version: objects[0].version, }, - id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', - migrationVersion: objects[0].migrationVersion, - references: [ - { - id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - name: 'panel_0', - type: 'visualization', - }, - ], - type: 'dashboard', - updated_at: '2017-09-21T18:57:40.826Z', - version: objects[0].version, - }]); + ]); expect(objects[0].migrationVersion).to.be.ok(); - expect(() => JSON.parse(objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON)).not.to.throwError(); + expect(() => + JSON.parse(objects[0].attributes.kibanaSavedObjectMeta.searchSourceJSON) + ).not.to.throwError(); expect(() => JSON.parse(objects[0].attributes.optionsJSON)).not.to.throwError(); expect(() => JSON.parse(objects[0].attributes.panelsJSON)).not.to.throwError(); }); @@ -357,14 +414,15 @@ export default function ({ getService }) { id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', }, ], + excludeExportDetails: true, }) .expect(400) - .then((resp) => { + .then(resp => { expect(resp.body).to.eql({ statusCode: 400, error: 'Bad Request', message: '"value" contains a conflict between exclusive peers [type, objects]', - validation: { source: 'payload', keys: [ 'value' ] }, + validation: { source: 'payload', keys: ['value'] }, }); }); }); @@ -382,14 +440,12 @@ export default function ({ getService }) { }, }) .expect(200) - .then((resp) => { + .then(resp => { customVisId = resp.body.id; }); }); after(async () => { - await supertest - .delete(`/api/saved_objects/visualization/${customVisId}`) - .expect(200); + await supertest.delete(`/api/saved_objects/visualization/${customVisId}`).expect(200); await esArchiver.unload('saved_objects/10k'); }); @@ -398,13 +454,14 @@ export default function ({ getService }) { .post('/api/saved_objects/_export') .send({ type: ['dashboard', 'visualization', 'search', 'index-pattern'], + excludeExportDetails: true, }) .expect(400) - .then((resp) => { + .then(resp => { expect(resp.body).to.eql({ statusCode: 400, error: 'Bad Request', - message: `Can't export more than 10000 objects` + message: `Can't export more than 10000 objects`, }); }); }); @@ -412,22 +469,24 @@ export default function ({ getService }) { }); describe('without kibana index', () => { - before(async () => ( - // just in case the kibana server has recreated it - await es.indices.delete({ - index: '.kibana', - ignore: [404], - }) - )); + before( + async () => + // just in case the kibana server has recreated it + await es.indices.delete({ + index: '.kibana', + ignore: [404], + }) + ); it('should return empty response', async () => { await supertest .post('/api/saved_objects/_export') .send({ type: ['index-pattern', 'search', 'visualization', 'dashboard'], + excludeExportDetails: true, }) .expect(200) - .then((resp) => { + .then(resp => { expect(resp.text).to.eql(''); }); }); diff --git a/test/functional/apps/dashboard/data_shared_attributes.js b/test/functional/apps/dashboard/data_shared_attributes.js index 12f85f1b5e46a..342eb45b84fef 100644 --- a/test/functional/apps/dashboard/data_shared_attributes.js +++ b/test/functional/apps/dashboard/data_shared_attributes.js @@ -22,7 +22,7 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { const retry = getService('retry'); const dashboardPanelActions = getService('dashboardPanelActions'); - const PageObjects = getPageObjects(['dashboard']); + const PageObjects = getPageObjects(['dashboard', 'timePicker']); describe('dashboard data-shared attributes', function describeIndexTests() { let originalPanelTitles; @@ -32,6 +32,13 @@ export default function ({ getService, getPageObjects }) { await PageObjects.dashboard.waitForRenderComplete(); }); + it('should have time picker with data-shared-timefilter-duration', async () => { + await retry.try(async () => { + const sharedData = await PageObjects.timePicker.getTimeDurationForSharing(); + expect(sharedData).to.not.be(null); + }); + }); + it('should have data-shared-items-count set to the number of embeddables on the dashboard', async () => { await retry.try(async () => { const sharedItemsCount = await PageObjects.dashboard.getSharedItemsCount(); diff --git a/test/functional/page_objects/time_picker.js b/test/functional/page_objects/time_picker.js index 054c3edd567e1..2b4147908559a 100644 --- a/test/functional/page_objects/time_picker.js +++ b/test/functional/page_objects/time_picker.js @@ -184,6 +184,14 @@ export function TimePickerPageProvider({ getService, getPageObjects }) { }; } + async getTimeDurationForSharing() { + return await retry.try(async () => { + const element = await testSubjects.find('dataSharedTimefilterDuration'); + const data = await element.getAttribute('data-shared-timefilter-duration'); + return data; + }); + } + async getTimeConfigAsAbsoluteTimes() { await this.showStartEndTimes(); diff --git a/test/plugin_functional/config.js b/test/plugin_functional/config.js index 7d4ece98249f8..e5ad767349358 100644 --- a/test/plugin_functional/config.js +++ b/test/plugin_functional/config.js @@ -19,6 +19,7 @@ import path from 'path'; import fs from 'fs'; +import { services } from './services'; export default async function ({ readConfigFile }) { const functionalConfig = await readConfigFile(require.resolve('../functional/config')); @@ -48,7 +49,10 @@ export default async function ({ readConfigFile }) { require.resolve('./test_suites/core_plugins'), ], - services: functionalConfig.get('services'), + services: { + ...functionalConfig.get('services'), + ...services, + }, pageObjects: functionalConfig.get('pageObjects'), servers: functionalConfig.get('servers'), esTestCluster: functionalConfig.get('esTestCluster'), diff --git a/test/plugin_functional/plugins/core_legacy_compat/package.json b/test/plugin_functional/plugins/core_legacy_compat/package.json deleted file mode 100644 index b42847356b9c2..0000000000000 --- a/test/plugin_functional/plugins/core_legacy_compat/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "core_legacy_compat", - "version": "1.0.0", - "main": "target/test/plugin_functional/plugins/core_legacy_compat", - "kibana": { - "version": "kibana", - "templateVersion": "1.0.0" - }, - "license": "Apache-2.0", - "scripts": { - "kbn": "node ../../../../scripts/kbn.js", - "build": "rm -rf './target' && tsc" - }, - "devDependencies": { - "typescript": "3.5.3" - } -} diff --git a/test/plugin_functional/plugins/core_legacy_compat/tsconfig.json b/test/plugin_functional/plugins/core_legacy_compat/tsconfig.json deleted file mode 100644 index 4a564ee1e5578..0000000000000 --- a/test/plugin_functional/plugins/core_legacy_compat/tsconfig.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "../../../../tsconfig.json", - "compilerOptions": { - "outDir": "./target", - "skipLibCheck": true - }, - "include": [ - "index.ts", - "public/**/*.ts", - "public/**/*.tsx", - "../../../../typings/**/*" - ], - "exclude": [] -} diff --git a/test/plugin_functional/plugins/core_plugin_legacy/index.ts b/test/plugin_functional/plugins/core_plugin_legacy/index.ts index b9cd4a7e8bb34..d8fcb534a0bb3 100644 --- a/test/plugin_functional/plugins/core_plugin_legacy/index.ts +++ b/test/plugin_functional/plugins/core_plugin_legacy/index.ts @@ -24,6 +24,13 @@ export default function(kibana: any) { return new kibana.Plugin({ id: 'core_plugin_legacy', require: ['kibana'], + uiExports: { + app: { + title: 'Core Legacy Compat', + description: 'This is a sample plugin to test core to legacy compatibility', + main: 'plugins/core_plugin_legacy/index', + }, + }, init(server: KbnServer) { const { http } = server.newPlatform.setup.core; const router = http.createRouter(); @@ -32,6 +39,11 @@ export default function(kibana: any) { const response = await context.core.elasticsearch.adminClient.callAsInternalUser('ping'); return res.ok({ body: `Pong in legacy via new platform: ${response}` }); }); + + router.get({ path: '/api/np-context-in-legacy', validate: false }, (context, req, res) => { + const contexts = Object.keys(context); + return res.ok({ body: { contexts } }); + }); }, }); } diff --git a/test/plugin_functional/plugins/core_legacy_compat/public/application.tsx b/test/plugin_functional/plugins/core_plugin_legacy/public/application.tsx similarity index 100% rename from test/plugin_functional/plugins/core_legacy_compat/public/application.tsx rename to test/plugin_functional/plugins/core_plugin_legacy/public/application.tsx diff --git a/test/plugin_functional/plugins/core_legacy_compat/public/index.ts b/test/plugin_functional/plugins/core_plugin_legacy/public/index.ts similarity index 100% rename from test/plugin_functional/plugins/core_legacy_compat/public/index.ts rename to test/plugin_functional/plugins/core_plugin_legacy/public/index.ts diff --git a/test/plugin_functional/services/index.js b/test/plugin_functional/services/index.js new file mode 100644 index 0000000000000..bf02587772f4b --- /dev/null +++ b/test/plugin_functional/services/index.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT 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 { KibanaSupertestProvider } from './supertest'; + +export const services = { + supertest: KibanaSupertestProvider, +}; diff --git a/src/legacy/ui/public/vis/editors/default/controls/agg_control_react_wrapper.tsx b/test/plugin_functional/services/supertest.js similarity index 66% rename from src/legacy/ui/public/vis/editors/default/controls/agg_control_react_wrapper.tsx rename to test/plugin_functional/services/supertest.js index 17c5b319d424a..390f89acaa775 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/agg_control_react_wrapper.tsx +++ b/test/plugin_functional/services/supertest.js @@ -17,18 +17,13 @@ * under the License. */ -import React from 'react'; -import { AggControlProps } from './agg_control_props'; -interface AggControlReactWrapperProps extends AggControlProps { - component: React.FunctionComponent>; -} +import { format as formatUrl } from 'url'; -function AggControlReactWrapper({ - component: Component, - ...rest -}: AggControlReactWrapperProps) { - return ; -} +import supertestAsPromised from 'supertest-as-promised'; -export { AggControlReactWrapper }; +export function KibanaSupertestProvider({ getService }) { + const config = getService('config'); + const kibanaServerUrl = formatUrl(config.get('servers.kibana')); + return supertestAsPromised(kibanaServerUrl); +} diff --git a/test/plugin_functional/test_suites/core_plugins/legacy_plugins.js b/test/plugin_functional/test_suites/core_plugins/legacy_plugins.js index 4e421f4523750..c6edf803c9938 100644 --- a/test/plugin_functional/test_suites/core_plugins/legacy_plugins.js +++ b/test/plugin_functional/test_suites/core_plugins/legacy_plugins.js @@ -21,21 +21,29 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['common']); - const browser = getService('browser'); const testSubjects = getService('testSubjects'); + const supertest = getService('supertest'); describe('legacy plugins', function describeIndexTests() { - it('have access to New Platform HTTP service', async () => { - const url = `${PageObjects.common.getHostPort()}/api/np-http-in-legacy`; - await browser.get(url); + describe('http', () => { + it('has access to New Platform HTTP service', async () => { + await supertest + .get('/api/np-http-in-legacy') + .expect(200) + .expect('Pong in legacy via new platform: true'); + }); - const pageSource = await browser.execute('return window.document.body.textContent;'); - expect(pageSource).to.equal('Pong in legacy via new platform: true'); + it('has access to New Platform HTTP context providers', async () => { + await supertest + .get('/api/np-context-in-legacy') + .expect(200) + .expect(JSON.stringify({ contexts: ['core', 'search', 'pluginA'] })); + }); }); describe('application service compatibility layer', function describeIndexTests() { it('can render legacy apps', async () => { - await PageObjects.common.navigateToApp('core_legacy_compat'); + await PageObjects.common.navigateToApp('core_plugin_legacy'); expect(await testSubjects.exists('coreLegacyCompatH1')).to.be(true); }); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx index dfc9c95c9143a..34c46f84c76b9 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/Variables.tsx @@ -5,18 +5,12 @@ */ import theme from '@elastic/eui/dist/eui_theme_light.json'; +import styled from 'styled-components'; +import { EuiAccordion } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import styled from 'styled-components'; +import { borderRadius, px, unit, units } from '../../../style/variables'; import { IStackframe } from '../../../../typings/es_schemas/raw/fields/Stackframe'; -import { - borderRadius, - fontFamily, - px, - unit, - units -} from '../../../style/variables'; -import { Ellipsis } from '../Icons'; import { DottedKeyValueTable } from '../DottedKeyValueTable'; const VariablesContainer = styled.div` @@ -24,17 +18,6 @@ const VariablesContainer = styled.div` border-top: 1px solid ${theme.euiColorLightShade}; border-radius: 0 0 ${borderRadius} ${borderRadius}; padding: ${px(units.half)} ${px(unit)}; - font-family: ${fontFamily}; -`; - -const VariablesToggle = styled.a` - display: block; - cursor: pointer; - user-select: none; -`; - -const VariablesTableContainer = styled.div` - padding: ${px(units.plus)} ${px(unit)} 0; `; interface Props { @@ -42,34 +25,28 @@ interface Props { } export class Variables extends React.Component { - public state = { - isVisible: false - }; - - public onClick = () => { - this.setState(() => ({ isVisible: !this.state.isVisible })); - }; - public render() { if (!this.props.vars) { return null; } return ( - - - {' '} - {i18n.translate( - 'xpack.apm.stacktraceTab.localVariablesToogleButtonLabel', - { defaultMessage: 'Local variables' } - )} - - {this.state.isVisible && ( - - - - )} - + + + + + + + + + ); } } diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts index 03cfa7e0c8a83..79fcfe95c0552 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/es_client.ts @@ -17,6 +17,9 @@ import { cloneDeep, has, isString, set } from 'lodash'; import { OBSERVER_VERSION_MAJOR } from '../../../common/elasticsearch_fieldnames'; import { StringMap } from '../../../typings/common'; +// `type` was deprecated in 7.0 +export type APMIndexDocumentParams = Omit, 'type'>; + function getApmIndices(config: Legacy.KibanaConfig) { return [ config.get('apm_oss.errorIndices'), @@ -118,7 +121,7 @@ export function getESClient(req: Legacy.Request) { AggregationSearchResponseWithTotalHitsAsObject >; }, - index: (params: IndexDocumentParams) => { + index: (params: APMIndexDocumentParams) => { return cluster.callWithRequest(req, 'index', params); }, delete: (params: IndicesDeleteParams) => { diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts index 6edac00b8a1ab..cfc33b4fad7ea 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.test.ts @@ -6,7 +6,7 @@ import { Legacy } from 'kibana'; import { setupRequest } from './setup_request'; -import { uiSettingsServiceMock } from '../../../../../../../src/legacy/ui/ui_settings/ui_settings_service.mock'; +import { uiSettingsServiceMock } from 'src/core/server/mocks'; function getMockRequest() { const callWithRequestSpy = jest.fn(); @@ -125,7 +125,7 @@ describe('setupRequest', () => { it('should set `ignore_throttled=true` if `includeFrozen=false`', async () => { const { mockRequest, callWithRequestSpy } = getMockRequest(); - const uiSettingsService = uiSettingsServiceMock.create(); + const uiSettingsService = uiSettingsServiceMock.createClient(); // mock includeFrozen to return false uiSettingsService.get.mockResolvedValue(false); mockRequest.getUiSettingsService = () => uiSettingsService; @@ -138,7 +138,7 @@ describe('setupRequest', () => { it('should set `ignore_throttled=false` if `includeFrozen=true`', async () => { const { mockRequest, callWithRequestSpy } = getMockRequest(); - const uiSettingsService = uiSettingsServiceMock.create(); + const uiSettingsService = uiSettingsServiceMock.createClient(); // mock includeFrozen to return true uiSettingsService.get.mockResolvedValue(true); mockRequest.getUiSettingsService = () => uiSettingsService; diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts index 6450040098cd4..9f0d8e2f1c718 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/create_or_update_configuration.ts @@ -5,9 +5,9 @@ */ import hash from 'object-hash'; -import { IndexDocumentParams } from 'elasticsearch'; import { Setup } from '../../helpers/setup_request'; import { AgentConfiguration } from './configuration_types'; +import { APMIndexDocumentParams } from '../../helpers/es_client'; export async function createOrUpdateConfiguration({ configurationId, @@ -23,8 +23,7 @@ export async function createOrUpdateConfiguration({ }) { const { client, config } = setup; - const params: IndexDocumentParams = { - type: '_doc', + const params: APMIndexDocumentParams = { refresh: true, index: config.get('apm_oss.apmAgentConfigurationIndex'), body: { diff --git a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts index ea2b15e6985d5..867045142cea0 100644 --- a/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts +++ b/x-pack/legacy/plugins/apm/server/lib/settings/agent_configuration/mark_applied_by_agent.ts @@ -19,7 +19,6 @@ export async function markAppliedByAgent({ const { client, config } = setup; const params = { - type: '_doc', index: config.get('apm_oss.apmAgentConfigurationIndex'), id, // by specifying the `id` elasticsearch will do an "upsert" body: { diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/auto_refresh_controls.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/auto_refresh_controls.tsx index fde0e2b4012c1..97d8920d50dd3 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/auto_refresh_controls.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/auto_refresh_controls.tsx @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, ReactNode } from 'react'; +import React, { ReactNode } from 'react'; import PropTypes from 'prop-types'; import { EuiFlexGroup, - EuiFlexGrid, EuiFlexItem, EuiLink, EuiSpacer, @@ -16,10 +15,11 @@ import { EuiDescriptionList, EuiDescriptionListTitle, EuiDescriptionListDescription, - EuiFormLabel, + EuiTitle, EuiText, EuiButtonIcon, EuiToolTip, + htmlIdGenerator, } from '@elastic/eui'; import { timeDuration } from '../../../lib/time_duration'; import { RefreshControl } from '../refresh_control'; @@ -37,26 +37,36 @@ interface Props { } interface ListGroupProps { + 'aria-labelledby': string; + className: string; children: ReactNode; } interface RefreshItemProps { duration: number; label: string; + descriptionId: string; } -const ListGroup = ({ children }: ListGroupProps) => ( -
    {[children]}
+const ListGroup = ({ children, ...rest }: ListGroupProps) => ( +
    + {[children]} +
); +const generateId = htmlIdGenerator(); + export const AutoRefreshControls = ({ refreshInterval, setRefresh, disableInterval }: Props) => { - const RefreshItem = ({ duration, label }: RefreshItemProps) => ( + const RefreshItem = ({ duration, label, descriptionId }: RefreshItemProps) => (
  • - setRefresh(duration)}>{label} + setRefresh(duration)} aria-describedby={descriptionId}> + {label} +
  • ); const interval = timeDuration(refreshInterval); + const intervalTitleId = generateId(); return ( @@ -67,11 +77,9 @@ export const AutoRefreshControls = ({ refreshInterval, setRefresh, disableInterv {strings.getRefreshListTitle()} {refreshInterval > 0 ? ( - - {timeStrings.getCycleTimeText(interval.length, interval.format)} - + <>{timeStrings.getCycleTimeText(interval.length, interval.format)} ) : ( - {strings.getRefreshListDurationManualText()} + <>{strings.getRefreshListDurationManualText()} )} @@ -98,31 +106,73 @@ export const AutoRefreshControls = ({ refreshInterval, setRefresh, disableInterv - {strings.getIntervalFormLabelText()} + +

    {strings.getIntervalFormLabelText()}

    +
    - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/control_settings.scss b/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/control_settings.scss index beaead8b99fc3..3d217dd1fc180 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/control_settings.scss +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/control_settings.scss @@ -1,3 +1,7 @@ .canvasControlSettings__popover { width: 600px; } + +.canvasControlSettings__list { + columns: 2; +} diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/kiosk_controls.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/kiosk_controls.tsx index a98cdddc21e19..9e6f0a91c6120 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/kiosk_controls.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/control_settings/kiosk_controls.tsx @@ -10,15 +10,15 @@ import { EuiDescriptionList, EuiDescriptionListDescription, EuiDescriptionListTitle, - EuiFormLabel, + EuiTitle, EuiHorizontalRule, EuiLink, EuiSpacer, EuiSwitch, EuiText, - EuiFlexGrid, EuiFlexItem, EuiFlexGroup, + htmlIdGenerator, } from '@elastic/eui'; import { timeDuration } from '../../../lib/time_duration'; import { CustomInterval } from './custom_interval'; @@ -36,31 +36,40 @@ interface Props { } interface ListGroupProps { + 'aria-labelledby'?: string; + className: string; children: ReactNode; } - interface RefreshItemProps { duration: number; label: string; + descriptionId: string; } -const ListGroup = ({ children }: ListGroupProps) => ( -
      {[children]}
    +const ListGroup = ({ children, ...rest }: ListGroupProps) => ( +
      + {[children]} +
    ); +const generateId = htmlIdGenerator(); + export const KioskControls = ({ autoplayEnabled, autoplayInterval, onSetEnabled, onSetInterval, }: Props) => { - const RefreshItem = ({ duration, label }: RefreshItemProps) => ( + const RefreshItem = ({ duration, label, descriptionId }: RefreshItemProps) => (
  • - onSetInterval(duration)}>{label} + onSetInterval(duration)} aria-describedby={descriptionId}> + {label} +
  • ); const interval = timeDuration(autoplayInterval); + const intervalTitleId = generateId(); return ( @@ -68,40 +77,55 @@ export const KioskControls = ({ {strings.getTitle()} - {timeStrings.getCycleTimeText(interval.length, interval.format)} + {timeStrings.getCycleTimeText(interval.length, interval.format)} -
    - onSetEnabled(ev.target.checked)} - /> - -
    + onSetEnabled(ev.target.checked)} + /> + - {strings.getCycleFormLabel()} + +

    {strings.getCycleFormLabel()}

    +
    - - - - - - - - - - - - - - - - - + + + + + + + + diff --git a/x-pack/legacy/plugins/code/common/constants.ts b/x-pack/legacy/plugins/code/common/constants.ts index ec7356bccb3f7..15c4ffd09f033 100644 --- a/x-pack/legacy/plugins/code/common/constants.ts +++ b/x-pack/legacy/plugins/code/common/constants.ts @@ -6,3 +6,4 @@ export const APP_TITLE = 'Code (Beta)'; export const APP_USAGE_TYPE = 'code'; +export const SAVED_OBJ_REPO = 'code-repo'; diff --git a/x-pack/legacy/plugins/code/index.ts b/x-pack/legacy/plugins/code/index.ts index 421f8e01c5594..d0d17aa9a802b 100644 --- a/x-pack/legacy/plugins/code/index.ts +++ b/x-pack/legacy/plugins/code/index.ts @@ -10,9 +10,10 @@ import { Legacy } from 'kibana'; import { resolve } from 'path'; import { CoreSetup } from 'src/core/server'; -import { APP_TITLE } from './common/constants'; +import { APP_TITLE, SAVED_OBJ_REPO } from './common/constants'; import { codePlugin } from './server'; import { PluginSetupContract } from '../../../plugins/code/server'; +import { mappings } from './mappings'; export type RequestFacade = Legacy.Request; export type RequestQueryFacade = RequestQuery; @@ -43,6 +44,12 @@ export const code = (kibana: any) => }; }, hacks: ['plugins/code/hacks/toggle_app_link_in_nav'], + savedObjectSchemas: { + [SAVED_OBJ_REPO]: { + isNamespaceAgnostic: false, + }, + }, + mappings, }, config(Joi: typeof JoiNamespace) { return Joi.object({ diff --git a/x-pack/legacy/plugins/code/mappings.ts b/x-pack/legacy/plugins/code/mappings.ts new file mode 100644 index 0000000000000..651ee607473d3 --- /dev/null +++ b/x-pack/legacy/plugins/code/mappings.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 { SAVED_OBJ_REPO } from './common/constants'; + +export const mappings = { + [SAVED_OBJ_REPO]: { + properties: { + uri: { + type: 'keyword', + }, + }, + }, +}; diff --git a/x-pack/legacy/plugins/code/model/search.ts b/x-pack/legacy/plugins/code/model/search.ts index 04f99952d7c72..8d2a9f29254db 100644 --- a/x-pack/legacy/plugins/code/model/search.ts +++ b/x-pack/legacy/plugins/code/model/search.ts @@ -231,3 +231,64 @@ export interface SearchOptions { defaultRepoScopeOn: boolean; defaultRepoScope?: Repository; } + +export function emptySearchResult(): SearchResult { + return { + total: 0, + took: 0, + }; +} + +export function emptyRepositorySearchResult(): RepositorySearchResult { + return { + ...emptySearchResult(), + repositories: [], + from: 0, + page: 0, + totalPage: 0, + }; +} + +export function emptySymbolSearchResult(): SymbolSearchResult { + return { + ...emptySearchResult(), + symbols: [], + }; +} + +export function emptyDocumentSearchResult(query: string): DocumentSearchResult { + return { + ...emptySearchResult(), + query, + from: 0, + page: 0, + totalPage: 0, + stats: { + total: 0, + from: 0, + to: 0, + page: 0, + totalPage: 0, + repoStats: [], + languageStats: [], + }, + results: [], + repoAggregations: [], + langAggregations: [], + }; +} + +export function emptyCommitSearchResult(query: string): CommitSearchResult { + return { + ...emptyDocumentSearchResult(query), + commits: [], + }; +} + +export function emptyIntegrationsSearchResult(): IntegrationsSearchResult { + return { + ...emptySearchResult(), + results: [], + fallback: false, + }; +} diff --git a/x-pack/legacy/plugins/code/server/routes/file.ts b/x-pack/legacy/plugins/code/server/routes/file.ts index 973587a2888eb..10a9050fa0a90 100644 --- a/x-pack/legacy/plugins/code/server/routes/file.ts +++ b/x-pack/legacy/plugins/code/server/routes/file.ts @@ -14,6 +14,7 @@ import { EsClientWithRequest } from '../utils/esclient_with_request'; import { decodeRevisionString } from '../../common/uri_util'; import { CodeServices } from '../distributed/code_services'; import { GitServiceDefinition } from '../distributed/apis'; +import { getReferenceHelper } from '../utils/repository_reference_helper'; export function fileRoute(router: CodeServerRouter, codeServices: CodeServices) { const gitService = codeServices.serviceFor(GitServiceDefinition); @@ -26,6 +27,7 @@ export function fileRoute(router: CodeServerRouter, codeServices: CodeServices) try { const repo = await repoObjectClient.getRepository(repoUri); + await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repo.uri); return repo.uri; } catch (e) { return undefined; diff --git a/x-pack/legacy/plugins/code/server/routes/lsp.ts b/x-pack/legacy/plugins/code/server/routes/lsp.ts index 6aa2ca072cae0..10acb1e3863e8 100644 --- a/x-pack/legacy/plugins/code/server/routes/lsp.ts +++ b/x-pack/legacy/plugins/code/server/routes/lsp.ts @@ -26,6 +26,8 @@ import { RequestFacade, ResponseToolkitFacade } from '../..'; import { CodeServices } from '../distributed/code_services'; import { GitServiceDefinition, LspServiceDefinition } from '../distributed/apis'; import { findTitleFromHover, groupFiles } from '../utils/lsp_utils'; +import { getReferenceHelper } from '../utils/repository_reference_helper'; +import { SymbolSearchResult } from '../../model'; const LANG_SERVER_ERROR = 'language server error'; @@ -48,6 +50,7 @@ export function lspRoute( const params = (req.payload as unknown) as any; const uri = params.textDocument.uri; const { repoUri } = parseLspUrl(uri)!; + await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri); const endpoint = await codeServices.locate(req, repoUri); const requestPromise = lspService.sendRequest(endpoint, { method: `textDocument/${method}`, @@ -95,7 +98,9 @@ export function lspRoute( // @ts-ignore const { textDocument, position } = req.payload; const { uri } = textDocument; - const endpoint = await codeServices.locate(req, parseLspUrl(uri).repoUri); + const { repoUri } = parseLspUrl(uri); + await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri); + const endpoint = await codeServices.locate(req, repoUri); const response: ResponseMessage = await promiseTimeout( serverOptions.lsp.requestTimeoutMs, lspService.sendRequest(endpoint, { @@ -115,11 +120,12 @@ export function lspRoute( const locators = response.result as SymbolLocator[]; const locations = []; + const repoScope = await getReferenceHelper(req.getSavedObjectsClient()).findReferences(); for (const locator of locators) { if (locator.location) { locations.push(locator.location); - } else if (locator.qname) { - const searchResults = await symbolSearchClient.findByQname(req.params.qname); + } else if (locator.qname && repoScope.length > 0) { + const searchResults = await symbolSearchClient.findByQname(req.params.qname, repoScope); for (const symbol of searchResults.symbols) { locations.push(symbol.symbolInformation.location); } @@ -141,7 +147,9 @@ export function lspRoute( // @ts-ignore const { textDocument, position } = req.payload; const { uri } = textDocument; - const endpoint = await codeServices.locate(req, parseLspUrl(uri).repoUri); + const { repoUri } = parseLspUrl(uri); + await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri); + const endpoint = await codeServices.locate(req, repoUri); const response: ResponseMessage = await promiseTimeout( serverOptions.lsp.requestTimeoutMs, lspService.sendRequest(endpoint, { @@ -189,7 +197,15 @@ export function symbolByQnameRoute(router: CodeServerRouter, log: Logger) { async handler(req: RequestFacade) { try { const symbolSearchClient = new SymbolSearchClient(new EsClientWithRequest(req), log); - return await symbolSearchClient.findByQname(req.params.qname); + const repoScope = await getReferenceHelper(req.getSavedObjectsClient()).findReferences(); + if (repoScope.length === 0) { + return { + symbols: [], + total: 0, + took: 0, + } as SymbolSearchResult; + } + return await symbolSearchClient.findByQname(req.params.qname, repoScope); } catch (error) { return Boom.internal(`Search Exception`); } diff --git a/x-pack/legacy/plugins/code/server/routes/repository.ts b/x-pack/legacy/plugins/code/server/routes/repository.ts index f40a562178079..5947dc869968a 100644 --- a/x-pack/legacy/plugins/code/server/routes/repository.ts +++ b/x-pack/legacy/plugins/code/server/routes/repository.ts @@ -6,6 +6,7 @@ import Boom from 'boom'; +import { i18n } from '@kbn/i18n'; import { RequestFacade, ResponseToolkitFacade } from '../..'; import { validateGitUrl } from '../../common/git_url_utils'; import { RepositoryUtils } from '../../common/repository_utils'; @@ -19,6 +20,7 @@ import { EsClientWithRequest } from '../utils/esclient_with_request'; import { CodeServerRouter } from '../security'; import { CodeServices } from '../distributed/code_services'; import { RepositoryServiceDefinition } from '../distributed/apis'; +import { getReferenceHelper } from '../utils/repository_reference_helper'; export function repositoryRoute( router: CodeServerRouter, @@ -56,12 +58,32 @@ export function repositoryRoute( try { // Check if the repository already exists await repoObjectClient.getRepository(repo.uri); + // distinguish between that the repository exists in the current space and that the repository exists in + // another space, and return the default message if error happens during reference checking. + try { + const hasRef = await getReferenceHelper(req.getSavedObjectsClient()).hasReference( + repo.uri + ); + if (!hasRef) { + return Boom.conflict( + i18n.translate('xpack.code.repositoryManagement.repoOtherSpaceImportedMessage', { + defaultMessage: 'The repository has already been imported in another space!', + }) + ); + } + } catch (e) { + log.error(`Failed to check reference for ${repo.uri} in current space`); + } const msg = `Repository ${repoUrl} already exists. Skip clone.`; log.info(msg); return h.response(msg).code(304); // Not Modified } catch (error) { log.info(`Repository ${repoUrl} does not exist. Go ahead with clone.`); try { + // create the reference first, and make the creation idempotent, to avoid potential dangling repositories + // which have no references from any space, in case the writes to ES may fail independently + await getReferenceHelper(req.getSavedObjectsClient()).createReference(repo.uri); + // Create the index for the repository const initializer = (await repoIndexInitializerFactory.create( repo.uri, @@ -106,6 +128,9 @@ export function repositoryRoute( const repoUri: string = req.params.uri as string; const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req)); try { + // make sure the repo belongs to the current space + getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri); + // Check if the repository already exists. If not, an error will be thrown. await repoObjectClient.getRepository(repoUri); @@ -129,6 +154,9 @@ export function repositoryRoute( }; const endpoint = await codeServices.locate(req, repoUri); await repositoryService.delete(endpoint, payload); + + // delete the reference last to avoid dangling repositories + await getReferenceHelper(req.getSavedObjectsClient()).deleteReference(repoUri); return {}; } catch (error) { const msg = `Issue repository delete request for ${repoUri} error`; @@ -146,6 +174,7 @@ export function repositoryRoute( async handler(req: RequestFacade) { const repoUri = req.params.uri as string; try { + await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri); const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req)); return await repoObjectClient.getRepository(repoUri); } catch (error) { @@ -164,25 +193,30 @@ export function repositoryRoute( const repoUri = req.params.uri as string; try { const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req)); - let gitStatus = null; - try { - gitStatus = await repoObjectClient.getRepositoryGitStatus(repoUri); - } catch (error) { - log.debug(`Get repository git status ${repoUri} error: ${error}`); - } + let gitStatus = null; let indexStatus = null; - try { - indexStatus = await repoObjectClient.getRepositoryIndexStatus(repoUri); - } catch (error) { - log.debug(`Get repository index status ${repoUri} error: ${error}`); - } - let deleteStatus = null; - try { - deleteStatus = await repoObjectClient.getRepositoryDeleteStatus(repoUri); - } catch (error) { - log.debug(`Get repository delete status ${repoUri} error: ${error}`); + const hasRef = await getReferenceHelper(req.getSavedObjectsClient()).hasReference(repoUri); + + if (hasRef) { + try { + gitStatus = await repoObjectClient.getRepositoryGitStatus(repoUri); + } catch (error) { + log.debug(`Get repository git status ${repoUri} error: ${error}`); + } + + try { + indexStatus = await repoObjectClient.getRepositoryIndexStatus(repoUri); + } catch (error) { + log.debug(`Get repository index status ${repoUri} error: ${error}`); + } + + try { + deleteStatus = await repoObjectClient.getRepositoryDeleteStatus(repoUri); + } catch (error) { + log.debug(`Get repository delete status ${repoUri} error: ${error}`); + } } return { gitStatus, @@ -204,8 +238,9 @@ export function repositoryRoute( method: 'GET', async handler(req: RequestFacade) { try { + const uris = await getReferenceHelper(req.getSavedObjectsClient()).findReferences(); const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req)); - return await repoObjectClient.getAllRepositories(); + return await repoObjectClient.getRepositories(uris); } catch (error) { const msg = `Get all repositories error`; log.error(msg); @@ -226,6 +261,7 @@ export function repositoryRoute( const repoUri = req.params.uri as string; const reindex: boolean = (req.payload as any).reindex; try { + await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri); const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req)); const cloneStatus = await repoObjectClient.getRepositoryGitStatus(repoUri); @@ -258,6 +294,7 @@ export function repositoryRoute( try { // Check if the repository exists + await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri); await repoObjectClient.getRepository(repoUri); } catch (error) { return Boom.badRequest(`Repository not existed for ${repoUri}`); @@ -284,6 +321,7 @@ export function repositoryRoute( async handler(req: RequestFacade) { const repoUri = req.params.uri as string; try { + await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri); const repoObjectClient = new RepositoryObjectClient(new EsClientWithRequest(req)); return await repoObjectClient.getRepositoryConfig(repoUri); } catch (error) { diff --git a/x-pack/legacy/plugins/code/server/routes/search.ts b/x-pack/legacy/plugins/code/server/routes/search.ts index 496d3ba5ff5d1..86bdc931cff7a 100644 --- a/x-pack/legacy/plugins/code/server/routes/search.ts +++ b/x-pack/legacy/plugins/code/server/routes/search.ts @@ -26,6 +26,7 @@ import { } from '../search'; import { EsClientWithRequest } from '../utils/esclient_with_request'; import { CodeServerRouter } from '../security'; +import { getReferenceHelper } from '../utils/repository_reference_helper'; export function repositorySearchRoute(router: CodeServerRouter, log: Logger) { router.route({ @@ -38,15 +39,10 @@ export function repositorySearchRoute(router: CodeServerRouter, log: Logger) { page = parseInt(p as string, 10); } - let scope: string[] = []; - if (typeof repoScope === 'string') { - scope = repoScope.split(','); - } - const searchReq: RepositorySearchRequest = { query: q as string, page, - repoScope: scope, + repoScope: await getScope(req, repoScope), }; try { const repoSearchClient = new RepositorySearchClient(new EsClientWithRequest(req), log); @@ -68,15 +64,10 @@ export function repositorySearchRoute(router: CodeServerRouter, log: Logger) { page = parseInt(p as string, 10); } - let scope: string[] = []; - if (typeof repoScope === 'string') { - scope = repoScope.split(','); - } - const searchReq: RepositorySearchRequest = { query: q as string, page, - repoScope: scope, + repoScope: await getScope(req, repoScope), }; try { const repoSearchClient = new RepositorySearchClient(new EsClientWithRequest(req), log); @@ -100,19 +91,12 @@ export function documentSearchRoute(router: CodeServerRouter, log: Logger) { page = parseInt(p as string, 10); } - let scope: string[] = []; - if (typeof repoScope === 'string') { - scope = [repoScope]; - } else if (Array.isArray(repoScope)) { - scope = repoScope; - } - const searchReq: DocumentSearchRequest = { query: q as string, page, langFilters: langs ? (langs as string).split(',') : [], repoFilters: repos ? decodeURIComponent(repos as string).split(',') : [], - repoScope: scope, + repoScope: await getScope(req, repoScope), }; try { const docSearchClient = new DocumentSearchClient(new EsClientWithRequest(req), log); @@ -134,15 +118,10 @@ export function documentSearchRoute(router: CodeServerRouter, log: Logger) { page = parseInt(p as string, 10); } - let scope: string[] = []; - if (typeof repoScope === 'string') { - scope = repoScope.split(','); - } - const searchReq: DocumentSearchRequest = { query: q as string, page, - repoScope: scope, + repoScope: await getScope(req, repoScope), }; try { const docSearchClient = new DocumentSearchClient(new EsClientWithRequest(req), log); @@ -166,13 +145,17 @@ export function documentSearchRoute(router: CodeServerRouter, log: Logger) { method: 'POST', async handler(req: RequestFacade) { const reqs: StackTraceSnippetsRequest[] = (req.payload as any).requests; + const scopes = new Set( + await getReferenceHelper(req.getSavedObjectsClient()).findReferences() + ); return await Promise.all( reqs.map((stacktraceReq: StackTraceSnippetsRequest) => { const integClient = new IntegrationsSearchClient(new EsClientWithRequest(req), log); return Promise.all( stacktraceReq.stacktraceItems.map((stacktrace: StackTraceItem) => { + const repoUris = stacktraceReq.repoUris.filter(uri => scopes.has(uri)); const integrationReq: ResolveSnippetsRequest = { - repoUris: stacktraceReq.repoUris, + repoUris, revision: stacktraceReq.revision, filePath: stacktrace.filePath, lineNumStart: stacktrace.lineNumStart, @@ -195,15 +178,10 @@ export function symbolSearchRoute(router: CodeServerRouter, log: Logger) { page = parseInt(p as string, 10); } - let scope: string[] = []; - if (typeof repoScope === 'string') { - scope = repoScope.split(','); - } - const searchReq: SymbolSearchRequest = { query: q as string, page, - repoScope: scope, + repoScope: await getScope(req, repoScope), }; try { const symbolSearchClient = new SymbolSearchClient(new EsClientWithRequest(req), log); @@ -238,18 +216,11 @@ export function commitSearchRoute(router: CodeServerRouter, log: Logger) { page = parseInt(p as string, 10); } - let scope: string[] = []; - if (typeof repoScope === 'string') { - scope = [repoScope]; - } else if (Array.isArray(repoScope)) { - scope = repoScope; - } - const searchReq: CommitSearchRequest = { query: q as string, page, repoFilters: repos ? decodeURIComponent(repos as string).split(',') : [], - repoScope: scope, + repoScope: await getScope(req, repoScope), }; try { const commitSearchClient = new CommitSearchClient(new EsClientWithRequest(req), log); @@ -261,3 +232,15 @@ export function commitSearchRoute(router: CodeServerRouter, log: Logger) { }, }); } + +async function getScope(req: RequestFacade, repoScope: string | string[]): Promise { + let scope: string[] = await getReferenceHelper(req.getSavedObjectsClient()).findReferences(); + if (typeof repoScope === 'string') { + const uriSet = new Set(repoScope.split(',')); + scope = scope.filter(uri => uriSet.has(uri)); + } else if (Array.isArray(repoScope)) { + const uriSet = new Set(repoScope); + scope = scope.filter(uri => uriSet.has(uri)); + } + return scope; +} diff --git a/x-pack/legacy/plugins/code/server/routes/status.ts b/x-pack/legacy/plugins/code/server/routes/status.ts index c4e9519aa2529..56b2972bd4147 100644 --- a/x-pack/legacy/plugins/code/server/routes/status.ts +++ b/x-pack/legacy/plugins/code/server/routes/status.ts @@ -17,6 +17,7 @@ import { EsClientWithRequest } from '../utils/esclient_with_request'; import { CodeServices } from '../distributed/code_services'; import { GitServiceDefinition, LspServiceDefinition } from '../distributed/apis'; import { Endpoint } from '../distributed/resource_locator'; +import { getReferenceHelper } from '../utils/repository_reference_helper'; export function statusRoute(router: CodeServerRouter, codeServices: CodeServices) { const gitService = codeServices.serviceFor(GitServiceDefinition); @@ -115,7 +116,8 @@ export function statusRoute(router: CodeServerRouter, codeServices: CodeServices try { // Check if the repository already exists - await repoObjectClient.getRepository(uri); + const repo = await repoObjectClient.getRepository(uri); + await getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repo.uri); } catch (e) { return Boom.notFound(`repo ${uri} not found`); } diff --git a/x-pack/legacy/plugins/code/server/routes/workspace.ts b/x-pack/legacy/plugins/code/server/routes/workspace.ts index 708c2ebe8ac7f..8a112af297245 100644 --- a/x-pack/legacy/plugins/code/server/routes/workspace.ts +++ b/x-pack/legacy/plugins/code/server/routes/workspace.ts @@ -11,6 +11,7 @@ import { ServerOptions } from '../server_options'; import { CodeServerRouter } from '../security'; import { CodeServices } from '../distributed/code_services'; import { WorkspaceDefinition } from '../distributed/apis'; +import { getReferenceHelper } from '../utils/repository_reference_helper'; export function workspaceRoute( router: CodeServerRouter, @@ -33,6 +34,7 @@ export function workspaceRoute( method: 'POST', async handler(req: RequestFacade) { const repoUri = req.params.uri as string; + getReferenceHelper(req.getSavedObjectsClient()).ensureReference(repoUri); const revision = req.params.revision as string; const repoConfig = serverOptions.repoConfigs[repoUri]; const force = !!(req.query as RequestQueryFacade).force; diff --git a/x-pack/legacy/plugins/code/server/search/commit_search_client.ts b/x-pack/legacy/plugins/code/server/search/commit_search_client.ts index 5b46371fce531..82a5e7bf5e64e 100644 --- a/x-pack/legacy/plugins/code/server/search/commit_search_client.ts +++ b/x-pack/legacy/plugins/code/server/search/commit_search_client.ts @@ -11,6 +11,7 @@ import { CommitSearchRequest, CommitSearchResult, CommitSearchResultItem, + emptyCommitSearchResult, } from '../../model'; import { CommitSearchIndexWithScope, CommitIndexNamePrefix } from '../indexer/schema'; import { EsClient } from '../lib/esqueue'; @@ -30,6 +31,9 @@ export class CommitSearchClient extends AbstractSearchClient { const index = req.repoScope ? CommitSearchIndexWithScope(req.repoScope) : `${CommitIndexNamePrefix}*`; + if (index.length === 0) { + return emptyCommitSearchResult(req.query); + } // Post filters for repository let repositoryPostFilters: object[] = []; diff --git a/x-pack/legacy/plugins/code/server/search/document_search_client.ts b/x-pack/legacy/plugins/code/server/search/document_search_client.ts index 860557a6602c9..21f19b36b1cc4 100644 --- a/x-pack/legacy/plugins/code/server/search/document_search_client.ts +++ b/x-pack/legacy/plugins/code/server/search/document_search_client.ts @@ -14,6 +14,7 @@ import { SearchResultItem, SourceHit, SourceRange, + emptyDocumentSearchResult, } from '../../model'; import { DocumentIndexNamePrefix, DocumentSearchIndexWithScope } from '../indexer/schema'; import { EsClient } from '../lib/esqueue'; @@ -104,6 +105,9 @@ export class DocumentSearchClient extends AbstractSearchClient { const index = req.repoScope ? DocumentSearchIndexWithScope(req.repoScope) : `${DocumentIndexNamePrefix}*`; + if (index.length === 0) { + return emptyDocumentSearchResult(req.query); + } const rawRes = await this.client.search({ index, @@ -241,6 +245,9 @@ export class DocumentSearchClient extends AbstractSearchClient { const index = req.repoScope ? DocumentSearchIndexWithScope(req.repoScope) : `${DocumentIndexNamePrefix}*`; + if (index.length === 0) { + return emptyDocumentSearchResult(req.query); + } const queryStr = req.query.toLowerCase(); diff --git a/x-pack/legacy/plugins/code/server/search/integrations_search_client.ts b/x-pack/legacy/plugins/code/server/search/integrations_search_client.ts index 0a4b40297fbec..2c0a986745429 100644 --- a/x-pack/legacy/plugins/code/server/search/integrations_search_client.ts +++ b/x-pack/legacy/plugins/code/server/search/integrations_search_client.ts @@ -10,6 +10,7 @@ import { ResolveSnippetsRequest, SearchResultItem, SourceHit, + emptyIntegrationsSearchResult, } from '../../model'; import { DocumentSearchIndexWithScope } from '../indexer/schema'; import { EsClient } from '../lib/esqueue'; @@ -24,6 +25,9 @@ export class IntegrationsSearchClient extends DocumentSearchClient { public async resolveSnippets(req: ResolveSnippetsRequest): Promise { const { repoUris, filePath, lineNumStart, lineNumEnd } = req; const index = DocumentSearchIndexWithScope(repoUris); + if (index.length === 0) { + return emptyIntegrationsSearchResult(); + } let fallback = false; let rawRes = await this.client.search({ diff --git a/x-pack/legacy/plugins/code/server/search/repository_object_client.ts b/x-pack/legacy/plugins/code/server/search/repository_object_client.ts index e0e7badde4502..c7deb2faa3e7f 100644 --- a/x-pack/legacy/plugins/code/server/search/repository_object_client.ts +++ b/x-pack/legacy/plugins/code/server/search/repository_object_client.ts @@ -20,6 +20,7 @@ import { RepositoryIndexStatusReservedField, RepositoryRandomPathReservedField, RepositoryReservedField, + RepositorySearchIndexWithScope, } from '../indexer/schema'; import { EsClient } from '../lib/esqueue'; @@ -54,9 +55,20 @@ export class RepositoryObjectClient { return await this.getRepositoryObject(repoUri, RepositoryRandomPathReservedField); } + public async getRepositories(uris: string[]): Promise { + if (uris.length === 0) { + return []; + } + return this.getRepositoriesInternal(RepositorySearchIndexWithScope(uris)); + } + public async getAllRepositories(): Promise { + return await this.getRepositoriesInternal(`${RepositoryIndexNamePrefix}*`); + } + + private async getRepositoriesInternal(index: string) { const res = await this.esClient.search({ - index: `${RepositoryIndexNamePrefix}*`, + index, body: { query: { exists: { diff --git a/x-pack/legacy/plugins/code/server/search/repository_search_client.ts b/x-pack/legacy/plugins/code/server/search/repository_search_client.ts index 143537a93f74b..6e6f42cbd7b59 100644 --- a/x-pack/legacy/plugins/code/server/search/repository_search_client.ts +++ b/x-pack/legacy/plugins/code/server/search/repository_search_client.ts @@ -4,7 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Repository, RepositorySearchRequest, RepositorySearchResult } from '../../model'; +import { + Repository, + RepositorySearchRequest, + RepositorySearchResult, + emptyRepositorySearchResult, +} from '../../model'; import { RepositoryIndexNamePrefix, RepositoryReservedField, @@ -28,6 +33,10 @@ export class RepositorySearchClient extends AbstractSearchClient { ? RepositorySearchIndexWithScope(req.repoScope) : `${RepositoryIndexNamePrefix}*`; + if (index.length === 0) { + return emptyRepositorySearchResult(); + } + const queryStr = req.query.toLowerCase(); const rawRes = await this.client.search({ diff --git a/x-pack/legacy/plugins/code/server/search/symbol_search_client.ts b/x-pack/legacy/plugins/code/server/search/symbol_search_client.ts index 5d0d285d73ae2..686961333aed0 100644 --- a/x-pack/legacy/plugins/code/server/search/symbol_search_client.ts +++ b/x-pack/legacy/plugins/code/server/search/symbol_search_client.ts @@ -6,7 +6,7 @@ import { DetailSymbolInformation } from '@elastic/lsp-extension'; -import { SymbolSearchRequest, SymbolSearchResult } from '../../model'; +import { SymbolSearchRequest, SymbolSearchResult, emptySymbolSearchResult } from '../../model'; import { SymbolIndexNamePrefix, SymbolSearchIndexWithScope } from '../indexer/schema'; import { EsClient } from '../lib/esqueue'; import { Logger } from '../log'; @@ -17,10 +17,10 @@ export class SymbolSearchClient extends AbstractSearchClient { super(client, log); } - public async findByQname(qname: string): Promise { + public async findByQname(qname: string, repoScope: string[]): Promise { const [from, size] = [0, 1]; const rawRes = await this.client.search({ - index: `${SymbolIndexNamePrefix}*`, + index: SymbolSearchIndexWithScope(repoScope), body: { from, size, @@ -42,6 +42,9 @@ export class SymbolSearchClient extends AbstractSearchClient { const index = req.repoScope ? SymbolSearchIndexWithScope(req.repoScope) : `${SymbolIndexNamePrefix}*`; + if (index.length === 0) { + return emptySymbolSearchResult(); + } const rawRes = await this.client.search({ index, diff --git a/x-pack/legacy/plugins/code/server/utils/repository_reference_helper.ts b/x-pack/legacy/plugins/code/server/utils/repository_reference_helper.ts new file mode 100644 index 0000000000000..dcbd551a5eac2 --- /dev/null +++ b/x-pack/legacy/plugins/code/server/utils/repository_reference_helper.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract } from 'src/core/server'; +import Boom from 'boom'; +import { SAVED_OBJ_REPO } from '../../common/constants'; + +export interface RepositoryReferenceHelper { + /** + * Creates a reference from the current namespace to the given repository. + * + * @param uri The uri of the repository. + * @returns true if there is no other references for the repository, false otherwise. + */ + createReference(uri: string): Promise; + + /** + * Checks whether there is a reference from the current namespace to the given repository. + * + * @param uri The uri of the repository. + * @returns true if there is a reference from the current namespace to the given repository, false otherwise. + */ + hasReference(uri: string): Promise; + + /** + * Throws an error if there is no reference from the current namespace to the given repository. + * there is none. + * + * @param uri The uri of the repository. + */ + ensureReference(uri: string): Promise; + + /** + * Deletes the reference from the current namespace to the given repository. + * + * @param uri The uri of the repository. + * @returns True if there is no more references to the repository after the deletion. + */ + deleteReference(uri: string): Promise; + + /** + * Finds all repository uris of which the current namespace has references. + * + * @returns uris the current namespace references to. + */ + findReferences(): Promise; +} + +/** + * A factory function helps to create a appropriate helper instance. + * + * @param client A saved objects client instance. + * @returns An helper instance. + */ +export function getReferenceHelper(client: SavedObjectsClientContract): RepositoryReferenceHelper { + return new DefaultReferenceHelper(client); +} + +class DefaultReferenceHelper implements RepositoryReferenceHelper { + constructor(private readonly client: SavedObjectsClientContract) {} + + async createReference(uri: string): Promise { + try { + await this.client.create( + SAVED_OBJ_REPO, + { + uri, + }, + { + id: uri, + } + ); + return true; + } catch (e) { + if (Boom.isBoom(e) && e.output.statusCode === 409) { + return false; + } + throw e; + } + } + + async deleteReference(uri: string): Promise { + await this.client.delete(SAVED_OBJ_REPO, uri); + return true; + } + + async ensureReference(uri: string): Promise { + await this.client.get(SAVED_OBJ_REPO, uri); + } + + async hasReference(uri: string): Promise { + try { + await this.ensureReference(uri); + return true; + } catch (e) { + if (Boom.isBoom(e) && e.output.statusCode === 404) { + return false; + } + throw e; + } + } + + async findReferences(): Promise { + const resp = await this.client.find({ + type: SAVED_OBJ_REPO, + }); + return resp.saved_objects.map(obj => obj.attributes.uri as string); + } +} diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx index 83d4760259d9b..aa66febc5b7c4 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_jobs.tsx @@ -8,7 +8,7 @@ import createContainer from 'constate-latest'; import { useMemo, useCallback, useEffect } from 'react'; import { callGetMlModuleAPI } from './api/ml_get_module'; -import { bucketSpan } from '../../../../common/log_analysis'; +import { bucketSpan, getJobId } from '../../../../common/log_analysis'; import { useTrackedPromise } from '../../../utils/use_tracked_promise'; import { callJobsSummaryAPI } from './api/ml_get_jobs_summary_api'; import { callSetupMlModuleAPI, SetupMlModuleResponsePayload } from './api/ml_setup_module_api'; @@ -138,6 +138,12 @@ export const useLogAnalysisJobs = ({ fetchModuleDefinition(); }, [fetchModuleDefinition]); + const jobIds = useMemo(() => { + return { + 'log-entry-rate': getJobId(spaceId, sourceId, 'log-entry-rate'), + }; + }, [sourceId, spaceId]); + return { fetchJobStatus, isLoadingSetupStatus, @@ -149,6 +155,7 @@ export const useLogAnalysisJobs = ({ viewSetupForReconfiguration, viewSetupForUpdate, viewResults, + jobIds, }; }; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx index 33037aac9afe5..e846d4e9e4ac5 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/page_results_content.tsx @@ -19,9 +19,9 @@ import numeral from '@elastic/numeral'; import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment'; import React, { useCallback, useContext, useMemo, useState } from 'react'; +import euiStyled from '../../../../../../common/eui_styled_components'; import { TimeRange } from '../../../../common/http_api/shared/time_range'; import { bucketSpan } from '../../../../common/log_analysis'; -import euiStyled from '../../../../../../common/eui_styled_components'; import { LoadingPage } from '../../../components/loading_page'; import { LogAnalysisJobs, @@ -134,6 +134,7 @@ export const AnalysisResultsContent = ({ setupStatus, viewSetupForReconfiguration, viewSetupForUpdate, + jobIds, } = useContext(LogAnalysisJobs.Context); useInterval(() => { @@ -214,6 +215,7 @@ export const AnalysisResultsContent = ({ setTimeRange={handleChartTimeRangeChange} setupStatus={setupStatus} timeRange={queryTimeRange} + jobId={jobIds['log-entry-rate']} /> diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/analyze_in_ml_button.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/analyze_in_ml_button.tsx new file mode 100644 index 0000000000000..852ce20e37d41 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/analyze_in_ml_button.tsx @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import url from 'url'; +import { EuiButton } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import chrome from 'ui/chrome'; +import { QueryString } from 'ui/utils/query_string'; +import { encode } from 'rison-node'; +import { TimeRange } from '../../../../../common/http_api/shared/time_range'; + +export const AnalyzeInMlButton: React.FunctionComponent<{ + jobId: string; + partition?: string; + timeRange: TimeRange; +}> = ({ jobId, partition, timeRange }) => { + const pathname = chrome.addBasePath('/app/ml'); + const buttonLabel = ( + + ); + return partition ? ( + + {buttonLabel} + + ) : ( + + {buttonLabel} + + ); +}; + +const getOverallAnomalyExplorerLink = (pathname: string, jobId: string, timeRange: TimeRange) => { + const { from, to } = convertTimeRangeToParams(timeRange); + + const _g = encode({ + ml: { + jobIds: [jobId], + }, + time: { + from, + to, + }, + }); + + const hash = `/explorer?${QueryString.encode({ _g })}`; + + return url.format({ + pathname, + hash, + }); +}; + +const getPartitionSpecificSingleMetricViewerLink = ( + pathname: string, + jobId: string, + partition: string, + timeRange: TimeRange +) => { + const { from, to } = convertTimeRangeToParams(timeRange); + + const _g = encode({ + ml: { + jobIds: [jobId], + }, + time: { + from, + to, + mode: 'absolute', + }, + }); + + const _a = encode({ + mlTimeSeriesExplorer: { + entities: { 'event.dataset': partition }, + }, + }); + + const hash = `/timeseriesexplorer?${QueryString.encode({ _g, _a })}`; + + return url.format({ + pathname, + hash, + }); +}; + +const convertTimeRangeToParams = (timeRange: TimeRange): { from: string; to: string } => { + return { + from: new Date(timeRange.startTime).toISOString(), + to: new Date(timeRange.endTime).toISOString(), + }; +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx index a6f2ca71068c2..43cfbb306717d 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/expanded_row.tsx @@ -7,7 +7,7 @@ import React, { useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import numeral from '@elastic/numeral'; -import { EuiFlexGroup, EuiFlexItem, EuiStat } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiStat, EuiSpacer } from '@elastic/eui'; import { AnomaliesChart } from './chart'; import { GetLogEntryRateSuccessResponsePayload } from '../../../../../../common/http_api/log_analysis/results/log_entry_rate'; import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; @@ -17,6 +17,7 @@ import { formatAnomalyScore, getTotalNumberOfLogEntriesForPartition, } from '../helpers/data_formatters'; +import { AnalyzeInMlButton } from '../analyze_in_ml_button'; export const AnomaliesTableExpandedRow: React.FunctionComponent<{ partitionId: string; @@ -24,7 +25,8 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{ results: GetLogEntryRateSuccessResponsePayload['data']; setTimeRange: (timeRange: TimeRange) => void; timeRange: TimeRange; -}> = ({ results, timeRange, setTimeRange, topAnomalyScore, partitionId }) => { + jobId: string; +}> = ({ results, timeRange, setTimeRange, topAnomalyScore, partitionId, jobId }) => { const logEntryRateSeries = useMemo( () => results && results.histogramBuckets @@ -83,6 +85,12 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{ )} reverse /> + + + + + +
    ); diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx index 1bba8995f0c76..5aa5891d7981d 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/index.tsx @@ -5,7 +5,6 @@ */ import { - EuiButton, EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, @@ -16,7 +15,6 @@ import { } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; import React, { useMemo } from 'react'; import euiStyled from '../../../../../../../../common/eui_styled_components'; @@ -32,6 +30,7 @@ import { import { AnomaliesChart } from './chart'; import { AnomaliesTable } from './table'; import { LogAnalysisJobProblemIndicator } from '../../../../../components/logging/log_analysis_job_status'; +import { AnalyzeInMlButton } from '../analyze_in_ml_button'; export const AnomaliesResults: React.FunctionComponent<{ isLoading: boolean; @@ -42,6 +41,7 @@ export const AnomaliesResults: React.FunctionComponent<{ timeRange: TimeRange; viewSetupForReconfiguration: () => void; viewSetupForUpdate: () => void; + jobId: string; }> = ({ isLoading, jobStatus, @@ -51,6 +51,7 @@ export const AnomaliesResults: React.FunctionComponent<{ timeRange, viewSetupForReconfiguration, viewSetupForUpdate, + jobId, }) => { const title = i18n.translate('xpack.infra.logs.analysis.anomaliesSectionTitle', { defaultMessage: 'Anomalies', @@ -105,12 +106,7 @@ export const AnomaliesResults: React.FunctionComponent<{ - - - +
    @@ -193,7 +189,12 @@ export const AnomaliesResults: React.FunctionComponent<{ - + )} diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/table.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/table.tsx index 2a8ac44d09083..88ffcae6e9dea 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/table.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/analysis/sections/anomalies/table.tsx @@ -52,7 +52,8 @@ export const AnomaliesTable: React.FunctionComponent<{ results: GetLogEntryRateSuccessResponsePayload['data']; setTimeRange: (timeRange: TimeRange) => void; timeRange: TimeRange; -}> = ({ results, timeRange, setTimeRange }) => { + jobId: string; +}> = ({ results, timeRange, setTimeRange, jobId }) => { const tableItems: TableItem[] = useMemo(() => { return Object.entries(getTopAnomalyScoresByPartition(results)).map(([key, value]) => { return { @@ -112,6 +113,7 @@ export const AnomaliesTable: React.FunctionComponent<{ topAnomalyScore={item.topAnomalyScore} setTimeRange={setTimeRange} timeRange={timeRange} + jobId={jobId} /> ), }; diff --git a/x-pack/legacy/plugins/infra/public/utils/use_kibana_injected_var.ts b/x-pack/legacy/plugins/infra/public/utils/use_kibana_injected_var.ts index bcd36f72bef07..4afcb4fd88430 100644 --- a/x-pack/legacy/plugins/infra/public/utils/use_kibana_injected_var.ts +++ b/x-pack/legacy/plugins/infra/public/utils/use_kibana_injected_var.ts @@ -14,6 +14,5 @@ import { npSetup } from 'ui/new_platform'; */ export const useKibanaInjectedVar = (name: string, defaultValue?: unknown) => { const injectedMetadata = npSetup.core.injectedMetadata; - return injectedMetadata.getInjectedVar(name, defaultValue); }; diff --git a/x-pack/legacy/plugins/ml/common/types/modules.ts b/x-pack/legacy/plugins/ml/common/types/modules.ts index cb012c3641f3b..18879304d7c7f 100644 --- a/x-pack/legacy/plugins/ml/common/types/modules.ts +++ b/x-pack/legacy/plugins/ml/common/types/modules.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 { SavedObjectAttributes } from 'src/core/server/types'; import { Datafeed, Job } from '../../public/jobs/new_job_new/common/job_creator/configs'; -import { SavedObjectAttributes } from '../../../../../../target/types/core/server'; export interface ModuleJob { id: string; diff --git a/x-pack/legacy/plugins/ml/public/access_denied/index.html b/x-pack/legacy/plugins/ml/public/access_denied/index.html deleted file mode 100644 index e085e089b2728..0000000000000 --- a/x-pack/legacy/plugins/ml/public/access_denied/index.html +++ /dev/null @@ -1,46 +0,0 @@ - -
    -
    -
    -
    - - -
    -
    -

    -
    -
    - -
    - - -
    -
    diff --git a/x-pack/legacy/plugins/ml/public/access_denied/index.js b/x-pack/legacy/plugins/ml/public/access_denied/index.js deleted file mode 100644 index 52b813917e9be..0000000000000 --- a/x-pack/legacy/plugins/ml/public/access_denied/index.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - - - -import uiRoutes from 'ui/routes'; -import uiChrome from 'ui/chrome'; -import template from './index.html'; - -uiRoutes.when('/access-denied', { - template, - controllerAs: 'accessDenied', - controller($window, kbnUrl, kbnBaseUrl) { - this.goToKibana = () => { - $window.location.href = uiChrome.getBasePath() + kbnBaseUrl; - }; - - this.retry = () => { - return kbnUrl.redirect('/jobs'); - }; - } -}); diff --git a/x-pack/legacy/plugins/ml/public/access_denied/index.tsx b/x-pack/legacy/plugins/ml/public/access_denied/index.tsx new file mode 100644 index 0000000000000..883754896487e --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/access_denied/index.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import uiRoutes from 'ui/routes'; +import { I18nContext } from 'ui/i18n'; +// @ts-ignore +import { uiModules } from 'ui/modules'; +import { AccessDeniedPage } from './page'; + +const module = uiModules.get('apps/ml', ['react']); + +const template = ``; + +uiRoutes.when('/access-denied', { + template, +}); + +module.directive('accessDenied', function() { + return { + scope: {}, + restrict: 'E', + link: async (scope: ng.IScope, element: ng.IAugmentedJQuery) => { + ReactDOM.render( + {React.createElement(AccessDeniedPage)}, + element[0] + ); + + element.on('$destroy', () => { + ReactDOM.unmountComponentAtNode(element[0]); + scope.$destroy(); + }); + }, + }; +}); diff --git a/x-pack/legacy/plugins/ml/public/access_denied/page.tsx b/x-pack/legacy/plugins/ml/public/access_denied/page.tsx new file mode 100644 index 0000000000000..1c908e114cbeb --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/access_denied/page.tsx @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment } from 'react'; + +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { + EuiCallOut, + EuiPage, + EuiPageBody, + EuiPageContentBody, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiSpacer, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { NavigationMenu } from '../components/navigation_menu'; + +export const AccessDeniedPage = () => ( + + + + + + + +

    + +

    +
    +
    +
    + + + + +

    + +

    +
    +
    +
    +
    +
    +
    +); diff --git a/x-pack/legacy/plugins/ml/public/app.js b/x-pack/legacy/plugins/ml/public/app.js index a97150eaa3d2c..5208f9b24bc27 100644 --- a/x-pack/legacy/plugins/ml/public/app.js +++ b/x-pack/legacy/plugins/ml/public/app.js @@ -16,7 +16,6 @@ import 'plugins/ml/access_denied'; import 'plugins/ml/jobs'; import 'plugins/ml/overview'; import 'plugins/ml/services/calendar_service'; -import 'plugins/ml/components/messagebar'; import 'plugins/ml/data_frame_analytics'; import 'plugins/ml/datavisualizer'; import 'plugins/ml/explorer'; diff --git a/x-pack/legacy/plugins/ml/public/components/data_recognizer/data_recognizer_directive.js b/x-pack/legacy/plugins/ml/public/components/data_recognizer/data_recognizer_directive.js deleted file mode 100644 index 81186fb9af812..0000000000000 --- a/x-pack/legacy/plugins/ml/public/components/data_recognizer/data_recognizer_directive.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - - - - -import 'ngreact'; -import { DataRecognizer } from './data_recognizer'; - -import { uiModules } from 'ui/modules'; -const module = uiModules.get('apps/ml', ['react']); -module.directive('mlDataRecognizer', function (reactDirective) { - return reactDirective(DataRecognizer, undefined, { restrict: 'AE' }); -}); diff --git a/x-pack/legacy/plugins/ml/public/components/data_recognizer/index.ts b/x-pack/legacy/plugins/ml/public/components/data_recognizer/index.ts index bb5a2ac4e479f..cc66fdac85af7 100644 --- a/x-pack/legacy/plugins/ml/public/components/data_recognizer/index.ts +++ b/x-pack/legacy/plugins/ml/public/components/data_recognizer/index.ts @@ -4,6 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import './data_recognizer_directive'; - export { DataRecognizer } from './data_recognizer'; diff --git a/x-pack/legacy/plugins/ml/public/components/field_title_bar/field_title_bar_directive.js b/x-pack/legacy/plugins/ml/public/components/field_title_bar/field_title_bar_directive.js deleted file mode 100644 index 96ea2e7e27c37..0000000000000 --- a/x-pack/legacy/plugins/ml/public/components/field_title_bar/field_title_bar_directive.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - - - -import React from 'react'; -import ReactDOM from 'react-dom'; - -import { FieldTitleBar } from './field_title_bar'; -import { I18nContext } from 'ui/i18n'; - -import { uiModules } from 'ui/modules'; -const module = uiModules.get('apps/ml'); - -module.directive('mlFieldTitleBar', function () { - return { - restrict: 'E', - replace: false, - scope: { - card: '=' - }, - link: function (scope, element) { - scope.$watch('card', updateComponent); - - updateComponent(); - - function updateComponent() { - const props = { - card: scope.card - }; - - ReactDOM.render( - - {React.createElement(FieldTitleBar, props)} - , - element[0] - ); - } - } - }; -}); diff --git a/x-pack/legacy/plugins/ml/public/components/field_title_bar/index.js b/x-pack/legacy/plugins/ml/public/components/field_title_bar/index.js index f04b029bd7c74..e969d15d3abfa 100644 --- a/x-pack/legacy/plugins/ml/public/components/field_title_bar/index.js +++ b/x-pack/legacy/plugins/ml/public/components/field_title_bar/index.js @@ -4,8 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ - - -import './field_title_bar_directive'; - export { FieldTitleBar } from './field_title_bar'; diff --git a/x-pack/legacy/plugins/ml/public/components/field_type_icon/field_type_icon_directive.js b/x-pack/legacy/plugins/ml/public/components/field_type_icon/field_type_icon_directive.js deleted file mode 100644 index 2be9129df6f7c..0000000000000 --- a/x-pack/legacy/plugins/ml/public/components/field_type_icon/field_type_icon_directive.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - - - -import React from 'react'; -import ReactDOM from 'react-dom'; - -import { FieldTypeIcon } from './field_type_icon.js'; -import { I18nContext } from 'ui/i18n'; - -import { uiModules } from 'ui/modules'; -const module = uiModules.get('apps/ml'); - -module.directive('mlFieldTypeIcon', function () { - return { - restrict: 'E', - replace: false, - scope: { - tooltipEnabled: '=', - type: '=' - }, - link: function (scope, element) { - scope.$watch('type', updateComponent); - - updateComponent(); - - function updateComponent() { - const props = { - tooltipEnabled: scope.tooltipEnabled, - type: scope.type - }; - - ReactDOM.render( - - {React.createElement(FieldTypeIcon, props)} - , - element[0] - ); - } - } - }; -}); diff --git a/x-pack/legacy/plugins/ml/public/components/field_type_icon/index.js b/x-pack/legacy/plugins/ml/public/components/field_type_icon/index.js index 93f2f659b52d5..c9c8fc3e5e751 100644 --- a/x-pack/legacy/plugins/ml/public/components/field_type_icon/index.js +++ b/x-pack/legacy/plugins/ml/public/components/field_type_icon/index.js @@ -5,7 +5,4 @@ */ - -import './field_type_icon_directive'; - export { FieldTypeIcon } from './field_type_icon'; diff --git a/x-pack/legacy/plugins/ml/public/components/full_time_range_selector/full_time_range_selector_directive.js b/x-pack/legacy/plugins/ml/public/components/full_time_range_selector/full_time_range_selector_directive.js deleted file mode 100644 index 5c78d376317ac..0000000000000 --- a/x-pack/legacy/plugins/ml/public/components/full_time_range_selector/full_time_range_selector_directive.js +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - - -import React from 'react'; -import ReactDOM from 'react-dom'; - -import { uiModules } from 'ui/modules'; -const module = uiModules.get('apps/ml', ['react']); - -import { I18nContext } from 'ui/i18n'; - -import { FullTimeRangeSelector } from './index'; - -// Angular directive wrapper for the 'Use full time range' button. -module.directive('mlFullTimeRangeSelector', function () { - return { - restrict: 'E', - replace: true, - scope: { - indexPattern: '=', - disabled: '=', - query: '=' - }, - link: (scope, element) => { - - function renderComponent() { - const props = { - indexPattern: scope.indexPattern, - query: scope.query, - disabled: scope.disabled - }; - - ReactDOM.render( - - {React.createElement(FullTimeRangeSelector, props)} - , - element[0] - ); - } - - renderComponent(); - - // As the directive is only used in the job wizards and the data visualizer, - // it is safe to only watch the disabled property. - scope.$watch('disabled', renderComponent); - - element.on('$destroy', () => { - scope.$destroy(); - }); - - } - - }; -}); diff --git a/x-pack/legacy/plugins/ml/public/components/messagebar/__tests__/messagebar.js b/x-pack/legacy/plugins/ml/public/components/messagebar/__tests__/messagebar.js deleted file mode 100644 index 5cbcdd9b8afc2..0000000000000 --- a/x-pack/legacy/plugins/ml/public/components/messagebar/__tests__/messagebar.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - - - -import ngMock from 'ng_mock'; -import expect from '@kbn/expect'; - -describe('ML - Message Bar Controller', () => { - beforeEach(() => { - ngMock.module('kibana'); - }); - - it('Initialize Message Bar Controller', (done) => { - ngMock.inject(function ($rootScope, $controller) { - const scope = $rootScope.$new(); - - expect(() => { - $controller('MlMessageBarController', { $scope: scope }); - }).to.not.throwError(); - - expect(scope.messages).to.eql([]); - done(); - }); - }); -}); diff --git a/x-pack/legacy/plugins/ml/public/components/messagebar/_index.scss b/x-pack/legacy/plugins/ml/public/components/messagebar/_index.scss deleted file mode 100644 index 55e87d2640465..0000000000000 --- a/x-pack/legacy/plugins/ml/public/components/messagebar/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'messagebar'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/ml/public/components/messagebar/_messagebar.scss b/x-pack/legacy/plugins/ml/public/components/messagebar/_messagebar.scss deleted file mode 100644 index a4d48ff64ad6b..0000000000000 --- a/x-pack/legacy/plugins/ml/public/components/messagebar/_messagebar.scss +++ /dev/null @@ -1,35 +0,0 @@ - -ml-message-bar { - font-size: $euiFontSizeS; - - // SASSTODO: Needs proper selector - div { - padding: $euiSizeXS; - } - - .ml-message { - border-bottom: 1px solid $euiColorEmptyShade; - color: $euiColorEmptyShade; - margin: 0px; - border-radius: $euiBorderRadius; - padding: $euiSizeXS $euiSizeS; - - // SASSTODO: Needs proper selector - a { - color: $euiColorEmptyShade; - float: right; - } - } - - // SASSTODO: Needs proper variables - .ml-message-info { - background-color: #858585; - } - .ml-message-warning { - background-color: #FF7800; - } - .ml-message-error { - background-color: #e74c3c; - } - -} diff --git a/x-pack/legacy/plugins/ml/public/components/messagebar/index.js b/x-pack/legacy/plugins/ml/public/components/messagebar/index.ts similarity index 80% rename from x-pack/legacy/plugins/ml/public/components/messagebar/index.js rename to x-pack/legacy/plugins/ml/public/components/messagebar/index.ts index 6dc4892b37a2b..35130d28a890d 100644 --- a/x-pack/legacy/plugins/ml/public/components/messagebar/index.js +++ b/x-pack/legacy/plugins/ml/public/components/messagebar/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import './messagebar'; +export { mlMessageBarService } from './messagebar_service'; diff --git a/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar.html b/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar.html deleted file mode 100644 index ef06d3e4c48fe..0000000000000 --- a/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar.html +++ /dev/null @@ -1,7 +0,0 @@ -
    -
    - {{msg.text}} x -
    -
    \ No newline at end of file diff --git a/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar.js b/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar.js deleted file mode 100644 index 293b0bc507b29..0000000000000 --- a/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - - - -import template from './messagebar.html'; - -import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service'; - -import { uiModules } from 'ui/modules'; -const module = uiModules.get('apps/ml'); - -module - .controller('MlMessageBarController', function ($scope) { - $scope.messages = mlMessageBarService.getMessages(); - $scope.removeMessage = mlMessageBarService.removeMessage; - }) - .directive('mlMessageBar', function () { - return { - restrict: 'AE', - template - }; - }); diff --git a/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar_service.d.ts b/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar_service.d.ts index 747ee928ac0c1..29a537a7ca8d8 100644 --- a/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar_service.d.ts +++ b/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar_service.d.ts @@ -5,13 +5,6 @@ */ declare interface MlMessageBarService { - getMessages(): any[]; - addMessage(msg: any): void; - removeMessage(index: number): void; - clear(): void; - info(text: any): void; - warning(text: any): void; - error(text: any, resp?: any): void; notify: { error(text: any, resp?: any): void; }; diff --git a/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar_service.js b/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar_service.js index 79412c1d2d72f..a373ed71772f0 100644 --- a/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar_service.js +++ b/x-pack/legacy/plugins/ml/public/components/messagebar/messagebar_service.js @@ -9,57 +9,6 @@ import { toastNotifications } from 'ui/notify'; import { MLRequestFailure } from '../../util/ml_error'; import { i18n } from '@kbn/i18n'; -const messages = []; - - -const MSG_STYLE = { INFO: 'ml-message-info', WARNING: 'ml-message-warning', ERROR: 'ml-message-error' }; - -function getMessages() { - return messages; -} - -function addMessage(msg) { - if (messages.find(m => (m.text === msg.text && m.style === msg.style)) === undefined) { - messages.push(msg); - } -} - -function removeMessage(index) { - messages.splice(index, 1); -} - -function clear() { - messages.length = 0; -} - -function info(text) { - addMessage({ text, style: MSG_STYLE.INFO }); -} - -function warning(text) { - addMessage({ text, style: MSG_STYLE.WARNING }); -} - -function error(text, resp) { - text = `${text} ${expandErrorMessageObj(resp)}`; - addMessage({ text, style: MSG_STYLE.ERROR }); -} - -function expandErrorMessageObj(resp) { - let txt = ''; - if (resp !== undefined && typeof resp === 'object') { - try { - const respObj = JSON.parse(resp.response); - if (typeof respObj === 'object' && respObj.error !== undefined) { - txt = respObj.error.reason; - } - } catch(e) { - txt = resp.message; - } - } - return txt; -} - function errorNotify(text, resp) { let err = null; if (typeof text === 'object' && text.response !== undefined) { @@ -78,13 +27,6 @@ function errorNotify(text, resp) { } export const mlMessageBarService = { - getMessages, - addMessage, - removeMessage, - clear, - info, - warning, - error, notify: { error: errorNotify } diff --git a/x-pack/legacy/plugins/ml/public/components/navigation_menu/index.ts b/x-pack/legacy/plugins/ml/public/components/navigation_menu/index.ts index 55fe88ec90869..60baa8a9559e9 100644 --- a/x-pack/legacy/plugins/ml/public/components/navigation_menu/index.ts +++ b/x-pack/legacy/plugins/ml/public/components/navigation_menu/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import './navigation_menu_react_wrapper_directive'; +export { NavigationMenu } from './navigation_menu'; diff --git a/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu_react_wrapper_directive.js b/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu_react_wrapper_directive.js deleted file mode 100644 index 543f04b6a4125..0000000000000 --- a/x-pack/legacy/plugins/ml/public/components/navigation_menu/navigation_menu_react_wrapper_directive.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - - -import React from 'react'; -import ReactDOM from 'react-dom'; - -import { uiModules } from 'ui/modules'; -const module = uiModules.get('apps/ml'); - -import { NavigationMenu } from './navigation_menu'; - -module.directive('mlNavMenu', function () { - return { - restrict: 'E', - transclude: true, - link: function (scope, element, attrs) { - ReactDOM.render(, element[0]); - - element.on('$destroy', () => { - ReactDOM.unmountComponentAtNode(element[0]); - scope.$destroy(); - }); - } - }; -}); diff --git a/x-pack/legacy/plugins/ml/public/components/validate_job/index.js b/x-pack/legacy/plugins/ml/public/components/validate_job/index.ts similarity index 82% rename from x-pack/legacy/plugins/ml/public/components/validate_job/index.js rename to x-pack/legacy/plugins/ml/public/components/validate_job/index.ts index 07e6725e33518..fed452502cc89 100644 --- a/x-pack/legacy/plugins/ml/public/components/validate_job/index.js +++ b/x-pack/legacy/plugins/ml/public/components/validate_job/index.ts @@ -4,6 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ - - -import './validate_job_directive'; +export { ValidateJob } from './validate_job_view'; diff --git a/x-pack/legacy/plugins/ml/public/components/validate_job/validate_job_directive.js b/x-pack/legacy/plugins/ml/public/components/validate_job/validate_job_directive.js deleted file mode 100644 index 29455f3949157..0000000000000 --- a/x-pack/legacy/plugins/ml/public/components/validate_job/validate_job_directive.js +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - - - - -import 'ngreact'; - -import { wrapInI18nContext } from 'ui/i18n'; -import { uiModules } from 'ui/modules'; -const module = uiModules.get('apps/ml', ['react']); - -import { ValidateJob } from './validate_job_view'; -import { mlJobService } from 'plugins/ml/services/job_service'; - -module.directive('mlValidateJob', function (reactDirective) { - return reactDirective( - wrapInI18nContext(ValidateJob), - undefined, - { restrict: 'E' }, - { mlJobService } - ); -}); diff --git a/x-pack/legacy/plugins/ml/public/components/validate_job/validate_job_view.js b/x-pack/legacy/plugins/ml/public/components/validate_job/validate_job_view.js index 88e85cc5e530f..42ade2d9f9360 100644 --- a/x-pack/legacy/plugins/ml/public/components/validate_job/validate_job_view.js +++ b/x-pack/legacy/plugins/ml/public/components/validate_job/validate_job_view.js @@ -167,7 +167,7 @@ Modal.propType = { title: PropTypes.string }; -class ValidateJob extends Component { +export class ValidateJob extends Component { constructor(props) { super(props); this.state = getDefaultState(); @@ -329,5 +329,3 @@ ValidateJob.propTypes = { setIsValid: PropTypes.func, idFilterList: PropTypes.array, }; - -export { ValidateJob }; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/page.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/page.tsx index ad12404419133..0bbc313704174 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/page.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_exploration/page.tsx @@ -20,7 +20,7 @@ import { EuiTitle, } from '@elastic/eui'; -import { NavigationMenu } from '../../../components/navigation_menu/navigation_menu'; +import { NavigationMenu } from '../../../components/navigation_menu'; import { Exploration } from './components/exploration'; import { RegressionExploration } from './components/regression_exploration'; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx index e0e45eddfe171..75841b52521bd 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.tsx @@ -21,17 +21,14 @@ import { createPermissionFailureMessage, } from '../../../../../privilege/check_privilege'; -import { DataFrameAnalyticsListRow, DATA_FRAME_TASK_STATE } from './common'; +import { isDataFrameAnalyticsRunning, DataFrameAnalyticsListRow } from './common'; interface DeleteActionProps { item: DataFrameAnalyticsListRow; } export const DeleteAction: FC = ({ item }) => { - const disabled = - item.stats.state === DATA_FRAME_TASK_STATE.STARTED || - item.stats.state === DATA_FRAME_TASK_STATE.ANALYZING || - item.stats.state === DATA_FRAME_TASK_STATE.REINDEXING; + const disabled = isDataFrameAnalyticsRunning(item.stats.state); const canDeleteDataFrameAnalytics: boolean = checkPermission('canDeleteDataFrameAnalytics'); diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx index 59496f00d8fe4..7ad86c5d380ca 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx @@ -56,7 +56,7 @@ export const getActions = () => { AnalyticsViewAction, { render: (item: DataFrameAnalyticsListRow) => { - if (!isDataFrameAnalyticsRunning(item.stats)) { + if (!isDataFrameAnalyticsRunning(item.stats.state)) { return ; } diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index 45f53691eab8f..8cc1719ffea8c 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -8,12 +8,7 @@ import React, { Fragment, FC, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { - // EuiBadge, - EuiButtonEmpty, - EuiCallOut, - EuiEmptyPrompt, -} from '@elastic/eui'; +import { EuiButtonEmpty, EuiCallOut, EuiEmptyPrompt } from '@elastic/eui'; import { DataFrameAnalyticsId, useRefreshAnalyticsList } from '../../../../common'; import { checkPermission } from '../../../../../privilege/check_privilege'; @@ -24,7 +19,6 @@ import { DataFrameAnalyticsListRow, ItemIdToExpandedRowMap, DATA_FRAME_TASK_STATE, - // DATA_FRAME_MODE, Query, Clause, } from './common'; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx index 07adff6c3e415..19d7b4db36c9b 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx @@ -20,10 +20,12 @@ import { import { getAnalysisType, DataFrameAnalyticsId } from '../../../../common'; import { getDataFrameAnalyticsProgress, + isDataFrameAnalyticsFailed, + isDataFrameAnalyticsRunning, + isDataFrameAnalyticsStopped, DataFrameAnalyticsListColumn, DataFrameAnalyticsListRow, DataFrameAnalyticsStats, - DATA_FRAME_TASK_STATE, } from './common'; import { getActions } from './actions'; @@ -32,6 +34,7 @@ enum TASK_STATE_COLOR { failed = 'danger', reindexing = 'primary', started = 'primary', + starting = 'primary', stopped = 'hollow', } @@ -41,7 +44,7 @@ export const getTaskStateBadge = ( ) => { const color = TASK_STATE_COLOR[state]; - if (state === DATA_FRAME_TASK_STATE.FAILED && reason !== undefined) { + if (isDataFrameAnalyticsFailed(state) && reason !== undefined) { return ( @@ -91,10 +94,10 @@ export const progressColumn = { {!isBatchTransform && ( - {item.stats.state === DATA_FRAME_TASK_STATE.STARTED && ( + {isDataFrameAnalyticsRunning(item.stats.state) && ( )} - {item.stats.state === DATA_FRAME_TASK_STATE.STOPPED && ( + {isDataFrameAnalyticsStopped(item.stats.state) && ( )} diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/common.test.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/common.test.ts index 3da57a24cfcdf..30f87ad8a375b 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/common.test.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/common.test.ts @@ -11,10 +11,12 @@ import { isCompletedAnalyticsJob, isDataFrameAnalyticsRunning, isDataFrameAnalyticsStats, + DataFrameAnalyticsStats, + DATA_FRAME_TASK_STATE, } from './common'; -const completedJob = StatsMock.data_frame_analytics[0]; -const runningJob = StatsMock.data_frame_analytics[1]; +const completedJob = StatsMock.data_frame_analytics[0] as DataFrameAnalyticsStats; +const runningJob = StatsMock.data_frame_analytics[1] as DataFrameAnalyticsStats; describe('Data Frame Analytics: common utils', () => { test('isCompletedAnalyticsJob()', () => { @@ -23,8 +25,16 @@ describe('Data Frame Analytics: common utils', () => { }); test('isDataFrameAnalyticsRunning()', () => { - expect(isDataFrameAnalyticsRunning(completedJob)).toBe(false); - expect(isDataFrameAnalyticsRunning(runningJob)).toBe(true); + expect(isDataFrameAnalyticsRunning(completedJob.state)).toBe(false); + expect(isDataFrameAnalyticsRunning(runningJob.state)).toBe(true); + runningJob.state = DATA_FRAME_TASK_STATE.STARTED; + expect(isDataFrameAnalyticsRunning(runningJob.state)).toBe(true); + runningJob.state = DATA_FRAME_TASK_STATE.STARTING; + expect(isDataFrameAnalyticsRunning(runningJob.state)).toBe(true); + runningJob.state = DATA_FRAME_TASK_STATE.REINDEXING; + expect(isDataFrameAnalyticsRunning(runningJob.state)).toBe(true); + runningJob.state = DATA_FRAME_TASK_STATE.FAILED; + expect(isDataFrameAnalyticsRunning(runningJob.state)).toBe(false); }); test('isDataFrameAnalyticsStats()', () => { diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts index fa2f6719bcb8e..99d1889b265ed 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/common.ts @@ -12,6 +12,7 @@ export enum DATA_FRAME_TASK_STATE { FAILED = 'failed', REINDEXING = 'reindexing', STARTED = 'started', + STARTING = 'starting', STOPPED = 'stopped', } @@ -54,14 +55,23 @@ export interface DataFrameAnalyticsStats { state: DATA_FRAME_TASK_STATE; } -export function isDataFrameAnalyticsRunning(stats: DataFrameAnalyticsStats) { +export function isDataFrameAnalyticsFailed(state: DATA_FRAME_TASK_STATE) { + return state === DATA_FRAME_TASK_STATE.FAILED; +} + +export function isDataFrameAnalyticsRunning(state: DATA_FRAME_TASK_STATE) { return ( - stats.state === DATA_FRAME_TASK_STATE.ANALYZING || - stats.state === DATA_FRAME_TASK_STATE.STARTED || - stats.state === DATA_FRAME_TASK_STATE.REINDEXING + state === DATA_FRAME_TASK_STATE.ANALYZING || + state === DATA_FRAME_TASK_STATE.REINDEXING || + state === DATA_FRAME_TASK_STATE.STARTED || + state === DATA_FRAME_TASK_STATE.STARTING ); } +export function isDataFrameAnalyticsStopped(state: DATA_FRAME_TASK_STATE) { + return state === DATA_FRAME_TASK_STATE.STOPPED; +} + export function isDataFrameAnalyticsStats(arg: any): arg is DataFrameAnalyticsStats { return ( typeof arg === 'object' && diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx index ab8ef81c593a2..67bad23adacf6 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/components/analytics_list/expanded_row.tsx @@ -20,6 +20,7 @@ import { ExpandedRowJsonPane } from './expanded_row_json_pane'; import { ProgressBar } from './progress_bar'; import { getDependentVar, getValuesFromResponse, loadEvalData, Eval } from '../../../../common'; import { isCompletedAnalyticsJob } from './common'; +import { isRegressionAnalysis } from '../../../../common/analytics'; // import { ExpandedRowMessagesPane } from './expanded_row_messages_pane'; function getItemDescription(value: any) { @@ -60,6 +61,7 @@ export const ExpandedRow: FC = ({ item }) => { const index = idx(item, _ => _.config.dest.index) as string; const dependentVariable = getDependentVar(item.config.analysis); const jobIsCompleted = isCompletedAnalyticsJob(item.stats); + const isRegressionJob = isRegressionAnalysis(item.config.analysis); const loadData = async () => { setIsLoadingGeneralization(true); @@ -113,7 +115,7 @@ export const ExpandedRow: FC = ({ item }) => { }; useEffect(() => { - if (jobIsCompleted) { + if (jobIsCompleted && isRegressionJob) { loadData(); } }, [jobIsCompleted]); @@ -162,7 +164,7 @@ export const ExpandedRow: FC = ({ item }) => { position: 'right', }; - if (jobIsCompleted) { + if (jobIsCompleted && isRegressionJob) { stats.items.push( { title: 'generalization mean squared error', diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/page.tsx b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/page.tsx index c846a196dafab..fcff4aa06b6bb 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/page.tsx +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/page.tsx @@ -23,7 +23,7 @@ import { EuiTitle, } from '@elastic/eui'; -import { NavigationMenu } from '../../../components/navigation_menu/navigation_menu'; +import { NavigationMenu } from '../../../components/navigation_menu'; import { CreateAnalyticsButton } from './components/create_analytics_button'; import { DataFrameAnalyticsList } from './components/analytics_list'; import { RefreshAnalyticsListButton } from './components/refresh_analytics_list_button'; diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts index dde4f8efc899c..fb366b517f0b7 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/delete_analytics.ts @@ -11,18 +11,14 @@ import { ml } from '../../../../../services/ml_api_service'; import { refreshAnalyticsList$, REFRESH_ANALYTICS_LIST_STATE } from '../../../../common'; import { - DATA_FRAME_TASK_STATE, + isDataFrameAnalyticsFailed, DataFrameAnalyticsListRow, } from '../../components/analytics_list/common'; export const deleteAnalytics = async (d: DataFrameAnalyticsListRow) => { try { - if (d.stats.state === DATA_FRAME_TASK_STATE.FAILED) { - await ml.dataFrameAnalytics.stopDataFrameAnalytics( - d.config.id, - d.stats.state === DATA_FRAME_TASK_STATE.FAILED, - true - ); + if (isDataFrameAnalyticsFailed(d.stats.state)) { + await ml.dataFrameAnalytics.stopDataFrameAnalytics(d.config.id, true, true); } await ml.dataFrameAnalytics.deleteDataFrameAnalytics(d.config.id); toastNotifications.addSuccess( diff --git a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/stop_analytics.ts b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/stop_analytics.ts index 44286c8b1f7dd..84d1835c6e1e3 100644 --- a/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/stop_analytics.ts +++ b/x-pack/legacy/plugins/ml/public/data_frame_analytics/pages/analytics_management/services/analytics_service/stop_analytics.ts @@ -11,7 +11,7 @@ import { ml } from '../../../../../services/ml_api_service'; import { refreshAnalyticsList$, REFRESH_ANALYTICS_LIST_STATE } from '../../../../common'; import { - DATA_FRAME_TASK_STATE, + isDataFrameAnalyticsFailed, DataFrameAnalyticsListRow, } from '../../components/analytics_list/common'; @@ -19,7 +19,7 @@ export const stopAnalytics = async (d: DataFrameAnalyticsListRow) => { try { await ml.dataFrameAnalytics.stopDataFrameAnalytics( d.config.id, - d.stats.state === DATA_FRAME_TASK_STATE.FAILED, + isDataFrameAnalyticsFailed(d.stats.state), true ); toastNotifications.addSuccess( diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/datavisualizer_selector.js b/x-pack/legacy/plugins/ml/public/datavisualizer/datavisualizer_selector.js index 65cc2533ed4fb..3bd825aee15ef 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/datavisualizer_selector.js +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/datavisualizer_selector.js @@ -24,7 +24,7 @@ import { isFullLicense } from '../license/check_license'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; import { timefilter } from 'ui/timefilter'; -import { NavigationMenu } from '../components/navigation_menu/navigation_menu'; +import { NavigationMenu } from '../components/navigation_menu'; function startTrialDescription() { return ( diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/file_based/file_datavisualizer.js b/x-pack/legacy/plugins/ml/public/datavisualizer/file_based/file_datavisualizer.js index 241d80a2fcb0b..588c0b1ac2843 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/file_based/file_datavisualizer.js +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/file_based/file_datavisualizer.js @@ -8,7 +8,7 @@ import React, { Fragment } from 'react'; import { timefilter } from 'ui/timefilter'; -import { NavigationMenu } from '../../components/navigation_menu/navigation_menu'; +import { NavigationMenu } from '../../components/navigation_menu'; import { FileDataVisualizerView } from './components/file_datavisualizer_view'; diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/page.tsx b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/page.tsx index e9ee556caaa1c..9123858eef4e5 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/page.tsx +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/page.tsx @@ -26,6 +26,7 @@ import { EuiTitle, } from '@elastic/eui'; +import { NavigationMenu } from '../../components/navigation_menu'; import { KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; import { ML_JOB_FIELD_TYPES } from '../../../common/constants/field_types'; import { SEARCH_QUERY_LANGUAGE } from '../../../common/constants/search'; @@ -591,87 +592,90 @@ export const Page: FC = () => { } return ( - - - - - -

    {currentIndexPattern.title}

    -
    -
    - {currentIndexPattern.timeFieldName !== undefined && ( - - + + + + + + + +

    {currentIndexPattern.title}

    +
    - )} -
    - - - - - - - - - {totalMetricFieldCount > 0 && ( - - - - - )} - - - - - {showActionsPanel === true && ( - - - + {currentIndexPattern.timeFieldName !== undefined && ( + + + )} - - -
    -
    +
    + + + + + + + + + {totalMetricFieldCount > 0 && ( + + + + + )} + + + + + {showActionsPanel === true && ( + + + + )} + + +
    +
    +
    ); }; diff --git a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/route.ts b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/route.ts index ff08591ac24d0..66edcbd945f77 100644 --- a/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/route.ts +++ b/x-pack/legacy/plugins/ml/public/datavisualizer/index_based/route.ts @@ -15,7 +15,7 @@ import { loadCurrentIndexPattern, loadCurrentSavedSearch } from '../../util/inde import { checkMlNodesAvailable } from '../../ml_nodes_check'; import { getDataVisualizerBreadcrumbs } from './breadcrumbs'; -const template = ``; +const template = ``; uiRoutes.when('/jobs/new_job/datavisualizer', { template, diff --git a/x-pack/legacy/plugins/ml/public/explorer/explorer.js b/x-pack/legacy/plugins/ml/public/explorer/explorer.js index 70a5c9c505acc..c9a2de3969280 100644 --- a/x-pack/legacy/plugins/ml/public/explorer/explorer.js +++ b/x-pack/legacy/plugins/ml/public/explorer/explorer.js @@ -43,7 +43,7 @@ import { InfluencersList } from '../components/influencers_list'; import { ALLOW_CELL_RANGE_SELECTION, dragSelect$, explorer$ } from './explorer_dashboard_service'; import { mlResultsService } from 'plugins/ml/services/results_service'; import { LoadingIndicator } from '../components/loading_indicator/loading_indicator'; -import { NavigationMenu } from '../components/navigation_menu/navigation_menu'; +import { NavigationMenu } from '../components/navigation_menu'; import { CheckboxShowCharts, showCharts$ } from '../components/controls/checkbox_showcharts'; import { JobSelector } from '../components/job_selector'; import { SelectInterval, interval$ } from '../components/controls/select_interval/select_interval'; diff --git a/x-pack/legacy/plugins/ml/public/index.scss b/x-pack/legacy/plugins/ml/public/index.scss index af616ff54222f..97f0e037e2648 100644 --- a/x-pack/legacy/plugins/ml/public/index.scss +++ b/x-pack/legacy/plugins/ml/public/index.scss @@ -41,7 +41,6 @@ @import 'components/items_grid/index'; @import 'components/job_selector/index'; @import 'components/loading_indicator/index'; // SASSTODO: This component should be replaced with EuiLoadingSpinner - @import 'components/messagebar/index'; @import 'components/navigation_menu/index'; @import 'components/rule_editor/index'; // SASSTODO: This file overwrites EUI directly @import 'components/stats_bar/index'; diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js index 56b59eb949ee8..acc5a469204f9 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/edit_job_flyout/edit_job_flyout.js @@ -26,7 +26,7 @@ import { JobDetails, Detectors, Datafeed, CustomUrls } from './tabs'; import { saveJob } from './edit_utils'; import { loadFullJob } from '../utils'; import { validateModelMemoryLimit, validateGroupNames, isValidCustomUrls } from '../validate_job'; -import { mlMessageBarService } from '../../../../components/messagebar/messagebar_service'; +import { mlMessageBarService } from '../../../../components/messagebar'; import { toastNotifications } from 'ui/notify'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js index dea9c27e1a90e..d104d2e02d07b 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/multi_job_actions/group_selector/group_selector.js @@ -28,7 +28,7 @@ import { cloneDeep } from 'lodash'; import { ml } from '../../../../../services/ml_api_service'; import { GroupList } from './group_list'; import { NewGroupInput } from './new_group_input'; -import { mlMessageBarService } from '../../../../../components/messagebar/messagebar_service'; +import { mlMessageBarService } from '../../../../../components/messagebar'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; function createSelectedGroups(jobs, groups) { diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/utils.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/utils.js index 484b7ad18f586..3344d2d4ad4a5 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/utils.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/components/utils.js @@ -6,7 +6,7 @@ import { each } from 'lodash'; import { toastNotifications } from 'ui/notify'; -import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service'; +import { mlMessageBarService } from 'plugins/ml/components/messagebar'; import rison from 'rison-node'; import chrome from 'ui/chrome'; diff --git a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/jobs.js b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/jobs.js index 5bdf9c1944e43..188048d2d2f05 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/jobs_list/jobs.js +++ b/x-pack/legacy/plugins/ml/public/jobs/jobs_list/jobs.js @@ -6,7 +6,7 @@ import React, { Fragment } from 'react'; -import { NavigationMenu } from '../../components/navigation_menu/navigation_menu'; +import { NavigationMenu } from '../../components/navigation_menu'; import { JobsListView } from './components/jobs_list_view'; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts index f60c9c3fb8ca8..f86baaccb84d3 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/bucket_span_estimator/estimate_bucket_span.ts @@ -15,7 +15,7 @@ import { } from '../../../../../common/job_creator'; import { ml, BucketSpanEstimatorData } from '../../../../../../../services/ml_api_service'; import { useKibanaContext } from '../../../../../../../contexts/kibana'; -import { mlMessageBarService } from '../../../../../../../components/messagebar/messagebar_service'; +import { mlMessageBarService } from '../../../../../../../components/messagebar'; export enum ESTIMATE_STATUS { NOT_RUNNING, diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx index e42bb76373e62..f913b5a5720d6 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection.tsx @@ -15,7 +15,7 @@ import { AggFieldPair } from '../../../../../../../../common/types/fields'; import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings'; import { MetricSelector } from './metric_selector'; import { ChartGrid } from './chart_grid'; -import { mlMessageBarService } from '../../../../../../../components/messagebar/messagebar_service'; +import { mlMessageBarService } from '../../../../../../../components/messagebar'; interface Props { setIsValid: (na: boolean) => void; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx index 44b8fd0255d3d..f39a316440e74 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/multi_metric_view/metric_selection_summary.tsx @@ -12,7 +12,7 @@ import { Results, ModelItem, Anomaly } from '../../../../../common/results_loade import { LineChartData } from '../../../../../common/chart_loader'; import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings'; import { ChartGrid } from './chart_grid'; -import { mlMessageBarService } from '../../../../../../../components/messagebar/messagebar_service'; +import { mlMessageBarService } from '../../../../../../../components/messagebar'; export const MultiMetricDetectorsSummary: FC = () => { const { jobCreator: jc, chartLoader, resultsLoader, chartInterval } = useContext( diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/population_view/metric_selection.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/population_view/metric_selection.tsx index 00948902c0407..87d3e87d65536 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/population_view/metric_selection.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/population_view/metric_selection.tsx @@ -17,7 +17,7 @@ import { getChartSettings, defaultChartSettings } from '../../../charts/common/s import { MetricSelector } from './metric_selector'; import { SplitFieldSelector } from '../split_field'; import { ChartGrid } from './chart_grid'; -import { mlMessageBarService } from '../../../../../../../components/messagebar/messagebar_service'; +import { mlMessageBarService } from '../../../../../../../components/messagebar'; interface Props { setIsValid: (na: boolean) => void; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx index 5b7d16e2bd7c9..b13f8e3a73a10 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/population_view/metric_selection_summary.tsx @@ -15,7 +15,7 @@ import { LineChartData } from '../../../../../common/chart_loader'; import { Field, AggFieldPair } from '../../../../../../../../common/types/fields'; import { getChartSettings, defaultChartSettings } from '../../../charts/common/settings'; import { ChartGrid } from './chart_grid'; -import { mlMessageBarService } from '../../../../../../../components/messagebar/messagebar_service'; +import { mlMessageBarService } from '../../../../../../../components/messagebar'; type DetectorFieldValues = Record; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx index 5a6e08cb2948a..45872faae4c09 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/single_metric_view/metric_selection.tsx @@ -13,7 +13,7 @@ import { newJobCapsService } from '../../../../../../../services/new_job_capabil import { AggFieldPair } from '../../../../../../../../common/types/fields'; import { AnomalyChart, CHART_TYPE } from '../../../charts/anomaly_chart'; import { getChartSettings } from '../../../charts/common/settings'; -import { mlMessageBarService } from '../../../../../../../components/messagebar/messagebar_service'; +import { mlMessageBarService } from '../../../../../../../components/messagebar'; interface Props { setIsValid: (na: boolean) => void; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx index 8124f20a33da8..85fb5890307ba 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/pick_fields_step/components/single_metric_view/metric_selection_summary.tsx @@ -11,7 +11,7 @@ import { Results, ModelItem, Anomaly } from '../../../../../common/results_loade import { LineChartData } from '../../../../../common/chart_loader'; import { AnomalyChart, CHART_TYPE } from '../../../charts/anomaly_chart'; import { getChartSettings } from '../../../charts/common/settings'; -import { mlMessageBarService } from '../../../../../../../components/messagebar/messagebar_service'; +import { mlMessageBarService } from '../../../../../../../components/messagebar'; const DTR_IDX = 0; diff --git a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/validation_step/validation.tsx b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/validation_step/validation.tsx index ac7e89ecf95b7..b60e225cdff4d 100644 --- a/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/validation_step/validation.tsx +++ b/x-pack/legacy/plugins/ml/public/jobs/new_job_new/pages/components/validation_step/validation.tsx @@ -9,7 +9,7 @@ import { WizardNav } from '../wizard_nav'; import { WIZARD_STEPS, StepProps } from '../step_types'; import { JobCreatorContext } from '../job_creator_context'; import { mlJobService } from '../../../../../services/job_service'; -import { ValidateJob } from '../../../../../components/validate_job/validate_job_view'; +import { ValidateJob } from '../../../../../components/validate_job'; import { JOB_TYPE } from '../../../common/job_creator/util/constants'; const idFilterList = [ diff --git a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_stats_bar.tsx b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_stats_bar.tsx index 63d363e2f5a4d..19a907ff8e899 100644 --- a/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_stats_bar.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/components/analytics_panel/analytics_stats_bar.tsx @@ -8,8 +8,10 @@ import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; import { StatsBar, AnalyticStatsBarStats } from '../../../components/stats_bar'; import { + isDataFrameAnalyticsFailed, + isDataFrameAnalyticsRunning, + isDataFrameAnalyticsStopped, DataFrameAnalyticsListRow, - DATA_FRAME_TASK_STATE, } from '../../../data_frame_analytics/pages/analytics_management/components/analytics_list/common'; function getAnalyticsStats(analyticsList: any[]) { @@ -53,15 +55,11 @@ function getAnalyticsStats(analyticsList: any[]) { let stoppedJobs = 0; analyticsList.forEach(job => { - if (job.stats.state === DATA_FRAME_TASK_STATE.FAILED) { + if (isDataFrameAnalyticsFailed(job.stats.state)) { failedJobs++; - } else if ( - job.stats.state === DATA_FRAME_TASK_STATE.STARTED || - job.stats.state === DATA_FRAME_TASK_STATE.ANALYZING || - job.stats.state === DATA_FRAME_TASK_STATE.REINDEXING - ) { + } else if (isDataFrameAnalyticsRunning(job.stats.state)) { startedJobs++; - } else if (job.stats.state === DATA_FRAME_TASK_STATE.STOPPED) { + } else if (isDataFrameAnalyticsStopped(job.stats.state)) { stoppedJobs++; } }); diff --git a/x-pack/legacy/plugins/ml/public/overview/overview_page.tsx b/x-pack/legacy/plugins/ml/public/overview/overview_page.tsx index bb9e45ae84a41..39c3205d54eda 100644 --- a/x-pack/legacy/plugins/ml/public/overview/overview_page.tsx +++ b/x-pack/legacy/plugins/ml/public/overview/overview_page.tsx @@ -6,7 +6,7 @@ import React, { Fragment, FC } from 'react'; import { EuiFlexGroup, EuiPage, EuiPageBody } from '@elastic/eui'; -import { NavigationMenu } from '../components/navigation_menu/navigation_menu'; +import { NavigationMenu } from '../components/navigation_menu'; import { OverviewSideBar } from './components/sidebar'; import { OverviewContent } from './components/content'; diff --git a/x-pack/legacy/plugins/ml/public/services/calendar_service.js b/x-pack/legacy/plugins/ml/public/services/calendar_service.js index 84f5bdb3c8e18..09e3001a0f7f5 100644 --- a/x-pack/legacy/plugins/ml/public/services/calendar_service.js +++ b/x-pack/legacy/plugins/ml/public/services/calendar_service.js @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { ml } from 'plugins/ml/services/ml_api_service'; import { mlJobService } from 'plugins/ml/services/job_service'; -import { mlMessageBarService } from 'plugins/ml/components/messagebar/messagebar_service'; +import { mlMessageBarService } from 'plugins/ml/components/messagebar'; diff --git a/x-pack/legacy/plugins/ml/public/services/job_service.js b/x-pack/legacy/plugins/ml/public/services/job_service.js index 845481d41da77..bea6631a6a2c4 100644 --- a/x-pack/legacy/plugins/ml/public/services/job_service.js +++ b/x-pack/legacy/plugins/ml/public/services/job_service.js @@ -13,7 +13,7 @@ import { i18n } from '@kbn/i18n'; import { ml } from './ml_api_service'; -import { mlMessageBarService } from '../components/messagebar/messagebar_service'; +import { mlMessageBarService } from '../components/messagebar'; import { isWebUrl } from '../util/url_utils'; import { ML_DATA_PREVIEW_COUNT } from '../../common/util/job_utils'; import { parseInterval } from '../../common/util/parse_interval'; diff --git a/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.js b/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.js index 1351a06c21f2c..12c8339c52d71 100644 --- a/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.js +++ b/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.js @@ -21,7 +21,7 @@ import { import chrome from 'ui/chrome'; import { toastNotifications } from 'ui/notify'; -import { NavigationMenu } from '../../../components/navigation_menu/navigation_menu'; +import { NavigationMenu } from '../../../components/navigation_menu'; import { getCalendarSettingsData, validateCalendarId } from './utils'; import { CalendarForm } from './calendar_form/'; diff --git a/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.test.js b/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.test.js index bc2bddab7a6cb..a7547b9e43c73 100644 --- a/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.test.js +++ b/x-pack/legacy/plugins/ml/public/settings/calendars/edit/new_calendar.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../../../components/navigation_menu/navigation_menu', () => ({ +jest.mock('../../../components/navigation_menu', () => ({ NavigationMenu: () =>
    })); jest.mock('../../../privilege/check_privilege', () => ({ diff --git a/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.js b/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.js index 0b5657b62ebf3..b7ad2c36f3b43 100644 --- a/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.js +++ b/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.js @@ -17,7 +17,7 @@ import { EUI_MODAL_CONFIRM_BUTTON, } from '@elastic/eui'; -import { NavigationMenu } from '../../../components/navigation_menu/navigation_menu'; +import { NavigationMenu } from '../../../components/navigation_menu'; import { CalendarsListHeader } from './header'; import { CalendarsListTable } from './table/'; import { ml } from '../../../services/ml_api_service'; diff --git a/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.test.js b/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.test.js index 84211597f313c..d32b0ae9a4d5f 100644 --- a/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.test.js +++ b/x-pack/legacy/plugins/ml/public/settings/calendars/list/calendars_list.test.js @@ -10,7 +10,7 @@ import { ml } from '../../../services/ml_api_service'; import { CalendarsList } from './calendars_list'; -jest.mock('../../../components/navigation_menu/navigation_menu', () => ({ +jest.mock('../../../components/navigation_menu', () => ({ NavigationMenu: () =>
    })); jest.mock('../../../privilege/check_privilege', () => ({ diff --git a/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.js b/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.js index 2a27dd0fc9774..aed789304186e 100644 --- a/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.js +++ b/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.js @@ -31,7 +31,7 @@ import { toastNotifications } from 'ui/notify'; import { EditFilterListHeader } from './header'; import { EditFilterListToolbar } from './toolbar'; import { ItemsGrid } from '../../../components/items_grid'; -import { NavigationMenu } from '../../../components/navigation_menu/navigation_menu'; +import { NavigationMenu } from '../../../components/navigation_menu'; import { isValidFilterListId, saveFilterList diff --git a/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.test.js b/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.test.js index 035913d5dc465..d2030a3dc944e 100644 --- a/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.test.js +++ b/x-pack/legacy/plugins/ml/public/settings/filter_lists/edit/edit_filter_list.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -jest.mock('../../../components/navigation_menu/navigation_menu', () => ({ +jest.mock('../../../components/navigation_menu', () => ({ NavigationMenu: () =>
    })); diff --git a/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.js b/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.js index 8ea2bb8929913..c6b177bcceaef 100644 --- a/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.js +++ b/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.js @@ -21,7 +21,7 @@ import { injectI18n } from '@kbn/i18n/react'; import { toastNotifications } from 'ui/notify'; -import { NavigationMenu } from '../../../components/navigation_menu/navigation_menu'; +import { NavigationMenu } from '../../../components/navigation_menu'; import { FilterListsHeader } from './header'; import { FilterListsTable } from './table'; diff --git a/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.test.js b/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.test.js index 3c6a564ec92b4..ddf832565a8be 100644 --- a/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.test.js +++ b/x-pack/legacy/plugins/ml/public/settings/filter_lists/list/filter_lists.test.js @@ -9,7 +9,7 @@ import React from 'react'; import { FilterLists } from './filter_lists'; -jest.mock('../../../components/navigation_menu/navigation_menu', () => ({ +jest.mock('../../../components/navigation_menu', () => ({ NavigationMenu: () =>
    })); jest.mock('../../../privilege/check_privilege', () => ({ diff --git a/x-pack/legacy/plugins/ml/public/settings/settings.js b/x-pack/legacy/plugins/ml/public/settings/settings.js index e8d2533af7f4b..f04283563797f 100644 --- a/x-pack/legacy/plugins/ml/public/settings/settings.js +++ b/x-pack/legacy/plugins/ml/public/settings/settings.js @@ -23,7 +23,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { useUiChromeContext } from '../contexts/ui/use_ui_chrome_context'; -import { NavigationMenu } from '../components/navigation_menu/navigation_menu'; +import { NavigationMenu } from '../components/navigation_menu'; export function Settings({ canGetFilters, diff --git a/x-pack/legacy/plugins/ml/public/settings/settings.test.js b/x-pack/legacy/plugins/ml/public/settings/settings.test.js index 0fe0a4ab50be4..8efe558fda961 100644 --- a/x-pack/legacy/plugins/ml/public/settings/settings.test.js +++ b/x-pack/legacy/plugins/ml/public/settings/settings.test.js @@ -10,7 +10,7 @@ import React from 'react'; import { Settings } from './settings'; jest.mock('../contexts/ui/use_ui_chrome_context'); -jest.mock('../components/navigation_menu/navigation_menu', () => ({ +jest.mock('../components/navigation_menu', () => ({ NavigationMenu: () =>
    , })); diff --git a/x-pack/legacy/plugins/ml/public/timeseriesexplorer/components/forecasting_modal/forecasting_modal_directive.js b/x-pack/legacy/plugins/ml/public/timeseriesexplorer/components/forecasting_modal/forecasting_modal_directive.js deleted file mode 100644 index cf101256724e9..0000000000000 --- a/x-pack/legacy/plugins/ml/public/timeseriesexplorer/components/forecasting_modal/forecasting_modal_directive.js +++ /dev/null @@ -1,27 +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 'ngreact'; - -import { wrapInI18nContext } from 'ui/i18n'; -import { timefilter } from 'ui/timefilter'; -import { uiModules } from 'ui/modules'; -const module = uiModules.get('apps/ml', ['react']); - -import { ForecastingModal } from './forecasting_modal'; - -module.directive('mlForecastingModal', function ($injector) { - const reactDirective = $injector.get('reactDirective'); - return reactDirective( - wrapInI18nContext(ForecastingModal), - // reactDirective service requires for react component to have propTypes, but injectI18n doesn't copy propTypes from wrapped component. - // That's why we pass propTypes directly to reactDirective service. - Object.keys(ForecastingModal.WrappedComponent.propTypes || {}), - { restrict: 'E' }, - { timefilter } - ); -}); diff --git a/x-pack/legacy/plugins/ml/public/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/legacy/plugins/ml/public/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index e078a04213462..81852f025fb1f 100644 --- a/x-pack/legacy/plugins/ml/public/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/legacy/plugins/ml/public/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -587,7 +587,15 @@ const TimeseriesChartIntl = injectI18n(class TimeseriesChart extends React.Compo // If an anomaly coincides with a gap in the data, use the anomaly actual value. metricValue = Array.isArray(d.actual) ? d.actual[0] : d.actual; } - return d.lower !== undefined ? Math.min(metricValue, d.lower) : metricValue; + if (d.lower !== undefined) { + if (metricValue !== null && metricValue !== undefined) { + return Math.min(metricValue, d.lower); + } else { + // Set according to the minimum of the lower of the model plot results. + return d.lower; + } + } + return metricValue; }); yMax = d3.max(combinedData, (d) => { let metricValue = d.value; diff --git a/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js b/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js index 5afb5ae64385d..cea2dc37bfad3 100644 --- a/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/legacy/plugins/ml/public/timeseriesexplorer/timeseriesexplorer.js @@ -51,7 +51,7 @@ import { EntityControl } from './components/entity_control'; import { ForecastingModal } from './components/forecasting_modal/forecasting_modal'; import { JobSelector } from '../components/job_selector'; import { LoadingIndicator } from '../components/loading_indicator/loading_indicator'; -import { NavigationMenu } from '../components/navigation_menu/navigation_menu'; +import { NavigationMenu } from '../components/navigation_menu'; import { severity$, SelectSeverity } from '../components/controls/select_severity/select_severity'; import { interval$, SelectInterval } from '../components/controls/select_interval/select_interval'; import { TimeseriesChart } from './components/timeseries_chart/timeseries_chart'; diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json index f91e102a3f1c1..98d4feb550c31 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_low_request_rate_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json index 824d6a934d865..5ea53cf35ff4b 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_request_rate_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json index 824d6a934d865..5ea53cf35ff4b 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_source_ip_url_count_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json index 824d6a934d865..5ea53cf35ff4b 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_status_code_rate_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json index 0a98563f98817..7e5f70e8c8b0b 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apache_ecs/ml/datafeed_visitor_rate_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_response_time.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_response_time.json index 2f22f2c9be3c8..dc37d05d18111 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_response_time.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_transaction/ml/datafeed_high_mean_response_time.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_high_count_process_events_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_high_count_process_events_ecs.json index 483e4dab68333..7370aed5321ac 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_high_count_process_events_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_high_count_process_events_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_rare_process_activity_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_rare_process_activity_ecs.json index 483e4dab68333..7370aed5321ac 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_rare_process_activity_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_docker_ecs/ml/datafeed_docker_rare_process_activity_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_high_count_process_events_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_high_count_process_events_ecs.json index 30d43b331b839..9c04257fb8904 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_high_count_process_events_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_high_count_process_events_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_rare_process_activity_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_rare_process_activity_ecs.json index dea861b70305e..9c04257fb8904 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_rare_process_activity_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/auditbeat_process_hosts_ecs/ml/datafeed_hosts_rare_process_activity_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { @@ -10,7 +10,7 @@ ], "must": { "exists": { "field": "auditd.data.syscall" } - }, + }, "must_not": { "exists": { "field": "container.runtime" } } diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/datafeed_log_entry_rate.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/datafeed_log_entry_rate.json index 073e72a188616..2ece259e2bb45 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/datafeed_log_entry_rate.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/logs_ui_analysis/ml/datafeed_log_entry_rate.json @@ -1,4 +1,4 @@ { "job_id": "JOB_ID", - "indexes": ["INDEX_PATTERN_NAME"] + "indices": ["INDEX_PATTERN_NAME"] } diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_high_mean_cpu_iowait_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_high_mean_cpu_iowait_ecs.json index fd164e218ee22..fe87160142cff 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_high_mean_cpu_iowait_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_high_mean_cpu_iowait_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_max_disk_utilization_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_max_disk_utilization_ecs.json index 0b1a6099d6794..6ccbfe94c220c 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_max_disk_utilization_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_max_disk_utilization_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_metricbeat_outages_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_metricbeat_outages_ecs.json index 35974310eadb2..d6f33127dfc08 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_metricbeat_outages_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/metricbeat_system_ecs/ml/datafeed_metricbeat_outages_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json index d3333928299ea..92f7663f42653 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_low_request_rate_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json index bccf1bd8de6d5..682844b1bc5c7 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_request_rate_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json index bccf1bd8de6d5..682844b1bc5c7 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_source_ip_url_count_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json index bccf1bd8de6d5..682844b1bc5c7 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_status_code_rate_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json index e3faf85461938..7027d3e8902bc 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/nginx_ecs/ml/datafeed_visitor_rate_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "query": { diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/datafeed_high_sum_total_sales.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/datafeed_high_sum_total_sales.json index 193239995c6d3..0a955a766bd53 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/datafeed_high_sum_total_sales.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_ecommerce/ml/datafeed_high_sum_total_sales.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": ["INDEX_PATTERN_NAME"], + "indices": ["INDEX_PATTERN_NAME"], "query": { "bool": { "filter": [{ "term": { "_index": "kibana_sample_data_ecommerce" } }] diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_low_request_rate.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_low_request_rate.json index e2682e2c15008..843a7d1651dc8 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_low_request_rate.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_low_request_rate.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": ["INDEX_PATTERN_NAME"], + "indices": ["INDEX_PATTERN_NAME"], "query": { "bool": { "filter": [{ "term": { "_index": "kibana_sample_data_logs" } }] diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_response_code_rates.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_response_code_rates.json index 3afbfdefc31e8..3a0f67daa392a 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_response_code_rates.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_response_code_rates.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": ["INDEX_PATTERN_NAME"], + "indices": ["INDEX_PATTERN_NAME"], "query": { "bool": { "filter": [{ "term": { "_index": "kibana_sample_data_logs" } }] diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_url_scanning.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_url_scanning.json index 3afbfdefc31e8..3a0f67daa392a 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_url_scanning.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/sample_data_weblogs/ml/datafeed_url_scanning.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": ["INDEX_PATTERN_NAME"], + "indices": ["INDEX_PATTERN_NAME"], "query": { "bool": { "filter": [{ "term": { "_index": "kibana_sample_data_logs" } }] diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_rare_process_by_host_linux_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_rare_process_by_host_linux_ecs.json index 9de27f5d213f2..93a5646a7bf01 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_rare_process_by_host_linux_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_rare_process_by_host_linux_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "max_empty_searches": 10, diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_suspicious_login_activity_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_suspicious_login_activity_ecs.json index e92ba08378fab..a177abfd0f116 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_suspicious_login_activity_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat/ml/datafeed_suspicious_login_activity_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "max_empty_searches": 10, diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/datafeed_suspicious_login_activity_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/datafeed_suspicious_login_activity_ecs.json index 75e7148b4db1a..386b9fab25667 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/datafeed_suspicious_login_activity_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_auditbeat_auth/ml/datafeed_suspicious_login_activity_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "max_empty_searches": 10, diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_rare_process_by_host_windows_ecs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_rare_process_by_host_windows_ecs.json index 81519bf6001e3..6daa5881575ab 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_rare_process_by_host_windows_ecs.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat/ml/datafeed_rare_process_by_host_windows_ecs.json @@ -1,6 +1,6 @@ { "job_id": "JOB_ID", - "indexes": [ + "indices": [ "INDEX_PATTERN_NAME" ], "max_empty_searches": 10, diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/windows_rare_user_type10_remote_login.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/windows_rare_user_type10_remote_login.json index b9992a9e01182..ee009e465ec23 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/windows_rare_user_type10_remote_login.json +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/siem_winlogbeat_auth/ml/windows_rare_user_type10_remote_login.json @@ -1,6 +1,6 @@ { "job_type": "anomaly_detector", - "description": "SIEM Winlogbeat Auth: Unusual terminal services users can indicate account takeover or credentialed access (beta)", + "description": "SIEM Winlogbeat Auth: Unusual RDP (remote desktop protocol) user logins can indicate account takeover or credentialed access (beta)", "groups": [ "siem", "winlogbeat", @@ -49,4 +49,4 @@ } ] } -} \ No newline at end of file +} diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/cluster_status/index.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/cluster_status/index.js index b9fb2c0b9f962..5c730215f93c9 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/cluster_status/index.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/cluster_status/index.js @@ -40,7 +40,7 @@ export function ClusterStatus({ stats }) { }, { label: i18n.translate('xpack.monitoring.elasticsearch.clusterStatus.memoryLabel', { - defaultMessage: 'Memory' + defaultMessage: 'JVM Heap' }), value: formatMetric(memUsed, 'byte') + ' / ' + formatMetric(memMax, 'byte'), 'data-test-subj': 'memory' diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js index 6bc4dd509f0b8..72a74964fd35e 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js @@ -206,7 +206,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { cols.push({ name: i18n.translate('xpack.monitoring.elasticsearch.nodes.jvmMemoryColumnTitle', { - defaultMessage: '{javaVirtualMachine} Memory', + defaultMessage: '{javaVirtualMachine} Heap', values: { javaVirtualMachine: 'JVM' } diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/layout.ts b/x-pack/legacy/plugins/reporting/export_types/common/layouts/layout.ts index c2773a12b5804..54fae60a0773c 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/layout.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/layout.ts @@ -26,8 +26,7 @@ export interface LayoutSelectorDictionary { screenshot: string; renderComplete: string; itemsCountAttribute: string; - timefilterFromAttribute: string; - timefilterToAttribute: string; + timefilterDurationAttribute: string; toastHeader: string; } @@ -36,6 +35,14 @@ export interface PdfImageSize { height?: number; } +export const getDefaultLayoutSelectors = (): LayoutSelectorDictionary => ({ + screenshot: '[data-shared-items-container]', + renderComplete: '[data-shared-item]', + itemsCountAttribute: 'data-shared-items-count', + timefilterDurationAttribute: 'data-shared-timefilter-duration', + toastHeader: '[data-test-subj="euiToastHeader"]', +}); + export abstract class Layout { public id: string = ''; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.ts b/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.ts index bfc66aefd0d6e..cfa421b6f66ab 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.ts @@ -5,21 +5,19 @@ */ import path from 'path'; import { LayoutTypes } from '../constants'; -import { Layout, LayoutSelectorDictionary, PageSizeParams, Size } from './layout'; +import { + getDefaultLayoutSelectors, + Layout, + LayoutSelectorDictionary, + PageSizeParams, + Size, +} from './layout'; // We use a zoom of two to bump up the resolution of the screenshot a bit. const ZOOM: number = 2; export class PreserveLayout extends Layout { - public readonly selectors: LayoutSelectorDictionary = { - screenshot: '[data-shared-items-container]', - renderComplete: '[data-shared-item]', - itemsCountAttribute: 'data-shared-items-count', - timefilterFromAttribute: 'data-shared-timefilter-from', - timefilterToAttribute: 'data-shared-timefilter-to', - toastHeader: '[data-test-subj="euiToastHeader"]', - }; - + public readonly selectors: LayoutSelectorDictionary = getDefaultLayoutSelectors(); public readonly groupCount = 1; private readonly height: number; private readonly width: number; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print_layout.ts b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print_layout.ts index 60cb569ea2b62..9d672318000c1 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print_layout.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print_layout.ts @@ -9,21 +9,15 @@ import { LevelLogger } from '../../../server/lib'; import { HeadlessChromiumDriver } from '../../../server/browsers/chromium/driver'; import { KbnServer } from '../../../types'; import { LayoutTypes } from '../constants'; -import { Layout, LayoutSelectorDictionary, Size } from './layout'; +import { getDefaultLayoutSelectors, Layout, LayoutSelectorDictionary, Size } from './layout'; import { CaptureConfig } from './types'; export class PrintLayout extends Layout { public readonly selectors: LayoutSelectorDictionary = { + ...getDefaultLayoutSelectors(), screenshot: '[data-shared-item]', - renderComplete: '[data-shared-item]', - itemsCountAttribute: 'data-shared-items-count', - timefilterFromAttribute: 'data-shared-timefilter-from', - timefilterToAttribute: 'data-shared-timefilter-to', - toastHeader: '[data-test-subj="euiToastHeader"]', }; - public readonly groupCount = 2; - private captureConfig: CaptureConfig; constructor(server: KbnServer) { diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_time_range.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_time_range.ts index ee01d86d15165..db63748c534d5 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_time_range.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/get_time_range.ts @@ -17,27 +17,25 @@ export const getTimeRange = async ( logger.debug('getting timeRange'); const timeRange: TimeRange | null = await browser.evaluate({ - fn: (fromAttribute, toAttribute) => { - const fromElement = document.querySelector(`[${fromAttribute}]`); - const toElement = document.querySelector(`[${toAttribute}]`); + fn: durationAttribute => { + const durationElement = document.querySelector(`[${durationAttribute}]`); - if (!fromElement || !toElement) { + if (!durationElement) { return null; } - const from = fromElement.getAttribute(fromAttribute); - const to = toElement.getAttribute(toAttribute); - if (!to || !from) { + const duration = durationElement.getAttribute(durationAttribute); + if (!duration) { return null; } - return { from, to }; + return { duration }; }, - args: [layout.selectors.timefilterFromAttribute, layout.selectors.timefilterToAttribute], + args: [layout.selectors.timefilterDurationAttribute], }); if (timeRange) { - logger.debug(`timeRange from ${timeRange.from} to ${timeRange.to}`); + logger.info(`timeRange: ${timeRange.duration}`); } else { logger.debug('no timeRange'); } diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts index bcf6c63b08dd8..9713adc76a5fa 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts @@ -25,6 +25,7 @@ import { waitForElementsToBeInDOM } from './wait_for_dom_elements'; import { getTimeRange } from './get_time_range'; import { getElementPositionAndAttributes } from './get_element_position_data'; import { getScreenshots } from './get_screenshots'; +import { skipTelemetry } from './skip_telemetry'; // NOTE: Typescript does not throw an error if this interface has errors! interface ScreenshotResults { @@ -56,6 +57,10 @@ export function screenshotsObservableFactory(server: KbnServer) { (browser: HeadlessBrowser) => openUrl(browser, url, conditionalHeaders, logger), browser => browser ), + mergeMap( + (browser: HeadlessBrowser) => skipTelemetry(browser, logger), + browser => browser + ), mergeMap( (browser: HeadlessBrowser) => { logger.debug( diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/skip_telemetry.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/skip_telemetry.ts new file mode 100644 index 0000000000000..367354032a843 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/skip_telemetry.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 { HeadlessChromiumDriver as HeadlessBrowser } from '../../../../server/browsers/chromium/driver'; +import { LevelLogger } from '../../../../server/lib'; + +const LAST_REPORT_STORAGE_KEY = 'xpack.data'; + +export async function skipTelemetry(browser: HeadlessBrowser, logger: LevelLogger) { + const storageData = await browser.evaluate({ + fn: storageKey => { + // set something + const optOutJSON = JSON.stringify({ lastReport: Date.now() }); + localStorage.setItem(storageKey, optOutJSON); + + // get it + const session = localStorage.getItem(storageKey); + + // return it + return session; + }, + args: [LAST_REPORT_STORAGE_KEY], + }); + + logger.debug(`added data to localStorage to skip telmetry: ${storageData}`); +} diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts index 586814d2d1959..249cc14026aa4 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts @@ -17,8 +17,7 @@ export interface ScreenshotObservableOpts { } export interface TimeRange { - from: any; - to: any; + duration: string; } export interface AttributesMap { @@ -31,7 +30,7 @@ export interface ElementsPositionAndAttribute { } export interface Screenshot { - base64EncodedData: any; - title: any; - description: any; + base64EncodedData: string; + title: string; + description: string; } diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts index 9d378c2feff1c..3d09d2dae42a1 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts @@ -26,7 +26,7 @@ function generatePngObservableFn(server: KbnServer) { const captureConcurrency = 1; // prettier-ignore - const createPngWithScreenshots = async ({ urlScreenshots }: { urlScreenshots: UrlScreenshot[] }) => { + const createPngWithScreenshots = async ({ urlScreenshots }: { urlScreenshots: UrlScreenshot[] }): Promise => { if (urlScreenshots.length !== 1) { throw new Error( `Expected there to be 1 URL screenshot, but there are ${urlScreenshots.length}` @@ -47,7 +47,7 @@ function generatePngObservableFn(server: KbnServer) { browserTimezone: string, conditionalHeaders: ConditionalHeaders, layoutParams: LayoutParams - ) { + ): Rx.Observable { if (!layoutParams || !layoutParams.dimensions) { throw new Error(`LayoutParams.Dimensions is undefined.`); } diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts index 044e650c91cc4..e57e18eff1b67 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts @@ -6,7 +6,6 @@ import * as Rx from 'rxjs'; import { toArray, mergeMap } from 'rxjs/operators'; -import moment from 'moment-timezone'; import { groupBy } from 'lodash'; import { LevelLogger } from '../../../../server/lib'; import { KbnServer, ConditionalHeaders } from '../../../../types'; @@ -39,10 +38,6 @@ const getTimeRange = (urlScreenshots: UrlScreenshot[]) => { return null; }; -const formatDate = (date: Date, timezone: string) => { - return moment.tz(date, timezone).format('llll'); -}; - function generatePdfObservableFn(server: KbnServer) { const screenshotsObservable = screenshotsObservableFactory(server); const captureConcurrency = 1; @@ -72,12 +67,7 @@ function generatePdfObservableFn(server: KbnServer) { if (title) { const timeRange = getTimeRange(urlScreenshots); - title += timeRange - ? ` — ${formatDate(timeRange.from, browserTimezone)} to ${formatDate( - timeRange.to, - browserTimezone - )}` - : ''; + title += timeRange ? ` - ${timeRange.duration}` : ''; pdfOutput.setTitle(title); } diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js index 3ab7b1ababa25..9f638670a6b59 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/__tests__/worker.js @@ -16,7 +16,7 @@ import { constants } from '../constants'; const anchor = '2016-04-02T01:02:03.456'; // saturday const defaults = { timeout: 10000, - size: 10, + size: 1, unknownMime: false, contentBody: null, }; @@ -107,7 +107,6 @@ describe('Worker class', function () { expect(worker).to.have.property('queue', mockQueue); expect(worker).to.have.property('jobtype', jobtype); expect(worker).to.have.property('workerFn', workerFn); - expect(worker).to.have.property('checkSize'); }); it('should have a unique ID', function () { @@ -380,12 +379,6 @@ describe('Worker class', function () { const { body } = getSearchParams(jobtype); expect(body).to.have.property('size', defaults.size); }); - - it('should observe the size option', function () { - const size = 25; - const { body } = getSearchParams(jobtype, { size }); - expect(body).to.have.property('size', size); - }); }); }); diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/default_settings.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/default_settings.js index 5df580a063dd9..3e23f3611b870 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/default_settings.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/constants/default_settings.js @@ -12,4 +12,5 @@ export const defaultSettings = { number_of_shards: 1, auto_expand_replicas: '0-1', }, + DEFAULT_WORKER_CHECK_SIZE: 1, }; diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/worker.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/worker.js index 2bf2cd20200b6..61af0437575e9 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/worker.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/worker.js @@ -87,7 +87,6 @@ export class Worker extends events.EventEmitter { this._client = this.queue.client; this.jobtype = type; this.workerFn = workerFn; - this.checkSize = opts.size || 10; this.debug = getLogger(opts, this.id, 'debug'); this.warn = getLogger(opts, this.id, 'warning'); @@ -421,7 +420,7 @@ export class Worker extends events.EventEmitter { { priority: { order: 'asc' } }, { created_at: { order: 'asc' } } ], - size: this.checkSize + size: constants.DEFAULT_WORKER_CHECK_SIZE, }; return this._client.callWithInternalUser('search', { diff --git a/x-pack/legacy/plugins/security/public/views/account/components/change_password/change_password.tsx b/x-pack/legacy/plugins/security/public/views/account/components/change_password/change_password.tsx index 08a6eaaf5102f..63abb4539470d 100644 --- a/x-pack/legacy/plugins/security/public/views/account/components/change_password/change_password.tsx +++ b/x-pack/legacy/plugins/security/public/views/account/components/change_password/change_password.tsx @@ -35,7 +35,7 @@ export class ChangePassword extends Component { return ( {changePasswordTitle}} + title={

    {changePasswordTitle}

    } description={

    { +

    -

    + } description={ { -

    +

    -

    +

    diff --git a/x-pack/legacy/plugins/security/public/views/management/users_grid/components/users_list_page.tsx b/x-pack/legacy/plugins/security/public/views/management/users_grid/components/users_list_page.tsx index 664c3cea285d5..651b073cb114e 100644 --- a/x-pack/legacy/plugins/security/public/views/management/users_grid/components/users_list_page.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/users_grid/components/users_list_page.tsx @@ -63,12 +63,12 @@ class UsersListPageUI extends Component { +

    -

    + } body={

    @@ -209,12 +209,12 @@ class UsersListPageUI extends Component { -

    +

    -

    +
    diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx index fdf8381685118..efab2ac52a0b6 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx @@ -116,7 +116,7 @@ export const Timeline = React.memo( indexPattern, browserFields, filters: [], - kqlQuery: { query: kqlQueryExpression, language: 'keury' }, + kqlQuery: { query: kqlQueryExpression, language: 'kuery' }, kqlMode, start, end, diff --git a/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts index 6616087d0ea66..ea6607325bef7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts +++ b/x-pack/legacy/plugins/siem/server/lib/framework/kibana_framework_adapter.ts @@ -5,6 +5,7 @@ */ import { GenericParams } from 'elasticsearch'; +import { EnvironmentMode } from 'kibana/public'; import { GraphQLSchema } from 'graphql'; import { Legacy } from 'kibana'; @@ -28,9 +29,11 @@ interface CallWithRequestParams extends GenericParams { export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { public version: string; + public envMode: EnvironmentMode; constructor(private server: Legacy.Server) { this.version = server.config().get('pkg.version'); + this.envMode = server.newPlatform.env.mode; } public async callWithRequest( @@ -90,19 +93,21 @@ export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { plugin: graphqlHapi, }); - this.server.register({ - options: { - graphiqlOptions: { - endpointURL: routePath, - passHeader: `'kbn-version': '${this.version}'`, - }, - path: `${routePath}/graphiql`, - route: { - tags: ['access:siem'], + if (!this.envMode.prod) { + this.server.register({ + options: { + graphiqlOptions: { + endpointURL: routePath, + passHeader: `'kbn-version': '${this.version}'`, + }, + path: `${routePath}/graphiql`, + route: { + tags: ['access:siem'], + }, }, - }, - plugin: graphiqlHapi, - }); + plugin: graphiqlHapi, + }); + } } public getIndexPatternsService( diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap index 43149d7b3911f..43c5e05725414 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap +++ b/x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap @@ -13,7 +13,7 @@ exports[`it renders without blowing up 1`] = ` -

    +

    Elasticsearch -

    +
    { -

    +

    {this.props.iconType && ( { )} {this.props.title} -

    +
    {this.props.collapsible && ( diff --git a/x-pack/legacy/plugins/task_manager/task_manager.test.ts b/x-pack/legacy/plugins/task_manager/task_manager.test.ts index 25e68a91fe680..2bcfdb2f4d4e3 100644 --- a/x-pack/legacy/plugins/task_manager/task_manager.test.ts +++ b/x-pack/legacy/plugins/task_manager/task_manager.test.ts @@ -6,7 +6,7 @@ import _ from 'lodash'; import sinon from 'sinon'; -import { TaskManager } from './task_manager'; +import { TaskManager, claimAvailableTasks } from './task_manager'; import { SavedObjectsClientMock } from 'src/core/server/mocks'; import { SavedObjectsSerializer, SavedObjectsSchema } from 'src/core/server'; import { mockLogger } from './test_utils'; @@ -25,6 +25,9 @@ describe('TaskManager', () => { poll_interval: 6000000, }, }, + server: { + uuid: 'some-uuid', + }, }; const config = { get: (path: string) => _.get(defaultConfig, path), @@ -43,6 +46,29 @@ describe('TaskManager', () => { afterEach(() => clock.restore()); + test('throws if no valid UUID is available', async () => { + expect(() => { + const configWithoutServerUUID = { + xpack: { + task_manager: { + max_workers: 10, + index: 'foo', + max_attempts: 9, + poll_interval: 6000000, + }, + }, + }; + new TaskManager({ + ...taskManagerOpts, + config: { + get: (path: string) => _.get(configWithoutServerUUID, path), + }, + }); + }).toThrowErrorMatchingInlineSnapshot( + `"TaskManager is unable to start as Kibana has no valid UUID assigned to it."` + ); + }); + test('allows and queues scheduling tasks before starting', async () => { const client = new TaskManager(taskManagerOpts); client.registerTaskDefinitions({ @@ -66,6 +92,7 @@ describe('TaskManager', () => { const promise = client.schedule(task); client.start(); await promise; + expect(savedObjectsClient.create).toHaveBeenCalled(); }); @@ -164,4 +191,28 @@ describe('TaskManager', () => { /Cannot add middleware after the task manager is initialized/i ); }); + + describe('claimAvailableTasks', () => { + test('should claim Available Tasks when there are available workers', () => { + const logger = mockLogger(); + const claim = jest.fn(() => Promise.resolve({ docs: [], claimedTasks: 0 })); + + const availableWorkers = 1; + + claimAvailableTasks(claim, availableWorkers, logger); + + expect(claim).toHaveBeenCalledTimes(1); + }); + + test('shouldnt claim Available Tasks when there are no available workers', () => { + const logger = mockLogger(); + const claim = jest.fn(() => Promise.resolve({ docs: [], claimedTasks: 0 })); + + const availableWorkers = 0; + + claimAvailableTasks(claim, availableWorkers, logger); + + expect(claim).not.toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/legacy/plugins/task_manager/task_manager.ts b/x-pack/legacy/plugins/task_manager/task_manager.ts index 7d2794fa33bcc..41b08cda98f21 100644 --- a/x-pack/legacy/plugins/task_manager/task_manager.ts +++ b/x-pack/legacy/plugins/task_manager/task_manager.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import uuid from 'uuid'; import { SavedObjectsClientContract, SavedObjectsSerializer } from 'src/core/server'; import { Logger } from './types'; import { fillPool } from './lib/fill_pool'; @@ -21,7 +20,13 @@ import { import { TaskPoller } from './task_poller'; import { TaskPool } from './task_pool'; import { TaskManagerRunner } from './task_runner'; -import { FetchOpts, FetchResult, TaskStore } from './task_store'; +import { + FetchOpts, + FetchResult, + TaskStore, + OwnershipClaimingOpts, + ClaimOwnershipResult, +} from './task_store'; export interface TaskManagerOpts { logger: Logger; @@ -31,12 +36,6 @@ export interface TaskManagerOpts { serializer: SavedObjectsSerializer; } -function generateTaskManagerUUID(logger: Logger): string { - const taskManagerUUID = uuid.v4(); - logger.info(`Initialising Task Manager with UUID: ${taskManagerUUID}`); - return taskManagerUUID; -} - /* * The TaskManager is the public interface into the task manager system. This glues together * all of the disparate modules in one integration point. The task manager operates in two different ways: @@ -76,6 +75,16 @@ export class TaskManager { this.definitions = {}; this.logger = opts.logger; + const taskManagerId = opts.config.get('server.uuid'); + if (!taskManagerId) { + this.logger.error( + `TaskManager is unable to start as there the Kibana UUID is invalid (value of the "server.uuid" configuration is ${taskManagerId})` + ); + throw new Error(`TaskManager is unable to start as Kibana has no valid UUID assigned to it.`); + } else { + this.logger.info(`TaskManager is identified by the Kibana UUID: ${taskManagerId}`); + } + /* Kibana UUID needs to be pulled live (not cached), as it takes a long time * to initialize, and can change after startup */ const store = new TaskStore({ @@ -85,7 +94,7 @@ export class TaskManager { index: opts.config.get('xpack.task_manager.index'), maxAttempts: opts.config.get('xpack.task_manager.max_attempts'), definitions: this.definitions, - taskManagerId: generateTaskManagerUUID(this.logger), + taskManagerId: `kibana:${taskManagerId}`, }); const pool = new TaskPool({ @@ -103,7 +112,17 @@ export class TaskManager { const poller = new TaskPoller({ logger: this.logger, pollInterval: opts.config.get('xpack.task_manager.poll_interval'), - work: (): Promise => fillPool(pool.run, () => this.claimAvailableTasks(), createRunner), + work: (): Promise => + fillPool( + pool.run, + () => + claimAvailableTasks( + this.store.claimAvailableTasks.bind(this.store), + this.pool.availableWorkers, + this.logger + ), + createRunner + ), }); this.pool = pool; @@ -135,20 +154,6 @@ export class TaskManager { startPoller(); } - private async claimAvailableTasks() { - const { docs, claimedTasks } = await this.store.claimAvailableTasks({ - size: this.pool.availableWorkers, - claimOwnershipUntil: intervalFromNow('30s')!, - }); - - if (docs.length !== claimedTasks) { - this.logger.warn( - `[Task Ownership error]: (${claimedTasks}) tasks were claimed by Kibana, but (${docs.length}) tasks were fetched` - ); - } - return docs; - } - private async waitUntilStarted() { if (!this.isStarted) { await new Promise(resolve => { @@ -247,3 +252,27 @@ export class TaskManager { } } } + +export async function claimAvailableTasks( + claim: (opts: OwnershipClaimingOpts) => Promise, + availableWorkers: number, + logger: Logger +) { + if (availableWorkers > 0) { + const { docs, claimedTasks } = await claim({ + size: availableWorkers, + claimOwnershipUntil: intervalFromNow('30s')!, + }); + + if (docs.length !== claimedTasks) { + logger.warn( + `[Task Ownership error]: (${claimedTasks}) tasks were claimed by Kibana, but (${docs.length}) tasks were fetched` + ); + } + return docs; + } + logger.info( + `[Task Ownership]: Task Manager has skipped Claiming Ownership of available tasks at it has ran out Available Workers. If this happens often, consider adjusting the "xpack.task_manager.max_workers" configuration.` + ); + return []; +} diff --git a/x-pack/plugins/code/server/plugin.ts b/x-pack/plugins/code/server/plugin.ts index 63948ee48501d..208ee6de014e0 100644 --- a/x-pack/plugins/code/server/plugin.ts +++ b/x-pack/plugins/code/server/plugin.ts @@ -16,6 +16,7 @@ import { import { deepFreeze } from '../../../../src/core/utils'; import { PluginSetupContract as FeaturesSetupContract } from '../../features/server'; import { CodeConfigSchema } from './config'; +import { SAVED_OBJ_REPO } from '../../../legacy/plugins/code/common/constants'; /** * Describes public Code plugin contract returned at the `setup` stage. @@ -57,7 +58,7 @@ export class CodePlugin { excludeFromBasePrivileges: true, api: ['code_user', 'code_admin'], savedObject: { - all: [], + all: [SAVED_OBJ_REPO], read: ['config'], }, ui: ['show', 'user', 'admin'], @@ -66,7 +67,7 @@ export class CodePlugin { api: ['code_user'], savedObject: { all: [], - read: ['config'], + read: ['config', SAVED_OBJ_REPO], }, ui: ['show', 'user'], }, diff --git a/x-pack/plugins/features/server/feature_registry.test.ts b/x-pack/plugins/features/server/feature_registry.test.ts index 1e2814021f0f6..66460a811009e 100644 --- a/x-pack/plugins/features/server/feature_registry.test.ts +++ b/x-pack/plugins/features/server/feature_registry.test.ts @@ -494,6 +494,8 @@ describe('FeatureRegistry', () => { featureRegistry.getAll(); expect(() => { featureRegistry.register(feature2); - }).toThrowErrorMatchingInlineSnapshot(`"Features are locked, can't register new features"`); + }).toThrowErrorMatchingInlineSnapshot( + `"Features are locked, can't register new features. Attempt to register test-feature-2 failed."` + ); }); }); diff --git a/x-pack/plugins/features/server/feature_registry.ts b/x-pack/plugins/features/server/feature_registry.ts index c430811f2ead5..bec0ab1ed0bf7 100644 --- a/x-pack/plugins/features/server/feature_registry.ts +++ b/x-pack/plugins/features/server/feature_registry.ts @@ -15,7 +15,9 @@ export class FeatureRegistry { public register(feature: FeatureWithAllOrReadPrivileges) { if (this.locked) { - throw new Error(`Features are locked, can't register new features`); + throw new Error( + `Features are locked, can't register new features. Attempt to register ${feature.id} failed.` + ); } validateFeature(feature); diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts index 10e427a29e442..f01a28847bbdf 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.test.ts @@ -138,6 +138,7 @@ describe('copySavedObjectsToSpaces', () => { Array [ Array [ Object { + "excludeExportDetails": true, "exportSizeLimit": 1000, "includeReferencesDeep": true, "namespace": "sourceSpace", diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.ts index 608d57d873687..76c3037e672ad 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/copy_to_spaces.ts @@ -36,6 +36,7 @@ export function copySavedObjectsToSpacesFactory( const objectStream = await importExport.getSortedObjectsForExport({ namespace: spaceIdToNamespace(sourceSpaceId), includeReferencesDeep: options.includeReferences, + excludeExportDetails: true, objects: options.objects, savedObjectsClient, types: eligibleTypes, diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts index fbafb18699081..97b7480ea4af8 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.test.ts @@ -158,6 +158,7 @@ describe('resolveCopySavedObjectsToSpacesConflicts', () => { Array [ Array [ Object { + "excludeExportDetails": true, "exportSizeLimit": 1000, "includeReferencesDeep": true, "namespace": "sourceSpace", diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.ts index d7c602c28b253..22ceeb9dd4dfa 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/resolve_copy_conflicts.ts @@ -31,6 +31,7 @@ export function resolveCopySavedObjectsToSpacesConflictsFactory( const objectStream = await importExport.getSortedObjectsForExport({ namespace: spaceIdToNamespace(sourceSpaceId), includeReferencesDeep: options.includeReferences, + excludeExportDetails: true, objects: options.objects, savedObjectsClient, types: eligibleTypes, diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 0159e1886f6f5..a930e5c1366a9 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -123,7 +123,7 @@ "common.ui.aggTypes.dateRanges.addRangeButtonLabel": "範囲を追加", "common.ui.aggTypes.dateRanges.fromColumnLabel": "From", "common.ui.aggTypes.dateRanges.toColumnLabel": "To", - "common.ui.aggTypes.definiteMetricLabel": "メトリック: {safeMakeLabel}", + "common.ui.aggTypes.definiteMetricLabel": "メトリック: {metric}", "common.ui.aggTypes.dropPartialBucketsLabel": "不完全なバケットをドロップ", "common.ui.aggTypes.dropPartialBucketsTooltip": "時間範囲外にわたるバケットを削除してヒストグラムが不完全なバケットで開始・終了しないようにします。", "common.ui.aggTypes.extendedBounds.errorMessage": "最低値は最大値以下でなければなりません。", @@ -228,8 +228,6 @@ "common.ui.aggTypes.onlyRequestDataAroundMapExtentLabel": "マップ範囲のデータのみリクエストしてください", "common.ui.aggTypes.onlyRequestDataAroundMapExtentTooltip": "geo_bounding_box フィルター集約を適用して、襟付きのマップビューボックスにサブジェクトエリアを絞ります", "common.ui.aggTypes.orderAgg.alphabeticalLabel": "アルファベット順", - "common.ui.aggTypes.orderAgg.customMetricLabel": "カスタムメトリック", - "common.ui.aggTypes.orderAgg.metricLabel": "メトリック: {metric}", "common.ui.aggTypes.orderAgg.orderByLabel": "並び順", "common.ui.aggTypes.orderLabel": "順序", "common.ui.aggTypes.otherBucket.groupValuesLabel": "他の値を別のバケットにまとめる", @@ -1884,8 +1882,6 @@ "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModalTitle": "保存されたオブジェクトの削除", "kbn.management.objects.objectsTable.export.dangerNotification": "エクスポートを生成できません", "kbn.management.objects.objectsTable.export.successNotification": "ファイルはバックグラウンドでダウンロード中です", - "kbn.management.objects.objectsTable.exportAll.dangerNotification": "エクスポートを生成できません", - "kbn.management.objects.objectsTable.exportAll.successNotification": "ファイルはバックグラウンドでダウンロード中です", "kbn.management.objects.objectsTable.exportObjectsConfirmModal.cancelButtonLabel": "キャンセル", "kbn.management.objects.objectsTable.exportObjectsConfirmModal.exportAllButtonLabel": "すべてエクスポート:", "kbn.management.objects.objectsTable.exportObjectsConfirmModal.exportOptionsLabel": "オプション", @@ -5773,10 +5769,6 @@ "xpack.maps.tooltip.toolsControl.cancelDrawButtonLabel": "キャンセル", "xpack.maps.xyztmssource.attributionLink": "属性テキストにはリンクが必要です", "xpack.maps.xyztmssource.attributionText": "属性 URL にはテキストが必要です", - "xpack.ml.accessDenied.backToKibanaHomeButtonLabel": "Kibana ホームに戻る", - "xpack.ml.accessDenied.noGrantedPrivilegesDescription": "{kibanaUserParam} と {machineLearningUserParam} ロールの権限が必要です。{br}これらのロールはシステム管理者がユーザー管理ページで設定します。", - "xpack.ml.accessDenied.noPermissionToAccessMLLabel": "機械学習へのアクセスにはパーミッションが必要です", - "xpack.ml.accessDenied.retryButtonLabel": "再試行", "xpack.ml.annotationsTable.actionsColumnName": "アクション", "xpack.ml.annotationsTable.annotationColumnName": "注釈", "xpack.ml.annotationsTable.annotationsNotCreatedTitle": "このジョブには注釈が作成されていません", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 1d98f9f4bf227..c522c4cda63a0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -123,7 +123,7 @@ "common.ui.aggTypes.dateRanges.addRangeButtonLabel": "添加范围", "common.ui.aggTypes.dateRanges.fromColumnLabel": "从", "common.ui.aggTypes.dateRanges.toColumnLabel": "到", - "common.ui.aggTypes.definiteMetricLabel": "指标:{safeMakeLabel}", + "common.ui.aggTypes.definiteMetricLabel": "指标:{metric}", "common.ui.aggTypes.dropPartialBucketsLabel": "丢弃部分存储桶", "common.ui.aggTypes.dropPartialBucketsTooltip": "移除超出时间范围的存储桶,以便直方图不以不完整的存储桶开始和结束。", "common.ui.aggTypes.extendedBounds.errorMessage": "最小值应小于或等于最大值。", @@ -228,8 +228,6 @@ "common.ui.aggTypes.onlyRequestDataAroundMapExtentLabel": "仅请求地图范围的数据", "common.ui.aggTypes.onlyRequestDataAroundMapExtentTooltip": "应用 geo_bounding_box 筛选聚合以使用领口将主题区域缩小到地图视图框", "common.ui.aggTypes.orderAgg.alphabeticalLabel": "按字母顺序", - "common.ui.aggTypes.orderAgg.customMetricLabel": "定制指标", - "common.ui.aggTypes.orderAgg.metricLabel": "指标:{metric}", "common.ui.aggTypes.orderAgg.orderByLabel": "排序依据", "common.ui.aggTypes.orderLabel": "顺序", "common.ui.aggTypes.otherBucket.groupValuesLabel": "在单独的存储桶中对其他值分组", @@ -1885,8 +1883,6 @@ "kbn.management.objects.objectsTable.deleteSavedObjectsConfirmModalTitle": "删除已保存对象", "kbn.management.objects.objectsTable.export.dangerNotification": "无法生成报告", "kbn.management.objects.objectsTable.export.successNotification": "您的文件正在后台下载", - "kbn.management.objects.objectsTable.exportAll.dangerNotification": "无法生成报告", - "kbn.management.objects.objectsTable.exportAll.successNotification": "您的文件正在后台下载", "kbn.management.objects.objectsTable.exportObjectsConfirmModal.cancelButtonLabel": "取消", "kbn.management.objects.objectsTable.exportObjectsConfirmModal.exportAllButtonLabel": "全部导出", "kbn.management.objects.objectsTable.exportObjectsConfirmModal.exportOptionsLabel": "选项", @@ -4067,6 +4063,7 @@ "xpack.code.repoItem.reindexConfirmTitle": "重新索引此存储库?", "xpack.code.repoItem.settingsButtonLabel": "设置", "xpack.code.repositoryManagement.repoImportedMessage": "此存储库已导入!", + "xpack.code.repositoryManagement.repoOtherSpaceImportedMessage": "此存储库已在其他空间导入!", "xpack.code.repositoryManagement.repoSubmittedMessage": "{name} 已成功提交!", "xpack.code.repoFileStatus.langugageServerIsInitializitingMessage": "语言服务器正在初始化。", "xpack.code.repoFileStatus.languageServerInitializedMessage": "语言服务器已初始化。", @@ -5775,10 +5772,6 @@ "xpack.maps.tooltip.toolsControl.cancelDrawButtonLabel": "取消", "xpack.maps.xyztmssource.attributionLink": "属性文本必须附带链接", "xpack.maps.xyztmssource.attributionText": "属性 url 必须附带文本", - "xpack.ml.accessDenied.backToKibanaHomeButtonLabel": "返回 Kibana 主页", - "xpack.ml.accessDenied.noGrantedPrivilegesDescription": "您必须具有 {kibanaUserParam} 和 {machineLearningUserParam} 角色授予的权限。{br}您的系统管理员可以在“管理用户”页面上设置这些角色。", - "xpack.ml.accessDenied.noPermissionToAccessMLLabel": "您需要具备访问 Machine Learning 的权限", - "xpack.ml.accessDenied.retryButtonLabel": "重试", "xpack.ml.annotationsTable.actionsColumnName": "操作", "xpack.ml.annotationsTable.annotationColumnName": "注释", "xpack.ml.annotationsTable.annotationsNotCreatedTitle": "没有为此作业创建注释", diff --git a/x-pack/test/api_integration/apis/code/feature_controls.ts b/x-pack/test/api_integration/apis/code/feature_controls.ts index 37149f6bba25a..a96b3074e4f81 100644 --- a/x-pack/test/api_integration/apis/code/feature_controls.ts +++ b/x-pack/test/api_integration/apis/code/feature_controls.ts @@ -12,7 +12,6 @@ export default function featureControlsTests({ getService }: FtrProviderContext) const supertest = getService('supertestWithoutAuth'); const security: SecurityService = getService('security'); const spaces: SpacesService = getService('spaces'); - const log = getService('log'); const expect404 = (result: any) => { expect(result.error).to.be(undefined); @@ -38,12 +37,14 @@ export default function featureControlsTests({ getService }: FtrProviderContext) url: `/api/code/repo/github.com/elastic/code-examples_empty-file`, expectForbidden: expect404, expectResponse: expect200, + isRepoApi: true, }, { // Get the status of one repository. url: `/api/code/repo/status/github.com/elastic/code-examples_empty-file`, expectForbidden: expect404, expectResponse: expect200, + isRepoApi: true, }, { // Get all language server installation status. @@ -104,10 +105,13 @@ export default function featureControlsTests({ getService }: FtrProviderContext) username: string, password: string, spaceId: string, - expectation: 'forbidden' | 'response' + expectation: 'forbidden' | 'response', + skipRepoApi: boolean = false ) { for (const endpoint of endpoints) { - log.debug(`hitting ${endpoint}`); + if (skipRepoApi && endpoint.isRepoApi === true) { + continue; + } const result = await executeRequest(endpoint.url, username, password, spaceId); if (expectation === 'forbidden') { endpoint.expectForbidden(result); @@ -337,6 +341,12 @@ export default function featureControlsTests({ getService }: FtrProviderContext) }); await security.role.create(roleName, { kibana: [ + { + feature: { + code: ['read'], + }, + spaces: ['default'], + }, { feature: { code: ['read'], @@ -364,8 +374,12 @@ export default function featureControlsTests({ getService }: FtrProviderContext) await security.user.delete(username); }); - it('user_1 can access APIs in space_1', async () => { - await executeRequests(username, password, space1Id, 'response'); + it('user_1 can access all APIs in default space', async () => { + await executeRequests(username, password, '', 'response'); + }); + + it('user_1 can access code admin APIs in space_1', async () => { + await executeRequests(username, password, space1Id, 'response', true); }); it(`user_1 cannot access APIs in space_2`, async () => { diff --git a/x-pack/test/api_integration/apis/siem/feature_controls.ts b/x-pack/test/api_integration/apis/siem/feature_controls.ts index 836c35386b332..9bb36810021af 100644 --- a/x-pack/test/api_integration/apis/siem/feature_controls.ts +++ b/x-pack/test/api_integration/apis/siem/feature_controls.ts @@ -20,6 +20,7 @@ const introspectionQuery = gql` `; export default function({ getService }: FtrProviderContext) { + const config = getService('config'); const supertest = getService('supertestWithoutAuth'); const security: SecurityService = getService('security'); const spaces: SpacesService = getService('spaces'); @@ -82,6 +83,11 @@ export default function({ getService }: FtrProviderContext) { }; describe('feature controls', () => { + let isProd = false; + before(() => { + const kbnConfig = config.get('servers.kibana'); + isProd = kbnConfig.hostname === 'localhost' && kbnConfig.port === 5620 ? false : true; + }); it(`APIs can't be accessed by user with no privileges`, async () => { const username = 'logstash_read'; const roleName = 'logstash_read'; @@ -130,7 +136,11 @@ export default function({ getService }: FtrProviderContext) { expectGraphQLResponse(graphQLResult); const graphQLIResult = await executeGraphIQLRequest(username, password); - expectGraphIQLResponse(graphQLIResult); + if (!isProd) { + expectGraphIQLResponse(graphQLIResult); + } else { + expectGraphIQL404(graphQLIResult); + } } finally { await security.role.delete(roleName); await security.user.delete(username); @@ -225,7 +235,11 @@ export default function({ getService }: FtrProviderContext) { expectGraphQLResponse(graphQLResult); const graphQLIResult = await executeGraphIQLRequest(username, password, space1Id); - expectGraphIQLResponse(graphQLIResult); + if (!isProd) { + expectGraphIQLResponse(graphQLIResult); + } else { + expectGraphIQL404(graphQLIResult); + } }); it(`user_1 can't access APIs in space_2`, async () => { diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/indices.js b/x-pack/test/functional/apps/monitoring/elasticsearch/indices.js index ff809b95a834e..1ed7c15a9ecf1 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/indices.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/indices.js @@ -34,7 +34,7 @@ export default function ({ getService, getPageObjects }) { expect(await esClusterSummaryStatus.getContent()).to.eql({ nodesCount: 'Nodes\n1', indicesCount: 'Indices\n19', - memory: 'Memory\n267.7 MB / 676.8 MB', + memory: 'JVM Heap\n267.7 MB / 676.8 MB', totalShards: 'Total shards\n46', unassignedShards: 'Unassigned shards\n23', documentCount: 'Documents\n4,535', diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js b/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js index 86f47775e50cd..7ad09e034e13b 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/nodes.js @@ -38,7 +38,7 @@ export default function ({ getService, getPageObjects }) { expect(await esClusterSummaryStatus.getContent()).to.eql({ nodesCount: 'Nodes\n2', indicesCount: 'Indices\n20', - memory: 'Memory\n696.6 MB / 1.3 GB', + memory: 'JVM Heap\n696.6 MB / 1.3 GB', totalShards: 'Total shards\n79', unassignedShards: 'Unassigned shards\n7', documentCount: 'Documents\n25,758', @@ -213,7 +213,7 @@ export default function ({ getService, getPageObjects }) { expect(await esClusterSummaryStatus.getContent()).to.eql({ nodesCount: 'Nodes\n3', indicesCount: 'Indices\n20', - memory: 'Memory\n575.3 MB / 2.0 GB', + memory: 'JVM Heap\n575.3 MB / 2.0 GB', totalShards: 'Total shards\n80', unassignedShards: 'Unassigned shards\n5', documentCount: 'Documents\n25,927', diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/overview.js b/x-pack/test/functional/apps/monitoring/elasticsearch/overview.js index d86127b3e6fb8..a0e91e2336e08 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/overview.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/overview.js @@ -34,7 +34,7 @@ export default function ({ getService, getPageObjects }) { expect(await esClusterSummaryStatus.getContent()).to.eql({ nodesCount: 'Nodes\n3', indicesCount: 'Indices\n20', - memory: 'Memory\n575.3 MB / 2.0 GB', + memory: 'JVM Heap\n575.3 MB / 2.0 GB', totalShards: 'Total shards\n80', unassignedShards: 'Unassigned shards\n5', documentCount: 'Documents\n25,927', diff --git a/x-pack/test/functional/es_archives/code/repositories/code_examples_flatten_directory/data.json.gz b/x-pack/test/functional/es_archives/code/repositories/code_examples_flatten_directory/data.json.gz index f06cede816146..8c9134e2ce38b 100644 Binary files a/x-pack/test/functional/es_archives/code/repositories/code_examples_flatten_directory/data.json.gz and b/x-pack/test/functional/es_archives/code/repositories/code_examples_flatten_directory/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/code/repositories/typescript_node_starter/data.json.gz b/x-pack/test/functional/es_archives/code/repositories/typescript_node_starter/data.json.gz index 953af474d5bda..5ba03fcf9b56f 100644 Binary files a/x-pack/test/functional/es_archives/code/repositories/typescript_node_starter/data.json.gz and b/x-pack/test/functional/es_archives/code/repositories/typescript_node_starter/data.json.gz differ diff --git a/x-pack/test/functional/page_objects/graph_page.ts b/x-pack/test/functional/page_objects/graph_page.ts index 52b535a92e63e..780a541db0890 100644 --- a/x-pack/test/functional/page_objects/graph_page.ts +++ b/x-pack/test/functional/page_objects/graph_page.ts @@ -239,10 +239,10 @@ export function GraphPageProvider({ getService, getPageObjects }: FtrProviderCon } async deleteGraph(name: string) { - await this.searchForWorkspaceWithName(name); await testSubjects.click('checkboxSelectAll'); await this.clickDeleteSelectedWorkspaces(); await PageObjects.common.clickConfirmOnModal(); + await testSubjects.find('graphCreateGraphPromptButton'); } async getWorkspaceCount() { diff --git a/x-pack/test/saved_object_api_integration/common/suites/export.ts b/x-pack/test/saved_object_api_integration/common/suites/export.ts index d7d1a99e63e02..114a1fe53ccd6 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/export.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/export.ts @@ -104,6 +104,7 @@ export function exportTestSuiteFactory(esArchiver: any, supertest: SuperTest