diff --git a/.eslintrc.js b/.eslintrc.js index 09de32a91bca3..67c52117399cc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -446,6 +446,7 @@ module.exports = { '!(src|x-pack)/plugins/**/(public|server)/mocks/index.{js,mjs,ts}', '!(src|x-pack)/plugins/**/(public|server)/(index|mocks).{js,mjs,ts,tsx}', '!(src|x-pack)/plugins/**/__stories__/index.{js,mjs,ts,tsx}', + '!(src|x-pack)/plugins/**/__fixtures__/index.{js,mjs,ts,tsx}', ], allowSameFolder: true, errorMessage: 'Plugins may only import from top-level public and server modules.', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 168db2c4efb84..0744112650c23 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -249,14 +249,14 @@ #CC# /x-pack/plugins/translations/ @elastic/kibana-localization @elastic/kibana-core # Security -/src/core/server/csp/ @elastic/kibana-security @elastic/kibana-core +/src/core/server/csp/ @elastic/kibana-security @elastic/kibana-core /src/plugins/security_oss/ @elastic/kibana-security /src/plugins/spaces_oss/ @elastic/kibana-security /src/plugins/user_setup/ @elastic/kibana-security /test/security_functional/ @elastic/kibana-security -/x-pack/plugins/spaces/ @elastic/kibana-security -/x-pack/plugins/encrypted_saved_objects/ @elastic/kibana-security -/x-pack/plugins/security/ @elastic/kibana-security +/x-pack/plugins/spaces/ @elastic/kibana-security +/x-pack/plugins/encrypted_saved_objects/ @elastic/kibana-security +/x-pack/plugins/security/ @elastic/kibana-security /x-pack/test/api_integration/apis/security/ @elastic/kibana-security /x-pack/test/ui_capabilities/ @elastic/kibana-security /x-pack/test/encrypted_saved_objects_api_integration/ @elastic/kibana-security @@ -265,6 +265,8 @@ /x-pack/test/security_functional/ @elastic/kibana-security /x-pack/test/spaces_api_integration/ @elastic/kibana-security /x-pack/test/saved_object_api_integration/ @elastic/kibana-security +/src/core/server/csp/ @elastic/kibana-security @elastic/kibana-core +/examples/preboot_example/ @elastic/kibana-security @elastic/kibana-core #CC# /x-pack/plugins/security/ @elastic/kibana-security # Kibana Alerting Services diff --git a/.i18nrc.json b/.i18nrc.json index 732644b43e1f7..0ee1e55ed62c6 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -18,6 +18,7 @@ "expressions": "src/plugins/expressions", "expressionError": "src/plugins/expression_error", "expressionRevealImage": "src/plugins/expression_reveal_image", + "expressionShape": "src/plugins/expression_shape", "inputControl": "src/plugins/input_control_vis", "inspector": "src/plugins/inspector", "inspectorViews": "src/legacy/core_plugins/inspector_views", @@ -28,6 +29,7 @@ "management": ["src/legacy/core_plugins/management", "src/plugins/management"], "maps_legacy": "src/plugins/maps_legacy", "monaco": "packages/kbn-monaco/src", + "esQuery": "packages/kbn-es-query/src", "presentationUtil": "src/plugins/presentation_util", "indexPatternFieldEditor": "src/plugins/index_pattern_field_editor", "indexPatternManagement": "src/plugins/index_pattern_management", diff --git a/api_docs/alerting.json b/api_docs/alerting.json index ec784dc8fc991..7c860a5c19e34 100644 --- a/api_docs/alerting.json +++ b/api_docs/alerting.json @@ -669,10 +669,10 @@ "children": [ { "parentPluginId": "alerting", - "id": "def-server.AlertingApiRequestHandlerContext.getAlertsClient", + "id": "def-server.AlertingApiRequestHandlerContext.getRulesClient", "type": "Function", "tags": [], - "label": "getAlertsClient", + "label": "getRulesClient", "description": [], "signature": [ "() => ", @@ -680,8 +680,8 @@ "pluginId": "alerting", "scope": "server", "docId": "kibAlertingPluginApi", - "section": "def-server.AlertsClient", - "text": "AlertsClient" + "section": "def-server.RulesClient", + "text": "RulesClient" } ], "source": { @@ -1170,7 +1170,7 @@ "" ], "source": { - "path": "x-pack/plugins/alerting/server/alerts_client/alerts_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", "lineNumber": 140 }, "deprecated": false, @@ -1183,7 +1183,7 @@ "label": "page", "description": [], "source": { - "path": "x-pack/plugins/alerting/server/alerts_client/alerts_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", "lineNumber": 141 }, "deprecated": false @@ -1196,7 +1196,7 @@ "label": "perPage", "description": [], "source": { - "path": "x-pack/plugins/alerting/server/alerts_client/alerts_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", "lineNumber": 142 }, "deprecated": false @@ -1209,7 +1209,7 @@ "label": "total", "description": [], "source": { - "path": "x-pack/plugins/alerting/server/alerts_client/alerts_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", "lineNumber": 143 }, "deprecated": false @@ -1233,7 +1233,7 @@ ", \"enabled\" | \"id\" | \"name\" | \"params\" | \"actions\" | \"tags\" | \"alertTypeId\" | \"consumer\" | \"schedule\" | \"scheduledTaskId\" | \"createdBy\" | \"updatedBy\" | \"createdAt\" | \"updatedAt\" | \"apiKeyOwner\" | \"throttle\" | \"notifyWhen\" | \"muteAll\" | \"mutedInstanceIds\" | \"executionStatus\">[]" ], "source": { - "path": "x-pack/plugins/alerting/server/alerts_client/alerts_client.ts", + "path": "x-pack/plugins/alerting/server/rules_client/rules_client.ts", "lineNumber": 144 }, "deprecated": false @@ -1343,10 +1343,10 @@ }, { "parentPluginId": "alerting", - "id": "def-server.PluginStartContract.getAlertsClientWithRequest", + "id": "def-server.PluginStartContract.getRulesClientWithRequest", "type": "Function", "tags": [], - "label": "getAlertsClientWithRequest", + "label": "getRulesClientWithRequest", "description": [], "signature": [ "(request: ", @@ -1362,8 +1362,8 @@ "pluginId": "alerting", "scope": "server", "docId": "kibAlertingPluginApi", - "section": "def-server.AlertsClient", - "text": "AlertsClient" + "section": "def-server.RulesClient", + "text": "RulesClient" }, ", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"aggregate\" | \"enable\" | \"disable\" | \"muteAll\" | \"getAlertState\" | \"getAlertInstanceSummary\" | \"updateApiKey\" | \"unmuteAll\" | \"muteInstance\" | \"unmuteInstance\" | \"listAlertTypes\">" ], @@ -1375,7 +1375,7 @@ "children": [ { "parentPluginId": "alerting", - "id": "def-server.PluginStartContract.getAlertsClientWithRequest.$1", + "id": "def-server.PluginStartContract.getRulesClientWithRequest.$1", "type": "Object", "tags": [], "label": "request", @@ -1571,10 +1571,10 @@ }, { "parentPluginId": "alerting", - "id": "def-server.AlertsClient", + "id": "def-server.RulesClient", "type": "Type", "tags": [], - "label": "AlertsClient", + "label": "RulesClient", "description": [], "signature": [ "{ get: = never>({ id, }: { id: string; }) => Promise | [audit_logger.ts#L8](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/authorization/audit_logger.ts#L8) | - | | | [audit_logger.ts#L21](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/authorization/audit_logger.ts#L21) | - | | | [audit_logger.ts#L23](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/authorization/audit_logger.ts#L23) | - | -| | [alerts_client_factory.test.ts#L23](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/alerts_client_factory.test.ts#L23) | - | -| | [alerts_client_factory.test.ts#L98](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/alerts_client_factory.test.ts#L98) | - | +| | [rules_client_factory.test.ts#L23](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/rules_client_factory.test.ts#L23) | - | +| | [rules_client_factory.test.ts#L98](https://github.com/elastic/kibana/tree/master/x-pack/plugins/alerting/server/rules_client_factory.test.ts#L98) | - | diff --git a/api_docs/security.json b/api_docs/security.json index ac0982be3f19e..a02e4cebc5fbf 100644 --- a/api_docs/security.json +++ b/api_docs/security.json @@ -1739,14 +1739,14 @@ { "plugin": "alerting", "link": { - "path": "x-pack/plugins/alerting/server/alerts_client_factory.test.ts", + "path": "x-pack/plugins/alerting/server/rules_client_factory.test.ts", "lineNumber": 23 } }, { "plugin": "alerting", "link": { - "path": "x-pack/plugins/alerting/server/alerts_client_factory.test.ts", + "path": "x-pack/plugins/alerting/server/rules_client_factory.test.ts", "lineNumber": 98 } } diff --git a/docs/apm/api.asciidoc b/docs/apm/api.asciidoc index d9a8d0558714f..fe4c8a9280158 100644 --- a/docs/apm/api.asciidoc +++ b/docs/apm/api.asciidoc @@ -11,6 +11,7 @@ Some APM app features are provided via a REST API: * <> * <> * <> +* <> [float] [[apm-api-example]] @@ -72,6 +73,7 @@ curl -X POST \ //// ******************************************************* +******************************************************* //// [role="xpack"] @@ -202,11 +204,9 @@ DELETE /api/apm/settings/agent-configuration ******************************************************* //// - [[apm-list-config]] ==== List configuration - [[apm-list-config-req]] ===== Request @@ -274,7 +274,6 @@ GET /api/apm/settings/agent-configuration ******************************************************* //// - [[apm-search-config]] ==== Search configuration @@ -472,6 +471,7 @@ curl -X POST \ //// ******************************************************* +******************************************************* //// [[kibana-api]] @@ -553,3 +553,239 @@ The API returns the following: // More examples will go here More information on Kibana's API is available in <>. + +//// +******************************************************* +******************************************************* +//// + +[role="xpack"] +[[rum-sourcemap-api]] +=== RUM source map API + +IMPORTANT: This endpoint is only compatible with the +{apm-server-ref}/apm-integration.html[APM integration for Elastic Agent]. +Users with a standalone APM Server should instead use the APM Server +{apm-server-ref}/sourcemap-api.html[source map upload API]. + +A source map allows minified files to be mapped back to original source code -- +allowing you to maintain the speed advantage of minified code, +without losing the ability to quickly and easily debug your application. + +For best results, uploading source maps should become a part of your deployment procedure, +and not something you only do when you see unhelpful errors. +That’s because uploading source maps after errors happen won’t make old errors magically readable -- +errors must occur again for source mapping to occur. + +The following APIs are available: + +* <> +* <> +* <> + +[float] +[[use-sourcemap-api]] +==== How to use APM APIs + +.Expand for required headers, privileges, and usage details +[%collapsible%closed] +====== +include::api.asciidoc[tag=using-the-APIs] +====== + +//// +******************************************************* +//// + +[[rum-sourcemap-post]] +==== Create or update source map + +Create or update a source map for a specific service and version. + +[[rum-sourcemap-post-privs]] +===== Privileges + +The user accessing this endpoint requires `All` Kibana privileges for the {beat_kib_app} feature. +For more information, see <>. + +[[apm-sourcemap-post-req]] +===== Request + +`POST /api/apm/sourcemaps` + +[role="child_attributes"] +[[apm-sourcemap-post-req-body]] +===== Request body + +`service_name`:: +(required, string) The name of the service that the service map should apply to. + +`service_version`:: +(required, string) The version of the service that the service map should apply to. + +`bundle_filepath`:: +(required, string) The absolute path of the final bundle as used in the web application. + +`sourcemap`:: +(required, string or file upload) The source map. It must follow the +https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k[source map revision 3 proposal]. + +[[apm-sourcemap-post-example]] +===== Examples + +The following example uploads a source map for a service named `foo` and a service version of `1.0.0`: + +[source,curl] +-------------------------------------------------- +curl -X POST "http://localhost:5601/api/apm/sourcemaps" \ +-H 'Content-Type: multipart/form-data' \ +-H 'kbn-xsrf: true' \ +-H 'Authorization: ApiKey ${YOUR_API_KEY}' \ +-F 'service_name="foo"' \ +-F 'service_version="1.0.0"' \ +-F 'bundle_filepath="/test/e2e/general-usecase/bundle.js.map"' \ +-F 'sourcemap="{\"version\":3,\"file\":\"static/js/main.chunk.js\",\"sources\":[\"fleet-source-map-client/src/index.css\",\"fleet-source-map-client/src/App.js\",\"webpack:///./src/index.css?bb0a\",\"fleet-source-map-client/src/index.js\",\"fleet-source-map-client/src/reportWebVitals.js\"],\"sourcesContent\":[\"content\"],\"mappings\":\"mapping\",\"sourceRoot\":\"\"}"' <1> +-------------------------------------------------- +<1> Alternatively, upload the source map as a file with `-F 'sourcemap=@path/to/source_map/bundle.js.map'` + +[[apm-sourcemap-post-body]] +===== Response body + +[source,js] +-------------------------------------------------- +{ + "type": "sourcemap", + "identifier": "foo-1.0.0", + "relative_url": "/api/fleet/artifacts/foo-1.0.0/644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", + "body": "eJyFkL1OwzAUhd/Fc+MbYMuCEBIbHRjKgBgc96R16tiWr1OQqr47NwqJxEK3q/PzWccXxchnZ7E1A1SjuhjVZtF2yOxiEPlO17oWox3D3uPFeSRTjmJQARfCPeiAgGx8NTKsYdAc1T3rwaSJGcds8Sp3c1HnhfywUZ3QhMTFFGepZxqMC9oex3CS9tpk1XyozgOlmoVKuJX1DqEQZ0su7PGtLU+V/3JPKc3cL7TJ2FNDRPov4bFta3MDM4f7W69lpJjLO9qdK8bzVPhcJz3HUCQ4LbO/p5hCSC4cZPByrp/wFqOklbpefwAhzpqI", + "created": "2021-07-09T20:47:44.812Z", + "id": "apm:foo-1.0.0-644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", + "compressionAlgorithm": "zlib", + "decodedSha256": "644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", + "decodedSize": 441, + "encodedSha256": "024c72749c3e3dd411b103f7040ae62633558608f480bce4b108cf5b2275bd24", + "encodedSize": 237, + "encryptionAlgorithm": "none", + "packageName": "apm" +} +-------------------------------------------------- + +//// +******************************************************* +//// + +[[rum-sourcemap-get]] +==== Get source maps + +Returns an array of Fleet artifacts, including source map uploads. + +[[rum-sourcemap-get-privs]] +===== Privileges + +The user accessing this endpoint requires `Read` or `All` Kibana privileges for the {beat_kib_app} feature. +For more information, see <>. + +[[apm-sourcemap-get-req]] +===== Request + +`GET /api/apm/sourcemaps` + +[[apm-sourcemap-get-example]] +===== Example + +The following example requests all uploaded source maps: + +[source,curl] +-------------------------------------------------- +curl -X GET "http://localhost:5601/api/apm/sourcemaps" \ +-H 'Content-Type: application/json' \ +-H 'kbn-xsrf: true' \ +-H 'Authorization: ApiKey ${YOUR_API_KEY}' +-------------------------------------------------- + +[[apm-sourcemap-get-body]] +===== Response body + +[source,js] +-------------------------------------------------- +{ + "artifacts": [ + { + "type": "sourcemap", + "identifier": "foo-1.0.0", + "relative_url": "/api/fleet/artifacts/foo-1.0.0/644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", + "body": { + "serviceName": "foo", + "serviceVersion": "1.0.0", + "bundleFilepath": "/test/e2e/general-usecase/bundle.js.map", + "sourceMap": { + "version": 3, + "file": "static/js/main.chunk.js", + "sources": [ + "fleet-source-map-client/src/index.css", + "fleet-source-map-client/src/App.js", + "webpack:///./src/index.css?bb0a", + "fleet-source-map-client/src/index.js", + "fleet-source-map-client/src/reportWebVitals.js" + ], + "sourcesContent": [ + "content" + ], + "mappings": "mapping", + "sourceRoot": "" + } + }, + "created": "2021-07-09T20:47:44.812Z", + "id": "apm:foo-1.0.0-644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", + "compressionAlgorithm": "zlib", + "decodedSha256": "644fd5a997d1ddd90ee131ba18e2b3d03931d89dd1fe4599143c0b3264b3e456", + "decodedSize": 441, + "encodedSha256": "024c72749c3e3dd411b103f7040ae62633558608f480bce4b108cf5b2275bd24", + "encodedSize": 237, + "encryptionAlgorithm": "none", + "packageName": "apm" + } + ] +} +-------------------------------------------------- + +//// +******************************************************* +//// + +[[rum-sourcemap-delete]] +==== Delete source map + +Delete a previously uploaded source map. + +[[rum-sourcemap-delete-privs]] +===== Privileges + +The user accessing this endpoint requires `All` Kibana privileges for the {beat_kib_app} feature. +For more information, see <>. + +[[apm-sourcemap-delete-req]] +===== Request + +`DELETE /api/apm/sourcemaps/:id` + +[[apm-sourcemap-delete-example]] +===== Example + +The following example deletes a source map with an id of `apm:foo-1.0.0-644fd5a9`: + +[source,curl] +-------------------------------------------------- +curl -X DELETE "http://localhost:5601/api/apm/sourcemaps/apm:foo-1.0.0-644fd5a9" \ +-H 'Content-Type: application/json' \ +-H 'kbn-xsrf: true' \ +-H 'Authorization: ApiKey ${YOUR_API_KEY}' +-------------------------------------------------- + +[[apm-sourcemap-delete-body]] +===== Response body + +[source,js] +-------------------------------------------------- +{} +-------------------------------------------------- diff --git a/docs/apm/apm-app-users.asciidoc b/docs/apm/apm-app-users.asciidoc index 9b8a9c64ac43b..a8eb619a8eab8 100644 --- a/docs/apm/apm-app-users.asciidoc +++ b/docs/apm/apm-app-users.asciidoc @@ -3,7 +3,6 @@ == APM app users and privileges :beat_default_index_prefix: apm -:beat_kib_app: APM app :annotation_index: observability-annotations ++++ @@ -22,7 +21,7 @@ In general, there are three types of privileges you'll work with: * **Elasticsearch cluster privileges**: Manage the actions a user can perform against your cluster. * **Elasticsearch index privileges**: Control access to the data in specific indices your cluster. -* **Kibana space privileges**: Grant users write or read access to features and apps within Kibana. +* **Kibana feature privileges**: Grant users write or read access to features and apps within Kibana. Select your use-case to get started: @@ -88,18 +87,18 @@ include::./tab-widgets/apm-app-reader/widget.asciidoc[] TIP: Using the {apm-server-ref-v}/apm-integration.html[APM integration for Elastic Agent]? Add the privileges under the **Data streams** tab. -. Assign space privileges to any Kibana space that the user needs access to. +. Assign feature privileges to any Kibana feature that the user needs access to. Here are two examples: + [options="header"] |==== |Type | Privilege | Purpose -| Spaces -| `Read` or `All` on the {beat_kib_app} -| Allow the use of the the {beat_kib_app} +| Kibana +| `Read` or `All` on the {beat_kib_app} feature +| Allow the use of the the {beat_kib_app} apps -| Spaces +| Kibana | `Read` or `All` on Dashboards and Discover | Allow the user to view, edit, and create dashboards, as well as browse data. |==== @@ -190,16 +189,16 @@ include::./tab-widgets/central-config-users/widget.asciidoc[] TIP: Using the {apm-server-ref-v}/apm-integration.html[APM integration for Elastic Agent]? Add the privileges under the **Data streams** tab. -. Assign the `central-config-manager` role created in the previous step, and the following Kibana space privileges to -anyone who needs to manage central configurations: +. Assign the `central-config-manager` role created in the previous step, +and the following Kibana feature privileges to anyone who needs to manage central configurations: + [options="header"] |==== |Type | Privilege | Purpose -| Spaces -|`All` on {beat_kib_app} -|Allow full use of the {beat_kib_app} +| Kibana +|`All` on the {beat_kib_app} feature +|Allow full use of the {beat_kib_app} apps |==== [[apm-app-central-config-reader]] @@ -217,16 +216,16 @@ include::./tab-widgets/central-config-users/widget.asciidoc[] TIP: Using the {apm-server-ref-v}/apm-integration.html[APM integration for Elastic Agent]? Add the privileges under the **Data streams** tab. -. Assign the `central-config-reader` role created in the previous step, and the following Kibana space privileges to -anyone who needs to read central configurations: +. Assign the `central-config-reader` role created in the previous step, +and the following Kibana feature privileges to anyone who needs to read central configurations: + [options="header"] |==== |Type | Privilege | Purpose -| Spaces -|`read` on the {beat_kib_app} -|Allow read access to the {beat_kib_app} +| Kibana +|`read` on the {beat_kib_app} feature +|Allow read access to the {beat_kib_app} apps |==== [[apm-app-central-config-api]] @@ -253,15 +252,15 @@ include::./tab-widgets/code.asciidoc[] Users can list, search, create, update, and delete central configurations via the APM app API. -. Assign the following Kibana space privileges: +. Assign the following Kibana feature privileges: + [options="header"] |==== |Type | Privilege | Purpose -| Spaces -|`all` on the {beat_kib_app} -|Allow all access to the {beat_kib_app} +| Kibana +|`all` on the {beat_kib_app} feature +|Allow all access to the {beat_kib_app} apps |==== [[apm-app-api-config-reader]] @@ -269,15 +268,15 @@ Users can list, search, create, update, and delete central configurations via th Sometimes a user only needs to list and search central configurations via the APM app API. -. Assign the following Kibana space privileges: +. Assign the following Kibana feature privileges: + [options="header"] |==== |Type | Privilege | Purpose -| Spaces -|`read` on the {beat_kib_app} -|Allow read access to the {beat_kib_app} +| Kibana +|`read` on the {beat_kib_app} feature +|Allow read access to the {beat_kib_app} apps |==== [[apm-app-api-annotation-manager]] @@ -310,15 +309,15 @@ and assign the following privileges: |==== . Assign the `annotation_role` created previously, -and the following Kibana space privileges to any annotation API users: +and the following Kibana feature privileges to any annotation API users: + [options="header"] |==== |Type | Privilege | Purpose -| Spaces -|`all` on the {beat_kib_app} -|Allow all access to the {beat_kib_app} +| Kibana +|`all` on the {beat_kib_app} feature +|Allow all access to the {beat_kib_app} apps |==== //LEARN MORE diff --git a/docs/apm/index.asciidoc b/docs/apm/index.asciidoc index 53ffee5e061d6..f4d35a2d554ba 100644 --- a/docs/apm/index.asciidoc +++ b/docs/apm/index.asciidoc @@ -25,6 +25,8 @@ you can also see contextual information such as the request header, user informa system values, or custom data that you manually attached to the request. -- +:beat_kib_app: APM and User Experience + include::set-up.asciidoc[] include::getting-started.asciidoc[] diff --git a/docs/concepts/images/add-filter-popup.png b/docs/concepts/images/add-filter-popup.png index f96c8746ef17a..28a2dc0013f60 100644 Binary files a/docs/concepts/images/add-filter-popup.png and b/docs/concepts/images/add-filter-popup.png differ diff --git a/docs/concepts/images/global-search.png b/docs/concepts/images/global-search.png index a867477938219..c5080da40d97e 100644 Binary files a/docs/concepts/images/global-search.png and b/docs/concepts/images/global-search.png differ diff --git a/docs/concepts/images/refresh-every.png b/docs/concepts/images/refresh-every.png index ea3b24d80b8c3..6b9ff7c8f9d9f 100644 Binary files a/docs/concepts/images/refresh-every.png and b/docs/concepts/images/refresh-every.png differ diff --git a/docs/concepts/images/saved-query-popup.png b/docs/concepts/images/saved-query-popup.png index 93973b8de0a54..a139cab486257 100644 Binary files a/docs/concepts/images/saved-query-popup.png and b/docs/concepts/images/saved-query-popup.png differ diff --git a/docs/concepts/images/saved-query.png b/docs/concepts/images/saved-query.png index 5db819ce648e8..eb0c0f984f483 100644 Binary files a/docs/concepts/images/saved-query.png and b/docs/concepts/images/saved-query.png differ diff --git a/docs/concepts/images/time-filter.png b/docs/concepts/images/time-filter.png index e3a1ce2216c1c..7bf7839ec773d 100644 Binary files a/docs/concepts/images/time-filter.png and b/docs/concepts/images/time-filter.png differ diff --git a/docs/concepts/images/time-relative.png b/docs/concepts/images/time-relative.png index b77d55df2d9ea..462687de720e0 100644 Binary files a/docs/concepts/images/time-relative.png and b/docs/concepts/images/time-relative.png differ diff --git a/docs/concepts/index.asciidoc b/docs/concepts/index.asciidoc index 43e5ae733a760..ae6e48556f684 100644 --- a/docs/concepts/index.asciidoc +++ b/docs/concepts/index.asciidoc @@ -83,7 +83,7 @@ resubmit your searches. You can also click *Refresh* to resubmit the search. This might be useful if you use {kib} to monitor the underlying data. [role="screenshot"] -image:concepts/images/refresh-every.png["section of time filter where you can configure a refresh rate"] +image:concepts/images/refresh-every.png["section of time filter where you can configure a refresh rate", width=75%] [float] diff --git a/docs/concepts/kuery.asciidoc b/docs/concepts/kuery.asciidoc index a92fc182f388c..53d445b932d62 100644 --- a/docs/concepts/kuery.asciidoc +++ b/docs/concepts/kuery.asciidoc @@ -6,10 +6,7 @@ free text search or field-based search. KQL is only used for filtering data, and no role in sorting or aggregating the data. KQL is able to suggest field names, values, and operators as you type. -The performance of the suggestions is controlled by <>: - -[role="screenshot"] -image::images/kql-autocomplete.png[Autocomplete in Search bar] +The performance of the suggestions is controlled by <>. KQL has a different set of features than the <>. KQL is able to query nested fields and <>. KQL does not support regular expressions diff --git a/docs/concepts/save-query.asciidoc b/docs/concepts/save-query.asciidoc index fa626f6eaa913..61113b5491c29 100644 --- a/docs/concepts/save-query.asciidoc +++ b/docs/concepts/save-query.asciidoc @@ -35,7 +35,7 @@ For more information, see <>. * *Refresh every* to specify an automatic refresh rate. + [role="screenshot"] -image::concepts/images/time-filter.png[Time filter menu] +image::concepts/images/time-filter.png["Time filter menu", width=75%] . To set start and end times, click the bar next to the time filter. In the popup, select *Absolute*, *Relative* or *Now*, then specify the required options. + [role="screenshot"] -image::concepts/images/time-relative.png[Time filter showing relative time] +image::concepts/images/time-relative.png["Time filter showing relative time", width=75%] diff --git a/docs/dev-tools/console/console.asciidoc b/docs/dev-tools/console/console.asciidoc index 7cef937633062..731290fea22d0 100644 --- a/docs/dev-tools/console/console.asciidoc +++ b/docs/dev-tools/console/console.asciidoc @@ -1,7 +1,7 @@ [[console-kibana]] == Console -Console enables you to interact with the REST API of {es}. You can: +*Console* enables you to interact with the REST API of {es}. You can: * Send requests to {es} and view the responses * View API documentation @@ -12,13 +12,13 @@ To get started, open the main menu, click *Dev Tools*, then click *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. +NOTE: You cannot to interact with the REST API of {kib} with the Console. [float] [[console-api]] === Write requests -Console understands commands in a cURL-like syntax. +*Console* understands commands in a cURL-like syntax. For example, the following is a `GET` request to the {es} `_search` API. [source,js] @@ -43,8 +43,8 @@ curl -XGET "http://localhost:9200/_search" -d' }' ---------------------------------- -When you paste the command into Console, {kib} automatically converts it -to Console syntax. Alternatively, if you want to see Console syntax in cURL, +When you paste the command into *Console*, {kib} automatically converts it +to *Console* syntax. Alternatively, if you want to see *Console* syntax in cURL, click the action icon (image:dev-tools/console/images/wrench.png[]) and select *Copy as cURL*. Once copied, the username and password will need to be provided for the calls to work from external environments. @@ -53,7 +53,7 @@ for the calls to work from external environments. [[console-autocomplete]] ==== Autocomplete -When you're typing a command, Console makes context-sensitive suggestions. +When you're typing a command, *Console* makes context-sensitive suggestions. These suggestions show you the parameters for each API and speed up your typing. To configure your preferences for autocomplete, go to <>. @@ -69,15 +69,16 @@ and then select *Auto indent*. For example, you might have a request formatted like this: [role="screenshot"] -image::dev-tools/console/images/copy-curl.png["Console close-up"] +image::dev-tools/console/images/copy-curl.png["Console close-up", width=75%] +] -Console adjusts the JSON body of the request to apply the indents. +*Console* adjusts the JSON body of the request to apply the indents. [role="screenshot"] -image::dev-tools/console/images/request.png["Console close-up"] +image::dev-tools/console/images/request.png["Console close-up", width=75%] If you select *Auto indent* on a request that is already well formatted, -Console collapses the request body to a single line per document. +*Console* collapses the request body to a single line per document. This is helpful when working with the {es} {ref}/docs-bulk.html[bulk APIs]. @@ -90,8 +91,9 @@ When you're ready to submit the request to {es}, click the green triangle. You can select multiple requests and submit them together. -Console sends the requests to {es} one by one and shows the output -in the response pane. Submitting multiple request is helpful when you're debugging an issue or trying query +*Console* sends the requests to {es} one by one and shows the output +in the response pane. Submitting multiple requests is helpful +when you're debugging an issue or trying query combinations in multiple scenarios. @@ -107,7 +109,7 @@ the action icon (image:dev-tools/console/images/wrench.png[]) and select [[console-history]] === Get your request history -Console maintains a list of the last 500 requests that {es} successfully executed. +*Console* maintains a list of the last 500 requests that {es} successfully executed. To view your most recent requests, click *History*. If you select a request and click *Apply*, {kib} adds it to the editor at the current cursor position. @@ -115,11 +117,11 @@ and click *Apply*, {kib} adds it to the editor at the current cursor position. [[configuring-console]] === Configure Console settings -You can configure the Console font size, JSON syntax, +You can configure the *Console* font size, JSON syntax, and autocomplete suggestions in *Settings*. [role="screenshot"] -image::dev-tools/console/images/console-settings.png["Console Settings"] +image::dev-tools/console/images/console-settings.png["Console Settings", width=60%] [float] [[keyboard-shortcuts]] @@ -132,7 +134,7 @@ shortcuts, click *Help*. [[console-settings]] === Disable Console -If you don’t want to use Console, you can disable it by setting `console.enabled` +If you don’t want to use *Console*, you can disable it by setting `console.enabled` to `false` in your `kibana.yml` configuration file. Changing this setting causes the server to regenerate assets on the next startup, which might cause a delay before pages start being served. diff --git a/docs/dev-tools/console/images/console.png b/docs/dev-tools/console/images/console.png index 0511ed858d1c3..88f069388ea67 100644 Binary files a/docs/dev-tools/console/images/console.png and b/docs/dev-tools/console/images/console.png differ diff --git a/docs/dev-tools/console/images/copy-curl.png b/docs/dev-tools/console/images/copy-curl.png index 4811c1d24bfb8..a6fb9cd1438f4 100644 Binary files a/docs/dev-tools/console/images/copy-curl.png and b/docs/dev-tools/console/images/copy-curl.png differ diff --git a/docs/dev-tools/console/images/request.png b/docs/dev-tools/console/images/request.png index a8332434ec186..c95b54dc95b0a 100644 Binary files a/docs/dev-tools/console/images/request.png and b/docs/dev-tools/console/images/request.png differ diff --git a/docs/dev-tools/grokdebugger/images/grok-debugger-custom-pattern.png b/docs/dev-tools/grokdebugger/images/grok-debugger-custom-pattern.png index 2cb6f1dbf7226..2a1660c860b4b 100644 Binary files a/docs/dev-tools/grokdebugger/images/grok-debugger-custom-pattern.png and b/docs/dev-tools/grokdebugger/images/grok-debugger-custom-pattern.png differ diff --git a/docs/dev-tools/grokdebugger/images/grok-debugger-overview.png b/docs/dev-tools/grokdebugger/images/grok-debugger-overview.png index b6e9b734b307e..4692c7a802067 100644 Binary files a/docs/dev-tools/grokdebugger/images/grok-debugger-overview.png and b/docs/dev-tools/grokdebugger/images/grok-debugger-overview.png differ diff --git a/docs/dev-tools/grokdebugger/index.asciidoc b/docs/dev-tools/grokdebugger/index.asciidoc index 82ae724f705f6..934452c54ccca 100644 --- a/docs/dev-tools/grokdebugger/index.asciidoc +++ b/docs/dev-tools/grokdebugger/index.asciidoc @@ -1,19 +1,19 @@ [role="xpack"] [[xpack-grokdebugger]] -== Debugging grok expressions +== Debug grok expressions You can build and debug grok patterns in the {kib} *Grok Debugger* -before you use them in your data processing pipelines. Grok is a pattern +before you use them in your data processing pipelines. Grok is a pattern matching syntax that you can use to parse arbitrary text and structure it. Grok is good for parsing syslog, apache, and other webserver logs, mysql logs, and in general, any log format that is -written for human consumption. +written for human consumption. Grok patterns are supported in the ingest node {ref}/grok-processor.html[grok processor] and the Logstash -{logstash-ref}/plugins-filters-grok.html[grok filter]. See +{logstash-ref}/plugins-filters-grok.html[grok filter]. See {logstash-ref}/plugins-filters-grok.html#_grok_basics[grok basics] -for more information on the syntax for a grok pattern. +for more information on the syntax for a grok pattern. The Elastic Stack ships with more than 120 reusable grok patterns. See @@ -27,10 +27,10 @@ in ingest node and Logstash. [float] [[grokdebugger-getting-started]] -=== Getting started with the Grok Debugger +=== Get started This example walks you through using the *Grok Debugger*. This tool -is automatically enabled in {kib}. +is automatically enabled in {kib}. NOTE: If you're using {stack-security-features}, you must have the `manage_pipeline` permission to use the Grok Debugger. @@ -66,12 +66,12 @@ image::dev-tools/grokdebugger/images/grok-debugger-overview.png["Grok Debugger"] [float] [[grokdebugger-custom-patterns]] -=== Testing custom patterns +=== Test custom patterns If the default grok pattern dictionary doesn't contain the patterns you need, -you can define, test, and debug custom patterns using the Grok Debugger. +you can define, test, and debug custom patterns using the *Grok Debugger*. -Custom patterns that you enter in the Grok Debugger are not saved. Custom patterns +Custom patterns that you enter in the *Grok Debugger* are not saved. Custom patterns are only available for the current debugging session and have no side effects. Follow this example to define a custom pattern. diff --git a/docs/dev-tools/painlesslab/images/painless-lab.png b/docs/dev-tools/painlesslab/images/painless-lab.png index fbfd54f69954d..65b4141ed5c54 100644 Binary files a/docs/dev-tools/painlesslab/images/painless-lab.png and b/docs/dev-tools/painlesslab/images/painless-lab.png differ diff --git a/docs/dev-tools/painlesslab/index.asciidoc b/docs/dev-tools/painlesslab/index.asciidoc index 5e329843843ec..4077ffe87ca1a 100644 --- a/docs/dev-tools/painlesslab/index.asciidoc +++ b/docs/dev-tools/painlesslab/index.asciidoc @@ -4,14 +4,15 @@ beta::[] -The Painless Lab is an interactive code editor that lets you test and +The *Painless Lab* is an interactive code editor that lets you test and debug {ref}/modules-scripting-painless.html[Painless scripts] in real-time. You can use the Painless scripting -language to create <>, +language to create <>, process {ref}/docs-reindex.html[reindexed data], define complex <>, and work with data in other contexts. -To get started, open the main menu, click *Dev Tools*, then click *Painless Lab*. +To get started, open the main menu, click *Dev Tools*, and then click *Painless Lab*. +[role="screenshot"] image::dev-tools/painlesslab/images/painless-lab.png[Painless Lab] diff --git a/docs/dev-tools/searchprofiler/getting-started.asciidoc b/docs/dev-tools/searchprofiler/getting-started.asciidoc deleted file mode 100644 index ad73d03bcbfd8..0000000000000 --- a/docs/dev-tools/searchprofiler/getting-started.asciidoc +++ /dev/null @@ -1,49 +0,0 @@ -[role="xpack"] -[[profiler-getting-started]] -=== Getting Started - -The {searchprofiler} is automatically enabled in {kib}. Open the main menu, click *Dev Tools*, then click *{searchprofiler}* -to get started. - -{searchprofiler} displays the names of the indices searched, the shards in each index, -and how long it took for the query to complete. To try it out, replace the default `match_all` query -with the query you want to profile and click *Profile*. - -The following example shows the results of profiling the `match_all` query. -If we take a closer look at the information for the `.kibana_1` sample index, the -Cumulative Time field shows us that the query took 1.279ms to execute. - -[role="screenshot"] -image::dev-tools/searchprofiler/images/overview.png["{searchprofiler} example"] - - -[NOTE] -==== -The Cumulative Time metric is the sum of individual shard times. -It is not necessarily the actual time it took for the query to return (wall clock time). -Because shards might be processed in parallel on multiple nodes, the wall clock time can -be significantly less than the Cumulative Time. However, if shards are colocated on the -same node and executed serially, the wall clock time is closer to the Cumulative Time. - -While the Cumulative Time metric is useful for comparing the performance of your -indices and shards, it doesn't necessarily represent the actual physical query times. -==== - -You can select the name of the shard and then click *View details* to see more profiling information, -including details about the query component(s) that ran on the shard, as well as the timing -breakdown of low-level Lucene methods. For more information, see {ref}/search-profile.html#profiling-queries[Profiling queries]. - -[float] -=== Index and type filtering - -By default, all queries executed by the {searchprofiler} are sent -to `GET /_search`. It searches across your entire cluster (all indices, all types). - -If you need to query a specific index or type (or several), you can use the Index -and Type filters. - -In the following example, the query is executed against the indices `test` and `kibana_1` -and the type `my_type`. This is equivalent making a request to `GET /test,kibana_1/my_type/_search`. - -[role="screenshot"] -image::dev-tools/searchprofiler/images/filter.png["Filtering by index and type"] diff --git a/docs/dev-tools/searchprofiler/gs-index.asciidoc b/docs/dev-tools/searchprofiler/gs-index.asciidoc deleted file mode 100644 index b4f5d48290f5e..0000000000000 --- a/docs/dev-tools/searchprofiler/gs-index.asciidoc +++ /dev/null @@ -1,20 +0,0 @@ -[role="xpack"] -[[xpack-profiler]] -= Profiling queries and aggregations - -[partintro] --- -{es} has a powerful {ref}/search-profile.html[Profile API] which can be used to inspect and analyze -your search queries. The response returns a large JSON blob, which can be -difficult to analyze manually. - -The {searchprofiler} tool can transform this JSON output -into a visualization that is easy to navigate, allowing you to diagnose and debug -poorly performing queries much faster. - -[role="screenshot"] -image::dev-tools/searchprofiler/images/overview.png["{searchprofiler} Visualization"] - --- - -include::getting-started.asciidoc[] diff --git a/docs/dev-tools/searchprofiler/images/filter.png b/docs/dev-tools/searchprofiler/images/filter.png index a740ec44b9d80..0bcfd7ca5cfad 100644 Binary files a/docs/dev-tools/searchprofiler/images/filter.png and b/docs/dev-tools/searchprofiler/images/filter.png differ diff --git a/docs/dev-tools/searchprofiler/images/gs10.png b/docs/dev-tools/searchprofiler/images/gs10.png index 6be78b2ce8eb3..e9a6615f50ac3 100644 Binary files a/docs/dev-tools/searchprofiler/images/gs10.png and b/docs/dev-tools/searchprofiler/images/gs10.png differ diff --git a/docs/dev-tools/searchprofiler/images/gs8.png b/docs/dev-tools/searchprofiler/images/gs8.png index 7ab8389897e4e..75b93d4dfbdb7 100644 Binary files a/docs/dev-tools/searchprofiler/images/gs8.png and b/docs/dev-tools/searchprofiler/images/gs8.png differ diff --git a/docs/dev-tools/searchprofiler/images/overview.png b/docs/dev-tools/searchprofiler/images/overview.png index 19df1700a5bae..2669adc13b349 100644 Binary files a/docs/dev-tools/searchprofiler/images/overview.png and b/docs/dev-tools/searchprofiler/images/overview.png differ diff --git a/docs/dev-tools/searchprofiler/images/pasting.png b/docs/dev-tools/searchprofiler/images/pasting.png deleted file mode 100644 index 466ab9159bfed..0000000000000 Binary files a/docs/dev-tools/searchprofiler/images/pasting.png and /dev/null differ diff --git a/docs/dev-tools/searchprofiler/images/search-profiler-json.png b/docs/dev-tools/searchprofiler/images/search-profiler-json.png new file mode 100644 index 0000000000000..a81286c9e6cca Binary files /dev/null and b/docs/dev-tools/searchprofiler/images/search-profiler-json.png differ diff --git a/docs/dev-tools/searchprofiler/index.asciidoc b/docs/dev-tools/searchprofiler/index.asciidoc index aca96dbfe3ee3..c323427318d54 100644 --- a/docs/dev-tools/searchprofiler/index.asciidoc +++ b/docs/dev-tools/searchprofiler/index.asciidoc @@ -1,20 +1,324 @@ [role="xpack"] [[xpack-profiler]] -== Profiling queries and aggregations +== Profile queries and aggregations -{es} has a powerful {ref}/search-profile.html[Profile API] which can be used to inspect and analyze +{es} has a powerful {ref}/search-profile.html[Profile API] that you can use to inspect and analyze your search queries. The response returns a large JSON blob, which can be difficult to analyze manually. -The {searchprofiler} tool can transform this JSON output +The *{searchprofiler}* tool can transform this JSON output into a visualization that is easy to navigate, allowing you to diagnose and debug poorly performing queries much faster. +[float] +[[search-profiler-getting-started]] +=== Get started -image::dev-tools/searchprofiler/images/overview.png["{searchprofiler} Visualization"] +*{searchprofiler}* is automatically enabled in {kib}. Open the main menu, +click *Dev Tools*, and then click *{searchprofiler}* +to get started. -include::getting-started.asciidoc[] +*{searchprofiler}* displays the names of the indices searched, the shards in each index, +and how long it took for the query to complete. To try it out, replace the default `match_all` query +with the query you want to profile, and then click *Profile*. -include::more-complicated.asciidoc[] +The following example shows the results of profiling the `match_all` query. +If you take a closer look at the information for the `.security_7` sample index, the +*Cumulative time* field shows you that the query took 0.028ms to execute. -include::pasting.asciidoc[] +[role="screenshot"] +image::dev-tools/searchprofiler/images/overview.png["{searchprofiler} visualization"] + + +[NOTE] +==== +The cumulative time metric is the sum of individual shard times. +It is not necessarily the actual time it took for the query to return (wall clock time). +Because shards might be processed in parallel on multiple nodes, the wall clock time can +be significantly less than the cumulative time. However, if shards are colocated on the +same node and executed serially, the wall clock time is closer to the cumulative time. + +While the cumulative time metric is useful for comparing the performance of your +indices and shards, it doesn't necessarily represent the actual physical query times. +==== + +To see more profiling information, click *View details*. You'll +see details about the query components that ran on the shard and the timing +breakdown of low-level methods. For more information, refer to {ref}/search-profile.html#profiling-queries[Profiling queries]. + +[float] +=== Filter for an index or type + +By default, all queries executed by the *{searchprofiler}* are sent +to `GET /_search`. It searches across your entire cluster (all indices, all types). + +To query a specific index or type, you can use the *Index* filter. + +In the following example, the query is executed against the indices `.security-7` and `kibana_sample_data_ecommerce`. +This is equivalent making a request to `GET /test,kibana_1/_search`. + +[role="screenshot"] +image::dev-tools/searchprofiler/images/filter.png["Filtering by index and type"] + +[[profile-complicated-query]] +[float] +=== Profile a more complicated query + +To understand how the query trees are displayed inside the *{searchprofiler}*, +take a look at a more complicated query. + +. Index the following data via *Console*: ++ +-- +[source,js] +-------------------------------------------------- +POST test/_bulk +{"index":{}} +{"name":"aaron","age":23,"hair":"brown"} +{"index":{}} +{"name":"sue","age":19,"hair":"red"} +{"index":{}} +{"name":"sally","age":19,"hair":"blonde"} +{"index":{}} +{"name":"george","age":19,"hair":"blonde"} +{"index":{}} +{"name":"fred","age":69,"hair":"blonde"} +-------------------------------------------------- +// CONSOLE +-- + +. From the *{searchprofiler}*, enter *test* in the *Index* field to restrict profiled +queries to the `test` index. + +. Replace the default `match_all` query in the query editor with a query that has two sub-query +components and includes a simple aggregation: ++ +-- +[source,js] +-------------------------------------------------- +{ + "query": { + "bool": { + "should": [ + { + "match": { + "name": "fred" + } + }, + { + "terms": { + "name": [ + "sue", + "sally" + ] + } + } + ] + } + }, + "aggs": { + "stats": { + "stats": { + "field": "price" + } + } + } +} +-------------------------------------------------- +// NOTCONSOLE +-- + +. Click *Profile* to profile the query and visualize the results. ++ +[role="screenshot"] +image::dev-tools/searchprofiler/images/gs8.png["Profiling the more complicated query"] ++ +- The top `BooleanQuery` component corresponds to the bool in the query. +- The second `BooleanQuery` corresponds to the terms query, which is internally +converted to a `Boolean` of should clauses. It has two child queries that correspond +to "sally" and "sue from the terms query. +- The `TermQuery` that's labeled with "name:fred" corresponds to match: fred in the query. ++ +If you look at the time columns, you can see that *Self time* and *Total time* are no longer +identical on all the rows. *Self time* represents how long the query component took to execute. +*Total time* is the time a query component and all its children took to execute. +Therefore, queries like the Boolean queries often have a larger total time than self time. + +. Click *Aggregation Profile* to view aggregation profiling statistics. ++ +This query includes a `stats` agg on the `"age"` field. +The *Aggregation Profile* tab is only enabled when the query being profiled contains an aggregation. + +. Click *View details* to view the timing breakdown. ++ +[role="screenshot"] +image::dev-tools/searchprofiler/images/gs10.png["Drilling into the first shard's details"] ++ +For more information about how the *{searchprofiler}* works, how timings are calculated, and +how to interpret various results, see +{ref}/search-profile.html#profiling-queries[Profiling queries]. + +[[profiler-render-JSON]] +[float] +=== Render pre-captured profiler JSON + +The *{searchprofiler}* queries the cluster that the {kib} node is attached to. +It does this by executing the query against the cluster and collecting the results. + +Sometimes you might want to investigate performance problems that are temporal in nature. +For example, a query might only be slow at certain time of day when many customers are using your system. +You can set up a process to automatically profile slow queries when they occur and then +save those profile responses for later analysis. + +The *{searchprofiler}* supports this workflow by allowing you to paste the +pre-captured JSON in the query editor. The *{searchprofiler}* will detect that you +have entered a JSON response (rather than a query) and will render just the visualization, +rather than querying the cluster. + +To see how this works, copy and paste the following profile response into the +query editor and click *Profile*. + +[source,js] +-------------------------------------------------- +{ + "took": 3, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "failed": 0 + }, + "hits": { + "total": 1, + "max_score": 1.3862944, + "hits": [ + { + "_index": "test", + "_type": "test", + "_id": "AVi3aRDmGKWpaS38wV57", + "_score": 1.3862944, + "_source": { + "name": "fred", + "age": 69, + "hair": "blonde" + } + } + ] + }, + "profile": { + "shards": [ + { + "id": "[O-l25nM4QN6Z68UA5rUYqQ][test][0]", + "searches": [ + { + "query": [ + { + "type": "BooleanQuery", + "description": "+name:fred #(ConstantScore(*:*))^0.0", + "time": "0.5884370000ms", + "breakdown": { + "score": 7243, + "build_scorer_count": 1, + "match_count": 0, + "create_weight": 196239, + "next_doc": 9851, + "match": 0, + "create_weight_count": 1, + "next_doc_count": 2, + "score_count": 1, + "build_scorer": 375099, + "advance": 0, + "advance_count": 0 + }, + "children": [ + { + "type": "TermQuery", + "description": "name:fred", + "time": "0.3016880000ms", + "breakdown": { + "score": 4218, + "build_scorer_count": 1, + "match_count": 0, + "create_weight": 132425, + "next_doc": 2196, + "match": 0, + "create_weight_count": 1, + "next_doc_count": 2, + "score_count": 1, + "build_scorer": 162844, + "advance": 0, + "advance_count": 0 + } + }, + { + "type": "BoostQuery", + "description": "(ConstantScore(*:*))^0.0", + "time": "0.1223030000ms", + "breakdown": { + "score": 0, + "build_scorer_count": 1, + "match_count": 0, + "create_weight": 17366, + "next_doc": 0, + "match": 0, + "create_weight_count": 1, + "next_doc_count": 0, + "score_count": 0, + "build_scorer": 102329, + "advance": 2604, + "advance_count": 2 + }, + "children": [ + { + "type": "MatchAllDocsQuery", + "description": "*:*", + "time": "0.03307600000ms", + "breakdown": { + "score": 0, + "build_scorer_count": 1, + "match_count": 0, + "create_weight": 6068, + "next_doc": 0, + "match": 0, + "create_weight_count": 1, + "next_doc_count": 0, + "score_count": 0, + "build_scorer": 25615, + "advance": 1389, + "advance_count": 2 + } + } + ] + } + ] + } + ], + "rewrite_time": 168640, + "collector": [ + { + "name": "CancellableCollector", + "reason": "search_cancelled", + "time": "0.02952900000ms", + "children": [ + { + "name": "SimpleTopScoreDocCollector", + "reason": "search_top_hits", + "time": "0.01931700000ms" + } + ] + } + ] + } + ], + "aggregations": [] + } + ] + } +} +-------------------------------------------------- +// NOTCONSOLE + +Your output should look similar to this: + +[role="screenshot"] +image::dev-tools/searchprofiler/images/search-profiler-json.png["Rendering pre-captured profiler JSON"] diff --git a/docs/dev-tools/searchprofiler/more-complicated.asciidoc b/docs/dev-tools/searchprofiler/more-complicated.asciidoc deleted file mode 100644 index 338341d65924d..0000000000000 --- a/docs/dev-tools/searchprofiler/more-complicated.asciidoc +++ /dev/null @@ -1,104 +0,0 @@ -[role="xpack"] -[[profiler-complicated]] -=== Profiling a more complicated query - -To understand how the query trees are displayed inside the {searchprofiler}, -let's look at a more complicated query. - -. Index the following data via *Console*: -+ --- -[source,js] --------------------------------------------------- -POST test/_bulk -{"index":{}} -{"name":"aaron","age":23,"hair":"brown"} -{"index":{}} -{"name":"sue","age":19,"hair":"red"} -{"index":{}} -{"name":"sally","age":19,"hair":"blonde"} -{"index":{}} -{"name":"george","age":19,"hair":"blonde"} -{"index":{}} -{"name":"fred","age":69,"hair":"blonde"} --------------------------------------------------- -// CONSOLE --- - -. From the {searchprofiler}, enter "test" in the *Index* field to restrict profiled -queries to the `test` index. - -. Replace the default `match_all` query in the query editor with a query that has two sub-query -components and includes a simple aggregation: -+ --- -[source,js] --------------------------------------------------- -{ - "query": { - "bool": { - "should": [ - { - "match": { - "name": "fred" - } - }, - { - "terms": { - "name": [ - "sue", - "sally" - ] - } - } - ] - } - }, - "aggs": { - "stats": { - "stats": { - "field": "price" - } - } - } -} --------------------------------------------------- -// NOTCONSOLE --- - -. Click *Profile* to profile the query and visualize the results. -. Select the shard to view the query details. -+ -[role="screenshot"] -image::dev-tools/searchprofiler/images/gs8.png["Profiling the more complicated query"] - - -The detail view contains a row for each query component: - - - The top-level `BooleanQuery` component corresponds to the bool in the query. - - The second `BooleanQuery` corresponds to the terms query, which is internally - converted to a `Boolean` of should clauses. It has two child queries that correspond - to "sue" and "sally" from the terms query. - - The `TermQuery` that's labeled with "name:fred" corresponds to match: fred in the query. - -If you look at the time columns, you can see that "Self time" and "Total time" are no longer -identical on all the rows. Self time represents how long the query component took to execute. -Total time is the time a query component and all its children took to execute. -Therefore, queries like the Boolean queries often have a larger total time than self time. - - -==== Aggregations - -This particular query also includes a aggregation (a `stats` agg on the `"age"` field). -Click *Aggregation Profile* to view aggregation profiling statistics (this tab -is only enabled if the query being profiled contains an aggregation). - - -Select the name of the shard to view the aggregation details and timing breakdown. - -[role="screenshot"] -image::dev-tools/searchprofiler/images/gs10.png["Drilling into the first shard's details"] - -For more information about how the {searchprofiler} works, how timings are calculated, and -how to interpret various results, see -{ref}/search-profile.html#profiling-queries[Profiling queries]. diff --git a/docs/dev-tools/searchprofiler/pasting.asciidoc b/docs/dev-tools/searchprofiler/pasting.asciidoc deleted file mode 100644 index 9257a4d84fb56..0000000000000 --- a/docs/dev-tools/searchprofiler/pasting.asciidoc +++ /dev/null @@ -1,161 +0,0 @@ -[role="xpack"] -[[profiler-render]] -=== Rendering pre-captured profiler JSON - -The {searchprofiler} queries the cluster that the Kibana node is attached to. -It does this by executing the query against the cluster and collecting the results. - -But sometimes you may want to investigate performance problems that are temporal in nature. -For example, a query might only be slow at certain time of day when many customers are using your system. -You can setup a process to automatically profile slow queries when they occur and then -save those profile responses for later analysis. - -The {searchprofiler} supports this workflow by allowing you to paste the -pre-captured JSON in the query editor. The {searchprofiler} will detect that you -have entered a JSON response (rather than a query) and will just render the visualization, -rather than querying the cluster. - -To see how this works, copy and paste the following profile response into the -query editor and click *Profile*. - -[source,js] --------------------------------------------------- -{ - "took": 3, - "timed_out": false, - "_shards": { - "total": 1, - "successful": 1, - "failed": 0 - }, - "hits": { - "total": 1, - "max_score": 1.3862944, - "hits": [ - { - "_index": "test", - "_type": "test", - "_id": "AVi3aRDmGKWpaS38wV57", - "_score": 1.3862944, - "_source": { - "name": "fred", - "age": 69, - "hair": "blonde" - } - } - ] - }, - "profile": { - "shards": [ - { - "id": "[O-l25nM4QN6Z68UA5rUYqQ][test][0]", - "searches": [ - { - "query": [ - { - "type": "BooleanQuery", - "description": "+name:fred #(ConstantScore(*:*))^0.0", - "time": "0.5884370000ms", - "breakdown": { - "score": 7243, - "build_scorer_count": 1, - "match_count": 0, - "create_weight": 196239, - "next_doc": 9851, - "match": 0, - "create_weight_count": 1, - "next_doc_count": 2, - "score_count": 1, - "build_scorer": 375099, - "advance": 0, - "advance_count": 0 - }, - "children": [ - { - "type": "TermQuery", - "description": "name:fred", - "time": "0.3016880000ms", - "breakdown": { - "score": 4218, - "build_scorer_count": 1, - "match_count": 0, - "create_weight": 132425, - "next_doc": 2196, - "match": 0, - "create_weight_count": 1, - "next_doc_count": 2, - "score_count": 1, - "build_scorer": 162844, - "advance": 0, - "advance_count": 0 - } - }, - { - "type": "BoostQuery", - "description": "(ConstantScore(*:*))^0.0", - "time": "0.1223030000ms", - "breakdown": { - "score": 0, - "build_scorer_count": 1, - "match_count": 0, - "create_weight": 17366, - "next_doc": 0, - "match": 0, - "create_weight_count": 1, - "next_doc_count": 0, - "score_count": 0, - "build_scorer": 102329, - "advance": 2604, - "advance_count": 2 - }, - "children": [ - { - "type": "MatchAllDocsQuery", - "description": "*:*", - "time": "0.03307600000ms", - "breakdown": { - "score": 0, - "build_scorer_count": 1, - "match_count": 0, - "create_weight": 6068, - "next_doc": 0, - "match": 0, - "create_weight_count": 1, - "next_doc_count": 0, - "score_count": 0, - "build_scorer": 25615, - "advance": 1389, - "advance_count": 2 - } - } - ] - } - ] - } - ], - "rewrite_time": 168640, - "collector": [ - { - "name": "CancellableCollector", - "reason": "search_cancelled", - "time": "0.02952900000ms", - "children": [ - { - "name": "SimpleTopScoreDocCollector", - "reason": "search_top_hits", - "time": "0.01931700000ms" - } - ] - } - ] - } - ], - "aggregations": [] - } - ] - } -} --------------------------------------------------- -// NOTCONSOLE - -image::dev-tools/searchprofiler/images/pasting.png["Visualizing pre-collected responses"] diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index c1535e8a2146f..8e08e5f4db1f9 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -99,6 +99,10 @@ want to incorporate their own functions, types, and renderers into the service for use in their own application. +|{kib-repo}blob/{branch}/src/plugins/expression_shape/README.md[expressionShape] +|Expression Shape plugin adds a shape function to the expression plugin and an associated renderer. The renderer will display the given shape with selected decorations. + + |{kib-repo}blob/{branch}/src/plugins/home/README.md[home] |Moves the legacy ui/registry/feature_catalogue module for registering "features" that should be shown in the home page's feature catalogue to a service within a "home" plugin. The feature catalogue refered to here should not be confused with the "feature" plugin for registering features used to derive UI capabilities for feature controls. diff --git a/docs/development/core/public/kibana-plugin-core-public.iexecutioncontextcontainer.md b/docs/development/core/public/kibana-plugin-core-public.iexecutioncontextcontainer.md index 413b4aaf46b50..96ca86cffbc5f 100644 --- a/docs/development/core/public/kibana-plugin-core-public.iexecutioncontextcontainer.md +++ b/docs/development/core/public/kibana-plugin-core-public.iexecutioncontextcontainer.md @@ -16,4 +16,5 @@ export interface IExecutionContextContainer | Property | Type | Description | | --- | --- | --- | | [toHeader](./kibana-plugin-core-public.iexecutioncontextcontainer.toheader.md) | () => Record<string, string> | | +| [toJSON](./kibana-plugin-core-public.iexecutioncontextcontainer.tojson.md) | () => Readonly<KibanaExecutionContext> | | diff --git a/docs/development/core/public/kibana-plugin-core-public.iexecutioncontextcontainer.tojson.md b/docs/development/core/public/kibana-plugin-core-public.iexecutioncontextcontainer.tojson.md new file mode 100644 index 0000000000000..916148141c8f3 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.iexecutioncontextcontainer.tojson.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [IExecutionContextContainer](./kibana-plugin-core-public.iexecutioncontextcontainer.md) > [toJSON](./kibana-plugin-core-public.iexecutioncontextcontainer.tojson.md) + +## IExecutionContextContainer.toJSON property + +Signature: + +```typescript +toJSON: () => Readonly; +``` diff --git a/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.id.md b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.id.md index 1b50d29094585..d17f9cb8a7ff3 100644 --- a/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.id.md +++ b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.id.md @@ -4,7 +4,7 @@ ## KibanaExecutionContext.id property -unique value to indentify find the source +unique value to identify the source Signature: diff --git a/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.md b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.md index 41724f4914264..afd66fbba18d3 100644 --- a/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.md +++ b/docs/development/core/public/kibana-plugin-core-public.kibanaexecutioncontext.md @@ -16,7 +16,7 @@ export interface KibanaExecutionContext | Property | Type | Description | | --- | --- | --- | | [description](./kibana-plugin-core-public.kibanaexecutioncontext.description.md) | string | human readable description. For example, a vis title, action name | -| [id](./kibana-plugin-core-public.kibanaexecutioncontext.id.md) | string | unique value to indentify find the source | +| [id](./kibana-plugin-core-public.kibanaexecutioncontext.id.md) | string | unique value to identify the source | | [name](./kibana-plugin-core-public.kibanaexecutioncontext.name.md) | string | public name of a user-facing feature | | [type](./kibana-plugin-core-public.kibanaexecutioncontext.type.md) | string | Kibana application initated an operation. Can be narrowed to an enum later. | | [url](./kibana-plugin-core-public.kibanaexecutioncontext.url.md) | string | in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url | diff --git a/docs/development/core/server/kibana-plugin-core-server.corepreboot.elasticsearch.md b/docs/development/core/server/kibana-plugin-core-server.corepreboot.elasticsearch.md new file mode 100644 index 0000000000000..7d3b5296b5988 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.corepreboot.elasticsearch.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CorePreboot](./kibana-plugin-core-server.corepreboot.md) > [elasticsearch](./kibana-plugin-core-server.corepreboot.elasticsearch.md) + +## CorePreboot.elasticsearch property + +[ElasticsearchServicePreboot](./kibana-plugin-core-server.elasticsearchservicepreboot.md) + +Signature: + +```typescript +elasticsearch: ElasticsearchServicePreboot; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.corepreboot.http.md b/docs/development/core/server/kibana-plugin-core-server.corepreboot.http.md new file mode 100644 index 0000000000000..0df643c6f133b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.corepreboot.http.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CorePreboot](./kibana-plugin-core-server.corepreboot.md) > [http](./kibana-plugin-core-server.corepreboot.http.md) + +## CorePreboot.http property + +[HttpServicePreboot](./kibana-plugin-core-server.httpservicepreboot.md) + +Signature: + +```typescript +http: HttpServicePreboot; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.corepreboot.md b/docs/development/core/server/kibana-plugin-core-server.corepreboot.md new file mode 100644 index 0000000000000..475b5f109d58c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.corepreboot.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CorePreboot](./kibana-plugin-core-server.corepreboot.md) + +## CorePreboot interface + +Context passed to the `setup` method of `preboot` plugins. + +Signature: + +```typescript +export interface CorePreboot +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [elasticsearch](./kibana-plugin-core-server.corepreboot.elasticsearch.md) | ElasticsearchServicePreboot | [ElasticsearchServicePreboot](./kibana-plugin-core-server.elasticsearchservicepreboot.md) | +| [http](./kibana-plugin-core-server.corepreboot.http.md) | HttpServicePreboot | [HttpServicePreboot](./kibana-plugin-core-server.httpservicepreboot.md) | +| [preboot](./kibana-plugin-core-server.corepreboot.preboot.md) | PrebootServicePreboot | [PrebootServicePreboot](./kibana-plugin-core-server.prebootservicepreboot.md) | + diff --git a/docs/development/core/server/kibana-plugin-core-server.corepreboot.preboot.md b/docs/development/core/server/kibana-plugin-core-server.corepreboot.preboot.md new file mode 100644 index 0000000000000..3780a92053a5e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.corepreboot.preboot.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CorePreboot](./kibana-plugin-core-server.corepreboot.md) > [preboot](./kibana-plugin-core-server.corepreboot.preboot.md) + +## CorePreboot.preboot property + +[PrebootServicePreboot](./kibana-plugin-core-server.prebootservicepreboot.md) + +Signature: + +```typescript +preboot: PrebootServicePreboot; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.md index a66db46adf0f7..b03101b4d9fe6 100644 --- a/docs/development/core/server/kibana-plugin-core-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.md @@ -4,7 +4,7 @@ ## CoreSetup interface -Context passed to the plugins `setup` method. +Context passed to the `setup` method of `standard` plugins. Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.discoveredplugin.md b/docs/development/core/server/kibana-plugin-core-server.discoveredplugin.md index b88a179c5c4b3..042f2d1485618 100644 --- a/docs/development/core/server/kibana-plugin-core-server.discoveredplugin.md +++ b/docs/development/core/server/kibana-plugin-core-server.discoveredplugin.md @@ -21,4 +21,5 @@ export interface DiscoveredPlugin | [optionalPlugins](./kibana-plugin-core-server.discoveredplugin.optionalplugins.md) | readonly PluginName[] | An optional list of the other plugins that if installed and enabled \*\*may be\*\* leveraged by this plugin for some additional functionality but otherwise are not required for this plugin to work properly. | | [requiredBundles](./kibana-plugin-core-server.discoveredplugin.requiredbundles.md) | readonly PluginName[] | List of plugin ids that this plugin's UI code imports modules from that are not in requiredPlugins. | | [requiredPlugins](./kibana-plugin-core-server.discoveredplugin.requiredplugins.md) | readonly PluginName[] | An optional list of the other plugins that \*\*must be\*\* installed and enabled for this plugin to function properly. | +| [type](./kibana-plugin-core-server.discoveredplugin.type.md) | PluginType | Type of the plugin, defaults to standard. | diff --git a/docs/development/core/server/kibana-plugin-core-server.discoveredplugin.type.md b/docs/development/core/server/kibana-plugin-core-server.discoveredplugin.type.md new file mode 100644 index 0000000000000..0a33be0d63f5c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.discoveredplugin.type.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [DiscoveredPlugin](./kibana-plugin-core-server.discoveredplugin.md) > [type](./kibana-plugin-core-server.discoveredplugin.type.md) + +## DiscoveredPlugin.type property + +Type of the plugin, defaults to `standard`. + +Signature: + +```typescript +readonly type: PluginType; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfigpreboot.credentialsspecified.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfigpreboot.credentialsspecified.md new file mode 100644 index 0000000000000..df99d5ec4b831 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfigpreboot.credentialsspecified.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchConfigPreboot](./kibana-plugin-core-server.elasticsearchconfigpreboot.md) > [credentialsSpecified](./kibana-plugin-core-server.elasticsearchconfigpreboot.credentialsspecified.md) + +## ElasticsearchConfigPreboot.credentialsSpecified property + +Indicates whether Elasticsearch configuration includes credentials (`username`, `password` or `serviceAccountToken`). + +Signature: + +```typescript +readonly credentialsSpecified: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfigpreboot.hosts.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfigpreboot.hosts.md new file mode 100644 index 0000000000000..e9ad47b61419e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfigpreboot.hosts.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchConfigPreboot](./kibana-plugin-core-server.elasticsearchconfigpreboot.md) > [hosts](./kibana-plugin-core-server.elasticsearchconfigpreboot.hosts.md) + +## ElasticsearchConfigPreboot.hosts property + +Hosts that the client will connect to. If sniffing is enabled, this list will be used as seeds to discover the rest of your cluster. + +Signature: + +```typescript +readonly hosts: string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfigpreboot.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfigpreboot.md new file mode 100644 index 0000000000000..bbccea80b672f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchconfigpreboot.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchConfigPreboot](./kibana-plugin-core-server.elasticsearchconfigpreboot.md) + +## ElasticsearchConfigPreboot interface + +A limited set of Elasticsearch configuration entries exposed to the `preboot` plugins at `setup`. + +Signature: + +```typescript +export interface ElasticsearchConfigPreboot +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [credentialsSpecified](./kibana-plugin-core-server.elasticsearchconfigpreboot.credentialsspecified.md) | boolean | Indicates whether Elasticsearch configuration includes credentials (username, password or serviceAccountToken). | +| [hosts](./kibana-plugin-core-server.elasticsearchconfigpreboot.hosts.md) | string[] | Hosts that the client will connect to. If sniffing is enabled, this list will be used as seeds to discover the rest of your cluster. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicepreboot.config.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicepreboot.config.md new file mode 100644 index 0000000000000..12a32b4544aba --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicepreboot.config.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchServicePreboot](./kibana-plugin-core-server.elasticsearchservicepreboot.md) > [config](./kibana-plugin-core-server.elasticsearchservicepreboot.config.md) + +## ElasticsearchServicePreboot.config property + +A limited set of Elasticsearch configuration entries. + +Signature: + +```typescript +readonly config: Readonly; +``` + +## Example + + +```js +const { hosts, credentialsSpecified } = core.elasticsearch.config; + +``` + diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicepreboot.createclient.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicepreboot.createclient.md new file mode 100644 index 0000000000000..d14e3e4efa400 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicepreboot.createclient.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchServicePreboot](./kibana-plugin-core-server.elasticsearchservicepreboot.md) > [createClient](./kibana-plugin-core-server.elasticsearchservicepreboot.createclient.md) + +## ElasticsearchServicePreboot.createClient property + +Create application specific Elasticsearch cluster API client with customized config. See [IClusterClient](./kibana-plugin-core-server.iclusterclient.md). + +Signature: + +```typescript +readonly createClient: (type: string, clientConfig?: Partial) => ICustomClusterClient; +``` + +## Example + + +```js +const client = elasticsearch.createClient('my-app-name', config); +const data = await client.asInternalUser.search(); + +``` + diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicepreboot.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicepreboot.md new file mode 100644 index 0000000000000..bf458004b488b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicepreboot.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchServicePreboot](./kibana-plugin-core-server.elasticsearchservicepreboot.md) + +## ElasticsearchServicePreboot interface + + +Signature: + +```typescript +export interface ElasticsearchServicePreboot +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [config](./kibana-plugin-core-server.elasticsearchservicepreboot.config.md) | Readonly<ElasticsearchConfigPreboot> | A limited set of Elasticsearch configuration entries. | +| [createClient](./kibana-plugin-core-server.elasticsearchservicepreboot.createclient.md) | (type: string, clientConfig?: Partial<ElasticsearchClientConfig>) => ICustomClusterClient | Create application specific Elasticsearch cluster API client with customized config. See [IClusterClient](./kibana-plugin-core-server.iclusterclient.md). | + diff --git a/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.basepath.md b/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.basepath.md new file mode 100644 index 0000000000000..9864f67d70a43 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.basepath.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpServicePreboot](./kibana-plugin-core-server.httpservicepreboot.md) > [basePath](./kibana-plugin-core-server.httpservicepreboot.basepath.md) + +## HttpServicePreboot.basePath property + +Access or manipulate the Kibana base path See [IBasePath](./kibana-plugin-core-server.ibasepath.md). + +Signature: + +```typescript +basePath: IBasePath; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.md b/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.md new file mode 100644 index 0000000000000..b4adf454a480f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.md @@ -0,0 +1,82 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpServicePreboot](./kibana-plugin-core-server.httpservicepreboot.md) + +## HttpServicePreboot interface + +Kibana HTTP Service provides an abstraction to work with the HTTP stack at the `preboot` stage. This functionality allows Kibana to serve user requests even before Kibana becomes fully operational. Only Core and `preboot` plugins can define HTTP routes at this stage. + +Signature: + +```typescript +export interface HttpServicePreboot +``` + +## Example + +To handle an incoming request in your preboot plugin you should: - Use `@kbn/config-schema` package to create a schema to validate the request `params`, `query`, and `body`. Every incoming request will be validated against the created schema. If validation failed, the request is rejected with `400` status and `Bad request` error without calling the route's handler. To opt out of validating the request, specify `false`. + +```ts +import { schema, TypeOf } from '@kbn/config-schema'; +const validate = { + params: schema.object({ + id: schema.string(), + }), +}; + +``` +- Declare a function to respond to incoming request. The function will receive `request` object containing request details: url, headers, matched route, as well as validated `params`, `query`, `body`. And `response` object instructing HTTP server to create HTTP response with information sent back to the client as the response body, headers, and HTTP status. Any exception raised during the handler call will generate `500 Server error` response and log error details for further investigation. See below for returning custom error responses. + +```ts +const handler = async (context: RequestHandlerContext, request: KibanaRequest, response: ResponseFactory) => { + const data = await findObject(request.params.id); + // creates a command to respond with 'not found' error + if (!data) { + return response.notFound(); + } + // creates a command to send found data to the client and set response headers + return response.ok({ + body: data, + headers: { 'content-type': 'application/json' } + }); +} + +``` +\* - Acquire `preboot` [IRouter](./kibana-plugin-core-server.irouter.md) instance and register route handler for GET request to 'path/{id}' path. + +```ts +import { schema, TypeOf } from '@kbn/config-schema'; + +const validate = { + params: schema.object({ + id: schema.string(), + }), +}; + +httpPreboot.registerRoutes('my-plugin', (router) => { + router.get({ path: 'path/{id}', validate }, async (context, request, response) => { + const data = await findObject(request.params.id); + if (!data) { + return response.notFound(); + } + return response.ok({ + body: data, + headers: { 'content-type': 'application/json' } + }); + }); +}); + +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [basePath](./kibana-plugin-core-server.httpservicepreboot.basepath.md) | IBasePath | Access or manipulate the Kibana base path See [IBasePath](./kibana-plugin-core-server.ibasepath.md). | + +## Methods + +| Method | Description | +| --- | --- | +| [registerRoutes(path, callback)](./kibana-plugin-core-server.httpservicepreboot.registerroutes.md) | Provides ability to acquire preboot [IRouter](./kibana-plugin-core-server.irouter.md) instance for a particular top-level path and register handler functions for any number of nested routes. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.registerroutes.md b/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.registerroutes.md new file mode 100644 index 0000000000000..c188f0ba0ce94 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpservicepreboot.registerroutes.md @@ -0,0 +1,40 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpServicePreboot](./kibana-plugin-core-server.httpservicepreboot.md) > [registerRoutes](./kibana-plugin-core-server.httpservicepreboot.registerroutes.md) + +## HttpServicePreboot.registerRoutes() method + +Provides ability to acquire `preboot` [IRouter](./kibana-plugin-core-server.irouter.md) instance for a particular top-level path and register handler functions for any number of nested routes. + +Signature: + +```typescript +registerRoutes(path: string, callback: (router: IRouter) => void): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| path | string | | +| callback | (router: IRouter) => void | | + +Returns: + +`void` + +## Remarks + +Each route can have only one handler function, which is executed when the route is matched. See the [IRouter](./kibana-plugin-core-server.irouter.md) documentation for more information. + +## Example + + +```ts +registerRoutes('my-plugin', (router) => { + // handler is called when '/my-plugin/path' resource is requested with `GET` method + router.get({ path: '/path', validate: false }, (context, req, res) => res.ok({ content: 'ok' })); +}); + +``` + diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.id.md b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.id.md index d86f621231214..4ade96691fb14 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.id.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.id.md @@ -4,7 +4,7 @@ ## KibanaExecutionContext.id property -unique value to indentify find the source +unique value to identify the source Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md index ebc2aeb419a75..517e8f3ffda1f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaexecutioncontext.md @@ -16,7 +16,7 @@ export interface KibanaExecutionContext | Property | Type | Description | | --- | --- | --- | | [description](./kibana-plugin-core-server.kibanaexecutioncontext.description.md) | string | human readable description. For example, a vis title, action name | -| [id](./kibana-plugin-core-server.kibanaexecutioncontext.id.md) | string | unique value to indentify find the source | +| [id](./kibana-plugin-core-server.kibanaexecutioncontext.id.md) | string | unique value to identify the source | | [name](./kibana-plugin-core-server.kibanaexecutioncontext.name.md) | string | public name of a user-facing feature | | [type](./kibana-plugin-core-server.kibanaexecutioncontext.type.md) | string | Kibana application initated an operation. Can be narrowed to an enum later. | | [url](./kibana-plugin-core-server.kibanaexecutioncontext.url.md) | string | in browser - url to navigate to a current page, on server - endpoint path, for task: task SO url | diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 4a203f10e7cd3..a26f8bd7b1594 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -41,6 +41,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | --- | --- | | [AuthResultType](./kibana-plugin-core-server.authresulttype.md) | | | [AuthStatus](./kibana-plugin-core-server.authstatus.md) | Status indicating an outcome of the authentication. | +| [PluginType](./kibana-plugin-core-server.plugintype.md) | | ## Interfaces @@ -60,7 +61,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [CapabilitiesSetup](./kibana-plugin-core-server.capabilitiessetup.md) | APIs to manage the [Capabilities](./kibana-plugin-core-server.capabilities.md) that will be used by the application.Plugins relying on capabilities to toggle some of their features should register them during the setup phase using the registerProvider method.Plugins having the responsibility to restrict capabilities depending on a given context should register their capabilities switcher using the registerSwitcher method.Refers to the methods documentation for complete description and examples. | | [CapabilitiesStart](./kibana-plugin-core-server.capabilitiesstart.md) | APIs to access the application [Capabilities](./kibana-plugin-core-server.capabilities.md). | | [ContextSetup](./kibana-plugin-core-server.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | -| [CoreSetup](./kibana-plugin-core-server.coresetup.md) | Context passed to the plugins setup method. | +| [CorePreboot](./kibana-plugin-core-server.corepreboot.md) | Context passed to the setup method of preboot plugins. | +| [CoreSetup](./kibana-plugin-core-server.coresetup.md) | Context passed to the setup method of standard plugins. | | [CoreStart](./kibana-plugin-core-server.corestart.md) | Context passed to the plugins start method. | | [CoreStatus](./kibana-plugin-core-server.corestatus.md) | Status of core services. | | [CountResponse](./kibana-plugin-core-server.countresponse.md) | | @@ -73,6 +75,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [DeprecationSettings](./kibana-plugin-core-server.deprecationsettings.md) | UiSettings deprecation field options. | | [DeprecationsServiceSetup](./kibana-plugin-core-server.deprecationsservicesetup.md) | The deprecations service provides a way for the Kibana platform to communicate deprecated features and configs with its users. These deprecations are only communicated if the deployment is using these features. Allowing for a user tailored experience for upgrading the stack version.The Deprecation service is consumed by the upgrade assistant to assist with the upgrade experience.If a deprecated feature can be resolved without manual user intervention. Using correctiveActions.api allows the Upgrade Assistant to use this api to correct the deprecation upon a user trigger. | | [DiscoveredPlugin](./kibana-plugin-core-server.discoveredplugin.md) | Small container object used to expose information about discovered plugins that may or may not have been started. | +| [ElasticsearchConfigPreboot](./kibana-plugin-core-server.elasticsearchconfigpreboot.md) | A limited set of Elasticsearch configuration entries exposed to the preboot plugins at setup. | +| [ElasticsearchServicePreboot](./kibana-plugin-core-server.elasticsearchservicepreboot.md) | | | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | | | [ElasticsearchServiceStart](./kibana-plugin-core-server.elasticsearchservicestart.md) | | | [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) | | @@ -87,6 +91,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [HttpResourcesServiceToolkit](./kibana-plugin-core-server.httpresourcesservicetoolkit.md) | Extended set of [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) helpers used to respond with HTML or JS resource. | | [HttpResponseOptions](./kibana-plugin-core-server.httpresponseoptions.md) | HTTP response parameters | | [HttpServerInfo](./kibana-plugin-core-server.httpserverinfo.md) | Information about what hostname, port, and protocol the server process is running on. Note that this may not match the URL that end-users access Kibana at. For the public URL, see [BasePath.publicBaseUrl](./kibana-plugin-core-server.basepath.publicbaseurl.md). | +| [HttpServicePreboot](./kibana-plugin-core-server.httpservicepreboot.md) | Kibana HTTP Service provides an abstraction to work with the HTTP stack at the preboot stage. This functionality allows Kibana to serve user requests even before Kibana becomes fully operational. Only Core and preboot plugins can define HTTP routes at this stage. | | [HttpServiceSetup](./kibana-plugin-core-server.httpservicesetup.md) | Kibana HTTP Service provides own abstraction for work with HTTP stack. Plugins don't have direct access to hapi server and its primitives anymore. Moreover, plugins shouldn't rely on the fact that HTTP Service uses one or another library under the hood. This gives the platform flexibility to upgrade or changing our internal HTTP stack without breaking plugins. If the HTTP Service lacks functionality you need, we are happy to discuss and support your needs. | | [HttpServiceStart](./kibana-plugin-core-server.httpservicestart.md) | | | [I18nServiceSetup](./kibana-plugin-core-server.i18nservicesetup.md) | | @@ -128,10 +133,12 @@ The plugin integrates with the core system via lifecycle events: `setup` | [OpsOsMetrics](./kibana-plugin-core-server.opsosmetrics.md) | OS related metrics | | [OpsProcessMetrics](./kibana-plugin-core-server.opsprocessmetrics.md) | Process related metrics | | [OpsServerMetrics](./kibana-plugin-core-server.opsservermetrics.md) | server related metrics | -| [Plugin](./kibana-plugin-core-server.plugin.md) | The interface that should be returned by a PluginInitializer. | +| [Plugin](./kibana-plugin-core-server.plugin.md) | The interface that should be returned by a PluginInitializer for a standard plugin. | | [PluginConfigDescriptor](./kibana-plugin-core-server.pluginconfigdescriptor.md) | Describes a plugin configuration properties. | | [PluginInitializerContext](./kibana-plugin-core-server.plugininitializercontext.md) | Context that's available to plugins during initialization stage. | | [PluginManifest](./kibana-plugin-core-server.pluginmanifest.md) | Describes the set of required and optional properties plugin can define in its mandatory JSON manifest file. | +| [PrebootPlugin](./kibana-plugin-core-server.prebootplugin.md) | The interface that should be returned by a PluginInitializer for a preboot plugin. | +| [PrebootServicePreboot](./kibana-plugin-core-server.prebootservicepreboot.md) | Kibana Preboot Service allows to control the boot flow of Kibana. Preboot plugins can use it to hold the boot until certain condition is met. | | [RegisterDeprecationsConfig](./kibana-plugin-core-server.registerdeprecationsconfig.md) | | | [RequestHandlerContext](./kibana-plugin-core-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients and services: - [savedObjects.client](./kibana-plugin-core-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [savedObjects.typeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) - Type registry containing all the registered types. - [elasticsearch.client](./kibana-plugin-core-server.iscopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.legacy.client](./kibana-plugin-core-server.legacyscopedclusterclient.md) - The legacy Elasticsearch data client which uses the credentials of the incoming request - [uiSettings.client](./kibana-plugin-core-server.iuisettingsclient.md) - uiSettings client which uses the credentials of the incoming request | | [ResolveCapabilitiesOptions](./kibana-plugin-core-server.resolvecapabilitiesoptions.md) | Defines a set of additional options for the resolveCapabilities method of [CapabilitiesStart](./kibana-plugin-core-server.capabilitiesstart.md). | diff --git a/docs/development/core/server/kibana-plugin-core-server.plugin.md b/docs/development/core/server/kibana-plugin-core-server.plugin.md index d9796202d7878..b1fce06d46f3a 100644 --- a/docs/development/core/server/kibana-plugin-core-server.plugin.md +++ b/docs/development/core/server/kibana-plugin-core-server.plugin.md @@ -4,7 +4,7 @@ ## Plugin interface -The interface that should be returned by a `PluginInitializer`. +The interface that should be returned by a `PluginInitializer` for a `standard` plugin. Signature: diff --git a/docs/development/core/server/kibana-plugin-core-server.plugininitializer.md b/docs/development/core/server/kibana-plugin-core-server.plugininitializer.md index fe55e131065dd..9b4d1b022db2a 100644 --- a/docs/development/core/server/kibana-plugin-core-server.plugininitializer.md +++ b/docs/development/core/server/kibana-plugin-core-server.plugininitializer.md @@ -9,5 +9,5 @@ The `plugin` export at the root of a plugin's `server` directory should conform Signature: ```typescript -export declare type PluginInitializer = (core: PluginInitializerContext) => Plugin | AsyncPlugin; +export declare type PluginInitializer = (core: PluginInitializerContext) => Plugin | PrebootPlugin | AsyncPlugin; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.env.md b/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.env.md index 76e4f222f0228..534f532850587 100644 --- a/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.env.md +++ b/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.env.md @@ -11,5 +11,6 @@ env: { mode: EnvironmentMode; packageInfo: Readonly; instanceUuid: string; + configs: readonly string[]; }; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.md b/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.md index 90a19d53bd5e1..9bc9d6d83674c 100644 --- a/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.md @@ -17,7 +17,7 @@ export interface PluginInitializerContext | Property | Type | Description | | --- | --- | --- | | [config](./kibana-plugin-core-server.plugininitializercontext.config.md) | {
legacy: {
globalConfig$: Observable<SharedGlobalConfig>;
get: () => SharedGlobalConfig;
};
create: <T = ConfigSchema>() => Observable<T>;
get: <T = ConfigSchema>() => T;
} | Accessors for the plugin's configuration | -| [env](./kibana-plugin-core-server.plugininitializercontext.env.md) | {
mode: EnvironmentMode;
packageInfo: Readonly<PackageInfo>;
instanceUuid: string;
} | | +| [env](./kibana-plugin-core-server.plugininitializercontext.env.md) | {
mode: EnvironmentMode;
packageInfo: Readonly<PackageInfo>;
instanceUuid: string;
configs: readonly string[];
} | | | [logger](./kibana-plugin-core-server.plugininitializercontext.logger.md) | LoggerFactory | instance already bound to the plugin's logging context | | [opaqueId](./kibana-plugin-core-server.plugininitializercontext.opaqueid.md) | PluginOpaqueId | | diff --git a/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md index b3e20bc7ed693..f8d4c3f1b9d15 100644 --- a/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md +++ b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.md @@ -31,6 +31,7 @@ Should never be used in code outside of Core but is exported for documentation p | [requiredPlugins](./kibana-plugin-core-server.pluginmanifest.requiredplugins.md) | readonly PluginName[] | An optional list of the other plugins that \*\*must be\*\* installed and enabled for this plugin to function properly. | | [server](./kibana-plugin-core-server.pluginmanifest.server.md) | boolean | Specifies whether plugin includes some server-side specific functionality. | | [serviceFolders](./kibana-plugin-core-server.pluginmanifest.servicefolders.md) | readonly string[] | Only used for the automatically generated API documentation. Specifying service folders will cause your plugin API reference to be broken up into sub sections. | +| [type](./kibana-plugin-core-server.pluginmanifest.type.md) | PluginType | Type of the plugin, defaults to standard. | | [ui](./kibana-plugin-core-server.pluginmanifest.ui.md) | boolean | Specifies whether plugin includes some client/browser specific functionality that should be included into client bundle via public/ui_plugin.js file. | | [version](./kibana-plugin-core-server.pluginmanifest.version.md) | string | Version of the plugin. | diff --git a/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.type.md b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.type.md new file mode 100644 index 0000000000000..6e82132919f6d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.pluginmanifest.type.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PluginManifest](./kibana-plugin-core-server.pluginmanifest.md) > [type](./kibana-plugin-core-server.pluginmanifest.type.md) + +## PluginManifest.type property + +Type of the plugin, defaults to `standard`. + +Signature: + +```typescript +readonly type: PluginType; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.plugintype.md b/docs/development/core/server/kibana-plugin-core-server.plugintype.md new file mode 100644 index 0000000000000..e4a252a392949 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.plugintype.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PluginType](./kibana-plugin-core-server.plugintype.md) + +## PluginType enum + + +Signature: + +```typescript +export declare enum PluginType +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| preboot | "preboot" | Preboot plugins are special-purpose plugins that only function during preboot stage. | +| standard | "standard" | Standard plugins are plugins that start to function as soon as Kibana is fully booted and are active until it shuts down. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.prebootplugin.md b/docs/development/core/server/kibana-plugin-core-server.prebootplugin.md new file mode 100644 index 0000000000000..df851daab7806 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.prebootplugin.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PrebootPlugin](./kibana-plugin-core-server.prebootplugin.md) + +## PrebootPlugin interface + +The interface that should be returned by a `PluginInitializer` for a `preboot` plugin. + +Signature: + +```typescript +export interface PrebootPlugin +``` + +## Methods + +| Method | Description | +| --- | --- | +| [setup(core, plugins)](./kibana-plugin-core-server.prebootplugin.setup.md) | | +| [stop()](./kibana-plugin-core-server.prebootplugin.stop.md) | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.prebootplugin.setup.md b/docs/development/core/server/kibana-plugin-core-server.prebootplugin.setup.md new file mode 100644 index 0000000000000..0ee2a26293e98 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.prebootplugin.setup.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PrebootPlugin](./kibana-plugin-core-server.prebootplugin.md) > [setup](./kibana-plugin-core-server.prebootplugin.setup.md) + +## PrebootPlugin.setup() method + +Signature: + +```typescript +setup(core: CorePreboot, plugins: TPluginsSetup): TSetup; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| core | CorePreboot | | +| plugins | TPluginsSetup | | + +Returns: + +`TSetup` + diff --git a/docs/development/core/server/kibana-plugin-core-server.prebootplugin.stop.md b/docs/development/core/server/kibana-plugin-core-server.prebootplugin.stop.md new file mode 100644 index 0000000000000..89566b2ae6687 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.prebootplugin.stop.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PrebootPlugin](./kibana-plugin-core-server.prebootplugin.md) > [stop](./kibana-plugin-core-server.prebootplugin.stop.md) + +## PrebootPlugin.stop() method + +Signature: + +```typescript +stop?(): void; +``` +Returns: + +`void` + diff --git a/docs/development/core/server/kibana-plugin-core-server.prebootservicepreboot.holdsetupuntilresolved.md b/docs/development/core/server/kibana-plugin-core-server.prebootservicepreboot.holdsetupuntilresolved.md new file mode 100644 index 0000000000000..7f158b46d1f06 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.prebootservicepreboot.holdsetupuntilresolved.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PrebootServicePreboot](./kibana-plugin-core-server.prebootservicepreboot.md) > [holdSetupUntilResolved](./kibana-plugin-core-server.prebootservicepreboot.holdsetupuntilresolved.md) + +## PrebootServicePreboot.holdSetupUntilResolved property + +Registers a `Promise` as a precondition before Kibana can proceed to `setup`. This method can be invoked multiple times and from multiple `preboot` plugins. Kibana will proceed to `setup` only when all registered `Promises` instances are resolved, or it will shut down if any of them is rejected. + +Signature: + +```typescript +readonly holdSetupUntilResolved: (reason: string, promise: Promise<{ + shouldReloadConfig: boolean; + } | undefined>) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.prebootservicepreboot.issetuponhold.md b/docs/development/core/server/kibana-plugin-core-server.prebootservicepreboot.issetuponhold.md new file mode 100644 index 0000000000000..1ba079da03208 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.prebootservicepreboot.issetuponhold.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PrebootServicePreboot](./kibana-plugin-core-server.prebootservicepreboot.md) > [isSetupOnHold](./kibana-plugin-core-server.prebootservicepreboot.issetuponhold.md) + +## PrebootServicePreboot.isSetupOnHold property + +Indicates whether Kibana is currently on hold and cannot proceed to `setup` yet. + +Signature: + +```typescript +readonly isSetupOnHold: () => boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.prebootservicepreboot.md b/docs/development/core/server/kibana-plugin-core-server.prebootservicepreboot.md new file mode 100644 index 0000000000000..bf503499b6298 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.prebootservicepreboot.md @@ -0,0 +1,45 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [PrebootServicePreboot](./kibana-plugin-core-server.prebootservicepreboot.md) + +## PrebootServicePreboot interface + +Kibana Preboot Service allows to control the boot flow of Kibana. Preboot plugins can use it to hold the boot until certain condition is met. + +Signature: + +```typescript +export interface PrebootServicePreboot +``` + +## Example + +A plugin can supply a `Promise` to a `holdSetupUntilResolved` method to signal Kibana to initialize and start `standard` plugins only after this `Promise` is resolved. If `Promise` is rejected, Kibana will shut down. + +```ts +core.preboot.holdSetupUntilResolved('Just waiting for 5 seconds', + new Promise((resolve) => { + setTimeout(resolve, 5000); + }) +); + +``` +If the supplied `Promise` resolves to an object with the `shouldReloadConfig` property set to `true`, Kibana will also reload its configuration from disk. + +```ts +let completeSetup: (result: { shouldReloadConfig: boolean }) => void; +core.preboot.holdSetupUntilResolved('Just waiting for 5 seconds before reloading configuration', + new Promise<{ shouldReloadConfig: boolean }>((resolve) => { + setTimeout(() => resolve({ shouldReloadConfig: true }), 5000); + }) +); + +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [holdSetupUntilResolved](./kibana-plugin-core-server.prebootservicepreboot.holdsetupuntilresolved.md) | (reason: string, promise: Promise<{
shouldReloadConfig: boolean;
} | undefined>) => void | Registers a Promise as a precondition before Kibana can proceed to setup. This method can be invoked multiple times and from multiple preboot plugins. Kibana will proceed to setup only when all registered Promises instances are resolved, or it will shut down if any of them is rejected. | +| [isSetupOnHold](./kibana-plugin-core-server.prebootservicepreboot.issetuponhold.md) | () => boolean | Indicates whether Kibana is currently on hold and cannot proceed to setup yet. | + diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getsearchsourcetimefilter.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getsearchsourcetimefilter.md index 1f8bc1300a0a8..9ebc685f2a77d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getsearchsourcetimefilter.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs.getsearchsourcetimefilter.md @@ -7,7 +7,7 @@ Signature: ```typescript -getSearchSourceTimeFilter(forceNow?: Date): RangeFilter[] | { +getSearchSourceTimeFilter(forceNow?: Date): import("@kbn/es-query").RangeFilter[] | { meta: { index: string | undefined; params: {}; @@ -43,7 +43,7 @@ getSearchSourceTimeFilter(forceNow?: Date): RangeFilter[] | { Returns: -`RangeFilter[] | { +`import("@kbn/es-query").RangeFilter[] | { meta: { index: string | undefined; params: {}; diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.customfilter.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.customfilter.md index 0a3b4e54cfe55..6763a8d2ba0bf 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.customfilter.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.customfilter.md @@ -4,10 +4,13 @@ ## CustomFilter type +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> + Signature: ```typescript -export declare type CustomFilter = Filter & { - query: any; -}; +declare type CustomFilter = oldCustomFilter; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md index d06ce1b2ef2bc..eb06d99426197 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esfilters.md @@ -10,23 +10,23 @@ esFilters: { FilterLabel: (props: import("./ui/filter_bar/filter_editor/lib/filter_label").FilterLabelProps) => JSX.Element; FilterItem: (props: import("./ui/filter_bar/filter_item").FilterItemProps) => JSX.Element; - FILTERS: typeof FILTERS; + FILTERS: typeof import("@kbn/es-query").FILTERS; FilterStateStore: typeof FilterStateStore; - buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("../common").Filter; - buildPhrasesFilter: (field: import("../common").IndexPatternFieldBase, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter; - buildExistsFilter: (field: import("../common").IndexPatternFieldBase, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter; - buildPhraseFilter: (field: import("../common").IndexPatternFieldBase, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter; - buildQueryFilter: (query: any, index: string, alias: string) => import("../common").QueryStringFilter; - buildRangeFilter: (field: import("../common").IndexPatternFieldBase, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter; - isPhraseFilter: (filter: any) => filter is import("../common").PhraseFilter; - isExistsFilter: (filter: any) => filter is import("../common").ExistsFilter; - isPhrasesFilter: (filter: any) => filter is import("../common").PhrasesFilter; - isRangeFilter: (filter: any) => filter is import("../common").RangeFilter; - isMatchAllFilter: (filter: any) => filter is import("../common").MatchAllFilter; - isMissingFilter: (filter: any) => filter is import("../common").MissingFilter; - isQueryStringFilter: (filter: any) => filter is import("../common").QueryStringFilter; - isFilterPinned: (filter: import("../common").Filter) => boolean | undefined; - toggleFilterNegated: (filter: import("../common").Filter) => { + buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("@kbn/es-query").Filter; + buildPhrasesFilter: (field: import("@kbn/es-query").IndexPatternFieldBase, params: any[], indexPattern: import("@kbn/es-query").IndexPatternBase) => import("@kbn/es-query").PhrasesFilter; + buildExistsFilter: (field: import("@kbn/es-query").IndexPatternFieldBase, indexPattern: import("@kbn/es-query").IndexPatternBase) => import("@kbn/es-query").ExistsFilter; + buildPhraseFilter: (field: import("@kbn/es-query").IndexPatternFieldBase, value: any, indexPattern: import("@kbn/es-query").IndexPatternBase) => import("@kbn/es-query").PhraseFilter; + buildQueryFilter: (query: any, index: string, alias: string) => import("@kbn/es-query").QueryStringFilter; + buildRangeFilter: (field: import("@kbn/es-query").IndexPatternFieldBase, params: import("@kbn/es-query").RangeFilterParams, indexPattern: import("@kbn/es-query").IndexPatternBase, formattedValue?: string | undefined) => import("@kbn/es-query").RangeFilter; + isPhraseFilter: (filter: any) => filter is import("@kbn/es-query").PhraseFilter; + isExistsFilter: (filter: any) => filter is import("@kbn/es-query").ExistsFilter; + isPhrasesFilter: (filter: any) => filter is import("@kbn/es-query").PhrasesFilter; + isRangeFilter: (filter: any) => filter is import("@kbn/es-query").RangeFilter; + isMatchAllFilter: (filter: any) => filter is import("@kbn/es-query").MatchAllFilter; + isMissingFilter: (filter: any) => filter is import("@kbn/es-query").MissingFilter; + isQueryStringFilter: (filter: any) => filter is import("@kbn/es-query").QueryStringFilter; + isFilterPinned: (filter: import("@kbn/es-query").Filter) => boolean | undefined; + toggleFilterNegated: (filter: import("@kbn/es-query").Filter) => { meta: { negate: boolean; alias: string | null; @@ -39,20 +39,20 @@ esFilters: { params?: any; value?: string | undefined; }; - $state?: import("../common").FilterState | undefined; + $state?: import("@kbn/es-query/target_types/filters/types").FilterState | undefined; query?: any; }; - disableFilter: (filter: import("../common").Filter) => import("../common").Filter; - getPhraseFilterField: (filter: import("../common").PhraseFilter) => string; - getPhraseFilterValue: (filter: import("../common").PhraseFilter) => string | number | boolean; + disableFilter: (filter: import("@kbn/es-query").Filter) => import("@kbn/es-query").Filter; + getPhraseFilterField: (filter: import("@kbn/es-query").PhraseFilter) => string; + getPhraseFilterValue: (filter: import("@kbn/es-query").PhraseFilter) => string | number | boolean; getDisplayValueFromFilter: typeof getDisplayValueFromFilter; - compareFilters: (first: import("../common").Filter | import("../common").Filter[], second: import("../common").Filter | import("../common").Filter[], comparatorOptions?: import("../common").FilterCompareOptions) => boolean; + compareFilters: (first: import("@kbn/es-query").Filter | import("@kbn/es-query").Filter[], second: import("@kbn/es-query").Filter | import("@kbn/es-query").Filter[], comparatorOptions?: import("../common").FilterCompareOptions) => boolean; COMPARE_ALL_OPTIONS: import("../common").FilterCompareOptions; generateFilters: typeof generateFilters; - onlyDisabledFiltersChanged: (newFilters?: import("../common").Filter[] | undefined, oldFilters?: import("../common").Filter[] | undefined) => boolean; + onlyDisabledFiltersChanged: (newFilters?: import("@kbn/es-query").Filter[] | undefined, oldFilters?: import("@kbn/es-query").Filter[] | undefined) => boolean; changeTimeFilter: typeof changeTimeFilter; convertRangeFilterToTimeRangeString: typeof convertRangeFilterToTimeRangeString; - mapAndFlattenFilters: (filters: import("../common").Filter[]) => import("../common").Filter[]; + mapAndFlattenFilters: (filters: import("@kbn/es-query").Filter[]) => import("@kbn/es-query").Filter[]; extractTimeFilter: typeof extractTimeFilter; extractTimeRange: typeof extractTimeRange; } diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md index 332114e637586..6ed9898ddd718 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.eskuery.md @@ -4,12 +4,17 @@ ## esKuery variable +> Warning: This API is now obsolete. +> +> Please import helpers from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> + Signature: ```typescript esKuery: { - nodeTypes: import("../common/es_query/kuery/node_types").NodeTypes; - fromKueryExpression: (expression: any, parseOptions?: Partial) => import("../common").KueryNode; - toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IndexPatternBase | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject; + nodeTypes: import("@kbn/es-query/target_types/kuery/node_types").NodeTypes; + fromKueryExpression: (expression: any, parseOptions?: Partial | undefined) => import("@kbn/es-query").KueryNode; + toElasticsearchQuery: (node: import("@kbn/es-query").KueryNode, indexPattern?: import("@kbn/es-query").IndexPatternBase | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject; } ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esquery.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esquery.md index 0bc9c0c12fc3a..fa2ee4faa7466 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esquery.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esquery.md @@ -4,19 +4,24 @@ ## esQuery variable +> Warning: This API is now obsolete. +> +> Please import helpers from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> + Signature: ```typescript esQuery: { - buildEsQuery: typeof buildEsQuery; + buildEsQuery: typeof import("@kbn/es-query").buildEsQuery; getEsQueryConfig: typeof getEsQueryConfig; - buildQueryFromFilters: (filters: import("../common").Filter[] | undefined, indexPattern: import("../common").IndexPatternBase | undefined, ignoreFilterIfFieldNotInIndex?: boolean) => { + buildQueryFromFilters: (filters: import("@kbn/es-query").Filter[] | undefined, indexPattern: import("@kbn/es-query").IndexPatternBase | undefined, ignoreFilterIfFieldNotInIndex?: boolean | undefined) => { must: never[]; - filter: import("../common").Filter[]; + filter: import("@kbn/es-query").Filter[]; should: never[]; - must_not: import("../common").Filter[]; + must_not: import("@kbn/es-query").Filter[]; }; - luceneStringToDsl: typeof luceneStringToDsl; - decorateQuery: typeof decorateQuery; + luceneStringToDsl: typeof import("@kbn/es-query").luceneStringToDsl; + decorateQuery: typeof import("@kbn/es-query").decorateQuery; } ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.allowleadingwildcards.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.allowleadingwildcards.md deleted file mode 100644 index 71eb23ac6299b..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.allowleadingwildcards.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [EsQueryConfig](./kibana-plugin-plugins-data-public.esqueryconfig.md) > [allowLeadingWildcards](./kibana-plugin-plugins-data-public.esqueryconfig.allowleadingwildcards.md) - -## EsQueryConfig.allowLeadingWildcards property - -Signature: - -```typescript -allowLeadingWildcards: boolean; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.dateformattz.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.dateformattz.md deleted file mode 100644 index e9c4c26878a97..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.dateformattz.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [EsQueryConfig](./kibana-plugin-plugins-data-public.esqueryconfig.md) > [dateFormatTZ](./kibana-plugin-plugins-data-public.esqueryconfig.dateformattz.md) - -## EsQueryConfig.dateFormatTZ property - -Signature: - -```typescript -dateFormatTZ?: string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.ignorefilteriffieldnotinindex.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.ignorefilteriffieldnotinindex.md deleted file mode 100644 index 9f765c51d0a69..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.ignorefilteriffieldnotinindex.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [EsQueryConfig](./kibana-plugin-plugins-data-public.esqueryconfig.md) > [ignoreFilterIfFieldNotInIndex](./kibana-plugin-plugins-data-public.esqueryconfig.ignorefilteriffieldnotinindex.md) - -## EsQueryConfig.ignoreFilterIfFieldNotInIndex property - -Signature: - -```typescript -ignoreFilterIfFieldNotInIndex: boolean; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.md index 5252f8058b488..4480329c2df19 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.md @@ -2,20 +2,15 @@ [Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [EsQueryConfig](./kibana-plugin-plugins-data-public.esqueryconfig.md) -## EsQueryConfig interface +## EsQueryConfig type + +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> Signature: ```typescript -export interface EsQueryConfig +declare type EsQueryConfig = oldEsQueryConfig; ``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [allowLeadingWildcards](./kibana-plugin-plugins-data-public.esqueryconfig.allowleadingwildcards.md) | boolean | | -| [dateFormatTZ](./kibana-plugin-plugins-data-public.esqueryconfig.dateformattz.md) | string | | -| [ignoreFilterIfFieldNotInIndex](./kibana-plugin-plugins-data-public.esqueryconfig.ignorefilteriffieldnotinindex.md) | boolean | | -| [queryStringOptions](./kibana-plugin-plugins-data-public.esqueryconfig.querystringoptions.md) | Record<string, any> | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.querystringoptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.querystringoptions.md deleted file mode 100644 index feaa8f1821e30..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.esqueryconfig.querystringoptions.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [EsQueryConfig](./kibana-plugin-plugins-data-public.esqueryconfig.md) > [queryStringOptions](./kibana-plugin-plugins-data-public.esqueryconfig.querystringoptions.md) - -## EsQueryConfig.queryStringOptions property - -Signature: - -```typescript -queryStringOptions: Record; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.existsfilter.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.existsfilter.md index f1279934db84c..ab756295eac8c 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.existsfilter.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.existsfilter.md @@ -4,11 +4,13 @@ ## ExistsFilter type +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> + Signature: ```typescript -export declare type ExistsFilter = Filter & { - meta: ExistsFilterMeta; - exists?: FilterExistsProperty; -}; +declare type ExistsFilter = oldExistsFilter; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filter.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filter.md index 9212b757e07df..bf8d4ced016a3 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filter.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filter.md @@ -4,12 +4,13 @@ ## Filter type +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> + Signature: ```typescript -export declare type Filter = { - $state?: FilterState; - meta: FilterMeta; - query?: any; -}; +declare type Filter = oldFilter; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.extract.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.extract.md new file mode 100644 index 0000000000000..60ea060cf6323 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.extract.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FilterManager](./kibana-plugin-plugins-data-public.filtermanager.md) > [extract](./kibana-plugin-plugins-data-public.filtermanager.extract.md) + +## FilterManager.extract property + +Signature: + +```typescript +extract: any; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.getallmigrations.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.getallmigrations.md new file mode 100644 index 0000000000000..0d46d806f0563 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.getallmigrations.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FilterManager](./kibana-plugin-plugins-data-public.filtermanager.md) > [getAllMigrations](./kibana-plugin-plugins-data-public.filtermanager.getallmigrations.md) + +## FilterManager.getAllMigrations property + +Signature: + +```typescript +getAllMigrations: () => {}; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.inject.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.inject.md new file mode 100644 index 0000000000000..0e3b84cd3cf80 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.inject.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FilterManager](./kibana-plugin-plugins-data-public.filtermanager.md) > [inject](./kibana-plugin-plugins-data-public.filtermanager.inject.md) + +## FilterManager.inject property + +Signature: + +```typescript +inject: any; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.migratetolatest.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.migratetolatest.md new file mode 100644 index 0000000000000..2235c55947865 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.migratetolatest.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FilterManager](./kibana-plugin-plugins-data-public.filtermanager.md) > [migrateToLatest](./kibana-plugin-plugins-data-public.filtermanager.migratetolatest.md) + +## FilterManager.migrateToLatest property + +Signature: + +```typescript +migrateToLatest: any; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.telemetry.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.telemetry.md new file mode 100644 index 0000000000000..bab6452c34903 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.filtermanager.telemetry.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [FilterManager](./kibana-plugin-plugins-data-public.filtermanager.md) > [telemetry](./kibana-plugin-plugins-data-public.filtermanager.telemetry.md) + +## FilterManager.telemetry property + +Signature: + +```typescript +telemetry: (filters: import("../../../../kibana_utils/common/persistable_state").SerializableState, collector: unknown) => {}; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.gettime.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.gettime.md index 3969a97fa7789..7fd1914d1a4a5 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.gettime.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.gettime.md @@ -10,7 +10,7 @@ export declare function getTime(indexPattern: IIndexPattern | undefined, timeRange: TimeRange, options?: { forceNow?: Date; fieldName?: string; -}): import("../..").RangeFilter | undefined; +}): import("@kbn/es-query").RangeFilter | undefined; ``` ## Parameters @@ -23,5 +23,5 @@ export declare function getTime(indexPattern: IIndexPattern | undefined, timeRan Returns: -`import("../..").RangeFilter | undefined` +`import("@kbn/es-query").RangeFilter | undefined` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldsubtype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldsubtype.md index 7e6ea86d7f3e8..d5d8a0b62d3c5 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldsubtype.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldsubtype.md @@ -2,18 +2,15 @@ [Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldSubType](./kibana-plugin-plugins-data-public.ifieldsubtype.md) -## IFieldSubType interface +## IFieldSubType type + +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> Signature: ```typescript -export interface IFieldSubType +declare type IFieldSubType = oldIFieldSubType; ``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [multi](./kibana-plugin-plugins-data-public.ifieldsubtype.multi.md) | {
parent: string;
} | | -| [nested](./kibana-plugin-plugins-data-public.ifieldsubtype.nested.md) | {
path: string;
} | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldsubtype.multi.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldsubtype.multi.md deleted file mode 100644 index 6cfc6f037d013..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldsubtype.multi.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldSubType](./kibana-plugin-plugins-data-public.ifieldsubtype.md) > [multi](./kibana-plugin-plugins-data-public.ifieldsubtype.multi.md) - -## IFieldSubType.multi property - -Signature: - -```typescript -multi?: { - parent: string; - }; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldsubtype.nested.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldsubtype.nested.md deleted file mode 100644 index f9308b90a1b96..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.ifieldsubtype.nested.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IFieldSubType](./kibana-plugin-plugins-data-public.ifieldsubtype.md) > [nested](./kibana-plugin-plugins-data-public.ifieldsubtype.nested.md) - -## IFieldSubType.nested property - -Signature: - -```typescript -nested?: { - path: string; - }; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md index 16546ceca958d..dc206ceabefe2 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.md @@ -37,7 +37,7 @@ export declare class IndexPatternField implements IFieldType | [searchable](./kibana-plugin-plugins-data-public.indexpatternfield.searchable.md) | | boolean | | | [sortable](./kibana-plugin-plugins-data-public.indexpatternfield.sortable.md) | | boolean | | | [spec](./kibana-plugin-plugins-data-public.indexpatternfield.spec.md) | | FieldSpec | | -| [subType](./kibana-plugin-plugins-data-public.indexpatternfield.subtype.md) | | import("../..").IFieldSubType | undefined | | +| [subType](./kibana-plugin-plugins-data-public.indexpatternfield.subtype.md) | | import("@kbn/es-query").IFieldSubType | undefined | | | [type](./kibana-plugin-plugins-data-public.indexpatternfield.type.md) | | string | | | [visualizable](./kibana-plugin-plugins-data-public.indexpatternfield.visualizable.md) | | boolean | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.subtype.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.subtype.md index 6cd5247291602..f5e25e3191f72 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.subtype.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.subtype.md @@ -7,5 +7,5 @@ Signature: ```typescript -get subType(): import("../..").IFieldSubType | undefined; +get subType(): import("@kbn/es-query").IFieldSubType | undefined; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md index b77f3d1f374fb..9afcef6afed3a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternfield.tojson.md @@ -19,7 +19,7 @@ toJSON(): { searchable: boolean; aggregatable: boolean; readFromDocValues: boolean; - subType: import("../..").IFieldSubType | undefined; + subType: import("@kbn/es-query").IFieldSubType | undefined; customLabel: string | undefined; }; ``` @@ -37,7 +37,7 @@ toJSON(): { searchable: boolean; aggregatable: boolean; readFromDocValues: boolean; - subType: import("../..").IFieldSubType | undefined; + subType: import("@kbn/es-query").IFieldSubType | undefined; customLabel: string | undefined; }` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.executioncontext.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.executioncontext.md new file mode 100644 index 0000000000000..18fce3e273a39 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.executioncontext.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) > [executionContext](./kibana-plugin-plugins-data-public.isearchoptions.executioncontext.md) + +## ISearchOptions.executionContext property + +Signature: + +```typescript +executionContext?: KibanaExecutionContext; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md index 21fb7e3dfc7e8..488695475dcbe 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md @@ -15,6 +15,7 @@ export interface ISearchOptions | Property | Type | Description | | --- | --- | --- | | [abortSignal](./kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | +| [executionContext](./kibana-plugin-plugins-data-public.isearchoptions.executioncontext.md) | KibanaExecutionContext | | | [indexPattern](./kibana-plugin-plugins-data-public.isearchoptions.indexpattern.md) | IndexPattern | Index pattern reference is used for better error messages | | [inspector](./kibana-plugin-plugins-data-public.isearchoptions.inspector.md) | IInspectorInfo | Inspector integration options | | [isRestore](./kibana-plugin-plugins-data-public.isearchoptions.isrestore.md) | boolean | Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilter.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilter.md index f1916e89c2c98..2848e20edde1b 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilter.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilter.md @@ -4,8 +4,13 @@ ## isFilter variable +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> + Signature: ```typescript -isFilter: (x: unknown) => x is Filter +isFilter: (x: unknown) => x is oldFilter ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilters.md index 558da72cc26bb..881d50b8a49e1 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilters.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isfilters.md @@ -4,8 +4,13 @@ ## isFilters variable +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> + Signature: ```typescript -isFilters: (x: unknown) => x is Filter[] +isFilters: (x: unknown) => x is oldFilter[] ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kuerynode.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kuerynode.md index 276f25da8cb9f..9cea144ff9d46 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kuerynode.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kuerynode.md @@ -2,17 +2,15 @@ [Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [KueryNode](./kibana-plugin-plugins-data-public.kuerynode.md) -## KueryNode interface +## KueryNode type + +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> Signature: ```typescript -export interface KueryNode +declare type KueryNode = oldKueryNode; ``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [type](./kibana-plugin-plugins-data-public.kuerynode.type.md) | keyof NodeTypes | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kuerynode.type.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kuerynode.type.md deleted file mode 100644 index 2ff96b6421c2e..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.kuerynode.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [KueryNode](./kibana-plugin-plugins-data-public.kuerynode.md) > [type](./kibana-plugin-plugins-data-public.kuerynode.type.md) - -## KueryNode.type property - -Signature: - -```typescript -type: keyof NodeTypes; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.matchallfilter.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.matchallfilter.md index 740b83bb5c563..39ae82865808c 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.matchallfilter.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.matchallfilter.md @@ -4,11 +4,13 @@ ## MatchAllFilter type +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> + Signature: ```typescript -export declare type MatchAllFilter = Filter & { - meta: MatchAllFilterMeta; - match_all: any; -}; +declare type MatchAllFilter = oldMatchAllFilter; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 7c2911875ee05..e60e26bcb503e 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -62,11 +62,9 @@ | [DataPublicPluginStart](./kibana-plugin-plugins-data-public.datapublicpluginstart.md) | Data plugin public Start contract | | [DataPublicPluginStartActions](./kibana-plugin-plugins-data-public.datapublicpluginstartactions.md) | utilities to generate filters from action context | | [DataPublicPluginStartUi](./kibana-plugin-plugins-data-public.datapublicpluginstartui.md) | Data plugin prewired UI components | -| [EsQueryConfig](./kibana-plugin-plugins-data-public.esqueryconfig.md) | | | [FieldFormatConfig](./kibana-plugin-plugins-data-public.fieldformatconfig.md) | | | [IDataPluginServices](./kibana-plugin-plugins-data-public.idatapluginservices.md) | | | [IEsSearchRequest](./kibana-plugin-plugins-data-public.iessearchrequest.md) | | -| [IFieldSubType](./kibana-plugin-plugins-data-public.ifieldsubtype.md) | | | [IFieldType](./kibana-plugin-plugins-data-public.ifieldtype.md) | | | [IIndexPattern](./kibana-plugin-plugins-data-public.iindexpattern.md) | | | [IIndexPatternFieldList](./kibana-plugin-plugins-data-public.iindexpatternfieldlist.md) | | @@ -79,7 +77,6 @@ | [ISearchSetup](./kibana-plugin-plugins-data-public.isearchsetup.md) | The setup contract exposed by the Search plugin exposes the search strategy extension point. | | [ISearchStart](./kibana-plugin-plugins-data-public.isearchstart.md) | search service | | [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md) | high level search service | -| [KueryNode](./kibana-plugin-plugins-data-public.kuerynode.md) | | | [OptionedValueProp](./kibana-plugin-plugins-data-public.optionedvalueprop.md) | | | [QueryState](./kibana-plugin-plugins-data-public.querystate.md) | All query state service state | | [QueryStateChange](./kibana-plugin-plugins-data-public.querystatechange.md) | | @@ -87,7 +84,6 @@ | [QuerySuggestionBasic](./kibana-plugin-plugins-data-public.querysuggestionbasic.md) | \* | | [QuerySuggestionField](./kibana-plugin-plugins-data-public.querysuggestionfield.md) | \* | | [QuerySuggestionGetFnArgs](./kibana-plugin-plugins-data-public.querysuggestiongetfnargs.md) | \* | -| [RangeFilterParams](./kibana-plugin-plugins-data-public.rangefilterparams.md) | | | [Reason](./kibana-plugin-plugins-data-public.reason.md) | | | [RefreshInterval](./kibana-plugin-plugins-data-public.refreshinterval.md) | | | [SavedQuery](./kibana-plugin-plugins-data-public.savedquery.md) | | @@ -151,6 +147,7 @@ | [CustomFilter](./kibana-plugin-plugins-data-public.customfilter.md) | | | [EsaggsExpressionFunctionDefinition](./kibana-plugin-plugins-data-public.esaggsexpressionfunctiondefinition.md) | | | [EsdslExpressionFunctionDefinition](./kibana-plugin-plugins-data-public.esdslexpressionfunctiondefinition.md) | | +| [EsQueryConfig](./kibana-plugin-plugins-data-public.esqueryconfig.md) | | | [EsQuerySortValue](./kibana-plugin-plugins-data-public.esquerysortvalue.md) | | | [EsRawResponseExpressionTypeDefinition](./kibana-plugin-plugins-data-public.esrawresponseexpressiontypedefinition.md) | | | [ExecutionContextSearch](./kibana-plugin-plugins-data-public.executioncontextsearch.md) | | @@ -170,6 +167,7 @@ | [IFieldFormat](./kibana-plugin-plugins-data-public.ifieldformat.md) | | | [IFieldFormatsRegistry](./kibana-plugin-plugins-data-public.ifieldformatsregistry.md) | | | [IFieldParamType](./kibana-plugin-plugins-data-public.ifieldparamtype.md) | | +| [IFieldSubType](./kibana-plugin-plugins-data-public.ifieldsubtype.md) | | | [IMetricAggType](./kibana-plugin-plugins-data-public.imetricaggtype.md) | | | [IndexPatternAggRestrictions](./kibana-plugin-plugins-data-public.indexpatternaggrestrictions.md) | | | [IndexPatternLoadExpressionFunctionDefinition](./kibana-plugin-plugins-data-public.indexpatternloadexpressionfunctiondefinition.md) | | @@ -181,16 +179,17 @@ | [ISessionsClient](./kibana-plugin-plugins-data-public.isessionsclient.md) | | | [ISessionService](./kibana-plugin-plugins-data-public.isessionservice.md) | | | [KibanaContext](./kibana-plugin-plugins-data-public.kibanacontext.md) | | +| [KueryNode](./kibana-plugin-plugins-data-public.kuerynode.md) | | | [MatchAllFilter](./kibana-plugin-plugins-data-public.matchallfilter.md) | | | [ParsedInterval](./kibana-plugin-plugins-data-public.parsedinterval.md) | | | [PhraseFilter](./kibana-plugin-plugins-data-public.phrasefilter.md) | | | [PhrasesFilter](./kibana-plugin-plugins-data-public.phrasesfilter.md) | | -| [Query](./kibana-plugin-plugins-data-public.query.md) | | | [QueryStart](./kibana-plugin-plugins-data-public.querystart.md) | | | [QuerySuggestion](./kibana-plugin-plugins-data-public.querysuggestion.md) | \* | | [QuerySuggestionGetFn](./kibana-plugin-plugins-data-public.querysuggestiongetfn.md) | | | [RangeFilter](./kibana-plugin-plugins-data-public.rangefilter.md) | | | [RangeFilterMeta](./kibana-plugin-plugins-data-public.rangefiltermeta.md) | | +| [RangeFilterParams](./kibana-plugin-plugins-data-public.rangefilterparams.md) | | | [SavedQueryTimeFilter](./kibana-plugin-plugins-data-public.savedquerytimefilter.md) | | | [SearchBarProps](./kibana-plugin-plugins-data-public.searchbarprops.md) | | | [StatefulSearchBarProps](./kibana-plugin-plugins-data-public.statefulsearchbarprops.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.phrasefilter.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.phrasefilter.md index 8d0447d58634c..ca38ac25dcf50 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.phrasefilter.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.phrasefilter.md @@ -4,17 +4,13 @@ ## PhraseFilter type +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> + Signature: ```typescript -export declare type PhraseFilter = Filter & { - meta: PhraseFilterMeta; - script?: { - script: { - source?: any; - lang?: estypes.ScriptLanguage; - params: any; - }; - }; -}; +declare type PhraseFilter = oldPhraseFilter; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.phrasesfilter.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.phrasesfilter.md index ab205cb62fd14..0c293cb909276 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.phrasesfilter.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.phrasesfilter.md @@ -4,10 +4,13 @@ ## PhrasesFilter type +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> + Signature: ```typescript -export declare type PhrasesFilter = Filter & { - meta: PhrasesFilterMeta; -}; +declare type PhrasesFilter = oldPhrasesFilter; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.query.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.query.md deleted file mode 100644 index e15b04236a0b5..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.query.md +++ /dev/null @@ -1,16 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [Query](./kibana-plugin-plugins-data-public.query.md) - -## Query type - -Signature: - -```typescript -export declare type Query = { - query: string | { - [key: string]: any; - }; - language: string; -}; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilter.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilter.md index 1cb627ec3a8f9..3d9af100a707a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilter.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilter.md @@ -4,18 +4,13 @@ ## RangeFilter type +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> + Signature: ```typescript -export declare type RangeFilter = Filter & EsRangeFilter & { - meta: RangeFilterMeta; - script?: { - script: { - params: any; - lang: estypes.ScriptLanguage; - source: any; - }; - }; - match_all?: any; -}; +declare type RangeFilter = oldRangeFilter; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefiltermeta.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefiltermeta.md index 609e98cb6faa8..4060a71e62cd0 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefiltermeta.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefiltermeta.md @@ -4,12 +4,13 @@ ## RangeFilterMeta type +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> + Signature: ```typescript -export declare type RangeFilterMeta = FilterMeta & { - params: RangeFilterParams; - field?: any; - formattedValue?: string; -}; +declare type RangeFilterMeta = oldRangeFilterMeta; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.format.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.format.md deleted file mode 100644 index 15926481923ab..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.format.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [RangeFilterParams](./kibana-plugin-plugins-data-public.rangefilterparams.md) > [format](./kibana-plugin-plugins-data-public.rangefilterparams.format.md) - -## RangeFilterParams.format property - -Signature: - -```typescript -format?: string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.from.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.from.md deleted file mode 100644 index 99b8d75e9c316..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.from.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [RangeFilterParams](./kibana-plugin-plugins-data-public.rangefilterparams.md) > [from](./kibana-plugin-plugins-data-public.rangefilterparams.from.md) - -## RangeFilterParams.from property - -Signature: - -```typescript -from?: number | string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.gt.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.gt.md deleted file mode 100644 index 32bfc6eeb68cb..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.gt.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [RangeFilterParams](./kibana-plugin-plugins-data-public.rangefilterparams.md) > [gt](./kibana-plugin-plugins-data-public.rangefilterparams.gt.md) - -## RangeFilterParams.gt property - -Signature: - -```typescript -gt?: number | string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.gte.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.gte.md deleted file mode 100644 index 81345e4a81610..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.gte.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [RangeFilterParams](./kibana-plugin-plugins-data-public.rangefilterparams.md) > [gte](./kibana-plugin-plugins-data-public.rangefilterparams.gte.md) - -## RangeFilterParams.gte property - -Signature: - -```typescript -gte?: number | string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.lt.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.lt.md deleted file mode 100644 index 6250fecfe59d6..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.lt.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [RangeFilterParams](./kibana-plugin-plugins-data-public.rangefilterparams.md) > [lt](./kibana-plugin-plugins-data-public.rangefilterparams.lt.md) - -## RangeFilterParams.lt property - -Signature: - -```typescript -lt?: number | string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.lte.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.lte.md deleted file mode 100644 index c4f3cbf00b304..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.lte.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [RangeFilterParams](./kibana-plugin-plugins-data-public.rangefilterparams.md) > [lte](./kibana-plugin-plugins-data-public.rangefilterparams.lte.md) - -## RangeFilterParams.lte property - -Signature: - -```typescript -lte?: number | string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.md index 977559f5e6cb2..cdf237ea5a1ec 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.md @@ -2,23 +2,15 @@ [Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [RangeFilterParams](./kibana-plugin-plugins-data-public.rangefilterparams.md) -## RangeFilterParams interface +## RangeFilterParams type + +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> Signature: ```typescript -export interface RangeFilterParams +declare type RangeFilterParams = oldRangeFilterParams; ``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [format](./kibana-plugin-plugins-data-public.rangefilterparams.format.md) | string | | -| [from](./kibana-plugin-plugins-data-public.rangefilterparams.from.md) | number | string | | -| [gt](./kibana-plugin-plugins-data-public.rangefilterparams.gt.md) | number | string | | -| [gte](./kibana-plugin-plugins-data-public.rangefilterparams.gte.md) | number | string | | -| [lt](./kibana-plugin-plugins-data-public.rangefilterparams.lt.md) | number | string | | -| [lte](./kibana-plugin-plugins-data-public.rangefilterparams.lte.md) | number | string | | -| [to](./kibana-plugin-plugins-data-public.rangefilterparams.to.md) | number | string | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.to.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.to.md deleted file mode 100644 index c9d0069fb75f5..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.rangefilterparams.to.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [RangeFilterParams](./kibana-plugin-plugins-data-public.rangefilterparams.md) > [to](./kibana-plugin-plugins-data-public.rangefilterparams.to.md) - -## RangeFilterParams.to property - -Signature: - -```typescript -to?: number | string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md index 981d956a9e89b..22dc6fa9f627b 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.md @@ -25,7 +25,7 @@ export interface SearchSourceFields | [highlightAll](./kibana-plugin-plugins-data-public.searchsourcefields.highlightall.md) | boolean | | | [index](./kibana-plugin-plugins-data-public.searchsourcefields.index.md) | IndexPattern | | | [parent](./kibana-plugin-plugins-data-public.searchsourcefields.parent.md) | SearchSourceFields | | -| [query](./kibana-plugin-plugins-data-public.searchsourcefields.query.md) | Query | [Query](./kibana-plugin-plugins-data-public.query.md) | +| [query](./kibana-plugin-plugins-data-public.searchsourcefields.query.md) | Query | | | [searchAfter](./kibana-plugin-plugins-data-public.searchsourcefields.searchafter.md) | EsQuerySearchAfter | | | [size](./kibana-plugin-plugins-data-public.searchsourcefields.size.md) | number | | | [sort](./kibana-plugin-plugins-data-public.searchsourcefields.sort.md) | EsQuerySortValue | EsQuerySortValue[] | [EsQuerySortValue](./kibana-plugin-plugins-data-public.esquerysortvalue.md) | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.query.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.query.md index 661ce94a06afb..78bf800c58c20 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.query.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsourcefields.query.md @@ -4,7 +4,6 @@ ## SearchSourceFields.query property -[Query](./kibana-plugin-plugins-data-public.query.md) Signature: diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md index 594afcf9ee0dd..9006b088993a1 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esfilters.md @@ -8,14 +8,14 @@ ```typescript esFilters: { - buildQueryFilter: (query: any, index: string, alias: string) => import("../common").QueryStringFilter; - buildCustomFilter: typeof buildCustomFilter; - buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("../common").Filter; - buildExistsFilter: (field: import("../common").IndexPatternFieldBase, indexPattern: import("../common").IndexPatternBase) => import("../common").ExistsFilter; - buildFilter: typeof buildFilter; - buildPhraseFilter: (field: import("../common").IndexPatternFieldBase, value: any, indexPattern: import("../common").IndexPatternBase) => import("../common").PhraseFilter; - buildPhrasesFilter: (field: import("../common").IndexPatternFieldBase, params: any[], indexPattern: import("../common").IndexPatternBase) => import("../common").PhrasesFilter; - buildRangeFilter: (field: import("../common").IndexPatternFieldBase, params: import("../common").RangeFilterParams, indexPattern: import("../common").IndexPatternBase, formattedValue?: string | undefined) => import("../common").RangeFilter; - isFilterDisabled: (filter: import("../common").Filter) => boolean; + buildQueryFilter: (query: any, index: string, alias: string) => import("@kbn/es-query").QueryStringFilter; + buildCustomFilter: typeof import("@kbn/es-query").buildCustomFilter; + buildEmptyFilter: (isPinned: boolean, index?: string | undefined) => import("@kbn/es-query").Filter; + buildExistsFilter: (field: import("@kbn/es-query").IndexPatternFieldBase, indexPattern: import("@kbn/es-query").IndexPatternBase) => import("@kbn/es-query").ExistsFilter; + buildFilter: typeof import("@kbn/es-query").buildFilter; + buildPhraseFilter: (field: import("@kbn/es-query").IndexPatternFieldBase, value: any, indexPattern: import("@kbn/es-query").IndexPatternBase) => import("@kbn/es-query").PhraseFilter; + buildPhrasesFilter: (field: import("@kbn/es-query").IndexPatternFieldBase, params: any[], indexPattern: import("@kbn/es-query").IndexPatternBase) => import("@kbn/es-query").PhrasesFilter; + buildRangeFilter: (field: import("@kbn/es-query").IndexPatternFieldBase, params: import("@kbn/es-query").RangeFilterParams, indexPattern: import("@kbn/es-query").IndexPatternBase, formattedValue?: string | undefined) => import("@kbn/es-query").RangeFilter; + isFilterDisabled: (filter: import("@kbn/es-query").Filter) => boolean; } ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md index fce25a899de8e..4989b2b5ad584 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.eskuery.md @@ -8,8 +8,8 @@ ```typescript esKuery: { - nodeTypes: import("../common/es_query/kuery/node_types").NodeTypes; - fromKueryExpression: (expression: any, parseOptions?: Partial) => import("../common").KueryNode; - toElasticsearchQuery: (node: import("../common").KueryNode, indexPattern?: import("../common").IndexPatternBase | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject; + nodeTypes: import("@kbn/es-query/target_types/kuery/node_types").NodeTypes; + fromKueryExpression: (expression: any, parseOptions?: Partial | undefined) => import("@kbn/es-query").KueryNode; + toElasticsearchQuery: (node: import("@kbn/es-query").KueryNode, indexPattern?: import("@kbn/es-query").IndexPatternBase | undefined, config?: Record | undefined, context?: Record | undefined) => import("@kbn/common-utils").JsonObject; } ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esquery.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esquery.md index 68507f3fb9b81..8dfea00081d89 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esquery.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esquery.md @@ -8,13 +8,13 @@ ```typescript esQuery: { - buildQueryFromFilters: (filters: import("../common").Filter[] | undefined, indexPattern: import("../common").IndexPatternBase | undefined, ignoreFilterIfFieldNotInIndex?: boolean) => { + buildQueryFromFilters: (filters: import("@kbn/es-query").Filter[] | undefined, indexPattern: import("@kbn/es-query").IndexPatternBase | undefined, ignoreFilterIfFieldNotInIndex?: boolean | undefined) => { must: never[]; - filter: import("../common").Filter[]; + filter: import("@kbn/es-query").Filter[]; should: never[]; - must_not: import("../common").Filter[]; + must_not: import("@kbn/es-query").Filter[]; }; getEsQueryConfig: typeof getEsQueryConfig; - buildEsQuery: typeof buildEsQuery; + buildEsQuery: typeof import("@kbn/es-query").buildEsQuery; } ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.allowleadingwildcards.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.allowleadingwildcards.md deleted file mode 100644 index ce8303d720747..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.allowleadingwildcards.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) > [allowLeadingWildcards](./kibana-plugin-plugins-data-server.esqueryconfig.allowleadingwildcards.md) - -## EsQueryConfig.allowLeadingWildcards property - -Signature: - -```typescript -allowLeadingWildcards: boolean; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.dateformattz.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.dateformattz.md deleted file mode 100644 index d3e86f19709f8..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.dateformattz.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) > [dateFormatTZ](./kibana-plugin-plugins-data-server.esqueryconfig.dateformattz.md) - -## EsQueryConfig.dateFormatTZ property - -Signature: - -```typescript -dateFormatTZ?: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.ignorefilteriffieldnotinindex.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.ignorefilteriffieldnotinindex.md deleted file mode 100644 index 93b3e8915c482..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.ignorefilteriffieldnotinindex.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) > [ignoreFilterIfFieldNotInIndex](./kibana-plugin-plugins-data-server.esqueryconfig.ignorefilteriffieldnotinindex.md) - -## EsQueryConfig.ignoreFilterIfFieldNotInIndex property - -Signature: - -```typescript -ignoreFilterIfFieldNotInIndex: boolean; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.md index 9ae604e07cabd..5c736f40cdbf4 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.md @@ -2,20 +2,15 @@ [Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) -## EsQueryConfig interface +## EsQueryConfig type + +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> Signature: ```typescript -export interface EsQueryConfig +declare type EsQueryConfig = oldEsQueryConfig; ``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [allowLeadingWildcards](./kibana-plugin-plugins-data-server.esqueryconfig.allowleadingwildcards.md) | boolean | | -| [dateFormatTZ](./kibana-plugin-plugins-data-server.esqueryconfig.dateformattz.md) | string | | -| [ignoreFilterIfFieldNotInIndex](./kibana-plugin-plugins-data-server.esqueryconfig.ignorefilteriffieldnotinindex.md) | boolean | | -| [queryStringOptions](./kibana-plugin-plugins-data-server.esqueryconfig.querystringoptions.md) | Record<string, any> | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.querystringoptions.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.querystringoptions.md deleted file mode 100644 index 437d36112d015..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.esqueryconfig.querystringoptions.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) > [queryStringOptions](./kibana-plugin-plugins-data-server.esqueryconfig.querystringoptions.md) - -## EsQueryConfig.queryStringOptions property - -Signature: - -```typescript -queryStringOptions: Record; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.filter.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.filter.md index 519bbaf8f9416..f46ff36277d93 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.filter.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.filter.md @@ -4,12 +4,13 @@ ## Filter type +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> + Signature: ```typescript -export declare type Filter = { - $state?: FilterState; - meta: FilterMeta; - query?: any; -}; +declare type Filter = oldFilter; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.gettime.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.gettime.md index 54e7cf92f500c..7f2267aff7049 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.gettime.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.gettime.md @@ -10,7 +10,7 @@ export declare function getTime(indexPattern: IIndexPattern | undefined, timeRange: TimeRange, options?: { forceNow?: Date; fieldName?: string; -}): import("../..").RangeFilter | undefined; +}): import("@kbn/es-query").RangeFilter | undefined; ``` ## Parameters @@ -23,5 +23,5 @@ export declare function getTime(indexPattern: IIndexPattern | undefined, timeRan Returns: -`import("../..").RangeFilter | undefined` +`import("@kbn/es-query").RangeFilter | undefined` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldsubtype.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldsubtype.md index 70140e51a7316..e8e872577b46b 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldsubtype.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldsubtype.md @@ -2,18 +2,15 @@ [Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldSubType](./kibana-plugin-plugins-data-server.ifieldsubtype.md) -## IFieldSubType interface +## IFieldSubType type + +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> Signature: ```typescript -export interface IFieldSubType +declare type IFieldSubType = oldIFieldSubType; ``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [multi](./kibana-plugin-plugins-data-server.ifieldsubtype.multi.md) | {
parent: string;
} | | -| [nested](./kibana-plugin-plugins-data-server.ifieldsubtype.nested.md) | {
path: string;
} | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldsubtype.multi.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldsubtype.multi.md deleted file mode 100644 index 31a3bc53d6343..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldsubtype.multi.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldSubType](./kibana-plugin-plugins-data-server.ifieldsubtype.md) > [multi](./kibana-plugin-plugins-data-server.ifieldsubtype.multi.md) - -## IFieldSubType.multi property - -Signature: - -```typescript -multi?: { - parent: string; - }; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldsubtype.nested.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldsubtype.nested.md deleted file mode 100644 index b53a4406aedc2..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.ifieldsubtype.nested.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IFieldSubType](./kibana-plugin-plugins-data-server.ifieldsubtype.md) > [nested](./kibana-plugin-plugins-data-server.ifieldsubtype.nested.md) - -## IFieldSubType.nested property - -Signature: - -```typescript -nested?: { - path: string; - }; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.executioncontext.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.executioncontext.md new file mode 100644 index 0000000000000..72e750e9be92a --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.executioncontext.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) > [executionContext](./kibana-plugin-plugins-data-server.isearchoptions.executioncontext.md) + +## ISearchOptions.executionContext property + +Signature: + +```typescript +executionContext?: KibanaExecutionContext; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md index cdb5664f96cdd..674cacc27a7e4 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md @@ -15,6 +15,7 @@ export interface ISearchOptions | Property | Type | Description | | --- | --- | --- | | [abortSignal](./kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | +| [executionContext](./kibana-plugin-plugins-data-server.isearchoptions.executioncontext.md) | KibanaExecutionContext | | | [indexPattern](./kibana-plugin-plugins-data-server.isearchoptions.indexpattern.md) | IndexPattern | Index pattern reference is used for better error messages | | [inspector](./kibana-plugin-plugins-data-server.isearchoptions.inspector.md) | IInspectorInfo | Inspector integration options | | [isRestore](./kibana-plugin-plugins-data-server.isearchoptions.isrestore.md) | boolean | Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kuerynode.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kuerynode.md index 3a258a5b98616..a5c14ee8627b1 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kuerynode.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kuerynode.md @@ -2,17 +2,15 @@ [Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [KueryNode](./kibana-plugin-plugins-data-server.kuerynode.md) -## KueryNode interface +## KueryNode type + +> Warning: This API is now obsolete. +> +> Please import from the package kbn/es-query directly. This import will be deprecated in v8.0.0. +> Signature: ```typescript -export interface KueryNode +declare type KueryNode = oldKueryNode; ``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [type](./kibana-plugin-plugins-data-server.kuerynode.type.md) | keyof NodeTypes | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kuerynode.type.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kuerynode.type.md deleted file mode 100644 index 192a2c05191c7..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.kuerynode.type.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [KueryNode](./kibana-plugin-plugins-data-server.kuerynode.md) > [type](./kibana-plugin-plugins-data-server.kuerynode.type.md) - -## KueryNode.type property - -Signature: - -```typescript -type: keyof NodeTypes; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index ab14abdd74e87..8e23f47976bd9 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -48,11 +48,9 @@ | [AggParamOption](./kibana-plugin-plugins-data-server.aggparamoption.md) | | | [AsyncSearchResponse](./kibana-plugin-plugins-data-server.asyncsearchresponse.md) | | | [AsyncSearchStatusResponse](./kibana-plugin-plugins-data-server.asyncsearchstatusresponse.md) | | -| [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) | | | [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) | | | [FieldFormatConfig](./kibana-plugin-plugins-data-server.fieldformatconfig.md) | | | [IEsSearchRequest](./kibana-plugin-plugins-data-server.iessearchrequest.md) | | -| [IFieldSubType](./kibana-plugin-plugins-data-server.ifieldsubtype.md) | | | [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) | | | [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) | Interface for an index pattern saved object | | [IScopedSearchClient](./kibana-plugin-plugins-data-server.iscopedsearchclient.md) | | @@ -61,7 +59,6 @@ | [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md) | | | [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) | | | [ISearchStrategy](./kibana-plugin-plugins-data-server.isearchstrategy.md) | Search strategy interface contains a search method that takes in a request and returns a promise that resolves to a response. | -| [KueryNode](./kibana-plugin-plugins-data-server.kuerynode.md) | | | [OptionedValueProp](./kibana-plugin-plugins-data-server.optionedvalueprop.md) | | | [PluginSetup](./kibana-plugin-plugins-data-server.pluginsetup.md) | | | [PluginStart](./kibana-plugin-plugins-data-server.pluginstart.md) | | @@ -97,6 +94,7 @@ | [AggGroupName](./kibana-plugin-plugins-data-server.agggroupname.md) | | | [AggParam](./kibana-plugin-plugins-data-server.aggparam.md) | | | [EsaggsExpressionFunctionDefinition](./kibana-plugin-plugins-data-server.esaggsexpressionfunctiondefinition.md) | | +| [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) | | | [ExecutionContextSearch](./kibana-plugin-plugins-data-server.executioncontextsearch.md) | | | [ExpressionFunctionKibana](./kibana-plugin-plugins-data-server.expressionfunctionkibana.md) | | | [ExpressionFunctionKibanaContext](./kibana-plugin-plugins-data-server.expressionfunctionkibanacontext.md) | | @@ -108,11 +106,12 @@ | [IEsSearchResponse](./kibana-plugin-plugins-data-server.iessearchresponse.md) | | | [IFieldFormatsRegistry](./kibana-plugin-plugins-data-server.ifieldformatsregistry.md) | | | [IFieldParamType](./kibana-plugin-plugins-data-server.ifieldparamtype.md) | | +| [IFieldSubType](./kibana-plugin-plugins-data-server.ifieldsubtype.md) | | | [IMetricAggType](./kibana-plugin-plugins-data-server.imetricaggtype.md) | | | [IndexPatternLoadExpressionFunctionDefinition](./kibana-plugin-plugins-data-server.indexpatternloadexpressionfunctiondefinition.md) | | | [KibanaContext](./kibana-plugin-plugins-data-server.kibanacontext.md) | | +| [KueryNode](./kibana-plugin-plugins-data-server.kuerynode.md) | | | [ParsedInterval](./kibana-plugin-plugins-data-server.parsedinterval.md) | | -| [Query](./kibana-plugin-plugins-data-server.query.md) | | | [SearchRequestHandlerContext](./kibana-plugin-plugins-data-server.searchrequesthandlercontext.md) | | | [TimeRange](./kibana-plugin-plugins-data-server.timerange.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.query.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.query.md deleted file mode 100644 index 6a7bdfe51f1c0..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.query.md +++ /dev/null @@ -1,16 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [Query](./kibana-plugin-plugins-data-server.query.md) - -## Query type - -Signature: - -```typescript -export declare type Query = { - query: string | { - [key: string]: any; - }; - language: string; -}; -``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.getexecutioncontext.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.getexecutioncontext.md new file mode 100644 index 0000000000000..bfe1925a21f68 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.getexecutioncontext.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [ExecutionContext](./kibana-plugin-plugins-expressions-public.executioncontext.md) > [getExecutionContext](./kibana-plugin-plugins-expressions-public.executioncontext.getexecutioncontext.md) + +## ExecutionContext.getExecutionContext property + +Contains the meta-data about the source of the expression. + +Signature: + +```typescript +getExecutionContext: () => IExecutionContextContainer | undefined; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.md index 1388e04c315e2..38165a1683316 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.executioncontext.md @@ -17,6 +17,7 @@ export interface ExecutionContextAbortSignal | Adds ability to abort current execution. | +| [getExecutionContext](./kibana-plugin-plugins-expressions-public.executioncontext.getexecutioncontext.md) | () => IExecutionContextContainer | undefined | Contains the meta-data about the source of the expression. | | [getKibanaRequest](./kibana-plugin-plugins-expressions-public.executioncontext.getkibanarequest.md) | () => KibanaRequest | Getter to retrieve the KibanaRequest object inside an expression function. Useful for functions which are running on the server and need to perform operations that are scoped to a specific user. | | [getSearchContext](./kibana-plugin-plugins-expressions-public.executioncontext.getsearchcontext.md) | () => ExecutionContextSearch | Get search context of the expression. | | [getSearchSessionId](./kibana-plugin-plugins-expressions-public.executioncontext.getsearchsessionid.md) | () => string | undefined | Search context in which expression should operate. | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.executioncontext.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.executioncontext.md new file mode 100644 index 0000000000000..504f5e2df7d6e --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.executioncontext.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [IExpressionLoaderParams](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md) > [executionContext](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.executioncontext.md) + +## IExpressionLoaderParams.executionContext property + +Signature: + +```typescript +executionContext?: IExecutionContextContainer; +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md index 69f9d380422b6..dcdbd663f84e4 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md @@ -19,14 +19,16 @@ export interface IExpressionLoaderParams | [customRenderers](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.customrenderers.md) | [] | | | [debug](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.debug.md) | boolean | | | [disableCaching](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.disablecaching.md) | boolean | | +| [executionContext](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.executioncontext.md) | IExecutionContextContainer | | | [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.hascompatibleactions.md) | ExpressionRenderHandlerParams['hasCompatibleActions'] | | | [inspectorAdapters](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.inspectoradapters.md) | Adapters | | | [onRenderError](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.onrendererror.md) | RenderErrorHandlerFnType | | -| [partial](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md) | boolean | | +| [partial](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md) | boolean | The flag to toggle on emitting partial results. By default, the partial results are disabled. | | [renderMode](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.rendermode.md) | RenderMode | | | [searchContext](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchcontext.md) | SerializableState | | | [searchSessionId](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.searchsessionid.md) | string | | | [syncColors](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.synccolors.md) | boolean | | +| [throttle](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.throttle.md) | number | Throttling of partial results in milliseconds. By default, throttling is disabled. | | [uiState](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.uistate.md) | unknown | | | [variables](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.variables.md) | Record<string, any> | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md index 84c42c3f59f26..8922b2d0f377e 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.partial.md @@ -4,6 +4,8 @@ ## IExpressionLoaderParams.partial property +The flag to toggle on emitting partial results. By default, the partial results are disabled. + Signature: ```typescript diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.throttle.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.throttle.md new file mode 100644 index 0000000000000..3383bce879776 --- /dev/null +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iexpressionloaderparams.throttle.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-public](./kibana-plugin-plugins-expressions-public.md) > [IExpressionLoaderParams](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.md) > [throttle](./kibana-plugin-plugins-expressions-public.iexpressionloaderparams.throttle.md) + +## IExpressionLoaderParams.throttle property + +Throttling of partial results in milliseconds. By default, throttling is disabled. + +Signature: + +```typescript +throttle?: number; +``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.getexecutioncontext.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.getexecutioncontext.md new file mode 100644 index 0000000000000..b4ceb8f96d698 --- /dev/null +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.getexecutioncontext.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-expressions-server](./kibana-plugin-plugins-expressions-server.md) > [ExecutionContext](./kibana-plugin-plugins-expressions-server.executioncontext.md) > [getExecutionContext](./kibana-plugin-plugins-expressions-server.executioncontext.getexecutioncontext.md) + +## ExecutionContext.getExecutionContext property + +Contains the meta-data about the source of the expression. + +Signature: + +```typescript +getExecutionContext: () => IExecutionContextContainer | undefined; +``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.md index 8503f81ad7d25..3b308ca46ab0a 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.executioncontext.md @@ -17,6 +17,7 @@ export interface ExecutionContextAbortSignal | Adds ability to abort current execution. | +| [getExecutionContext](./kibana-plugin-plugins-expressions-server.executioncontext.getexecutioncontext.md) | () => IExecutionContextContainer | undefined | Contains the meta-data about the source of the expression. | | [getKibanaRequest](./kibana-plugin-plugins-expressions-server.executioncontext.getkibanarequest.md) | () => KibanaRequest | Getter to retrieve the KibanaRequest object inside an expression function. Useful for functions which are running on the server and need to perform operations that are scoped to a specific user. | | [getSearchContext](./kibana-plugin-plugins-expressions-server.executioncontext.getsearchcontext.md) | () => ExecutionContextSearch | Get search context of the expression. | | [getSearchSessionId](./kibana-plugin-plugins-expressions-server.executioncontext.getsearchsessionid.md) | () => string | undefined | Search context in which expression should operate. | diff --git a/docs/discover/images/add-field-to-pattern.png b/docs/discover/images/add-field-to-pattern.png index 04f564ab89857..9a206f5f1bd1d 100644 Binary files a/docs/discover/images/add-field-to-pattern.png and b/docs/discover/images/add-field-to-pattern.png differ diff --git a/docs/discover/images/customer.png b/docs/discover/images/customer.png index f2042080f0a37..4c1ff2f2fddbd 100644 Binary files a/docs/discover/images/customer.png and b/docs/discover/images/customer.png differ diff --git a/docs/discover/images/discover-add-filter.png b/docs/discover/images/discover-add-filter.png index 4a5f6d1a9fa99..5ee2f01960248 100644 Binary files a/docs/discover/images/discover-add-filter.png and b/docs/discover/images/discover-add-filter.png differ diff --git a/docs/discover/images/discover-context-filters-inactive.png b/docs/discover/images/discover-context-filters-inactive.png index c53a8cedff336..26609392472fb 100644 Binary files a/docs/discover/images/discover-context-filters-inactive.png and b/docs/discover/images/discover-context-filters-inactive.png differ diff --git a/docs/discover/images/discover-context-load-newer-documents.png b/docs/discover/images/discover-context-load-newer-documents.png index 65d168f5ca4af..237dc4eb2683f 100644 Binary files a/docs/discover/images/discover-context-load-newer-documents.png and b/docs/discover/images/discover-context-load-newer-documents.png differ diff --git a/docs/discover/images/discover-context.png b/docs/discover/images/discover-context.png index 8ce68047e0d76..f73cba203f8f1 100644 Binary files a/docs/discover/images/discover-context.png and b/docs/discover/images/discover-context.png differ diff --git a/docs/discover/images/discover-from-visualize.png b/docs/discover/images/discover-from-visualize.png index 07d34b8b49018..42d46e6cbd5b5 100644 Binary files a/docs/discover/images/discover-from-visualize.png and b/docs/discover/images/discover-from-visualize.png differ diff --git a/docs/discover/images/discover-index-pattern.png b/docs/discover/images/discover-index-pattern.png index b9f96f1f0cedd..a7ff9bf834e40 100644 Binary files a/docs/discover/images/discover-index-pattern.png and b/docs/discover/images/discover-index-pattern.png differ diff --git a/docs/discover/images/discover-save-saved-search.png b/docs/discover/images/discover-save-saved-search.png index bf22408729b08..bd13f800b7f33 100644 Binary files a/docs/discover/images/discover-save-saved-search.png and b/docs/discover/images/discover-save-saved-search.png differ diff --git a/docs/discover/images/discover-search-field.png b/docs/discover/images/discover-search-field.png index 9acbf86067fb4..f04abfa566fce 100644 Binary files a/docs/discover/images/discover-search-field.png and b/docs/discover/images/discover-search-field.png differ diff --git a/docs/discover/images/discover-search-for-relevance.png b/docs/discover/images/discover-search-for-relevance.png index e42ed2416f79e..64cfd87b7aac2 100644 Binary files a/docs/discover/images/discover-search-for-relevance.png and b/docs/discover/images/discover-search-for-relevance.png differ diff --git a/docs/discover/images/discover-sidebar-available-fields.png b/docs/discover/images/discover-sidebar-available-fields.png index b34119ca4a039..e330036524ae7 100644 Binary files a/docs/discover/images/discover-sidebar-available-fields.png and b/docs/discover/images/discover-sidebar-available-fields.png differ diff --git a/docs/discover/images/discover-view-single-document.png b/docs/discover/images/discover-view-single-document.png index d803acc49ce24..18ff5f67eb5d8 100644 Binary files a/docs/discover/images/discover-view-single-document.png and b/docs/discover/images/discover-view-single-document.png differ diff --git a/docs/discover/images/discover-visualize.png b/docs/discover/images/discover-visualize.png index 242aa16709a9f..5aca5a210139d 100644 Binary files a/docs/discover/images/discover-visualize.png and b/docs/discover/images/discover-visualize.png differ diff --git a/docs/discover/images/document-table-expanded.png b/docs/discover/images/document-table-expanded.png index 38ea0987b120a..ebbd2e607eb5a 100644 Binary files a/docs/discover/images/document-table-expanded.png and b/docs/discover/images/document-table-expanded.png differ diff --git a/docs/discover/images/document-table.png b/docs/discover/images/document-table.png index 097feb4dc9034..5b5dbc08d6e64 100644 Binary files a/docs/discover/images/document-table.png and b/docs/discover/images/document-table.png differ diff --git a/docs/discover/images/find-manufacturer-field.png b/docs/discover/images/find-manufacturer-field.png index f02672f531edf..ce78aab933362 100644 Binary files a/docs/discover/images/find-manufacturer-field.png and b/docs/discover/images/find-manufacturer-field.png differ diff --git a/docs/discover/images/hello-field.png b/docs/discover/images/hello-field.png index fe4cfbb41fdc2..5c6348d4e90fe 100644 Binary files a/docs/discover/images/hello-field.png and b/docs/discover/images/hello-field.png differ diff --git a/docs/discover/images/kql-autocomplete.png b/docs/discover/images/kql-autocomplete.png index 60e2290c0bad7..599ff2aa2e65a 100644 Binary files a/docs/discover/images/kql-autocomplete.png and b/docs/discover/images/kql-autocomplete.png differ diff --git a/docs/discover/images/saved-search.png b/docs/discover/images/saved-search.png index cf0a89fe1a79f..10bc53d37785b 100644 Binary files a/docs/discover/images/saved-search.png and b/docs/discover/images/saved-search.png differ diff --git a/docs/discover/images/search-session-awhile.png b/docs/discover/images/search-session-awhile.png index 0e93fe16b427d..88a6f34e10e5c 100644 Binary files a/docs/discover/images/search-session-awhile.png and b/docs/discover/images/search-session-awhile.png differ diff --git a/docs/discover/images/search-session.png b/docs/discover/images/search-session.png index c71ca6f753497..ded51feb294c1 100644 Binary files a/docs/discover/images/search-session.png and b/docs/discover/images/search-session.png differ diff --git a/docs/discover/images/search-sessions-menu.png b/docs/discover/images/search-sessions-menu.png index 508142f6e17b6..5ce8c680e90b9 100644 Binary files a/docs/discover/images/search-sessions-menu.png and b/docs/discover/images/search-sessions-menu.png differ diff --git a/docs/discover/save-search.asciidoc b/docs/discover/save-search.asciidoc index b59f14180b1ff..a9fab17bdfda2 100644 --- a/docs/discover/save-search.asciidoc +++ b/docs/discover/save-search.asciidoc @@ -6,10 +6,6 @@ that you've created in *Discover*. Saved searches are good for adding search results to a dashboard, and can also serve as a foundation for building visualizations. -[role="screenshot"] -image::discover/images/saved-search.png[Example of Discover's save search option] - - A saved search stores the query text, filters, and current view of *Discover*—the columns selected in the document table, the sort order, and the index pattern. @@ -30,6 +26,10 @@ image::discover/images/read-only-badge.png[Example of Discover's read only acces . Once you've created a search worth saving, click *Save* in the toolbar. . Enter a name for the search and click *Save*. ++ +[role="screenshot"] +image:images/discover-save-saved-search.png[Save saved search in Discover, width=50%] + . To reload your search results in *Discover*, click *Open* in the toolbar, and select the saved search. + If the saved search is associated with a different index pattern than is currently diff --git a/docs/discover/search-sessions.asciidoc b/docs/discover/search-sessions.asciidoc index 652583db785ad..d71a1bb4a99d3 100644 --- a/docs/discover/search-sessions.asciidoc +++ b/docs/discover/search-sessions.asciidoc @@ -46,7 +46,7 @@ and then click *Save session*. Once you save a search session, you can start a n navigate to a different application, or close the browser. + [role="screenshot"] -image::images/search-session-awhile.png[Search Session indicator displaying the current state of the search, which you can click to stop or save a running Search Session ] +image::images/search-session-awhile.png[Search Session indicator displaying the current state of the search, which you can click to stop or save a running Search Session, width=75% ] . To view your saved searches, open the main menu, and then click *Stack Management > Search Sessions*. You can also open this view from the search sessions popup for a saved or completed session. diff --git a/docs/discover/view-document.asciidoc b/docs/discover/view-document.asciidoc index b471e238c1a0f..26029fe58029f 100644 --- a/docs/discover/view-document.asciidoc +++ b/docs/discover/view-document.asciidoc @@ -50,7 +50,7 @@ image::images/discover-context-filters-inactive.png[Filter in context view] By default, five documents are added with each click. + [role="screenshot"] -image::images/discover-context-load-newer-documents.png[Load button and the number of documents to load] +image::images/discover-context-load-newer-documents.png[Load button and the number of documents to load, width=50%] . To configure the number of documents to display and the number of documents to load with each button click, go to *Stack Management > Advanced Settings* and edit the <>. diff --git a/docs/fleet/fleet.asciidoc b/docs/fleet/fleet.asciidoc index 4777800ce5d57..abfc14b55e020 100644 --- a/docs/fleet/fleet.asciidoc +++ b/docs/fleet/fleet.asciidoc @@ -3,8 +3,6 @@ [[fleet]] = {fleet} -beta[] - {fleet} in {kib} enables you to add and manage integrations for popular services and platforms, as well as manage {elastic-agent} installations in standalone or {fleet} mode. diff --git a/docs/index-extra-title-page.html b/docs/index-extra-title-page.html index 7bcbc9f075124..2621848ebea8a 100644 --- a/docs/index-extra-title-page.html +++ b/docs/index-extra-title-page.html @@ -1,37 +1,151 @@
-

From creating beautiful visualizations to managing the Elastic Stack, learn how Kibana helps you get the most of your data.

-

Watch our videos

- - - - - - - - - -

New to Kibana?

Popular topics

- - -
-

All topics

+

+ From creating beautiful visualizations to managing the Elastic Stack, learn how Kibana helps you + get the most of your data. +

+

+ How-to videos +

+ + + + + + + + + + + + + + + + + + +

New to Kibana?

Popular topics

+ + + +

Analyze your data

Manage all things Stack

+ + + +
+ +

All topics

diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 66a23ee189ae1..32736522d583a 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -480,10 +480,11 @@ of buckets to try to represent. [horizontal] [[visualization-visualize-chartslibrary]]`visualization:visualize:legacyChartsLibrary`:: -Enables the legacy charts library for aggregation-based area, line, and bar charts in *Visualize*. +**The legacy XY charts are deprecated and will not be supported as of 7.16.** +The visualize editor uses a new XY charts library with improved performance, color palettes, fill capacity, and more. Enable this option if you prefer to use the legacy charts library. [[visualization-visualize-pieChartslibrary]]`visualization:visualize:legacyPieChartsLibrary`:: -Enables the legacy charts library for aggregation-based pie charts in *Visualize*. +The visualize editor uses new pie charts with improved performance, color palettes, label positioning, and more. Enable this option if you prefer to use to the legacy charts library. [[visualization-colormapping]]`visualization:colorMapping`:: **This setting is deprecated and will not be supported as of 8.0.** diff --git a/docs/management/images/management-license.png b/docs/management/images/management-license.png index 8df9402939b2e..66b9cf2bca18c 100644 Binary files a/docs/management/images/management-license.png and b/docs/management/images/management-license.png differ diff --git a/docs/management/images/tags/bulk-assign-selection.png b/docs/management/images/tags/bulk-assign-selection.png deleted file mode 100644 index 1c8687226b51f..0000000000000 Binary files a/docs/management/images/tags/bulk-assign-selection.png and /dev/null differ diff --git a/docs/management/images/tags/create-tag.png b/docs/management/images/tags/create-tag.png deleted file mode 100644 index a88e754457b9f..0000000000000 Binary files a/docs/management/images/tags/create-tag.png and /dev/null differ diff --git a/docs/management/images/tags/manage-assignments-flyout.png b/docs/management/images/tags/manage-assignments-flyout.png index a4e0b7a49d96a..92a78be5f0402 100644 Binary files a/docs/management/images/tags/manage-assignments-flyout.png and b/docs/management/images/tags/manage-assignments-flyout.png differ diff --git a/docs/management/images/tags/tag-management-section.png b/docs/management/images/tags/tag-management-section.png index 4aae3ea067820..34addfe4d326f 100644 Binary files a/docs/management/images/tags/tag-management-section.png and b/docs/management/images/tags/tag-management-section.png differ diff --git a/docs/management/index-patterns/images/create-index-pattern.png b/docs/management/index-patterns/images/create-index-pattern.png index 8d8efc7a6213e..67a2a2cb299d2 100644 Binary files a/docs/management/index-patterns/images/create-index-pattern.png and b/docs/management/index-patterns/images/create-index-pattern.png differ diff --git a/docs/management/managing-tags.asciidoc b/docs/management/managing-tags.asciidoc index 88fdef66a7418..a0b3dce7f4b27 100644 --- a/docs/management/managing-tags.asciidoc +++ b/docs/management/managing-tags.asciidoc @@ -2,28 +2,26 @@ [[managing-tags]] == Tags -Tags enable you to categorize your saved objects. You can then easily filter for related objects based on shared tags. - -To begin, open the main menu, click *Stack Management*, then click *Tags*. +Tags enable you to categorize your saved objects. +You can then filter for related objects based on shared tags. [role="screenshot"] -image::images/tags/tag-management-section.png[Tags management section] +image::images/tags/tag-management-section.png[Tags management] [float] === Required permissions -Access to *Tags* requires the `Tag Management` {kib} privilege. To add the privilege, open the menu, -click *Stack Management*, then click *Roles*. - -In addition: +To create tags, you must meet the minimum requirements. +* Access to *Tags* requires the `Tag Management` Kibana privilege. To add the privilege, open the main menu, +and then click *Stack Management > Roles*. * The `read` privilege allows you to assign tags to the saved objects for which you have write permission. * The `write` privilege enables you to create, edit, and delete tags. - NOTE: Having the `Tag Management` {kib} privilege is not required to -view tags assigned on objects the user has `read` access to, or to filter objects by tags -in {kib} applications or from the navigational search. +view tags assigned on objects you have `read` access to, or to filter objects by tags +from the global search. + [float] [[settings-create-tag]] @@ -31,10 +29,9 @@ in {kib} applications or from the navigational search. Create a tag to assign to your saved objects. +. Open the main menu, and then click *Stack Management > Tags*. . Click *Create tag*. -+ -[role="screenshot"] -image::images/tags/create-tag.png[Tag creation popin] + . Enter a name and select a color for the new tag. + The name cannot be longer than 50 characters. @@ -42,33 +39,32 @@ The name cannot be longer than 50 characters. [float] [[settings-assign-tag]] -=== Assign a tag to saved objects +=== Assign a tag to an object -Assign or remove tags to one or more saved objects. You must have `write` permission +To assign and remove tags from saved objects, you must have `write` permission on the objects to which you assign the tags. -. Click the action (...) icon in the tag row, and then select the *Manage assignments* action. +. In the *Tags* view, find the tag you want to assign. +. Click the action menu (...) in the tag row, +and then select the *Manage assignments* action. + +. Select the objects to which you want to assign or remove tags. + [role="screenshot"] image::images/tags/manage-assignments-flyout.png[Assign flyout] -. Select the objects to which you want to assign or remove tags. -. Click on *Save tag assignments*. -TIP: To assign multiple tags to objects at once, select their checkboxes -and then select *Manage tag assignments* from the *selected tags* menu. +. Click *Save tag assignments*. -[role="screenshot"] -image::images/tags/bulk-assign-selection.png[Bulk assign tags] +TIP: To assign, delete, or clear multiple tags at once, +select their checkboxes in the *Tags* view, and then select +the desired action from the *selected tags* menu. [float] [[settings-delete-tag]] === Delete a tag -Delete a tag and remove it from any saved objects. +When you delete a tag, you remove it from all saved objects that use it. -. Click the action (...) icon in the tag row, and then select the *Delete* action. +. Click the action menu (...) in the tag row, and then select the *Delete* action. . Click *Delete tag*. - -TIP: To delete multiple tags at once, select their checkboxes in the list view, -and then select *Delete* action from the *selected tags* menu. \ No newline at end of file diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index eb3130ba6fdb5..bcc00951de3c1 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -316,4 +316,29 @@ This content has moved. Refer to <> and <>. \ No newline at end of file +This content has moved. Refer to <>. + +[role="exclude",id="graph-getting-started"] +== Create a graph + +This content has moved. Refer to <>. + +[role="exclude",id="graph-limitations"] +== Graph limitations + +This content has moved. Refer to <>. + +[role="exclude",id="profiler-getting-started"] +== Getting start with Search Profiler + +This content has moved. Refer to <>. + +[role="exclude",id="profiler-complicated"] +== Profiling a more complicated querying + +This content has moved. Refer to <>. + +[role="exclude",id="profiler-render"] +== Rendering pre-captured profiler JSON + +This content has moved. Refer to <>. diff --git a/docs/settings/alert-action-settings.asciidoc b/docs/settings/alert-action-settings.asciidoc index d1d283ca60fbb..a523c2cb005a2 100644 --- a/docs/settings/alert-action-settings.asciidoc +++ b/docs/settings/alert-action-settings.asciidoc @@ -207,4 +207,10 @@ Use `full` to perform hostname verification, `certificate` to skip hostname veri [[alert-settings]] ==== Alerting settings -You do not need to configure any additional settings to use alerting in {kib}. +[cols="2*<"] +|=== + +| `xpack.alerting.maxEphemeralActionsPerAlert` + | Sets the number of actions that will be executed ephemerally. To use this, enable ephemeral tasks in task manager first with <> + +|=== \ No newline at end of file diff --git a/docs/settings/apm-settings.asciidoc b/docs/settings/apm-settings.asciidoc index dfb239f0e26c0..67de6f8d24960 100644 --- a/docs/settings/apm-settings.asciidoc +++ b/docs/settings/apm-settings.asciidoc @@ -65,7 +65,7 @@ Changing these settings may disable features of the APM App. | Index name where Observability annotations are stored. Defaults to `observability-annotations`. | `xpack.apm.searchAggregatedTransactions` - | experimental[] Enables Transaction histogram metrics. Defaults to `false`. When `true`, additional configuration in APM Server is required. + | experimental[] Enables Transaction histogram metrics. Defaults to `auto` and the UI will use metric indices over transaction indices for transactions if aggregated transactions are found. When set to `always`, additional configuration in APM Server is required. When set to `never`, aggregated transactions are not used. See {apm-server-ref-v}/transaction-metrics.html[Configure transaction metrics] for more information. | `apm_oss.indexPattern` {ess-icon} diff --git a/docs/settings/fleet-settings.asciidoc b/docs/settings/fleet-settings.asciidoc index 134d9de3f49d8..cb80165e70990 100644 --- a/docs/settings/fleet-settings.asciidoc +++ b/docs/settings/fleet-settings.asciidoc @@ -5,8 +5,6 @@ {fleet} settings ++++ -experimental[] - You can configure `xpack.fleet` settings in your `kibana.yml`. By default, {fleet} is enabled. To use {fleet}, you also need to configure {kib} and {es} hosts. diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index bd714c62ff543..455ee76deefe3 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -271,7 +271,7 @@ You can configure the following settings in the `kibana.yml` file. |[[xpack-session-idleTimeout]] `xpack.security.session.idleTimeout` {ess-icon} | Ensures that user sessions will expire after a period of inactivity. This and <> are both -highly recommended. You can also specify this setting for <>. If this is _not_ set or set to `0`, then sessions will never expire due to inactivity. By default, this setting is not set. +highly recommended. You can also specify this setting for <>. If this is set to `0`, then sessions will never expire due to inactivity. By default, this value is 1 hour. 2+a| [TIP] @@ -281,8 +281,8 @@ Use a string of `[ms\|s\|m\|h\|d\|w\|M\|Y]` (e.g. '20m', '24h', '7d', '1w |[[xpack-session-lifespan]] `xpack.security.session.lifespan` {ess-icon} | Ensures that user sessions will expire after the defined time period. This behavior is also known as an "absolute timeout". If -this is _not_ set or set to `0`, user sessions could stay active indefinitely. This and <> are both highly -recommended. You can also specify this setting for <>. By default, this setting is not set. +this is set to `0`, user sessions could stay active indefinitely. This and <> are both highly +recommended. You can also specify this setting for <>. By default, this value is 30 days. 2+a| [TIP] @@ -337,24 +337,12 @@ For more details and a reference of audit events, refer to <>, specify where you want to write the audit events using `xpack.security.audit.appender`. - -[cols="2*<,*50"] -|====== -| `xpack.security.audit.appender` -| Optional. Specifies where audit logs should be written to and how they should be formatted. +| Set to `true` _and_ configure an appender with `xpack.security.audit.appender` to enable ECS audit logging`. *Default:* `false` 2+a| For example: - [source,yaml] ---------------------------------------- +xpack.security.audit.enabled: true xpack.security.audit.appender: type: rolling-file fileName: ./audit.log @@ -370,7 +358,31 @@ xpack.security.audit.appender: <1> Rotates log files every 24 hours. <2> Keeps maximum of 10 log files before deleting older ones. -| `xpack.security.audit.appender.type` +[NOTE] +============ +{ess} does not support custom log file policies. To enable audit logging on {ess} only specify: + +[source,yaml] +---------------------------------------- +xpack.security.audit.enabled: true +xpack.security.audit.appender.type: rolling-file +---------------------------------------- +============ + +[NOTE] +============ +deprecated:[7.15.0,"In 8.0 and later, the legacy audit logger will be removed, and this setting will enable the ECS audit logger with a default appender."] To enable the legacy audit logger only specify: + +[source,yaml] +---------------------------------------- +xpack.security.audit.enabled: true +---------------------------------------- +============ + +| `xpack.security.audit.appender` {ess-icon} +| Optional. Specifies where audit logs should be written to and how they should be formatted. + +| `xpack.security.audit.appender.type` {ess-icon} | Required. Specifies where audit logs should be written to. Allowed values are `console`, `file`, or `rolling-file`. Refer to <> and <> for appender specific settings. diff --git a/docs/settings/task-manager-settings.asciidoc b/docs/settings/task-manager-settings.asciidoc index 7f4dbb3a96e6b..fa89b7780e475 100644 --- a/docs/settings/task-manager-settings.asciidoc +++ b/docs/settings/task-manager-settings.asciidoc @@ -37,6 +37,14 @@ Task Manager runs background tasks by polling for work on an interval. You can `monitored_stats_health_verbose_log.` `warn_delayed_task_start_in_seconds` | The amount of seconds we allow a task to delay before printing a warning server log. Defaults to 60. + + | `xpack.task_manager.ephemeral_tasks.enabled` + | Enables an experimental feature that executes a limited (and configurable) number of actions in the same task as the alert which triggered them. + These action tasks will reduce the latency of the time it takes an action to run after it's triggered, but are not persisted as SavedObjects. + These non-persisted action tasks have a risk that they won't be run at all if the Kibana instance running them exits unexpectedly. Defaults to false. + + | `xpack.task_manager.ephemeral_tasks.request_capacity` + | Sets the size of the ephemeral queue defined above. Defaults to 10. |=== [float] diff --git a/docs/setup/connect-to-elasticsearch.asciidoc b/docs/setup/connect-to-elasticsearch.asciidoc index 5e18d934863aa..880c98902983f 100644 --- a/docs/setup/connect-to-elasticsearch.asciidoc +++ b/docs/setup/connect-to-elasticsearch.asciidoc @@ -27,7 +27,7 @@ image::images/add-data-tutorials.png[Add Data tutorials] [discrete] === Add Elastic Agent -beta[] *Elastic Agent* is a sneak peek at the next generation of +*Elastic Agent* is the next generation of data integration modules, offering a centralized way to set up your integrations. With *Fleet*, you can add diff --git a/docs/setup/images/add-data-fleet.png b/docs/setup/images/add-data-fleet.png index b6d49cfaf8d3a..279c2f5436c73 100644 Binary files a/docs/setup/images/add-data-fleet.png and b/docs/setup/images/add-data-fleet.png differ diff --git a/docs/setup/images/add-data-fv.png b/docs/setup/images/add-data-fv.png index 7e253cdd0229d..062d5ed88b3ca 100644 Binary files a/docs/setup/images/add-data-fv.png and b/docs/setup/images/add-data-fv.png differ diff --git a/docs/setup/images/add-data-home.png b/docs/setup/images/add-data-home.png index 3a844b1c40de9..b346ca190055a 100644 Binary files a/docs/setup/images/add-data-home.png and b/docs/setup/images/add-data-home.png differ diff --git a/docs/setup/images/add-data-tutorials.png b/docs/setup/images/add-data-tutorials.png index 782b44e383772..2827d7e3367f8 100644 Binary files a/docs/setup/images/add-data-tutorials.png and b/docs/setup/images/add-data-tutorials.png differ diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 15abd0fa4ad96..a0611b79aae4c 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -782,13 +782,11 @@ out through *Advanced Settings*. *Default: `true`* | Set this value to true to allow Vega to use any URL to access external data sources and images. When false, Vega can only get data from {es}. *Default: `false`* -a| -`xpack.discoverEnhanced.actions.` +|[[settings-explore-data-in-context]] `xpack.discoverEnhanced.actions.` `exploreDataInContextMenu.enabled` | Enables the *Explore underlying data* option that allows you to open *Discover* from a dashboard panel and view the panel data. *Default: `false`* -a| -`xpack.discoverEnhanced.actions.` +|[[settings-explore-data-in-chart]] `xpack.discoverEnhanced.actions.` `exploreDataInChart.enabled` | Enables you to view the underlying documents in a data series from a dashboard panel. *Default: `false`* diff --git a/docs/spaces/images/edit-space-feature-visibility.png b/docs/spaces/images/edit-space-feature-visibility.png index 0132337f4a7c6..f1852d3cc03b5 100644 Binary files a/docs/spaces/images/edit-space-feature-visibility.png and b/docs/spaces/images/edit-space-feature-visibility.png differ diff --git a/docs/spaces/images/edit-space.png b/docs/spaces/images/edit-space.png index c78e0d14b0d4c..9785dd9e77aba 100644 Binary files a/docs/spaces/images/edit-space.png and b/docs/spaces/images/edit-space.png differ diff --git a/docs/user/dashboard/aggregation-based.asciidoc b/docs/user/dashboard/aggregation-based.asciidoc index cb102b73f93b4..7559cd3c1b1b8 100644 --- a/docs/user/dashboard/aggregation-based.asciidoc +++ b/docs/user/dashboard/aggregation-based.asciidoc @@ -178,4 +178,3 @@ image:images/bar-chart-tutorial-2.png[Bar chart with sample logs data] - diff --git a/docs/user/dashboard/dashboard.asciidoc b/docs/user/dashboard/dashboard.asciidoc index 516f4c66d47bb..c251ce7307968 100644 --- a/docs/user/dashboard/dashboard.asciidoc +++ b/docs/user/dashboard/dashboard.asciidoc @@ -265,33 +265,19 @@ Copy panels from one dashboard to another dashboard. [[explore-the-underlying-documents]] == Explore the underlying documents -To gain insight to the data, open the underlying panel or data series documents in *Discover*. The panel documents that you open in *Discover* have the same time range and filters as the source panel. +You can add additional interactions that allow you to open *Discover* from dashboard panels. To use the interactions, the panel must use only one index pattern. -[float] -[[explore-underlying-panel-documents]] -=== Explore the underlying panel documents - -When your visualization panel contains a single index pattern, you can open the panel documents in *Discover*. - -. Open the panel menu. - -. Click *Explore underlying data*. +Panel interaction:: Opens the data in *Discover* with the current dashboard filters, but does not take the filters +saved with the panel. + -[role="screenshot"] -image::images/explore_data_context_menu.png[Explore underlying data from panel context menu] +To enable panel interactions, refere to <>. -[float] -[[explore-underlying-data-series-documents]] -=== Explore the underlying data series documents - -To gain insight to a data series, open the documents in *Discover*. - -. Click the data series in the panel that you want to view. - -. Select *Explore underlying data*. +Series interaction:: +Opens the series data in *Discover* from inside the panel. + -[role="screenshot"] -image::images/explore_data_in_chart.png[Explore underlying data from chart] +To enable series interactions, refer to <>. + +NOTE: In {kib} 7.13 and earlier, the panel interaction was enabled by default. [float] [[download-csv]] diff --git a/docs/user/dashboard/images/explore_data_context_menu.png b/docs/user/dashboard/images/explore_data_context_menu.png deleted file mode 100644 index 5742991030c89..0000000000000 Binary files a/docs/user/dashboard/images/explore_data_context_menu.png and /dev/null differ diff --git a/docs/user/dashboard/images/explore_data_in_chart.png b/docs/user/dashboard/images/explore_data_in_chart.png deleted file mode 100644 index 05d4f5fac9b2f..0000000000000 Binary files a/docs/user/dashboard/images/explore_data_in_chart.png and /dev/null differ diff --git a/docs/user/graph/configuring-graph.asciidoc b/docs/user/graph/configuring-graph.asciidoc index 4eb8939b004ba..968e08db33d49 100644 --- a/docs/user/graph/configuring-graph.asciidoc +++ b/docs/user/graph/configuring-graph.asciidoc @@ -55,7 +55,7 @@ is displayed. For more information on granting access to Kibana, see <>. [role="screenshot"] -image::user/graph/images/graph-read-only-badge.png[Example of Graph's read only access indicator in Kibana's header] +image::user/graph/images/graph-read-only-badge.png[Example of Graph's read only access indicator in Kibana's header, width=50%] [discrete] [[disable-drill-down]] diff --git a/docs/user/graph/images/graph-control-bar.png b/docs/user/graph/images/graph-control-bar.png new file mode 100644 index 0000000000000..6dcf0d693d4c2 Binary files /dev/null and b/docs/user/graph/images/graph-control-bar.png differ diff --git a/docs/user/graph/images/graph-link-summary.png b/docs/user/graph/images/graph-link-summary.png deleted file mode 100644 index a3dfdc0f79d96..0000000000000 Binary files a/docs/user/graph/images/graph-link-summary.png and /dev/null differ diff --git a/docs/user/graph/images/graph-menu.png b/docs/user/graph/images/graph-menu.png new file mode 100644 index 0000000000000..5a46bd595baf8 Binary files /dev/null and b/docs/user/graph/images/graph-menu.png differ diff --git a/docs/user/graph/images/graph-url-connections.png b/docs/user/graph/images/graph-url-connections.png index 34b57d489b048..18b1b0354ee51 100644 Binary files a/docs/user/graph/images/graph-url-connections.png and b/docs/user/graph/images/graph-url-connections.png differ diff --git a/docs/user/graph/index.asciidoc b/docs/user/graph/index.asciidoc index 40c75c868e237..5e7b689b8d8f1 100644 --- a/docs/user/graph/index.asciidoc +++ b/docs/user/graph/index.asciidoc @@ -5,7 +5,7 @@ [partintro] -- The {graph-features} enable you to discover how items in an -Elasticsearch index are related. You can explore the connections between +{es} index are related. You can explore the connections between indexed terms and see which connections are the most meaningful. This can be useful in a variety of applications, from fraud detection to recommendation engines. @@ -15,15 +15,15 @@ that hackers are targeting so you can harden your website. Or, you might provide graph-based personalized recommendations to your e-commerce customers. The {graph-features} provide a simple, yet powerful {ref}/graph-explore-api.html[graph exploration API], -and an interactive graph visualization tool for Kibana. Both work out of the -box with existing Elasticsearch indices--you don't need to store any +and an interactive graph visualization app for {kib}. Both work out of the +box with existing {es} indices—you don't need to store any additional data to use these features. [discrete] [[how-graph-works]] == How Graph works The graph API provides an alternative way to extract and summarize information -about the documents and terms in your Elasticsearch index. A _graph_ is really +about the documents and terms in your {es} index. A _graph_ is really just a network of related items. In our case, this means a network of related terms in the index. @@ -37,14 +37,14 @@ image::user/graph/images/graph-vertices-connections.jpg["Graph components"] NOTE: If you're into https://en.wikipedia.org/wiki/Graph_theory[graph theory], you might know vertices and connections as _nodes_ and _edges_. They're the same thing, we just want to use terminology that makes sense to people who -aren't graph geeks and avoid any confusion with the nodes in an Elasticsearch +aren't graph geeks and avoid any confusion with the nodes in an {es} cluster. The graph vertices are simply the terms that you've already indexed. The -connections are derived on the fly using Elasticsearch aggregations. To +connections are derived on the fly using {es} aggregations. To identify the most _meaningful_ connections, the graph API leverages -Elasticsearch relevance scoring. The same data structures and relevance ranking -tools built into Elasticsearch to support text searches enable the graph API to +{es} relevance scoring. The same data structures and relevance ranking +tools built into {es} to support text searches enable the graph API to separate useful signals from the noise that is typical of most connected data. This foundation lets you easily answer questions like: @@ -55,21 +55,143 @@ be interested in? * Which people on Stack Overflow have expertise in both Hadoop-related technologies and Python-related tech? -But what about performance, you ask? The Elasticsearch aggregation framework +But what about performance? The {es} aggregation framework enables the graph API to quickly summarize millions of documents as a single super-connection. Instead of retrieving every banking transaction between accounts A and B, it derives a single connection that represents that relationship. And, of course, this summarization process works across -multi-node clusters and scales with your Elasticsearch deployment. +multi-node clusters and scales with your {es} deployment. Advanced options let you control how your data is sampled and summarized. You can also set timeouts to prevent graph queries from adversely affecting the cluster. --- -include::getting-started.asciidoc[] +[float] +[[graph-connection]] +== Create a graph + +Use *Graph* to reveal the relationships in your data. + +. Open the main menu, and then click *Graph*. ++ +If you're new to {kib}, and don't yet have any data, follow the link to add sample data. +This example uses the {kib} sample web logs data set. + +. Select the data source that you want to explore. ++ +{kib} graphs the relationships between the top fields. ++ +[role="screenshot"] +image::user/graph/images/graph-url-connections.png["URL connections"] + +. Add more fields, or click an existing field to edit, disable or deselect it. ++ +[role="screenshot"] +image::user/graph/images/graph-menu.png["menu for editing, disabling, or removing a field from the graph", width=75%] + + +. Enter a query to discover relationships between terms in the selected +fields. ++ +For example, +to generate a graph of the successful requests to a +particular location, search for the `geo.src` +field. The weight of the connection between two vertices indicates how strongly they +are related. + +. To view more information about a relationship, click any connection or vertex. ++ +[role="screenshot"] +image::user/graph/images/graph-control-bar.png["Graph toolbar", width=50%] + +. Use the graph toolbar to display additional connections: ++ +* To display additional vertices that connect to your graph, click the expand icon +image:user/graph/images/graph-expand-button.png[Expand Selection]. +* To display additional +connections between the displayed vertices, click the link icon +image:user/graph/images/graph-link-button.png[Add links to existing terms]. +* To explore a particular area of the +graph, select the vertices you are interested in, and then click expand or link. +* To step back through your changes to the graph, click undo +image:user/graph/images/graph-undo-button.png[Undo] and redo +image:user/graph/images/graph-redo-button.png[Redo]. + +. To view more relationships in your data, submit additional queries. + +. *Save* your graph. + +[float] +[[graph-customize]] +== Customize your graph + +Apply custom colors and icons to vertices, configure the number of vertices that +a search adds to the graph, block terms, and more. + +[float] +[[style-vertex-properties]] +==== Style vertex properties + +Each vertex has a color, icon, and label. To change +the color or icon of all vertices +of a certain field, click it's field, and then +select *Edit settings*. + +To change the color and label of selected vertices, +click the style icon image:user/graph/images/graph-style-button.png[Style] +in the control bar. + + +[float] +[[edit-graph-settings]] +==== Tune the noise level + +By default, *Graph* is configured to tune out noise in your data. +If this isn't a good fit for your data, open *Settings > Advanced settings*, +and then adjust the way *Graph* queries your data. You can tune the graph to show +only the results relevant to you and to improve performance. +For more information, see <>. + +You can configure the number of vertices that a search or +expand operation adds to the graph. +By default, only the five most relevant terms for any given field are added +at a time. This keeps the graph from overflowing. To increase this number, click +a field, select *Edit Settings*, and change *Terms per hop*. + +[float] +[[graph-block-terms]] +==== Block terms from the graph +Documents that match a blocked term are not allowed in the graph. +To block a term, select its vertex and click +the block icon +image:user/graph/images/graph-block-button.png[Block selection] +in the graph toolbar. +For a list of blocked terms, open *Settings > Blocked terms*. + +[float] +[[graph-drill-down]] +==== Drill down into raw documents +With drilldowns, you can display additional information about a +selected vertex in a new browser window. For example, you might +configure a drilldown URL to perform a web search for the selected vertex term. + +Use the drilldown icon image:user/graph/images/graph-info-icon.png[Drilldown selection] +in the graph toolbar to show the drilldown buttons for the selected vertices. +To configure drilldowns, go to *Settings > Drilldowns*. See also +<>. + +[float] +[[graph-run-layout]] +==== Run and pause the layout +Graph uses a "force layout", where vertices behave like magnets, +pushing off of one another. By default, when you add a new vertex to +the graph, all vertices begin moving. In some cases, the movement might +go on for some time. To freeze the current vertex position, +click the pause icon +image:user/graph/images/graph-pause-button.png[Block selection] +in the graph toolbar. + +-- include::configuring-graph.asciidoc[] include::troubleshooting.asciidoc[] - -include::limitations.asciidoc[] diff --git a/docs/user/graph/limitations.asciidoc b/docs/user/graph/limitations.asciidoc deleted file mode 100644 index e96910bd27b4c..0000000000000 --- a/docs/user/graph/limitations.asciidoc +++ /dev/null @@ -1,27 +0,0 @@ -[role="xpack"] -[[graph-limitations]] -== Graph limitations -++++ -Limitations -++++ - -[discrete] -=== Limited support for multiple indices -The graph API can explore multiple indices, types, or aliases in a -single API request, but the assumption is that each "hop" it performs -is querying the same set of indices. Currently, it is not possible to -take a term found in a field from one index and use that value to explore -connections in _a different field_ held in another type or index. - -A good example of where this might be useful is if an IP address is -found in the `remote_host` field of an index called "weblogs20160101", -you might want to follow that up by looking for the same address in -the `ip_address` field of an index called "knownthreats". - -Supporting this behaviour would require extra mappings to indicate that -the weblogs' `remote_host` field contained values that had currency and -meaning in the `ip_address` field of the threats index. - -Since we do not currently support this translation, you would have to -perform multiple calls to take the values from the weblogs index -response and build them into a separate request to the threats index. diff --git a/docs/user/graph/troubleshooting.asciidoc b/docs/user/graph/troubleshooting.asciidoc index eaac105c57358..f73d9142ff7e2 100644 --- a/docs/user/graph/troubleshooting.asciidoc +++ b/docs/user/graph/troubleshooting.asciidoc @@ -1,8 +1,8 @@ [role="xpack"] [[graph-troubleshooting]] -== Graph troubleshooting +== Graph troubleshooting and limitations ++++ -Troubleshoot +Troubleshooting and limitations ++++ [discrete] @@ -22,7 +22,7 @@ but they can miss details from individual documents. If you need to perform a detailed forensic analysis, you can adjust the following settings to ensure a graph exploration produces all of the relevant data: -* Increase the `sample_size` to a larger number of documents to analyse more +* Increase the `sample_size` to a larger number of documents to analyze more data on each shard. * Set the `use_significance` setting to `false` to retrieve terms regardless of any statistical correlation with the sample. @@ -53,7 +53,28 @@ large documents with your seed and guiding queries. so even increasing the frequency threshold by one can massively reduce the number of candidate terms whose background frequencies are checked. -Keep in mind that all of these options reduce the scope of information analysed +Keep in mind that all of these options reduce the scope of information analyzed and can increase the potential to miss what could be interesting details. However, the information that's lost tends to be associated with lower-quality documents with lower-frequency terms, which can be an acceptable trade-off. + +[discrete] +=== Limited support for multiple indices +The graph API can explore multiple indices, types, or aliases in a +single API request, but the assumption is that each "hop" it performs +is querying the same set of indices. Currently, it is not possible to +take a term found in a field from one index and use that value to explore +connections in _a different field_ held in another type or index. + +A good example of where this might be useful is if an IP address is +found in the `remote_host` field of an index called "weblogs20160101", +you might want to follow that up by looking for the same address in +the `ip_address` field of an index called "knownthreats". + +Supporting this behavior would require extra mappings to indicate that +the weblogs' `remote_host` field contained values that had currency and +meaning in the `ip_address` field of the threats index. + +Since we do not currently support this translation, you would have to +perform multiple calls to take the values from the weblogs index +response and build them into a separate request to the threats index. diff --git a/docs/user/images/app-navigation-search.png b/docs/user/images/app-navigation-search.png index 3b89eed44b28f..9a644909ceb88 100644 Binary files a/docs/user/images/app-navigation-search.png and b/docs/user/images/app-navigation-search.png differ diff --git a/docs/user/images/home-page.png b/docs/user/images/home-page.png deleted file mode 100755 index 9ca4b7f43f427..0000000000000 Binary files a/docs/user/images/home-page.png and /dev/null differ diff --git a/docs/user/images/kibana-main-menu.png b/docs/user/images/kibana-main-menu.png old mode 100755 new mode 100644 index 79e0a3dca8658..7ee18d9844c66 Binary files a/docs/user/images/kibana-main-menu.png and b/docs/user/images/kibana-main-menu.png differ diff --git a/docs/user/images/login-screen.png b/docs/user/images/login-screen.png deleted file mode 100755 index 7a97c952e1039..0000000000000 Binary files a/docs/user/images/login-screen.png and /dev/null differ diff --git a/docs/user/images/roles-and-privileges.png b/docs/user/images/roles-and-privileges.png deleted file mode 100755 index 28bff6d13c871..0000000000000 Binary files a/docs/user/images/roles-and-privileges.png and /dev/null differ diff --git a/docs/user/images/rules-and-connectors.png b/docs/user/images/rules-and-connectors.png index 5cda25b54536f..1a85eeb6c0bc2 100644 Binary files a/docs/user/images/rules-and-connectors.png and b/docs/user/images/rules-and-connectors.png differ diff --git a/docs/user/images/stack-management.png b/docs/user/images/stack-management.png new file mode 100644 index 0000000000000..a0600b53bd836 Binary files /dev/null and b/docs/user/images/stack-management.png differ diff --git a/docs/user/images/tags-search.png b/docs/user/images/tags-search.png old mode 100755 new mode 100644 index 67458200c50d1..5b0134f25282f Binary files a/docs/user/images/tags-search.png and b/docs/user/images/tags-search.png differ diff --git a/docs/user/introduction.asciidoc b/docs/user/introduction.asciidoc index 783fa2b1c521f..dc7b1ecc50f6e 100644 --- a/docs/user/introduction.asciidoc +++ b/docs/user/introduction.asciidoc @@ -59,7 +59,7 @@ and everything you need to visualize and analyze your data. To access all of {kib} features, use the main menu. Open this menu by clicking the -menu icon. To keep the main menu visible at all times, click *Dock navigation*. +menu icon. For a quick reference of all {kib} features, refer to <> [role="screenshot"] @@ -81,10 +81,6 @@ that it ran in, trace the transaction, and check the overall service availabilit * Designed for security analysts, {security-guide}/es-overview.html[*Elastic Security*] provides an overview of the events and alerts from your environment. Elastic Security helps you defend your organization from threats before damage and loss occur. -+ -[role="screenshot"] -image::siem/images/detections-ui.png[Detections view in Elastic Security] - [float] [[visualize-and-analyze]] @@ -165,7 +161,7 @@ guided processes for administering all things Elastic Stack, including data, indices, clusters, alerts, and security. [role="screenshot"] -image::images/intro-management.png[Index Management view in Stack Management] +image::images/stack-management.png[Index Management view in Stack Management] [float] ==== Manage your data, indices, and clusters @@ -429,7 +425,7 @@ the <>. |<> |Share your data -|<>, <> +|<>, <>, <> 2+|*Administer your Kibana instance* diff --git a/docs/user/security/audit-logging.asciidoc b/docs/user/security/audit-logging.asciidoc index db40feab20ce9..e2f21e3f8470c 100644 --- a/docs/user/security/audit-logging.asciidoc +++ b/docs/user/security/audit-logging.asciidoc @@ -14,16 +14,24 @@ by cluster-wide privileges. For more information on enabling audit logging in [IMPORTANT] ============================================================================ +Kibana offers two audit logs: a **deprecated** legacy audit logger, and a new +ECS-compliant audit logger. We strongly advise using the <>, +as the legacy audit logger will be removed in an upcoming version. +============================================================================ + +[NOTE] +============================================================================ Audit logs are **disabled** by default. To enable this functionality, you must -set `xpack.security.audit.enabled` to `true` in `kibana.yml`. +set `xpack.security.audit.enabled` to `true` in `kibana.yml`, and configure +an <> to write the audit log to a location of your choosing. ============================================================================ -The current version of the audit logger uses the standard {kib} logging output, +The legacy audit logger uses the standard {kib} logging output, which can be configured in `kibana.yml`. For more information, refer to <>. -The audit logger uses a separate logger and can be configured using +The <> uses a separate logger and can be configured using the options in <>. -==== Audit event types +==== Legacy audit event types When you are auditing security events, each request can generate multiple audit events. The following is a list of the events that can be generated: @@ -42,7 +50,7 @@ events. The following is a list of the events that can be generated: ============================================================================ The following events are only logged if the ECS audit logger is enabled. For information on how to configure `xpack.security.audit.appender`, refer to -<>. +<>. ============================================================================ Refer to the table of events that can be logged for auditing purposes. diff --git a/docs/user/security/session-management.asciidoc b/docs/user/security/session-management.asciidoc index ac7a777eb0580..b0f27d45bb826 100644 --- a/docs/user/security/session-management.asciidoc +++ b/docs/user/security/session-management.asciidoc @@ -12,24 +12,24 @@ To manage user sessions programmatically, {kib} exposes <[ms|s|m|h|d|w|M|Y]` (e.g. '20m', '24h', '7d', '1w'). For example, set the idle timeout to expire sessions after 1 hour of inactivity: +By default, sessions expire after 1 hour of inactivity. To define another value for a sliding session expiration, set the property in the `kibana.yml` configuration file. The idle timeout is formatted as a duration of `[ms|s|m|h|d|w|M|Y]` (e.g. '20m', '24h', '7d', '1w'). For example, set the idle timeout to expire sessions after 30 minutes of inactivity: -- [source,yaml] -------------------------------------------------------------------------------- -xpack.security.session.idleTimeout: "1h" +xpack.security.session.idleTimeout: "30m" -------------------------------------------------------------------------------- -- [[session-lifespan]] ==== Session lifespan -You can use `xpack.security.session.lifespan` to configure the maximum session duration or "lifespan" -- also known as the "absolute timeout". This and `xpack.security.session.idleTimeout` are both highly recommended. By default, sessions don't have a fixed lifespan, and if an idle timeout is defined, a session can still be extended indefinitely. To define a maximum session lifespan, set the property in the `kibana.yml` configuration file. The lifespan is formatted as a duration of `[ms|s|m|h|d|w|M|Y]` (e.g. '20m', '24h', '7d', '1w'). For example, set the lifespan to expire sessions after 30 days: +You can use `xpack.security.session.lifespan` to configure the maximum session duration or "lifespan" -- also known as the "absolute timeout". This and `xpack.security.session.idleTimeout` are both highly recommended. By default, a maximum session lifespan is 30 days. To define another lifespan, set the property in the `kibana.yml` configuration file. The lifespan is formatted as a duration of `[ms|s|m|h|d|w|M|Y]` (e.g. '20m', '24h', '7d', '1w'). For example, set the lifespan to expire sessions after 7 days: -- [source,yaml] -------------------------------------------------------------------------------- -xpack.security.session.lifespan: "30d" +xpack.security.session.lifespan: "7d" -------------------------------------------------------------------------------- -- @@ -38,7 +38,7 @@ xpack.security.session.lifespan: "30d" [IMPORTANT] ============================================================================ -If you specify neither session idle timeout nor lifespan, then {kib} will not automatically remove session information from the index unless you explicitly log out. This might lead to an infinitely growing session index. Configure the idle timeout and lifespan settings for the {kib} sessions so that they can be cleaned up even if you don't explicitly log out. +If you disable session idle timeout and lifespan, then Kibana will not automatically remove session information from the index unless you explicitly log out. This might lead to an infinitely growing session index. As long as either idle timeout or lifespan is configured, Kibana sessions will be cleaned up even if you don't explicitly log out. ============================================================================ You can configure the interval at which {kib} tries to remove expired and invalid sessions from the session index. By default, this value is 1 hour and cannot be less than 10 seconds. To define another interval, set the `xpack.security.session.cleanupInterval` property in the `kibana.yml` configuration file. The interval is formatted as a duration of `[ms|s|m|h|d|w|M|Y]` (e.g. '20m', '24h', '7d', '1w'). For example, schedule the session index cleanup to perform once a day: diff --git a/examples/preboot_example/README.md b/examples/preboot_example/README.md new file mode 100644 index 0000000000000..0f3ab6c6eae2a --- /dev/null +++ b/examples/preboot_example/README.md @@ -0,0 +1,3 @@ +# `prebootExample` plugin + +The example of the `preboot` plugin. \ No newline at end of file diff --git a/examples/preboot_example/kibana.json b/examples/preboot_example/kibana.json new file mode 100644 index 0000000000000..b8b5ceb1a9c6c --- /dev/null +++ b/examples/preboot_example/kibana.json @@ -0,0 +1,16 @@ +{ + "id": "prebootExample", + "owner": { + "name": "Core", + "githubTeam": "kibana-core" + }, + "description": "The example of the `preboot` plugin.", + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": ["prebootExample"], + "type": "preboot", + "server": true, + "ui": true, + "requiredPlugins": [], + "requiredBundles": [] +} diff --git a/examples/preboot_example/public/app.tsx b/examples/preboot_example/public/app.tsx new file mode 100644 index 0000000000000..364b7d5bfe8d3 --- /dev/null +++ b/examples/preboot_example/public/app.tsx @@ -0,0 +1,218 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { + EuiButton, + EuiCodeBlock, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiPageTemplate, + EuiPanel, + EuiText, +} from '@elastic/eui'; +import React, { useEffect, useState } from 'react'; +import type { HttpSetup, IHttpFetchError } from 'src/core/public'; + +export const App = ({ http, token }: { http: HttpSetup; token?: string }) => { + const onCompleteSetup = async ({ shouldReloadConfig }: { shouldReloadConfig: boolean }) => { + await http + .post('/api/preboot/complete_setup', { + body: JSON.stringify({ shouldReloadConfig }), + }) + .then(() => { + setTimeout(() => { + window.location.href = '/'; + }, 5000); + }); + }; + + const onWriteToken = async () => { + await http.post('/api/preboot/write_config', { body: JSON.stringify(configKeyValue) }); + }; + + const onConnect = async () => { + await http + .post('/api/preboot/connect_to_es', { body: JSON.stringify(elasticsearchConfig) }) + .then( + (response) => setConnectResponse(JSON.stringify(response)), + (err: IHttpFetchError) => setConnectResponse(err?.body?.message || 'ERROR') + ); + }; + + const [configKeyValue, setConfigKeyValue] = useState<{ key: string; value: string }>({ + key: '', + value: '', + }); + + const [elasticsearchConfig, setElasticsearchConfig] = useState<{ + host: string; + username: string; + password: string; + }>({ + host: 'http://localhost:9200', + username: 'kibana_system', + password: '', + }); + + const [connectResponse, setConnectResponse] = useState(null); + + const [isSetupModeActive, setIsSetupModeActive] = useState(false); + useEffect(() => { + http.get<{ isSetupModeActive: boolean }>('/api/preboot/state').then( + (response) => setIsSetupModeActive(response.isSetupModeActive), + (err: IHttpFetchError) => setIsSetupModeActive(false) + ); + }, [http]); + + if (!isSetupModeActive) { + return ( + + + Kibana server is not ready yet. + + + ); + } + + return ( + + + + + + + { + setConfigKeyValue({ ...configKeyValue, key: e.target.value }); + }} + /> + + + { + setConfigKeyValue({ ...configKeyValue, value: e.target.value }); + }} + /> + + + + Write config + + + + + + + + Token from config: {token} + + + onCompleteSetup({ shouldReloadConfig: true })} + > + Reload config and proceed to `setup` + + + + onCompleteSetup({ shouldReloadConfig: false })} + > + DO NOT reload config and proceed to `setup` + + + + + + + + { + setElasticsearchConfig({ ...elasticsearchConfig, host: e.target.value }); + }} + /> + + + { + setElasticsearchConfig({ + ...elasticsearchConfig, + username: e.target.value, + }); + }} + /> + + + { + setElasticsearchConfig({ + ...elasticsearchConfig, + password: e.target.value, + }); + }} + /> + + + + Connect + + + + + + + {connectResponse ?? ''} + + + + + + ); +}; diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_renderers/dict/index.ts b/examples/preboot_example/public/config.ts similarity index 86% rename from src/plugins/expression_reveal_image/common/i18n/expression_renderers/dict/index.ts rename to examples/preboot_example/public/config.ts index 4f70f9d30b74b..fc91296ce3724 100644 --- a/src/plugins/expression_reveal_image/common/i18n/expression_renderers/dict/index.ts +++ b/examples/preboot_example/public/config.ts @@ -6,4 +6,6 @@ * Side Public License, v 1. */ -export { strings as revealImage } from './reveal_image'; +export interface ConfigType { + token?: string; +} diff --git a/examples/preboot_example/public/index.ts b/examples/preboot_example/public/index.ts new file mode 100644 index 0000000000000..7859758c274f3 --- /dev/null +++ b/examples/preboot_example/public/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginInitializerContext } from 'kibana/public'; +import { PrebootExamplePlugin } from './plugin'; + +export const plugin = (initializerContext: PluginInitializerContext) => + new PrebootExamplePlugin(initializerContext); diff --git a/examples/preboot_example/public/plugin.tsx b/examples/preboot_example/public/plugin.tsx new file mode 100644 index 0000000000000..1db38e2240a86 --- /dev/null +++ b/examples/preboot_example/public/plugin.tsx @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { App } from './app'; +import { ConfigType } from './config'; + +export class PrebootExamplePlugin implements Plugin { + #config: ConfigType; + constructor(initializerContext: PluginInitializerContext) { + this.#config = initializerContext.config.get(); + } + + public setup(core: CoreSetup) { + core.application.register({ + id: 'prebootExample', + title: 'Preboot Example', + appRoute: '/', + chromeless: true, + mount: (params) => { + ReactDOM.render(, params.element); + return () => ReactDOM.unmountComponentAtNode(params.element); + }, + }); + } + + public start(core: CoreStart) {} +} diff --git a/examples/preboot_example/server/config.ts b/examples/preboot_example/server/config.ts new file mode 100644 index 0000000000000..db39f985bca30 --- /dev/null +++ b/examples/preboot_example/server/config.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { TypeOf } from '@kbn/config-schema'; +import { schema } from '@kbn/config-schema'; + +export type ConfigType = TypeOf; + +export const ConfigSchema = schema.object({ + enabled: schema.boolean({ defaultValue: false }), + token: schema.maybe(schema.string()), + skipSetup: schema.boolean({ defaultValue: false }), +}); diff --git a/examples/preboot_example/server/index.ts b/examples/preboot_example/server/index.ts new file mode 100644 index 0000000000000..0377250d0cf50 --- /dev/null +++ b/examples/preboot_example/server/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { TypeOf } from '@kbn/config-schema'; +import type { PluginConfigDescriptor, PluginInitializerContext } from 'src/core/server'; + +import { ConfigSchema } from './config'; +import { PrebootExamplePlugin } from './plugin'; + +export const config: PluginConfigDescriptor> = { + schema: ConfigSchema, + exposeToBrowser: { token: true }, +}; + +export const plugin = (context: PluginInitializerContext) => new PrebootExamplePlugin(context); diff --git a/examples/preboot_example/server/plugin.ts b/examples/preboot_example/server/plugin.ts new file mode 100644 index 0000000000000..a3c1e9d199143 --- /dev/null +++ b/examples/preboot_example/server/plugin.ts @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { schema } from '@kbn/config-schema'; +import type { CorePreboot, PrebootPlugin, PluginInitializerContext } from 'src/core/server'; +import fs from 'fs/promises'; +import { errors } from '@elastic/elasticsearch'; +import Boom from '@hapi/boom'; +import type { ConfigType } from './config'; + +export function getDetailedErrorMessage(error: any): string { + if (error instanceof errors.ResponseError) { + return JSON.stringify(error.body); + } + + if (Boom.isBoom(error)) { + return JSON.stringify(error.output.payload); + } + + return error.message; +} + +export class PrebootExamplePlugin implements PrebootPlugin { + readonly #initializerContext: PluginInitializerContext; + constructor(initializerContext: PluginInitializerContext) { + this.#initializerContext = initializerContext; + } + + public setup(core: CorePreboot) { + const { skipSetup } = this.#initializerContext.config.get(); + let completeSetup: (result: { shouldReloadConfig: boolean }) => void; + + core.http.registerRoutes('', (prebootRouter) => { + prebootRouter.get( + { + path: '/api/preboot/state', + validate: false, + options: { authRequired: false }, + }, + (_, request, response) => { + const isSetupModeActive = !skipSetup && core.preboot.isSetupOnHold(); + return response.ok({ body: { isSetupModeActive } }); + } + ); + if (skipSetup) { + return; + } + + prebootRouter.post( + { + path: '/api/preboot/complete_setup', + validate: { + body: schema.object({ shouldReloadConfig: schema.boolean() }), + }, + options: { authRequired: false }, + }, + (_, request, response) => { + completeSetup({ shouldReloadConfig: request.body.shouldReloadConfig }); + return response.noContent(); + } + ); + + prebootRouter.post( + { + path: '/api/preboot/write_config', + validate: { + body: schema.object({ key: schema.string(), value: schema.string() }), + }, + options: { authRequired: false }, + }, + async (_, request, response) => { + const configPath = this.#initializerContext.env.configs.find((path) => + path.includes('dev') + ); + + if (!configPath) { + return response.customError({ statusCode: 500, body: 'Cannot find dev config.' }); + } + + await fs.appendFile(configPath, `${request.body.key}: ${request.body.value}\n`); + return response.noContent(); + } + ); + + prebootRouter.post( + { + path: '/api/preboot/connect_to_es', + validate: { + body: schema.object({ + host: schema.string(), + username: schema.string(), + password: schema.string(), + }), + }, + options: { authRequired: false }, + }, + async (_, request, response) => { + const esClient = core.elasticsearch.createClient('data', { + hosts: [request.body.host], + }); + + const scopedClient = esClient.asScoped({ + headers: { + authorization: `Basic ${Buffer.from( + `${request.body.username}:${request.body.password}` + ).toString('base64')}`, + }, + }); + + try { + return response.ok({ + body: (await scopedClient.asCurrentUser.security.authenticate()).body, + }); + } catch (err) { + return response.customError({ statusCode: 500, body: getDetailedErrorMessage(err) }); + } + } + ); + + core.preboot.holdSetupUntilResolved( + 'Elasticsearch connection is not set up', + new Promise<{ shouldReloadConfig: boolean }>((resolve) => { + completeSetup = resolve; + }) + ); + }); + } + + public stop() {} +} diff --git a/examples/preboot_example/tsconfig.json b/examples/preboot_example/tsconfig.json new file mode 100644 index 0000000000000..d18953eadf330 --- /dev/null +++ b/examples/preboot_example/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": ["public/**/*", "server/**/*"], + "references": [{ "path": "../../src/core/tsconfig.json" }] +} diff --git a/package.json b/package.json index 0875242a18da9..9034638c689a4 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "dependencies": { "@elastic/apm-rum": "^5.8.0", "@elastic/apm-rum-react": "^1.2.11", - "@elastic/charts": "32.0.0", + "@elastic/charts": "33.0.0", "@elastic/datemath": "link:bazel-bin/packages/elastic-datemath", "@elastic/elasticsearch": "npm:@elastic/elasticsearch-canary@^8.0.0-canary.13", "@elastic/ems-client": "7.14.0", @@ -131,6 +131,7 @@ "@kbn/config": "link:bazel-bin/packages/kbn-config", "@kbn/config-schema": "link:bazel-bin/packages/kbn-config-schema", "@kbn/crypto": "link:bazel-bin/packages/kbn-crypto", + "@kbn/es-query": "link:bazel-bin/packages/kbn-es-query", "@kbn/i18n": "link:bazel-bin/packages/kbn-i18n", "@kbn/interpreter": "link:bazel-bin/packages/kbn-interpreter", "@kbn/io-ts-utils": "link:bazel-bin/packages/kbn-io-ts-utils", @@ -153,9 +154,9 @@ "@kbn/securitysolution-utils": "link:bazel-bin/packages/kbn-securitysolution-utils", "@kbn/server-http-tools": "link:bazel-bin/packages/kbn-server-http-tools", "@kbn/server-route-repository": "link:bazel-bin/packages/kbn-server-route-repository", - "@kbn/typed-react-router-config": "link:bazel-bin/packages/kbn-typed-react-router-config", "@kbn/std": "link:bazel-bin/packages/kbn-std", "@kbn/tinymath": "link:bazel-bin/packages/kbn-tinymath", + "@kbn/typed-react-router-config": "link:bazel-bin/packages/kbn-typed-react-router-config", "@kbn/ui-framework": "link:bazel-bin/packages/kbn-ui-framework", "@kbn/ui-shared-deps": "link:bazel-bin/packages/kbn-ui-shared-deps", "@kbn/utility-types": "link:bazel-bin/packages/kbn-utility-types", @@ -321,7 +322,7 @@ "p-retry": "^4.2.0", "papaparse": "^5.2.0", "pdfmake": "^0.1.65", - "peggy": "^1.0.0", + "peggy": "^1.2.0", "pegjs": "0.10.0", "pluralize": "3.1.0", "pngjs": "^3.4.0", @@ -689,7 +690,7 @@ "copy-webpack-plugin": "^6.0.2", "cpy": "^8.1.1", "css-loader": "^3.4.2", - "css-minimizer-webpack-plugin": "^1.3.0", + "cssnano": "^4.1.11", "cypress": "^6.8.0", "cypress-cucumber-preprocessor": "^2.5.2", "cypress-multi-reporters": "^1.4.0", @@ -741,7 +742,11 @@ "grunt-peg": "^2.0.1", "gulp": "4.0.2", "gulp-babel": "^8.0.0", + "gulp-brotli": "^3.0.0", + "gulp-postcss": "^8.0.0", "gulp-sourcemaps": "2.6.5", + "gulp-terser": "^2.0.1", + "gulp-gzip": "^1.4.2", "gulp-zip": "^5.0.2", "has-ansi": "^3.0.0", "hdr-histogram-js": "^1.2.0", @@ -829,6 +834,7 @@ "tempy": "^0.3.0", "terminal-link": "^2.1.1", "terser-webpack-plugin": "^2.1.2", + "terser": "^5.7.1", "ts-loader": "^7.0.5", "ts-morph": "^9.1.0", "tsd": "^0.13.1", diff --git a/packages/BUILD.bazel b/packages/BUILD.bazel index 938afdc205a44..0719357b6df35 100644 --- a/packages/BUILD.bazel +++ b/packages/BUILD.bazel @@ -21,6 +21,7 @@ filegroup( "//packages/kbn-docs-utils:build", "//packages/kbn-es:build", "//packages/kbn-es-archiver:build", + "//packages/kbn-es-query:build", "//packages/kbn-eslint-import-resolver-kibana:build", "//packages/kbn-eslint-plugin-eslint:build", "//packages/kbn-expect:build", diff --git a/packages/kbn-config/src/config_service.test.ts b/packages/kbn-config/src/config_service.test.ts index b1b622381abb1..d09c61a1c2110 100644 --- a/packages/kbn-config/src/config_service.test.ts +++ b/packages/kbn-config/src/config_service.test.ts @@ -37,6 +37,7 @@ const getRawConfigProvider = (rawConfig: Record) => beforeEach(() => { logger = loggerMock.create(); + mockApplyDeprecations.mockClear(); }); test('returns config at path as observable', async () => { @@ -485,6 +486,16 @@ test('does not log warnings for silent deprecations during validation', async () expect(loggerMock.collect(logger).warn).toMatchInlineSnapshot(`Array []`); }); +test('does not log warnings during validation if specifically requested', async () => { + const configService = new ConfigService(getRawConfigProvider({}), defaultEnv, logger); + loggerMock.clear(logger); + + await configService.validate({ logDeprecations: false }); + + expect(mockApplyDeprecations).not.toHaveBeenCalled(); + expect(loggerMock.collect(logger).warn).toMatchInlineSnapshot(`Array []`); +}); + describe('atPathSync', () => { test('returns the value at path', async () => { const rawConfig = getRawConfigProvider({ key: 'foo' }); diff --git a/packages/kbn-config/src/config_service.ts b/packages/kbn-config/src/config_service.ts index a80680bd46dfc..514992891ad1b 100644 --- a/packages/kbn-config/src/config_service.ts +++ b/packages/kbn-config/src/config_service.ts @@ -29,6 +29,14 @@ import { LegacyObjectToConfigAdapter } from './legacy'; /** @internal */ export type IConfigService = PublicMethodsOf; +/** @internal */ +export interface ConfigValidateParameters { + /** + * Indicates whether config deprecations should be logged during validation. + */ + logDeprecations: boolean; +} + /** @internal */ export class ConfigService { private readonly log: Logger; @@ -111,13 +119,16 @@ export class ConfigService { * * This must be done after every schemas and deprecation providers have been registered. */ - public async validate() { + public async validate(params: ConfigValidateParameters = { logDeprecations: true }) { const namespaces = [...this.schemas.keys()]; for (let i = 0; i < namespaces.length; i++) { await this.getValidatedConfigAtPath$(namespaces[i]).pipe(first()).toPromise(); } - await this.logDeprecation(); + if (params.logDeprecations) { + await this.logDeprecation(); + } + this.validated = true; } diff --git a/packages/kbn-config/src/index.ts b/packages/kbn-config/src/index.ts index 294caba4e7048..08cf12343f459 100644 --- a/packages/kbn-config/src/index.ts +++ b/packages/kbn-config/src/index.ts @@ -25,7 +25,7 @@ export { getConfigFromFiles, } from './raw'; -export { ConfigService, IConfigService } from './config_service'; +export { ConfigService, IConfigService, ConfigValidateParameters } from './config_service'; export { Config, ConfigPath, isConfigPath, hasConfigPathIntersection } from './config'; export { ObjectToConfigAdapter } from './object_to_config_adapter'; export { CliArgs, Env, RawPackageInfo } from './env'; diff --git a/packages/kbn-dev-utils/BUILD.bazel b/packages/kbn-dev-utils/BUILD.bazel index 9109f766e0e9f..7c9beafc711ee 100644 --- a/packages/kbn-dev-utils/BUILD.bazel +++ b/packages/kbn-dev-utils/BUILD.bazel @@ -43,6 +43,7 @@ NPM_MODULE_EXTRA_FILES = [ SRC_DEPS = [ "//packages/kbn-expect", + "//packages/kbn-std", "//packages/kbn-utils", "@npm//@babel/core", "@npm//axios", @@ -60,10 +61,12 @@ SRC_DEPS = [ "@npm//moment", "@npm//normalize-path", "@npm//rxjs", + "@npm//tar", "@npm//tree-kill", "@npm//tslib", "@npm//typescript", - "@npm//vinyl" + "@npm//vinyl", + "@npm//yauzl" ] TYPES_DEPS = [ @@ -76,8 +79,10 @@ TYPES_DEPS = [ "@npm//@types/node", "@npm//@types/normalize-path", "@npm//@types/react", + "@npm//@types/tar", "@npm//@types/testing-library__jest-dom", - "@npm//@types/vinyl" + "@npm//@types/vinyl", + "@npm//@types/yauzl" ] DEPS = SRC_DEPS + TYPES_DEPS diff --git a/packages/kbn-dev-utils/src/extract.ts b/packages/kbn-dev-utils/src/extract.ts new file mode 100644 index 0000000000000..05ad2b4bd99ec --- /dev/null +++ b/packages/kbn-dev-utils/src/extract.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import Fs from 'fs/promises'; +import { createWriteStream } from 'fs'; +import Path from 'path'; +import { pipeline } from 'stream'; +import { promisify } from 'util'; + +import { lastValueFrom } from '@kbn/std'; +import Tar from 'tar'; +import Yauzl, { ZipFile, Entry } from 'yauzl'; +import * as Rx from 'rxjs'; +import { map, mergeMap, takeUntil } from 'rxjs/operators'; + +const asyncPipeline = promisify(pipeline); + +interface Options { + /** + * Path to the archive to extract, .tar, .tar.gz, and .zip archives are supported + */ + archivePath: string; + + /** + * Directory where the contents of the archive will be written. Existing files in that + * directory will be overwritten. If the directory doesn't exist it will be created. + */ + targetDir: string; + + /** + * Number of path segments to strip form paths in the archive, like --strip-components from tar + */ + stripComponents?: number; +} + +/** + * Extract tar and zip archives using a single function, supporting stripComponents + * for both archive types, only tested with familiar archives we create so might not + * support some weird exotic zip features we don't use in our own snapshot/build tooling + */ +export async function extract({ archivePath, targetDir, stripComponents = 0 }: Options) { + await Fs.mkdir(targetDir, { recursive: true }); + + if (archivePath.endsWith('.tar') || archivePath.endsWith('.tar.gz')) { + return await Tar.x({ + file: archivePath, + cwd: targetDir, + stripComponents, + }); + } + + if (!archivePath.endsWith('.zip')) { + throw new Error('unsupported archive type'); + } + + // zip mode + const zipFile = await new Promise((resolve, reject) => { + Yauzl.open(archivePath, { lazyEntries: true }, (error, _zipFile) => { + if (error || !_zipFile) { + reject(error || new Error('no zipfile provided by yauzl')); + } else { + resolve(_zipFile); + } + }); + }); + + // bound version of zipFile.openReadStream which returns an observable, because of type defs the readStream + // result is technically optional (thanks callbacks) + const openReadStream$ = Rx.bindNodeCallback(zipFile.openReadStream.bind(zipFile)); + + const close$ = Rx.fromEvent(zipFile, 'close'); + const error$ = Rx.fromEvent(zipFile, 'error').pipe( + takeUntil(close$), + map((error) => { + throw error; + }) + ); + + const entry$ = Rx.fromEvent(zipFile, 'entry').pipe( + takeUntil(close$), + mergeMap((entry) => { + const entryPath = entry.fileName.split(/\/|\\/).slice(stripComponents).join(Path.sep); + const fileName = Path.resolve(targetDir, entryPath); + + // detect directories + if (entry.fileName.endsWith('/')) { + return Rx.defer(async () => { + // ensure the directory exists + await Fs.mkdir(fileName, { recursive: true }); + // tell yauzl to read the next entry + zipFile.readEntry(); + }); + } + + // file entry + return openReadStream$(entry).pipe( + mergeMap(async (readStream) => { + if (!readStream) { + throw new Error('no readstream provided by yauzl'); + } + + // write the file contents to disk + await asyncPipeline(readStream, createWriteStream(fileName)); + // tell yauzl to read the next entry + zipFile.readEntry(); + }) + ); + }) + ); + + // trigger the initial 'entry' event, happens async so the event will be delivered after the observable is subscribed + zipFile.readEntry(); + + await lastValueFrom(Rx.merge(entry$, error$)); +} diff --git a/packages/kbn-dev-utils/src/index.ts b/packages/kbn-dev-utils/src/index.ts index 3ac3927d25c05..9dc9d1723945a 100644 --- a/packages/kbn-dev-utils/src/index.ts +++ b/packages/kbn-dev-utils/src/index.ts @@ -31,3 +31,4 @@ export * from './plugin_list'; export * from './plugins'; export * from './streams'; export * from './babel'; +export * from './extract'; diff --git a/packages/kbn-dev-utils/src/run/help.ts b/packages/kbn-dev-utils/src/run/help.ts index bd85922d00207..ea197d1813086 100644 --- a/packages/kbn-dev-utils/src/run/help.ts +++ b/packages/kbn-dev-utils/src/run/help.ts @@ -8,6 +8,7 @@ import Path from 'path'; +import chalk from 'chalk'; import 'core-js/features/string/repeat'; import dedent from 'dedent'; @@ -116,7 +117,7 @@ export function getHelpForAllCommands({ : ''; return [ - dedent(command.usage || '') || command.name, + chalk.bold.whiteBright.bgBlack(` ${dedent(command.usage || '') || command.name} `), ` ${indent(dedent(command.description || 'Runs a dev task'), 2)}`, ...([indent(options, 2)] || []), ].join('\n'); diff --git a/packages/kbn-dev-utils/src/serializers/any_instance_serizlizer.ts b/packages/kbn-dev-utils/src/serializers/any_instance_serizlizer.ts index 122492d03a6f2..3bb69bcb580a3 100644 --- a/packages/kbn-dev-utils/src/serializers/any_instance_serizlizer.ts +++ b/packages/kbn-dev-utils/src/serializers/any_instance_serizlizer.ts @@ -6,9 +6,12 @@ * Side Public License, v 1. */ -export function createAnyInstanceSerializer(Class: Function, name?: string) { +export function createAnyInstanceSerializer( + Class: Function, + name?: string | ((instance: any) => string) +) { return { test: (v: any) => v instanceof Class, - serialize: () => `<${name ?? Class.name}>`, + serialize: (v: any) => `<${typeof name === 'function' ? name(v) : name ?? Class.name}>`, }; } diff --git a/packages/kbn-es-query/BUILD.bazel b/packages/kbn-es-query/BUILD.bazel new file mode 100644 index 0000000000000..9639a1057cac3 --- /dev/null +++ b/packages/kbn-es-query/BUILD.bazel @@ -0,0 +1,133 @@ +load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project") +load("@npm//peggy:index.bzl", "peggy") +load("@build_bazel_rules_nodejs//:index.bzl", "js_library", "pkg_npm") + +PKG_BASE_NAME = "kbn-es-query" +PKG_REQUIRE_NAME = "@kbn/es-query" + +SOURCE_FILES = glob( + [ + "src/**/*", + ], + exclude = [ + "**/*.test.*", + "**/__fixtures__/**", + "**/__mocks__/**", + "**/__snapshots__/**", + ], +) + +SRCS = SOURCE_FILES + +filegroup( + name = "srcs", + srcs = SRCS, +) + +NPM_MODULE_EXTRA_FILES = [ + "package.json", + "README.md", +] + +SRC_DEPS = [ + "//packages/kbn-common-utils", + "//packages/kbn-config-schema", + "//packages/kbn-i18n", + "@npm//@elastic/elasticsearch", + "@npm//load-json-file", + "@npm//lodash", + "@npm//moment-timezone", + "@npm//tslib", +] + +TYPES_DEPS = [ + "@npm//@types/jest", + "@npm//@types/lodash", + "@npm//@types/moment-timezone", + "@npm//@types/node", +] + +DEPS = SRC_DEPS + TYPES_DEPS + +peggy( + name = "grammar", + data = [ + ":grammar/grammar.peggy" + ], + output_dir = True, + args = [ + "--allowed-start-rules", + "start,Literal", + "-o", + "$(@D)/index.js", + "./%s/grammar/grammar.peggy" % package_name() + ], +) + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = [ + "//:tsconfig.base.json", + ], +) + +ts_config( + name = "tsconfig_browser", + src = "tsconfig.browser.json", + deps = [ + "//:tsconfig.base.json", + "//:tsconfig.browser.json", + ], +) + +ts_project( + name = "tsc", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = True, + declaration_dir = "target_types", + declaration_map = True, + incremental = True, + out_dir = "target_node", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig", +) + +ts_project( + name = "tsc_browser", + args = ['--pretty'], + srcs = SRCS, + deps = DEPS, + declaration = False, + incremental = True, + out_dir = "target_web", + source_map = True, + root_dir = "src", + tsconfig = ":tsconfig_browser", +) + +js_library( + name = PKG_BASE_NAME, + srcs = NPM_MODULE_EXTRA_FILES + [":grammar"], + deps = DEPS + [":tsc", ":tsc_browser"], + package_name = PKG_REQUIRE_NAME, + visibility = ["//visibility:public"], +) + +pkg_npm( + name = "npm_module", + deps = [ + ":%s" % PKG_BASE_NAME, + ] +) + +filegroup( + name = "build", + srcs = [ + ":npm_module", + ], + visibility = ["//visibility:public"], +) diff --git a/packages/kbn-es-query/README.md b/packages/kbn-es-query/README.md new file mode 100644 index 0000000000000..644fc4d559eb6 --- /dev/null +++ b/packages/kbn-es-query/README.md @@ -0,0 +1,3 @@ +# @kbn/es-query + +Shared common (client and server sie) utilities shared across packages and plugins. \ No newline at end of file diff --git a/src/plugins/data/common/es_query/kuery/ast/kuery.peg b/packages/kbn-es-query/grammar/grammar.peggy similarity index 95% rename from src/plugins/data/common/es_query/kuery/ast/kuery.peg rename to packages/kbn-es-query/grammar/grammar.peggy index dbea96eaac5b2..8f5ef17340aa9 100644 --- a/src/plugins/data/common/es_query/kuery/ast/kuery.peg +++ b/packages/kbn-es-query/grammar/grammar.peggy @@ -1,6 +1,9 @@ -/** - * To generate the parsing module (kuery.js), run `grunt peg` - * To watch changes and generate on file change, run `grunt watch:peg` +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. */ // Initialization block diff --git a/packages/kbn-es-query/jest.config.js b/packages/kbn-es-query/jest.config.js new file mode 100644 index 0000000000000..306e12f34f698 --- /dev/null +++ b/packages/kbn-es-query/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-es-query'], +}; diff --git a/packages/kbn-es-query/package.json b/packages/kbn-es-query/package.json new file mode 100644 index 0000000000000..335ef61b8b360 --- /dev/null +++ b/packages/kbn-es-query/package.json @@ -0,0 +1,9 @@ +{ + "name": "@kbn/es-query", + "browser": "./target_web/index.js", + "main": "./target_node/index.js", + "types": "./target_types/index.d.ts", + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0", + "private": true +} \ No newline at end of file diff --git a/src/plugins/data/common/es_query/__fixtures__/index_pattern_response.ts b/packages/kbn-es-query/src/__fixtures__/index_pattern_response.ts similarity index 100% rename from src/plugins/data/common/es_query/__fixtures__/index_pattern_response.ts rename to packages/kbn-es-query/src/__fixtures__/index_pattern_response.ts diff --git a/src/plugins/data/common/es_query/es_query/build_es_query.test.ts b/packages/kbn-es-query/src/es_query/build_es_query.test.ts similarity index 95% rename from src/plugins/data/common/es_query/es_query/build_es_query.test.ts rename to packages/kbn-es-query/src/es_query/build_es_query.test.ts index fa9a2c85aaef5..b31269c4f8160 100644 --- a/src/plugins/data/common/es_query/es_query/build_es_query.test.ts +++ b/packages/kbn-es-query/src/es_query/build_es_query.test.ts @@ -10,15 +10,16 @@ import { buildEsQuery } from './build_es_query'; import { fromKueryExpression, toElasticsearchQuery } from '../kuery'; import { luceneStringToDsl } from './lucene_string_to_dsl'; import { decorateQuery } from './decorate_query'; -import { IIndexPattern } from '../../index_patterns'; -import { MatchAllFilter } from '../filters'; -import { fields } from '../../index_patterns/mocks'; -import { Query } from '../../query/types'; +import { MatchAllFilter, Query } from '../filters'; +import { fields } from '../filters/stubs'; +import { IndexPatternBase } from './types'; + +jest.mock('../kuery/grammar'); describe('build query', () => { - const indexPattern: IIndexPattern = ({ + const indexPattern: IndexPatternBase = { fields, - } as unknown) as IIndexPattern; + }; describe('buildEsQuery', () => { it('should return the parameters of an Elasticsearch bool query', () => { diff --git a/src/plugins/data/common/es_query/es_query/build_es_query.ts b/packages/kbn-es-query/src/es_query/build_es_query.ts similarity index 97% rename from src/plugins/data/common/es_query/es_query/build_es_query.ts rename to packages/kbn-es-query/src/es_query/build_es_query.ts index d7b3c630d1a6e..e8a494ec1b8e4 100644 --- a/src/plugins/data/common/es_query/es_query/build_es_query.ts +++ b/packages/kbn-es-query/src/es_query/build_es_query.ts @@ -10,8 +10,7 @@ import { groupBy, has, isEqual } from 'lodash'; import { buildQueryFromKuery } from './from_kuery'; import { buildQueryFromFilters } from './from_filters'; import { buildQueryFromLucene } from './from_lucene'; -import { Filter } from '../filters'; -import { Query } from '../../query/types'; +import { Filter, Query } from '../filters'; import { IndexPatternBase } from './types'; export interface EsQueryConfig { diff --git a/src/plugins/data/common/es_query/es_query/decorate_query.test.ts b/packages/kbn-es-query/src/es_query/decorate_query.test.ts similarity index 100% rename from src/plugins/data/common/es_query/es_query/decorate_query.test.ts rename to packages/kbn-es-query/src/es_query/decorate_query.test.ts diff --git a/src/plugins/data/common/es_query/es_query/decorate_query.ts b/packages/kbn-es-query/src/es_query/decorate_query.ts similarity index 100% rename from src/plugins/data/common/es_query/es_query/decorate_query.ts rename to packages/kbn-es-query/src/es_query/decorate_query.ts diff --git a/src/plugins/data/common/es_query/es_query/es_query_dsl.ts b/packages/kbn-es-query/src/es_query/es_query_dsl.ts similarity index 100% rename from src/plugins/data/common/es_query/es_query/es_query_dsl.ts rename to packages/kbn-es-query/src/es_query/es_query_dsl.ts diff --git a/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts b/packages/kbn-es-query/src/es_query/filter_matches_index.test.ts similarity index 92% rename from src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts rename to packages/kbn-es-query/src/es_query/filter_matches_index.test.ts index ad4d7ff8d78e2..bf4e1291ca438 100644 --- a/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts +++ b/packages/kbn-es-query/src/es_query/filter_matches_index.test.ts @@ -8,12 +8,12 @@ import { Filter } from '../filters'; import { filterMatchesIndex } from './filter_matches_index'; -import { IIndexPattern } from '../../index_patterns'; +import { IndexPatternBase } from './types'; describe('filterMatchesIndex', () => { it('should return true if the filter has no meta', () => { const filter = {} as Filter; - const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] } as IIndexPattern; + const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] } as IndexPatternBase; expect(filterMatchesIndex(filter, indexPattern)).toBe(true); }); @@ -26,35 +26,35 @@ describe('filterMatchesIndex', () => { it('should return true if the filter key matches a field name', () => { const filter = { meta: { index: 'foo', key: 'bar' } } as Filter; - const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] } as IIndexPattern; + const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] } as IndexPatternBase; expect(filterMatchesIndex(filter, indexPattern)).toBe(true); }); it('should return true if custom filter for the same index is passed', () => { const filter = { meta: { index: 'foo', key: 'bar', type: 'custom' } } as Filter; - const indexPattern = { id: 'foo', fields: [{ name: 'bara' }] } as IIndexPattern; + const indexPattern = { id: 'foo', fields: [{ name: 'bara' }] } as IndexPatternBase; expect(filterMatchesIndex(filter, indexPattern)).toBe(true); }); it('should return false if custom filter for a different index is passed', () => { const filter = { meta: { index: 'foo', key: 'bar', type: 'custom' } } as Filter; - const indexPattern = { id: 'food', fields: [{ name: 'bara' }] } as IIndexPattern; + const indexPattern = { id: 'food', fields: [{ name: 'bara' }] } as IndexPatternBase; expect(filterMatchesIndex(filter, indexPattern)).toBe(false); }); it('should return false if the filter key does not match a field name', () => { const filter = { meta: { index: 'foo', key: 'baz' } } as Filter; - const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] } as IIndexPattern; + const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] } as IndexPatternBase; expect(filterMatchesIndex(filter, indexPattern)).toBe(false); }); it('should return true if the filter has meta without a key', () => { const filter = { meta: { index: 'foo' } } as Filter; - const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] } as IIndexPattern; + const indexPattern = { id: 'foo', fields: [{ name: 'bar' }] } as IndexPatternBase; expect(filterMatchesIndex(filter, indexPattern)).toBe(true); }); diff --git a/src/plugins/data/common/es_query/es_query/filter_matches_index.ts b/packages/kbn-es-query/src/es_query/filter_matches_index.ts similarity index 100% rename from src/plugins/data/common/es_query/es_query/filter_matches_index.ts rename to packages/kbn-es-query/src/es_query/filter_matches_index.ts diff --git a/src/plugins/data/common/es_query/es_query/from_filters.test.ts b/packages/kbn-es-query/src/es_query/from_filters.test.ts similarity index 96% rename from src/plugins/data/common/es_query/es_query/from_filters.test.ts rename to packages/kbn-es-query/src/es_query/from_filters.test.ts index 4a60db48c41b6..e3a56b5a9d63d 100644 --- a/src/plugins/data/common/es_query/es_query/from_filters.test.ts +++ b/packages/kbn-es-query/src/es_query/from_filters.test.ts @@ -7,14 +7,14 @@ */ import { buildQueryFromFilters } from './from_filters'; -import { IIndexPattern } from '../../index_patterns'; import { ExistsFilter, Filter, MatchAllFilter } from '../filters'; -import { fields } from '../../index_patterns/mocks'; +import { fields } from '../filters/stubs'; +import { IndexPatternBase } from './types'; describe('build query', () => { - const indexPattern: IIndexPattern = ({ + const indexPattern: IndexPatternBase = { fields, - } as unknown) as IIndexPattern; + }; describe('buildQueryFromFilters', () => { test('should return the parameters of an Elasticsearch bool query', () => { diff --git a/src/plugins/data/common/es_query/es_query/from_filters.ts b/packages/kbn-es-query/src/es_query/from_filters.ts similarity index 100% rename from src/plugins/data/common/es_query/es_query/from_filters.ts rename to packages/kbn-es-query/src/es_query/from_filters.ts diff --git a/src/plugins/data/common/es_query/es_query/from_kuery.test.ts b/packages/kbn-es-query/src/es_query/from_kuery.test.ts similarity index 91% rename from src/plugins/data/common/es_query/es_query/from_kuery.test.ts rename to packages/kbn-es-query/src/es_query/from_kuery.test.ts index 920102566f8b3..2458013854393 100644 --- a/src/plugins/data/common/es_query/es_query/from_kuery.test.ts +++ b/packages/kbn-es-query/src/es_query/from_kuery.test.ts @@ -8,14 +8,16 @@ import { buildQueryFromKuery } from './from_kuery'; import { fromKueryExpression, toElasticsearchQuery } from '../kuery'; -import { IIndexPattern } from '../../index_patterns'; -import { fields } from '../../index_patterns/mocks'; -import { Query } from '../../query/types'; +import { fields } from '../filters/stubs'; +import { IndexPatternBase } from './types'; +import { Query } from '..'; + +jest.mock('../kuery/grammar'); describe('build query', () => { - const indexPattern: IIndexPattern = ({ + const indexPattern: IndexPatternBase = { fields, - } as unknown) as IIndexPattern; + }; describe('buildQueryFromKuery', () => { test('should return the parameters of an Elasticsearch bool query', () => { diff --git a/src/plugins/data/common/es_query/es_query/from_kuery.ts b/packages/kbn-es-query/src/es_query/from_kuery.ts similarity index 96% rename from src/plugins/data/common/es_query/es_query/from_kuery.ts rename to packages/kbn-es-query/src/es_query/from_kuery.ts index 3eccfd8776113..efe8b26a81412 100644 --- a/src/plugins/data/common/es_query/es_query/from_kuery.ts +++ b/packages/kbn-es-query/src/es_query/from_kuery.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ +import { Query } from '../filters'; import { fromKueryExpression, toElasticsearchQuery, nodeTypes, KueryNode } from '../kuery'; import { IndexPatternBase } from './types'; -import { Query } from '../../query/types'; export function buildQueryFromKuery( indexPattern: IndexPatternBase | undefined, diff --git a/src/plugins/data/common/es_query/es_query/from_lucene.test.ts b/packages/kbn-es-query/src/es_query/from_lucene.test.ts similarity index 98% rename from src/plugins/data/common/es_query/es_query/from_lucene.test.ts rename to packages/kbn-es-query/src/es_query/from_lucene.test.ts index 2438a4b40e256..e4ca435ae8862 100644 --- a/src/plugins/data/common/es_query/es_query/from_lucene.test.ts +++ b/packages/kbn-es-query/src/es_query/from_lucene.test.ts @@ -9,7 +9,7 @@ import { buildQueryFromLucene } from './from_lucene'; import { decorateQuery } from './decorate_query'; import { luceneStringToDsl } from './lucene_string_to_dsl'; -import { Query } from '../../query/types'; +import { Query } from '..'; describe('build query', () => { describe('buildQueryFromLucene', () => { diff --git a/src/plugins/data/common/es_query/es_query/from_lucene.ts b/packages/kbn-es-query/src/es_query/from_lucene.ts similarity index 95% rename from src/plugins/data/common/es_query/es_query/from_lucene.ts rename to packages/kbn-es-query/src/es_query/from_lucene.ts index 6485281cc0fb3..cba789513c983 100644 --- a/src/plugins/data/common/es_query/es_query/from_lucene.ts +++ b/packages/kbn-es-query/src/es_query/from_lucene.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ +import { Query } from '..'; import { decorateQuery } from './decorate_query'; import { luceneStringToDsl } from './lucene_string_to_dsl'; -import { Query } from '../../query/types'; export function buildQueryFromLucene( queries: Query[], diff --git a/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts b/packages/kbn-es-query/src/es_query/handle_nested_filter.test.ts similarity index 98% rename from src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts rename to packages/kbn-es-query/src/es_query/handle_nested_filter.test.ts index 24852ebf33bda..9c7b6070c7ec0 100644 --- a/src/plugins/data/common/es_query/es_query/handle_nested_filter.test.ts +++ b/packages/kbn-es-query/src/es_query/handle_nested_filter.test.ts @@ -7,7 +7,7 @@ */ import { handleNestedFilter } from './handle_nested_filter'; -import { fields } from '../../index_patterns/mocks'; +import { fields } from '../filters/stubs'; import { buildPhraseFilter, buildQueryFilter } from '../filters'; import { IndexPatternBase } from './types'; diff --git a/src/plugins/data/common/es_query/es_query/handle_nested_filter.ts b/packages/kbn-es-query/src/es_query/handle_nested_filter.ts similarity index 100% rename from src/plugins/data/common/es_query/es_query/handle_nested_filter.ts rename to packages/kbn-es-query/src/es_query/handle_nested_filter.ts diff --git a/src/plugins/data/common/es_query/es_query/index.ts b/packages/kbn-es-query/src/es_query/index.ts similarity index 92% rename from src/plugins/data/common/es_query/es_query/index.ts rename to packages/kbn-es-query/src/es_query/index.ts index ecc7c8ba5a9f5..beba50f50dd81 100644 --- a/src/plugins/data/common/es_query/es_query/index.ts +++ b/packages/kbn-es-query/src/es_query/index.ts @@ -10,5 +10,4 @@ export { buildEsQuery, EsQueryConfig } from './build_es_query'; export { buildQueryFromFilters } from './from_filters'; export { luceneStringToDsl } from './lucene_string_to_dsl'; export { decorateQuery } from './decorate_query'; -export { getEsQueryConfig } from './get_es_query_config'; export { IndexPatternBase, IndexPatternFieldBase, IFieldSubType } from './types'; diff --git a/src/plugins/data/common/es_query/es_query/lucene_string_to_dsl.test.ts b/packages/kbn-es-query/src/es_query/lucene_string_to_dsl.test.ts similarity index 100% rename from src/plugins/data/common/es_query/es_query/lucene_string_to_dsl.test.ts rename to packages/kbn-es-query/src/es_query/lucene_string_to_dsl.test.ts diff --git a/src/plugins/data/common/es_query/es_query/lucene_string_to_dsl.ts b/packages/kbn-es-query/src/es_query/lucene_string_to_dsl.ts similarity index 100% rename from src/plugins/data/common/es_query/es_query/lucene_string_to_dsl.ts rename to packages/kbn-es-query/src/es_query/lucene_string_to_dsl.ts diff --git a/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts b/packages/kbn-es-query/src/es_query/migrate_filter.test.ts similarity index 100% rename from src/plugins/data/common/es_query/es_query/migrate_filter.test.ts rename to packages/kbn-es-query/src/es_query/migrate_filter.test.ts diff --git a/src/plugins/data/common/es_query/es_query/migrate_filter.ts b/packages/kbn-es-query/src/es_query/migrate_filter.ts similarity index 100% rename from src/plugins/data/common/es_query/es_query/migrate_filter.ts rename to packages/kbn-es-query/src/es_query/migrate_filter.ts diff --git a/src/plugins/data/common/es_query/es_query/types.ts b/packages/kbn-es-query/src/es_query/types.ts similarity index 98% rename from src/plugins/data/common/es_query/es_query/types.ts rename to packages/kbn-es-query/src/es_query/types.ts index 9282072cd444d..ca6a542779053 100644 --- a/src/plugins/data/common/es_query/es_query/types.ts +++ b/packages/kbn-es-query/src/es_query/types.ts @@ -34,4 +34,5 @@ export interface IndexPatternFieldBase { export interface IndexPatternBase { fields: IndexPatternFieldBase[]; id?: string; + title?: string; } diff --git a/src/plugins/data/common/es_query/filters/build_filter.test.ts b/packages/kbn-es-query/src/filters/build_filter.test.ts similarity index 94% rename from src/plugins/data/common/es_query/filters/build_filter.test.ts rename to packages/kbn-es-query/src/filters/build_filter.test.ts index 33221e3838d9e..d7cf7938b3737 100644 --- a/src/plugins/data/common/es_query/filters/build_filter.test.ts +++ b/packages/kbn-es-query/src/filters/build_filter.test.ts @@ -7,9 +7,15 @@ */ import { buildFilter, FilterStateStore, FILTERS } from '.'; -import { stubIndexPattern, stubFields } from '../../../common/stubs'; +import { IndexPatternBase } from '..'; +import { fields as stubFields } from './stubs'; describe('buildFilter', () => { + const stubIndexPattern: IndexPatternBase = { + id: 'logstash-*', + fields: stubFields, + }; + it('should build phrase filters', () => { const params = 'foo'; const alias = 'bar'; diff --git a/src/plugins/data/common/es_query/filters/build_filters.ts b/packages/kbn-es-query/src/filters/build_filters.ts similarity index 94% rename from src/plugins/data/common/es_query/filters/build_filters.ts rename to packages/kbn-es-query/src/filters/build_filters.ts index 1d8d67b6e937f..ae27e64f3a41d 100644 --- a/src/plugins/data/common/es_query/filters/build_filters.ts +++ b/packages/kbn-es-query/src/filters/build_filters.ts @@ -6,18 +6,16 @@ * Side Public License, v 1. */ -import { IndexPatternFieldBase, IndexPatternBase } from '../..'; - import { Filter, FILTERS, - FilterStateStore, - FilterMeta, buildPhraseFilter, buildPhrasesFilter, buildRangeFilter, buildExistsFilter, } from '.'; +import { IndexPatternFieldBase, IndexPatternBase } from '..'; +import { FilterMeta, FilterStateStore } from './types'; export function buildFilter( indexPattern: IndexPatternBase, diff --git a/src/plugins/data/common/es_query/filters/custom_filter.ts b/packages/kbn-es-query/src/filters/custom_filter.ts similarity index 91% rename from src/plugins/data/common/es_query/filters/custom_filter.ts rename to packages/kbn-es-query/src/filters/custom_filter.ts index bab226157ddd1..aa9e798a5b1ac 100644 --- a/src/plugins/data/common/es_query/filters/custom_filter.ts +++ b/packages/kbn-es-query/src/filters/custom_filter.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Filter } from './meta_filter'; +import { Filter } from './types'; export type CustomFilter = Filter & { query: any; diff --git a/src/plugins/data/common/es_query/filters/exists_filter.test.ts b/packages/kbn-es-query/src/filters/exists_filter.test.ts similarity index 81% rename from src/plugins/data/common/es_query/filters/exists_filter.test.ts rename to packages/kbn-es-query/src/filters/exists_filter.test.ts index 848fcead5f5d9..83976d45f8e04 100644 --- a/src/plugins/data/common/es_query/filters/exists_filter.test.ts +++ b/packages/kbn-es-query/src/filters/exists_filter.test.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ +import { IndexPatternBase } from '..'; import { buildExistsFilter, getExistsFilterField } from './exists_filter'; -import { IIndexPattern } from '../../index_patterns'; -import { fields } from '../../index_patterns/fields/fields.mocks'; +import { fields } from './stubs/fields.mocks'; describe('exists filter', function () { - const indexPattern: IIndexPattern = ({ + const indexPattern: IndexPatternBase = { fields, - } as unknown) as IIndexPattern; + }; describe('getExistsFilterField', function () { it('should return the name of the field an exists query is targeting', () => { diff --git a/src/plugins/data/common/es_query/filters/exists_filter.ts b/packages/kbn-es-query/src/filters/exists_filter.ts similarity index 95% rename from src/plugins/data/common/es_query/filters/exists_filter.ts rename to packages/kbn-es-query/src/filters/exists_filter.ts index 7a09adb7d9ed6..7785a62261fde 100644 --- a/src/plugins/data/common/es_query/filters/exists_filter.ts +++ b/packages/kbn-es-query/src/filters/exists_filter.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { Filter, FilterMeta } from './meta_filter'; import { IndexPatternFieldBase, IndexPatternBase } from '..'; +import { Filter, FilterMeta } from './types'; export type ExistsFilterMeta = FilterMeta; diff --git a/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.test.ts b/packages/kbn-es-query/src/filters/geo_bounding_box_filter.test.ts similarity index 100% rename from src/plugins/data/common/es_query/filters/geo_bounding_box_filter.test.ts rename to packages/kbn-es-query/src/filters/geo_bounding_box_filter.test.ts diff --git a/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts b/packages/kbn-es-query/src/filters/geo_bounding_box_filter.ts similarity index 93% rename from src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts rename to packages/kbn-es-query/src/filters/geo_bounding_box_filter.ts index 987055405886c..b309515109d48 100644 --- a/src/plugins/data/common/es_query/filters/geo_bounding_box_filter.ts +++ b/packages/kbn-es-query/src/filters/geo_bounding_box_filter.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Filter, FilterMeta, LatLon } from './meta_filter'; +import { Filter, FilterMeta, LatLon } from './types'; export type GeoBoundingBoxFilterMeta = FilterMeta & { params: { diff --git a/src/plugins/data/common/es_query/filters/geo_polygon_filter.test.ts b/packages/kbn-es-query/src/filters/geo_polygon_filter.test.ts similarity index 100% rename from src/plugins/data/common/es_query/filters/geo_polygon_filter.test.ts rename to packages/kbn-es-query/src/filters/geo_polygon_filter.test.ts diff --git a/src/plugins/data/common/es_query/filters/geo_polygon_filter.ts b/packages/kbn-es-query/src/filters/geo_polygon_filter.ts similarity index 93% rename from src/plugins/data/common/es_query/filters/geo_polygon_filter.ts rename to packages/kbn-es-query/src/filters/geo_polygon_filter.ts index 5b284f1b6e3a1..42e417f2c88a4 100644 --- a/src/plugins/data/common/es_query/filters/geo_polygon_filter.ts +++ b/packages/kbn-es-query/src/filters/geo_polygon_filter.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Filter, FilterMeta, LatLon } from './meta_filter'; +import { Filter, FilterMeta, LatLon } from './types'; export type GeoPolygonFilterMeta = FilterMeta & { params: { diff --git a/src/plugins/data/common/es_query/filters/get_filter_field.test.ts b/packages/kbn-es-query/src/filters/get_filter_field.test.ts similarity index 87% rename from src/plugins/data/common/es_query/filters/get_filter_field.test.ts rename to packages/kbn-es-query/src/filters/get_filter_field.test.ts index b9ae8f3abaa0c..0425aa6cc8c72 100644 --- a/src/plugins/data/common/es_query/filters/get_filter_field.test.ts +++ b/packages/kbn-es-query/src/filters/get_filter_field.test.ts @@ -9,14 +9,14 @@ import { buildPhraseFilter } from './phrase_filter'; import { buildQueryFilter } from './query_string_filter'; import { getFilterField } from './get_filter_field'; -import { IIndexPattern } from '../../index_patterns'; -import { fields } from '../../index_patterns/fields/fields.mocks'; +import { IndexPatternBase } from '..'; +import { fields } from './stubs/fields.mocks'; describe('getFilterField', function () { - const indexPattern: IIndexPattern = ({ + const indexPattern: IndexPatternBase = { id: 'logstash-*', fields, - } as unknown) as IIndexPattern; + }; it('should return the field name from known filter types that target a specific field', () => { const field = indexPattern.fields.find((patternField) => patternField.name === 'extension'); diff --git a/src/plugins/data/common/es_query/filters/get_filter_field.ts b/packages/kbn-es-query/src/filters/get_filter_field.ts similarity index 97% rename from src/plugins/data/common/es_query/filters/get_filter_field.ts rename to packages/kbn-es-query/src/filters/get_filter_field.ts index 0782e46bac784..4b540beb03754 100644 --- a/src/plugins/data/common/es_query/filters/get_filter_field.ts +++ b/packages/kbn-es-query/src/filters/get_filter_field.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Filter } from './meta_filter'; +import { Filter } from './types'; import { getExistsFilterField, isExistsFilter } from './exists_filter'; import { getGeoBoundingBoxFilterField, isGeoBoundingBoxFilter } from './geo_bounding_box_filter'; import { getGeoPolygonFilterField, isGeoPolygonFilter } from './geo_polygon_filter'; diff --git a/src/plugins/data/common/es_query/filters/get_filter_params.test.ts b/packages/kbn-es-query/src/filters/get_filter_params.test.ts similarity index 100% rename from src/plugins/data/common/es_query/filters/get_filter_params.test.ts rename to packages/kbn-es-query/src/filters/get_filter_params.test.ts diff --git a/src/plugins/data/common/es_query/filters/get_filter_params.ts b/packages/kbn-es-query/src/filters/get_filter_params.ts similarity index 100% rename from src/plugins/data/common/es_query/filters/get_filter_params.ts rename to packages/kbn-es-query/src/filters/get_filter_params.ts diff --git a/src/plugins/data/common/es_query/filters/index.ts b/packages/kbn-es-query/src/filters/index.ts similarity index 89% rename from src/plugins/data/common/es_query/filters/index.ts rename to packages/kbn-es-query/src/filters/index.ts index fe7cdadabaee3..90c1675e8a3cf 100644 --- a/src/plugins/data/common/es_query/filters/index.ts +++ b/packages/kbn-es-query/src/filters/index.ts @@ -7,7 +7,7 @@ */ import { omit, get } from 'lodash'; -import { Filter } from './meta_filter'; +import { Filter } from './types'; export * from './build_filters'; export * from './custom_filter'; @@ -24,7 +24,7 @@ export * from './phrases_filter'; export * from './query_string_filter'; export * from './range_filter'; -export * from './types'; +export { Query, Filter, FILTERS, LatLon, FilterStateStore, FieldFilter, FilterMeta } from './types'; /** * Clean out any invalid attributes from the filters diff --git a/src/plugins/data/common/es_query/filters/match_all_filter.ts b/packages/kbn-es-query/src/filters/match_all_filter.ts similarity index 92% rename from src/plugins/data/common/es_query/filters/match_all_filter.ts rename to packages/kbn-es-query/src/filters/match_all_filter.ts index 36eb5ee1fce18..a3fdd740986d4 100644 --- a/src/plugins/data/common/es_query/filters/match_all_filter.ts +++ b/packages/kbn-es-query/src/filters/match_all_filter.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Filter, FilterMeta } from './meta_filter'; +import { Filter, FilterMeta } from './types'; export interface MatchAllFilterMeta extends FilterMeta { field: any; diff --git a/src/plugins/data/common/es_query/filters/meta_filter.ts b/packages/kbn-es-query/src/filters/meta_filter.ts similarity index 69% rename from src/plugins/data/common/es_query/filters/meta_filter.ts rename to packages/kbn-es-query/src/filters/meta_filter.ts index 87455cf1cb763..25fd26c410826 100644 --- a/src/plugins/data/common/es_query/filters/meta_filter.ts +++ b/packages/kbn-es-query/src/filters/meta_filter.ts @@ -6,49 +6,7 @@ * Side Public License, v 1. */ -export enum FilterStateStore { - APP_STATE = 'appState', - GLOBAL_STATE = 'globalState', -} - -// eslint-disable-next-line -export type FilterState = { - store: FilterStateStore; -}; - -type FilterFormatterFunction = (value: any) => string; -export interface FilterValueFormatter { - convert: FilterFormatterFunction; - getConverterFor: (type: string) => FilterFormatterFunction; -} - -// eslint-disable-next-line -export type FilterMeta = { - alias: string | null; - disabled: boolean; - negate: boolean; - // controlledBy is there to identify who owns the filter - controlledBy?: string; - // index and type are optional only because when you create a new filter, there are no defaults - index?: string; - isMultiIndex?: boolean; - type?: string; - key?: string; - params?: any; - value?: string; -}; - -// eslint-disable-next-line -export type Filter = { - $state?: FilterState; - meta: FilterMeta; - query?: any; -}; - -export interface LatLon { - lat: number; - lon: number; -} +import { Filter, FilterMeta, FilterState, FilterStateStore } from './types'; export const buildEmptyFilter = (isPinned: boolean, index?: string): Filter => { const meta: FilterMeta = { diff --git a/src/plugins/data/common/es_query/filters/missing_filter.test.ts b/packages/kbn-es-query/src/filters/missing_filter.test.ts similarity index 100% rename from src/plugins/data/common/es_query/filters/missing_filter.test.ts rename to packages/kbn-es-query/src/filters/missing_filter.test.ts diff --git a/src/plugins/data/common/es_query/filters/missing_filter.ts b/packages/kbn-es-query/src/filters/missing_filter.ts similarity index 93% rename from src/plugins/data/common/es_query/filters/missing_filter.ts rename to packages/kbn-es-query/src/filters/missing_filter.ts index d0d337283ca60..0e10cb18d3c21 100644 --- a/src/plugins/data/common/es_query/filters/missing_filter.ts +++ b/packages/kbn-es-query/src/filters/missing_filter.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Filter, FilterMeta } from './meta_filter'; +import { Filter, FilterMeta } from './types'; export type MissingFilterMeta = FilterMeta; diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.test.ts b/packages/kbn-es-query/src/filters/phrase_filter.test.ts similarity index 85% rename from src/plugins/data/common/es_query/filters/phrase_filter.test.ts rename to packages/kbn-es-query/src/filters/phrase_filter.test.ts index 513f0e29b5b24..7e6ccadc800d4 100644 --- a/src/plugins/data/common/es_query/filters/phrase_filter.test.ts +++ b/packages/kbn-es-query/src/filters/phrase_filter.test.ts @@ -11,16 +11,17 @@ import { buildPhraseFilter, getPhraseFilterField, } from './phrase_filter'; -import { fields, getField } from '../../index_patterns/mocks'; -import { IIndexPattern } from '../../index_patterns'; +import { fields, getField } from '../filters/stubs'; +import { IndexPatternBase } from '../es_query'; describe('Phrase filter builder', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPatternBase; beforeEach(() => { indexPattern = { id: 'id', - } as IIndexPattern; + fields, + }; }); it('should be a function', () => { @@ -30,7 +31,7 @@ describe('Phrase filter builder', () => { it('should return a match query filter when passed a standard string field', () => { const field = getField('extension'); - expect(buildPhraseFilter(field, 'jpg', indexPattern)).toEqual({ + expect(buildPhraseFilter(field!, 'jpg', indexPattern)).toEqual({ meta: { index: 'id', }, @@ -45,7 +46,7 @@ describe('Phrase filter builder', () => { it('should return a match query filter when passed a standard numeric field', () => { const field = getField('bytes'); - expect(buildPhraseFilter(field, '5', indexPattern)).toEqual({ + expect(buildPhraseFilter(field!, '5', indexPattern)).toEqual({ meta: { index: 'id', }, @@ -60,7 +61,7 @@ describe('Phrase filter builder', () => { it('should return a match query filter when passed a standard bool field', () => { const field = getField('ssl'); - expect(buildPhraseFilter(field, 'true', indexPattern)).toEqual({ + expect(buildPhraseFilter(field!, 'true', indexPattern)).toEqual({ meta: { index: 'id', }, @@ -75,7 +76,7 @@ describe('Phrase filter builder', () => { it('should return a script filter when passed a scripted field', () => { const field = getField('script number'); - expect(buildPhraseFilter(field, 5, indexPattern)).toEqual({ + expect(buildPhraseFilter(field!, 5, indexPattern)).toEqual({ meta: { index: 'id', field: 'script number', @@ -95,7 +96,7 @@ describe('Phrase filter builder', () => { it('should return a script filter when passed a scripted field with numeric conversion', () => { const field = getField('script number'); - expect(buildPhraseFilter(field, '5', indexPattern)).toEqual({ + expect(buildPhraseFilter(field!, '5', indexPattern)).toEqual({ meta: { index: 'id', field: 'script number', @@ -140,9 +141,9 @@ describe('buildInlineScriptForPhraseFilter', () => { }); describe('getPhraseFilterField', function () { - const indexPattern: IIndexPattern = ({ + const indexPattern: IndexPatternBase = { fields, - } as unknown) as IIndexPattern; + }; it('should return the name of the field a phrase query is targeting', () => { const field = indexPattern.fields.find((patternField) => patternField.name === 'extension'); diff --git a/src/plugins/data/common/es_query/filters/phrase_filter.ts b/packages/kbn-es-query/src/filters/phrase_filter.ts similarity index 98% rename from src/plugins/data/common/es_query/filters/phrase_filter.ts rename to packages/kbn-es-query/src/filters/phrase_filter.ts index 68ad16cb31d42..fd5ded010bf07 100644 --- a/src/plugins/data/common/es_query/filters/phrase_filter.ts +++ b/packages/kbn-es-query/src/filters/phrase_filter.ts @@ -7,7 +7,7 @@ */ import type { estypes } from '@elastic/elasticsearch'; import { get, isPlainObject } from 'lodash'; -import { Filter, FilterMeta } from './meta_filter'; +import { Filter, FilterMeta } from './types'; import { IndexPatternFieldBase, IndexPatternBase } from '..'; export type PhraseFilterMeta = FilterMeta & { diff --git a/src/plugins/data/common/es_query/filters/phrases_filter.test.ts b/packages/kbn-es-query/src/filters/phrases_filter.test.ts similarity index 82% rename from src/plugins/data/common/es_query/filters/phrases_filter.test.ts rename to packages/kbn-es-query/src/filters/phrases_filter.test.ts index 68ef69ba76eeb..5e742187cdf05 100644 --- a/src/plugins/data/common/es_query/filters/phrases_filter.test.ts +++ b/packages/kbn-es-query/src/filters/phrases_filter.test.ts @@ -7,13 +7,13 @@ */ import { buildPhrasesFilter, getPhrasesFilterField } from './phrases_filter'; -import { IIndexPattern } from '../../index_patterns'; -import { fields } from '../../index_patterns/fields/fields.mocks'; +import { IndexPatternBase } from '..'; +import { fields } from './stubs/fields.mocks'; describe('phrases filter', function () { - const indexPattern: IIndexPattern = ({ + const indexPattern: IndexPatternBase = { fields, - } as unknown) as IIndexPattern; + }; describe('getPhrasesFilterField', function () { it('should return the name of the field a phrases query is targeting', () => { diff --git a/src/plugins/data/common/es_query/filters/phrases_filter.ts b/packages/kbn-es-query/src/filters/phrases_filter.ts similarity index 97% rename from src/plugins/data/common/es_query/filters/phrases_filter.ts rename to packages/kbn-es-query/src/filters/phrases_filter.ts index 7f7831e1c7978..451a0a46789fb 100644 --- a/src/plugins/data/common/es_query/filters/phrases_filter.ts +++ b/packages/kbn-es-query/src/filters/phrases_filter.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Filter, FilterMeta } from './meta_filter'; +import { Filter, FilterMeta } from './types'; import { getPhraseScript } from './phrase_filter'; import { FILTERS } from './index'; import { IndexPatternFieldBase, IndexPatternBase } from '../es_query'; diff --git a/src/plugins/data/common/es_query/filters/query_string_filter.test.ts b/packages/kbn-es-query/src/filters/query_string_filter.test.ts similarity index 100% rename from src/plugins/data/common/es_query/filters/query_string_filter.test.ts rename to packages/kbn-es-query/src/filters/query_string_filter.test.ts diff --git a/src/plugins/data/common/es_query/filters/query_string_filter.ts b/packages/kbn-es-query/src/filters/query_string_filter.ts similarity index 94% rename from src/plugins/data/common/es_query/filters/query_string_filter.ts rename to packages/kbn-es-query/src/filters/query_string_filter.ts index bf960f0ac04b3..b4774d5e65a67 100644 --- a/src/plugins/data/common/es_query/filters/query_string_filter.ts +++ b/packages/kbn-es-query/src/filters/query_string_filter.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { Filter, FilterMeta } from './meta_filter'; +import { Filter, FilterMeta } from './types'; export type QueryStringFilterMeta = FilterMeta; diff --git a/src/plugins/data/common/es_query/filters/range_filter.test.ts b/packages/kbn-es-query/src/filters/range_filter.test.ts similarity index 88% rename from src/plugins/data/common/es_query/filters/range_filter.test.ts rename to packages/kbn-es-query/src/filters/range_filter.test.ts index 30e52b21d52b7..634471b1d4d9e 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.test.ts +++ b/packages/kbn-es-query/src/filters/range_filter.test.ts @@ -8,7 +8,7 @@ import { each } from 'lodash'; import { buildRangeFilter, getRangeFilterField, RangeFilter } from './range_filter'; -import { fields, getField } from '../../index_patterns/mocks'; +import { fields, getField } from '../filters/stubs'; import { IndexPatternBase, IndexPatternFieldBase } from '../es_query'; describe('Range filter builder', () => { @@ -27,7 +27,7 @@ describe('Range filter builder', () => { it('should return a range filter when passed a standard field', () => { const field = getField('bytes'); - expect(buildRangeFilter(field, { gte: 1, lte: 3 }, indexPattern)).toEqual({ + expect(buildRangeFilter(field!, { gte: 1, lte: 3 }, indexPattern)).toEqual({ meta: { index: 'id', params: {}, @@ -44,7 +44,7 @@ describe('Range filter builder', () => { it('should return a script filter when passed a scripted field', () => { const field = getField('script number'); - expect(buildRangeFilter(field, { gte: 1, lte: 3 }, indexPattern)).toEqual({ + expect(buildRangeFilter(field!, { gte: 1, lte: 3 }, indexPattern)).toEqual({ meta: { field: 'script number', index: 'id', @@ -67,7 +67,7 @@ describe('Range filter builder', () => { it('should convert strings to numbers if the field is scripted and type number', () => { const field = getField('script number'); - expect(buildRangeFilter(field, { gte: '1', lte: '3' }, indexPattern)).toEqual({ + expect(buildRangeFilter(field!, { gte: '1', lte: '3' }, indexPattern)).toEqual({ meta: { field: 'script number', index: 'id', @@ -95,7 +95,7 @@ describe('Range filter builder', () => { `gte(() -> { ${field!.script} }, params.gte) && ` + `lte(() -> { ${field!.script} }, params.lte)`; - const rangeFilter = buildRangeFilter(field, { gte: 1, lte: 3 }, indexPattern); + const rangeFilter = buildRangeFilter(field!, { gte: 1, lte: 3 }, indexPattern); expect(rangeFilter.script!.script.source).toBe(expected); }); @@ -104,11 +104,11 @@ describe('Range filter builder', () => { const field = getField('script number'); expect(() => { - buildRangeFilter(field, { gte: 1, gt: 3 }, indexPattern); + buildRangeFilter(field!, { gte: 1, gt: 3 }, indexPattern); }).toThrowError(); expect(() => { - buildRangeFilter(field, { lte: 1, lt: 3 }, indexPattern); + buildRangeFilter(field!, { lte: 1, lt: 3 }, indexPattern); }).toThrowError(); }); @@ -120,7 +120,7 @@ describe('Range filter builder', () => { [key]: 5, }; - const filter = buildRangeFilter(field, params, indexPattern); + const filter = buildRangeFilter(field!, params, indexPattern); const script = filter.script!.script; expect(script.source).toBe('(' + field!.script + ')' + operator + key); @@ -134,7 +134,7 @@ describe('Range filter builder', () => { let filter: RangeFilter; beforeEach(() => { - field = getField('script number'); + field = getField('script number')!; filter = buildRangeFilter(field, { gte: 0, lt: Infinity }, indexPattern); }); @@ -164,7 +164,7 @@ describe('Range filter builder', () => { let filter: RangeFilter; beforeEach(() => { - field = getField('script number'); + field = getField('script number')!; filter = buildRangeFilter(field, { gte: -Infinity, lt: Infinity }, indexPattern); }); diff --git a/src/plugins/data/common/es_query/filters/range_filter.ts b/packages/kbn-es-query/src/filters/range_filter.ts similarity index 99% rename from src/plugins/data/common/es_query/filters/range_filter.ts rename to packages/kbn-es-query/src/filters/range_filter.ts index e44e23f64936e..2df313eb7dd76 100644 --- a/src/plugins/data/common/es_query/filters/range_filter.ts +++ b/packages/kbn-es-query/src/filters/range_filter.ts @@ -7,7 +7,7 @@ */ import type { estypes } from '@elastic/elasticsearch'; import { map, reduce, mapValues, get, keys, pickBy } from 'lodash'; -import { Filter, FilterMeta } from './meta_filter'; +import { Filter, FilterMeta } from './types'; import { IndexPatternBase, IndexPatternFieldBase } from '..'; const OPERANDS_IN_RANGE = 2; diff --git a/src/plugins/data/common/es_query/filters/stubs/exists_filter.ts b/packages/kbn-es-query/src/filters/stubs/exists_filter.ts similarity index 100% rename from src/plugins/data/common/es_query/filters/stubs/exists_filter.ts rename to packages/kbn-es-query/src/filters/stubs/exists_filter.ts diff --git a/packages/kbn-es-query/src/filters/stubs/fields.mocks.ts b/packages/kbn-es-query/src/filters/stubs/fields.mocks.ts new file mode 100644 index 0000000000000..2507293b87477 --- /dev/null +++ b/packages/kbn-es-query/src/filters/stubs/fields.mocks.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { IndexPatternFieldBase } from '../..'; + +/** + * Base index pattern fields for testing + */ +export const fields: IndexPatternFieldBase[] = [ + { + name: 'bytes', + type: 'number', + scripted: false, + }, + { + name: 'ssl', + type: 'boolean', + scripted: false, + }, + { + name: '@timestamp', + type: 'date', + scripted: false, + }, + { + name: 'extension', + type: 'string', + scripted: false, + }, + { + name: 'machine.os', + type: 'string', + scripted: false, + }, + { + name: 'machine.os.raw', + type: 'string', + scripted: false, + }, + { + name: 'script number', + type: 'number', + scripted: true, + script: '1234', + lang: 'expression', + }, + { + name: 'script date', + type: 'date', + scripted: true, + script: '1234', + lang: 'painless', + }, + { + name: 'script string', + type: 'string', + scripted: true, + script: '1234', + lang: 'painless', + }, + { + name: 'nestedField.child', + type: 'string', + scripted: false, + subType: { nested: { path: 'nestedField' } }, + }, + { + name: 'nestedField.nestedChild.doublyNestedChild', + type: 'string', + scripted: false, + subType: { nested: { path: 'nestedField.nestedChild' } }, + }, +]; + +export const getField = (name: string) => fields.find((field) => field.name === name); diff --git a/packages/kbn-es-query/src/filters/stubs/index.ts b/packages/kbn-es-query/src/filters/stubs/index.ts new file mode 100644 index 0000000000000..e4d99b5082257 --- /dev/null +++ b/packages/kbn-es-query/src/filters/stubs/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './exists_filter'; +export * from './phrase_filter'; +export * from './phrases_filter'; +export * from './range_filter'; +export * from './fields.mocks'; diff --git a/src/plugins/data/common/es_query/filters/stubs/phrase_filter.ts b/packages/kbn-es-query/src/filters/stubs/phrase_filter.ts similarity index 100% rename from src/plugins/data/common/es_query/filters/stubs/phrase_filter.ts rename to packages/kbn-es-query/src/filters/stubs/phrase_filter.ts diff --git a/src/plugins/data/common/es_query/filters/stubs/phrases_filter.ts b/packages/kbn-es-query/src/filters/stubs/phrases_filter.ts similarity index 100% rename from src/plugins/data/common/es_query/filters/stubs/phrases_filter.ts rename to packages/kbn-es-query/src/filters/stubs/phrases_filter.ts diff --git a/src/plugins/data/common/es_query/filters/stubs/range_filter.ts b/packages/kbn-es-query/src/filters/stubs/range_filter.ts similarity index 100% rename from src/plugins/data/common/es_query/filters/stubs/range_filter.ts rename to packages/kbn-es-query/src/filters/stubs/range_filter.ts diff --git a/src/plugins/data/common/es_query/filters/types.ts b/packages/kbn-es-query/src/filters/types.ts similarity index 59% rename from src/plugins/data/common/es_query/filters/types.ts rename to packages/kbn-es-query/src/filters/types.ts index a007189d81a03..13e4a941b9166 100644 --- a/src/plugins/data/common/es_query/filters/types.ts +++ b/packages/kbn-es-query/src/filters/types.ts @@ -40,3 +40,47 @@ export enum FILTERS { GEO_POLYGON = 'geo_polygon', SPATIAL_FILTER = 'spatial_filter', } + +export enum FilterStateStore { + APP_STATE = 'appState', + GLOBAL_STATE = 'globalState', +} + +// eslint-disable-next-line +export type FilterState = { + store: FilterStateStore; +}; + +// eslint-disable-next-line +export type FilterMeta = { + alias: string | null; + disabled: boolean; + negate: boolean; + // controlledBy is there to identify who owns the filter + controlledBy?: string; + // index and type are optional only because when you create a new filter, there are no defaults + index?: string; + isMultiIndex?: boolean; + type?: string; + key?: string; + params?: any; + value?: string; +}; + +// eslint-disable-next-line +export type Filter = { + $state?: FilterState; + meta: FilterMeta; + query?: any; // TODO: can we use the Query type her? +}; + +// eslint-disable-next-line +export type Query = { + query: string | { [key: string]: any }; + language: string; +}; + +export interface LatLon { + lat: number; + lon: number; +} diff --git a/packages/kbn-es-query/src/index.ts b/packages/kbn-es-query/src/index.ts new file mode 100644 index 0000000000000..bbba52871d4c8 --- /dev/null +++ b/packages/kbn-es-query/src/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './es_query'; +export * from './filters'; +export * from './kuery'; diff --git a/src/plugins/data/common/es_query/kuery/ast/ast.test.ts b/packages/kbn-es-query/src/kuery/ast/ast.test.ts similarity index 98% rename from src/plugins/data/common/es_query/kuery/ast/ast.test.ts rename to packages/kbn-es-query/src/kuery/ast/ast.test.ts index f8d7dc02d38fc..459ace026796c 100644 --- a/src/plugins/data/common/es_query/kuery/ast/ast.test.ts +++ b/packages/kbn-es-query/src/kuery/ast/ast.test.ts @@ -8,17 +8,19 @@ import { fromKueryExpression, fromLiteralExpression, toElasticsearchQuery } from './ast'; import { nodeTypes } from '../node_types'; -import { fields } from '../../../index_patterns/mocks'; -import { IIndexPattern } from '../../../index_patterns'; +import { IndexPatternBase } from '../..'; import { KueryNode } from '../types'; +import { fields } from '../../filters/stubs'; + +jest.mock('../grammar'); describe('kuery AST API', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPatternBase; beforeEach(() => { - indexPattern = ({ + indexPattern = { fields, - } as unknown) as IIndexPattern; + }; }); describe('fromKueryExpression', () => { diff --git a/src/plugins/data/common/es_query/kuery/ast/ast.ts b/packages/kbn-es-query/src/kuery/ast/ast.ts similarity index 96% rename from src/plugins/data/common/es_query/kuery/ast/ast.ts rename to packages/kbn-es-query/src/kuery/ast/ast.ts index 3e7b25897cab7..6f43098a752de 100644 --- a/src/plugins/data/common/es_query/kuery/ast/ast.ts +++ b/packages/kbn-es-query/src/kuery/ast/ast.ts @@ -11,8 +11,7 @@ import { nodeTypes } from '../node_types/index'; import { KQLSyntaxError } from '../kuery_syntax_error'; import { KueryNode, DslQuery, KueryParseOptions } from '../types'; -// @ts-ignore -import { parse as parseKuery } from './_generated_/kuery'; +import { parse as parseKuery } from '../grammar'; import { IndexPatternBase } from '../..'; const fromExpression = ( diff --git a/src/plugins/data/common/es_query/kuery/ast/index.ts b/packages/kbn-es-query/src/kuery/ast/index.ts similarity index 100% rename from src/plugins/data/common/es_query/kuery/ast/index.ts rename to packages/kbn-es-query/src/kuery/ast/index.ts diff --git a/src/plugins/data/common/es_query/kuery/functions/and.test.ts b/packages/kbn-es-query/src/kuery/functions/and.test.ts similarity index 88% rename from src/plugins/data/common/es_query/kuery/functions/and.test.ts rename to packages/kbn-es-query/src/kuery/functions/and.test.ts index fae7888d75536..1e6797485c964 100644 --- a/src/plugins/data/common/es_query/kuery/functions/and.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/and.test.ts @@ -7,24 +7,24 @@ */ import { nodeTypes } from '../node_types'; -import { fields } from '../../../index_patterns/mocks'; -import { IIndexPattern } from '../../../index_patterns'; +import { fields } from '../../filters/stubs'; import * as ast from '../ast'; - -// @ts-ignore import * as and from './and'; +import { IndexPatternBase } from '../../es_query'; + +jest.mock('../grammar'); const childNode1 = nodeTypes.function.buildNode('is', 'machine.os', 'osx'); const childNode2 = nodeTypes.function.buildNode('is', 'extension', 'jpg'); describe('kuery functions', () => { describe('and', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPatternBase; beforeEach(() => { - indexPattern = ({ + indexPattern = { fields, - } as unknown) as IIndexPattern; + }; }); describe('buildNodeParams', () => { diff --git a/src/plugins/data/common/es_query/kuery/functions/and.ts b/packages/kbn-es-query/src/kuery/functions/and.ts similarity index 93% rename from src/plugins/data/common/es_query/kuery/functions/and.ts rename to packages/kbn-es-query/src/kuery/functions/and.ts index ba7d5d1f6645b..239dd67e73d10 100644 --- a/src/plugins/data/common/es_query/kuery/functions/and.ts +++ b/packages/kbn-es-query/src/kuery/functions/and.ts @@ -7,7 +7,7 @@ */ import * as ast from '../ast'; -import { IndexPatternBase, KueryNode } from '../../..'; +import { IndexPatternBase, KueryNode } from '../..'; export function buildNodeParams(children: KueryNode[]) { return { diff --git a/src/plugins/data/common/es_query/kuery/functions/exists.test.ts b/packages/kbn-es-query/src/kuery/functions/exists.test.ts similarity index 92% rename from src/plugins/data/common/es_query/kuery/functions/exists.test.ts rename to packages/kbn-es-query/src/kuery/functions/exists.test.ts index 4036e7c4089f0..0941e478e816b 100644 --- a/src/plugins/data/common/es_query/kuery/functions/exists.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/exists.test.ts @@ -7,20 +7,22 @@ */ import { nodeTypes } from '../node_types'; -import { fields } from '../../../index_patterns/mocks'; -import { IIndexPattern } from '../../../index_patterns'; +import { fields } from '../../filters/stubs'; +import { IndexPatternBase } from '../..'; + +jest.mock('../grammar'); // @ts-ignore import * as exists from './exists'; describe('kuery functions', () => { describe('exists', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPatternBase; beforeEach(() => { - indexPattern = ({ + indexPattern = { fields, - } as unknown) as IIndexPattern; + }; }); describe('buildNodeParams', () => { diff --git a/src/plugins/data/common/es_query/kuery/functions/exists.ts b/packages/kbn-es-query/src/kuery/functions/exists.ts similarity index 93% rename from src/plugins/data/common/es_query/kuery/functions/exists.ts rename to packages/kbn-es-query/src/kuery/functions/exists.ts index 4df566d874d8b..0e05ade5181db 100644 --- a/src/plugins/data/common/es_query/kuery/functions/exists.ts +++ b/packages/kbn-es-query/src/kuery/functions/exists.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ +import { IndexPatternFieldBase, IndexPatternBase, KueryNode } from '../..'; import * as literal from '../node_types/literal'; -import { KueryNode, IndexPatternFieldBase, IndexPatternBase } from '../../..'; export function buildNodeParams(fieldName: string) { return { diff --git a/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.test.ts b/packages/kbn-es-query/src/kuery/functions/geo_bounding_box.test.ts similarity index 94% rename from src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.test.ts rename to packages/kbn-es-query/src/kuery/functions/geo_bounding_box.test.ts index 54c2383be785a..028c5e39bf5da 100644 --- a/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/geo_bounding_box.test.ts @@ -8,12 +8,13 @@ import { get } from 'lodash'; import { nodeTypes } from '../node_types'; -import { fields } from '../../../index_patterns/mocks'; -import { IIndexPattern } from '../../../index_patterns'; +import { fields } from '../../filters/stubs'; +import { IndexPatternBase } from '../..'; -// @ts-ignore import * as geoBoundingBox from './geo_bounding_box'; +jest.mock('../grammar'); + const params = { bottomRight: { lat: 50.73, @@ -27,12 +28,12 @@ const params = { describe('kuery functions', () => { describe('geoBoundingBox', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPatternBase; beforeEach(() => { - indexPattern = ({ + indexPattern = { fields, - } as unknown) as IIndexPattern; + }; }); describe('buildNodeParams', () => { diff --git a/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.ts b/packages/kbn-es-query/src/kuery/functions/geo_bounding_box.ts similarity index 96% rename from src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.ts rename to packages/kbn-es-query/src/kuery/functions/geo_bounding_box.ts index 79bef10b14f71..b1fd8680af604 100644 --- a/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.ts +++ b/packages/kbn-es-query/src/kuery/functions/geo_bounding_box.ts @@ -9,7 +9,7 @@ import _ from 'lodash'; import { nodeTypes } from '../node_types'; import * as ast from '../ast'; -import { IndexPatternBase, KueryNode, LatLon } from '../../..'; +import { IndexPatternBase, KueryNode, LatLon } from '../..'; export function buildNodeParams(fieldName: string, params: any) { params = _.pick(params, 'topLeft', 'bottomRight'); diff --git a/src/plugins/data/common/es_query/kuery/functions/geo_polygon.test.ts b/packages/kbn-es-query/src/kuery/functions/geo_polygon.test.ts similarity index 94% rename from src/plugins/data/common/es_query/kuery/functions/geo_polygon.test.ts rename to packages/kbn-es-query/src/kuery/functions/geo_polygon.test.ts index a106754a5f56f..f16ca378866f0 100644 --- a/src/plugins/data/common/es_query/kuery/functions/geo_polygon.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/geo_polygon.test.ts @@ -7,12 +7,13 @@ */ import { nodeTypes } from '../node_types'; -import { fields } from '../../../index_patterns/mocks'; -import { IIndexPattern } from '../../../index_patterns'; +import { fields } from '../../filters/stubs'; +import { IndexPatternBase } from '../..'; -// @ts-ignore import * as geoPolygon from './geo_polygon'; +jest.mock('../grammar'); + const points = [ { lat: 69.77, @@ -30,12 +31,12 @@ const points = [ describe('kuery functions', () => { describe('geoPolygon', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPatternBase; beforeEach(() => { - indexPattern = ({ + indexPattern = { fields, - } as unknown) as IIndexPattern; + }; }); describe('buildNodeParams', () => { diff --git a/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts b/packages/kbn-es-query/src/kuery/functions/geo_polygon.ts similarity index 96% rename from src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts rename to packages/kbn-es-query/src/kuery/functions/geo_polygon.ts index 2e3280138502a..d02ba84237c7f 100644 --- a/src/plugins/data/common/es_query/kuery/functions/geo_polygon.ts +++ b/packages/kbn-es-query/src/kuery/functions/geo_polygon.ts @@ -8,7 +8,7 @@ import { nodeTypes } from '../node_types'; import * as ast from '../ast'; -import { IndexPatternBase, KueryNode, LatLon } from '../../..'; +import { IndexPatternBase, KueryNode, LatLon } from '../..'; import { LiteralTypeBuildNode } from '../node_types/types'; export function buildNodeParams(fieldName: string, points: LatLon[]) { diff --git a/src/plugins/data/common/es_query/kuery/functions/index.ts b/packages/kbn-es-query/src/kuery/functions/index.ts similarity index 100% rename from src/plugins/data/common/es_query/kuery/functions/index.ts rename to packages/kbn-es-query/src/kuery/functions/index.ts diff --git a/src/plugins/data/common/es_query/kuery/functions/is.test.ts b/packages/kbn-es-query/src/kuery/functions/is.test.ts similarity index 97% rename from src/plugins/data/common/es_query/kuery/functions/is.test.ts rename to packages/kbn-es-query/src/kuery/functions/is.test.ts index 55aac8189c1d8..292159e727a92 100644 --- a/src/plugins/data/common/es_query/kuery/functions/is.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/is.test.ts @@ -7,20 +7,21 @@ */ import { nodeTypes } from '../node_types'; -import { fields } from '../../../index_patterns/mocks'; +import { fields } from '../../filters/stubs'; -// @ts-ignore import * as is from './is'; -import { IIndexPattern } from '../../../index_patterns'; +import { IndexPatternBase } from '../..'; + +jest.mock('../grammar'); describe('kuery functions', () => { describe('is', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPatternBase; beforeEach(() => { - indexPattern = ({ + indexPattern = { fields, - } as unknown) as IIndexPattern; + }; }); describe('buildNodeParams', () => { diff --git a/src/plugins/data/common/es_query/kuery/functions/is.ts b/packages/kbn-es-query/src/kuery/functions/is.ts similarity index 99% rename from src/plugins/data/common/es_query/kuery/functions/is.ts rename to packages/kbn-es-query/src/kuery/functions/is.ts index 381913670c26a..c8d33921b084a 100644 --- a/src/plugins/data/common/es_query/kuery/functions/is.ts +++ b/packages/kbn-es-query/src/kuery/functions/is.ts @@ -11,7 +11,7 @@ import { getPhraseScript } from '../../filters'; import { getFields } from './utils/get_fields'; import { getTimeZoneFromSettings } from '../../utils'; import { getFullFieldNameNode } from './utils/get_full_field_name_node'; -import { IndexPatternBase, KueryNode, IndexPatternFieldBase } from '../../..'; +import { IndexPatternBase, KueryNode, IndexPatternFieldBase } from '../..'; import * as ast from '../ast'; diff --git a/src/plugins/data/common/es_query/kuery/functions/nested.test.ts b/packages/kbn-es-query/src/kuery/functions/nested.test.ts similarity index 90% rename from src/plugins/data/common/es_query/kuery/functions/nested.test.ts rename to packages/kbn-es-query/src/kuery/functions/nested.test.ts index 50460500c0877..47e515b9bd47f 100644 --- a/src/plugins/data/common/es_query/kuery/functions/nested.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/nested.test.ts @@ -7,24 +7,25 @@ */ import { nodeTypes } from '../node_types'; -import { fields } from '../../../index_patterns/mocks'; -import { IIndexPattern } from '../../../index_patterns'; +import { fields } from '../../filters/stubs'; +import { IndexPatternBase } from '../..'; import * as ast from '../ast'; -// @ts-ignore import * as nested from './nested'; +jest.mock('../grammar'); + const childNode = nodeTypes.function.buildNode('is', 'child', 'foo'); describe('kuery functions', () => { describe('nested', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPatternBase; beforeEach(() => { - indexPattern = ({ + indexPattern = { fields, - } as unknown) as IIndexPattern; + }; }); describe('buildNodeParams', () => { diff --git a/src/plugins/data/common/es_query/kuery/functions/nested.ts b/packages/kbn-es-query/src/kuery/functions/nested.ts similarity index 95% rename from src/plugins/data/common/es_query/kuery/functions/nested.ts rename to packages/kbn-es-query/src/kuery/functions/nested.ts index 46ceeaf3e5de6..248de1c40d62a 100644 --- a/src/plugins/data/common/es_query/kuery/functions/nested.ts +++ b/packages/kbn-es-query/src/kuery/functions/nested.ts @@ -8,7 +8,7 @@ import * as ast from '../ast'; import * as literal from '../node_types/literal'; -import { IndexPatternBase, KueryNode } from '../../..'; +import { IndexPatternBase, KueryNode } from '../..'; export function buildNodeParams(path: any, child: any) { const pathNode = diff --git a/src/plugins/data/common/es_query/kuery/functions/not.test.ts b/packages/kbn-es-query/src/kuery/functions/not.test.ts similarity index 87% rename from src/plugins/data/common/es_query/kuery/functions/not.test.ts rename to packages/kbn-es-query/src/kuery/functions/not.test.ts index ab83df581edd7..a44f3e9c5dda8 100644 --- a/src/plugins/data/common/es_query/kuery/functions/not.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/not.test.ts @@ -7,24 +7,24 @@ */ import { nodeTypes } from '../node_types'; -import { fields } from '../../../index_patterns/mocks'; -import { IIndexPattern } from '../../../index_patterns'; +import { fields } from '../../filters/stubs'; +import { IndexPatternBase } from '../..'; import * as ast from '../ast'; - -// @ts-ignore import * as not from './not'; +jest.mock('../grammar'); + const childNode = nodeTypes.function.buildNode('is', 'extension', 'jpg'); describe('kuery functions', () => { describe('not', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPatternBase; beforeEach(() => { - indexPattern = ({ + indexPattern = { fields, - } as unknown) as IIndexPattern; + }; }); describe('buildNodeParams', () => { diff --git a/src/plugins/data/common/es_query/kuery/functions/not.ts b/packages/kbn-es-query/src/kuery/functions/not.ts similarity index 93% rename from src/plugins/data/common/es_query/kuery/functions/not.ts rename to packages/kbn-es-query/src/kuery/functions/not.ts index f837cd261c814..96404ee800010 100644 --- a/src/plugins/data/common/es_query/kuery/functions/not.ts +++ b/packages/kbn-es-query/src/kuery/functions/not.ts @@ -7,7 +7,7 @@ */ import * as ast from '../ast'; -import { IndexPatternBase, KueryNode } from '../../..'; +import { IndexPatternBase, KueryNode } from '../..'; export function buildNodeParams(child: KueryNode) { return { diff --git a/src/plugins/data/common/es_query/kuery/functions/or.test.ts b/packages/kbn-es-query/src/kuery/functions/or.test.ts similarity index 90% rename from src/plugins/data/common/es_query/kuery/functions/or.test.ts rename to packages/kbn-es-query/src/kuery/functions/or.test.ts index 5e151098a5ef9..15faa7e1753d3 100644 --- a/src/plugins/data/common/es_query/kuery/functions/or.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/or.test.ts @@ -7,25 +7,25 @@ */ import { nodeTypes } from '../node_types'; -import { fields } from '../../../index_patterns/mocks'; -import { IIndexPattern } from '../../../index_patterns'; +import { fields } from '../../filters/stubs'; +import { IndexPatternBase } from '../..'; import * as ast from '../ast'; -// @ts-ignore import * as or from './or'; +jest.mock('../grammar'); const childNode1 = nodeTypes.function.buildNode('is', 'machine.os', 'osx'); const childNode2 = nodeTypes.function.buildNode('is', 'extension', 'jpg'); describe('kuery functions', () => { describe('or', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPatternBase; beforeEach(() => { - indexPattern = ({ + indexPattern = { fields, - } as unknown) as IIndexPattern; + }; }); describe('buildNodeParams', () => { diff --git a/src/plugins/data/common/es_query/kuery/functions/or.ts b/packages/kbn-es-query/src/kuery/functions/or.ts similarity index 94% rename from src/plugins/data/common/es_query/kuery/functions/or.ts rename to packages/kbn-es-query/src/kuery/functions/or.ts index 7365cc39595e6..b94814e1f7c05 100644 --- a/src/plugins/data/common/es_query/kuery/functions/or.ts +++ b/packages/kbn-es-query/src/kuery/functions/or.ts @@ -7,7 +7,7 @@ */ import * as ast from '../ast'; -import { IndexPatternBase, KueryNode } from '../../..'; +import { IndexPatternBase, KueryNode } from '../..'; export function buildNodeParams(children: KueryNode[]) { return { diff --git a/src/plugins/data/common/es_query/kuery/functions/range.test.ts b/packages/kbn-es-query/src/kuery/functions/range.test.ts similarity index 96% rename from src/plugins/data/common/es_query/kuery/functions/range.test.ts rename to packages/kbn-es-query/src/kuery/functions/range.test.ts index c4bc9c1372b27..fa1805e64887c 100644 --- a/src/plugins/data/common/es_query/kuery/functions/range.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/range.test.ts @@ -8,21 +8,21 @@ import { get } from 'lodash'; import { nodeTypes } from '../node_types'; -import { fields } from '../../../index_patterns/mocks'; -import { IIndexPattern } from '../../../index_patterns'; +import { fields } from '../../filters/stubs'; +import { IndexPatternBase } from '../..'; import { RangeFilterParams } from '../../filters'; -// @ts-ignore import * as range from './range'; +jest.mock('../grammar'); describe('kuery functions', () => { describe('range', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPatternBase; beforeEach(() => { - indexPattern = ({ + indexPattern = { fields, - } as unknown) as IIndexPattern; + }; }); describe('buildNodeParams', () => { diff --git a/src/plugins/data/common/es_query/kuery/functions/range.ts b/packages/kbn-es-query/src/kuery/functions/range.ts similarity index 98% rename from src/plugins/data/common/es_query/kuery/functions/range.ts rename to packages/kbn-es-query/src/kuery/functions/range.ts index b134434dc182b..e80a365441c43 100644 --- a/src/plugins/data/common/es_query/kuery/functions/range.ts +++ b/packages/kbn-es-query/src/kuery/functions/range.ts @@ -13,7 +13,7 @@ import { getRangeScript, RangeFilterParams } from '../../filters'; import { getFields } from './utils/get_fields'; import { getTimeZoneFromSettings } from '../../utils'; import { getFullFieldNameNode } from './utils/get_full_field_name_node'; -import { IndexPatternBase, KueryNode } from '../../..'; +import { IndexPatternBase, KueryNode } from '../..'; export function buildNodeParams(fieldName: string, params: RangeFilterParams) { const paramsToMap = _.pick(params, 'gt', 'lt', 'gte', 'lte', 'format'); diff --git a/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.test.ts b/packages/kbn-es-query/src/kuery/functions/utils/get_fields.test.ts similarity index 97% rename from src/plugins/data/common/es_query/kuery/functions/utils/get_fields.test.ts rename to packages/kbn-es-query/src/kuery/functions/utils/get_fields.test.ts index 949f94d043553..4125b0a572566 100644 --- a/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/utils/get_fields.test.ts @@ -7,13 +7,13 @@ */ import { IndexPatternBase } from '../../..'; -import { fields } from '../../../../index_patterns/mocks'; +import { fields } from '../../../filters/stubs'; import { nodeTypes } from '../../index'; - -// @ts-ignore import { getFields } from './get_fields'; +jest.mock('../../grammar'); + describe('getFields', () => { let indexPattern: IndexPatternBase; diff --git a/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.ts b/packages/kbn-es-query/src/kuery/functions/utils/get_fields.ts similarity index 94% rename from src/plugins/data/common/es_query/kuery/functions/utils/get_fields.ts rename to packages/kbn-es-query/src/kuery/functions/utils/get_fields.ts index 7dac1262d5062..db3826d4ef8c4 100644 --- a/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.ts +++ b/packages/kbn-es-query/src/kuery/functions/utils/get_fields.ts @@ -8,7 +8,7 @@ import * as literal from '../../node_types/literal'; import * as wildcard from '../../node_types/wildcard'; -import { KueryNode, IndexPatternBase } from '../../../..'; +import { KueryNode, IndexPatternBase } from '../../..'; import { LiteralTypeBuildNode } from '../../node_types/types'; export function getFields(node: KueryNode, indexPattern?: IndexPatternBase) { diff --git a/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.test.ts b/packages/kbn-es-query/src/kuery/functions/utils/get_full_field_name_node.test.ts similarity index 92% rename from src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.test.ts rename to packages/kbn-es-query/src/kuery/functions/utils/get_full_field_name_node.test.ts index 639f5584ef592..dccfc5d1c463a 100644 --- a/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.test.ts +++ b/packages/kbn-es-query/src/kuery/functions/utils/get_full_field_name_node.test.ts @@ -7,19 +7,19 @@ */ import { nodeTypes } from '../../node_types'; -import { fields } from '../../../../index_patterns/mocks'; -import { IIndexPattern } from '../../../../index_patterns'; - -// @ts-ignore +import { fields } from '../../../filters/stubs'; +import { IndexPatternBase } from '../../..'; import { getFullFieldNameNode } from './get_full_field_name_node'; +jest.mock('../../grammar'); + describe('getFullFieldNameNode', function () { - let indexPattern: IIndexPattern; + let indexPattern: IndexPatternBase; beforeEach(() => { - indexPattern = ({ + indexPattern = { fields, - } as unknown) as IIndexPattern; + }; }); test('should return unchanged name node if no nested path is passed in', () => { diff --git a/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.ts b/packages/kbn-es-query/src/kuery/functions/utils/get_full_field_name_node.ts similarity index 99% rename from src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.ts rename to packages/kbn-es-query/src/kuery/functions/utils/get_full_field_name_node.ts index 2a31ebeee2fab..6b575fbdea8fb 100644 --- a/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.ts +++ b/packages/kbn-es-query/src/kuery/functions/utils/get_full_field_name_node.ts @@ -7,7 +7,7 @@ */ import { getFields } from './get_fields'; -import { IndexPatternBase, IndexPatternFieldBase, KueryNode } from '../../../..'; +import { IndexPatternBase, IndexPatternFieldBase, KueryNode } from '../../..'; export function getFullFieldNameNode( rootNameNode: any, diff --git a/packages/kbn-es-query/src/kuery/grammar/__mocks__/grammar.js b/packages/kbn-es-query/src/kuery/grammar/__mocks__/grammar.js new file mode 100644 index 0000000000000..89c13bb2b05c2 --- /dev/null +++ b/packages/kbn-es-query/src/kuery/grammar/__mocks__/grammar.js @@ -0,0 +1,2219 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// Generated by Peggy 1.2.0. +// +// https://peggyjs.org/ +/* eslint-disable */ + +"use strict"; + +function peg$subclass(child, parent) { + function C() { this.constructor = child; } + C.prototype = parent.prototype; + child.prototype = new C(); +} + +function peg$SyntaxError(message, expected, found, location) { + var self = Error.call(this, message); + if (Object.setPrototypeOf) { + Object.setPrototypeOf(self, peg$SyntaxError.prototype); + } + self.expected = expected; + self.found = found; + self.location = location; + self.name = "SyntaxError"; + return self; +} + +peg$subclass(peg$SyntaxError, Error); + +function peg$padEnd(str, targetLength, padString) { + padString = padString || " "; + if (str.length > targetLength) { return str; } + targetLength -= str.length; + padString += padString.repeat(targetLength); + return str + padString.slice(0, targetLength); +} + +peg$SyntaxError.prototype.format = function(sources) { + var str = "Error: " + this.message; + if (this.location) { + var src = null; + var k; + for (k = 0; k < sources.length; k++) { + if (sources[k].source === this.location.source) { + src = sources[k].text.split(/\r\n|\n|\r/g); + break; + } + } + var s = this.location.start; + var loc = this.location.source + ":" + s.line + ":" + s.column; + if (src) { + var e = this.location.end; + var filler = peg$padEnd("", s.line.toString().length); + var line = src[s.line - 1]; + var last = s.line === e.line ? e.column : line.length + 1; + str += "\n --> " + loc + "\n" + + filler + " |\n" + + s.line + " | " + line + "\n" + + filler + " | " + peg$padEnd("", s.column - 1) + + peg$padEnd("", last - s.column, "^"); + } else { + str += "\n at " + loc; + } + } + return str; +}; + +peg$SyntaxError.buildMessage = function(expected, found) { + var DESCRIBE_EXPECTATION_FNS = { + literal: function(expectation) { + return "\"" + literalEscape(expectation.text) + "\""; + }, + + class: function(expectation) { + var escapedParts = expectation.parts.map(function(part) { + return Array.isArray(part) + ? classEscape(part[0]) + "-" + classEscape(part[1]) + : classEscape(part); + }); + + return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; + }, + + any: function() { + return "any character"; + }, + + end: function() { + return "end of input"; + }, + + other: function(expectation) { + return expectation.description; + } + }; + + function hex(ch) { + return ch.charCodeAt(0).toString(16).toUpperCase(); + } + + function literalEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/"/g, "\\\"") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + } + + function classEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/\]/g, "\\]") + .replace(/\^/g, "\\^") + .replace(/-/g, "\\-") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + } + + function describeExpectation(expectation) { + return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); + } + + function describeExpected(expected) { + var descriptions = expected.map(describeExpectation); + var i, j; + + descriptions.sort(); + + if (descriptions.length > 0) { + for (i = 1, j = 1; i < descriptions.length; i++) { + if (descriptions[i - 1] !== descriptions[i]) { + descriptions[j] = descriptions[i]; + j++; + } + } + descriptions.length = j; + } + + switch (descriptions.length) { + case 1: + return descriptions[0]; + + case 2: + return descriptions[0] + " or " + descriptions[1]; + + default: + return descriptions.slice(0, -1).join(", ") + + ", or " + + descriptions[descriptions.length - 1]; + } + } + + function describeFound(found) { + return found ? "\"" + literalEscape(found) + "\"" : "end of input"; + } + + return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; +}; + +function peg$parse(input, options) { + options = options !== undefined ? options : {}; + + var peg$FAILED = {}; + var peg$source = options.grammarSource; + + var peg$startRuleFunctions = { start: peg$parsestart, Literal: peg$parseLiteral }; + var peg$startRuleFunction = peg$parsestart; + + var peg$c0 = "("; + var peg$c1 = ")"; + var peg$c2 = ":"; + var peg$c3 = "{"; + var peg$c4 = "}"; + var peg$c5 = "or"; + var peg$c6 = "and"; + var peg$c7 = "not"; + var peg$c8 = "\""; + var peg$c9 = "\\"; + var peg$c10 = "*"; + var peg$c11 = "\\t"; + var peg$c12 = "\\r"; + var peg$c13 = "\\n"; + var peg$c14 = "u"; + var peg$c15 = "<="; + var peg$c16 = ">="; + var peg$c17 = "<"; + var peg$c18 = ">"; + var peg$c19 = "@kuery-cursor@"; + + var peg$r0 = /^[\\"]/; + var peg$r1 = /^[^"]/; + var peg$r2 = /^[\\():<>"*{}]/; + var peg$r3 = /^[0-9a-f]/i; + var peg$r4 = /^[ \t\r\n\xA0]/; + + var peg$e0 = peg$literalExpectation("(", false); + var peg$e1 = peg$literalExpectation(")", false); + var peg$e2 = peg$literalExpectation(":", false); + var peg$e3 = peg$literalExpectation("{", false); + var peg$e4 = peg$literalExpectation("}", false); + var peg$e5 = peg$otherExpectation("fieldName"); + var peg$e6 = peg$otherExpectation("value"); + var peg$e7 = peg$otherExpectation("OR"); + var peg$e8 = peg$literalExpectation("or", true); + var peg$e9 = peg$otherExpectation("AND"); + var peg$e10 = peg$literalExpectation("and", true); + var peg$e11 = peg$otherExpectation("NOT"); + var peg$e12 = peg$literalExpectation("not", true); + var peg$e13 = peg$otherExpectation("literal"); + var peg$e14 = peg$literalExpectation("\"", false); + var peg$e15 = peg$literalExpectation("\\", false); + var peg$e16 = peg$classExpectation(["\\", "\""], false, false); + var peg$e17 = peg$classExpectation(["\""], true, false); + var peg$e18 = peg$anyExpectation(); + var peg$e19 = peg$literalExpectation("*", false); + var peg$e20 = peg$literalExpectation("\\t", false); + var peg$e21 = peg$literalExpectation("\\r", false); + var peg$e22 = peg$literalExpectation("\\n", false); + var peg$e23 = peg$classExpectation(["\\", "(", ")", ":", "<", ">", "\"", "*", "{", "}"], false, false); + var peg$e24 = peg$literalExpectation("u", false); + var peg$e25 = peg$classExpectation([["0", "9"], ["a", "f"]], false, true); + var peg$e26 = peg$literalExpectation("<=", false); + var peg$e27 = peg$literalExpectation(">=", false); + var peg$e28 = peg$literalExpectation("<", false); + var peg$e29 = peg$literalExpectation(">", false); + var peg$e30 = peg$otherExpectation("whitespace"); + var peg$e31 = peg$classExpectation([" ", "\t", "\r", "\n", "\xA0"], false, false); + var peg$e32 = peg$literalExpectation("@kuery-cursor@", false); + + var peg$f0 = function(query, trailing) { + if (trailing.type === 'cursor') { + return { + ...trailing, + suggestionTypes: ['conjunction'] + }; + } + if (query !== null) return query; + return nodeTypes.function.buildNode('is', '*', '*'); + }; + var peg$f1 = function(head, query) { return query; }; + var peg$f2 = function(head, tail) { + const nodes = [head, ...tail]; + const cursor = parseCursor && nodes.find(node => node.type === 'cursor'); + if (cursor) return cursor; + return buildFunctionNode('or', nodes); + }; + var peg$f3 = function(head, tail) { + const nodes = [head, ...tail]; + const cursor = parseCursor && nodes.find(node => node.type === 'cursor'); + if (cursor) return cursor; + return buildFunctionNode('and', nodes); + }; + var peg$f4 = function(query) { + if (query.type === 'cursor') return query; + return buildFunctionNode('not', [query]); + }; + var peg$f5 = function(query, trailing) { + if (trailing.type === 'cursor') { + return { + ...trailing, + suggestionTypes: ['conjunction'] + }; + } + return query; + }; + var peg$f6 = function(field, query, trailing) { + if (query.type === 'cursor') { + return { + ...query, + nestedPath: query.nestedPath ? `${field.value}.${query.nestedPath}` : field.value, + } + }; + + if (trailing.type === 'cursor') { + return { + ...trailing, + suggestionTypes: ['conjunction'] + }; + } + return buildFunctionNode('nested', [field, query]); + }; + var peg$f7 = function(field, operator, value) { + if (value.type === 'cursor') { + return { + ...value, + suggestionTypes: ['conjunction'] + }; + } + const range = buildNamedArgNode(operator, value); + return buildFunctionNode('range', [field, range]); + }; + var peg$f8 = function(field, partial) { + if (partial.type === 'cursor') { + return { + ...partial, + fieldName: field.value, + suggestionTypes: ['value', 'conjunction'] + }; + } + return partial(field); + }; + var peg$f9 = function(partial) { + if (partial.type === 'cursor') { + const fieldName = `${partial.prefix}${partial.suffix}`.trim(); + return { + ...partial, + fieldName, + suggestionTypes: ['field', 'operator', 'conjunction'] + }; + } + const field = buildLiteralNode(null); + return partial(field); + }; + var peg$f10 = function(partial, trailing) { + if (trailing.type === 'cursor') { + return { + ...trailing, + suggestionTypes: ['conjunction'] + }; + } + return partial; + }; + var peg$f11 = function(head, partial) { return partial; }; + var peg$f12 = function(head, tail) { + const nodes = [head, ...tail]; + const cursor = parseCursor && nodes.find(node => node.type === 'cursor'); + if (cursor) { + return { + ...cursor, + suggestionTypes: ['value'] + }; + } + return (field) => buildFunctionNode('or', nodes.map(partial => partial(field))); + }; + var peg$f13 = function(head, tail) { + const nodes = [head, ...tail]; + const cursor = parseCursor && nodes.find(node => node.type === 'cursor'); + if (cursor) { + return { + ...cursor, + suggestionTypes: ['value'] + }; + } + return (field) => buildFunctionNode('and', nodes.map(partial => partial(field))); + }; + var peg$f14 = function(partial) { + if (partial.type === 'cursor') { + return { + ...list, + suggestionTypes: ['value'] + }; + } + return (field) => buildFunctionNode('not', [partial(field)]); + }; + var peg$f15 = function(value) { + if (value.type === 'cursor') return value; + const isPhrase = buildLiteralNode(true); + return (field) => buildFunctionNode('is', [field, value, isPhrase]); + }; + var peg$f16 = function(value) { + if (value.type === 'cursor') return value; + + if (!allowLeadingWildcards && value.type === 'wildcard' && nodeTypes.wildcard.hasLeadingWildcard(value)) { + error('Leading wildcards are disabled. See query:allowLeadingWildcards in Advanced Settings.'); + } + + const isPhrase = buildLiteralNode(false); + return (field) => buildFunctionNode('is', [field, value, isPhrase]); + }; + var peg$f17 = function() { return parseCursor; }; + var peg$f18 = function(prefix, cursor, suffix) { + const { start, end } = location(); + return { + type: 'cursor', + start: start.offset, + end: end.offset - cursor.length, + prefix: prefix.join(''), + suffix: suffix.join(''), + text: text().replace(cursor, '') + }; + }; + var peg$f19 = function(chars) { + return buildLiteralNode(chars.join('')); + }; + var peg$f20 = function(char) { return char; }; + var peg$f21 = function(chars) { + const sequence = chars.join('').trim(); + if (sequence === 'null') return buildLiteralNode(null); + if (sequence === 'true') return buildLiteralNode(true); + if (sequence === 'false') return buildLiteralNode(false); + if (chars.includes(wildcardSymbol)) return buildWildcardNode(sequence); + return buildLiteralNode(sequence); + }; + var peg$f22 = function() { return wildcardSymbol; }; + var peg$f23 = function() { return '\t'; }; + var peg$f24 = function() { return '\r'; }; + var peg$f25 = function() { return '\n'; }; + var peg$f26 = function(keyword) { return keyword; }; + var peg$f27 = function(sequence) { return sequence; }; + var peg$f28 = function(digits) { + return String.fromCharCode(parseInt(digits, 16)); + }; + var peg$f29 = function() { return 'lte'; }; + var peg$f30 = function() { return 'gte'; }; + var peg$f31 = function() { return 'lt'; }; + var peg$f32 = function() { return 'gt'; }; + var peg$f33 = function() { return cursorSymbol; }; + + var peg$currPos = 0; + var peg$savedPos = 0; + var peg$posDetailsCache = [{ line: 1, column: 1 }]; + var peg$maxFailPos = 0; + var peg$maxFailExpected = []; + var peg$silentFails = 0; + + var peg$result; + + if ("startRule" in options) { + if (!(options.startRule in peg$startRuleFunctions)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + } + + function text() { + return input.substring(peg$savedPos, peg$currPos); + } + + function offset() { + return peg$savedPos; + } + + function range() { + return { + source: peg$source, + start: peg$savedPos, + end: peg$currPos + }; + } + + function location() { + return peg$computeLocation(peg$savedPos, peg$currPos); + } + + function expected(description, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildStructuredError( + [peg$otherExpectation(description)], + input.substring(peg$savedPos, peg$currPos), + location + ); + } + + function error(message, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildSimpleError(message, location); + } + + function peg$literalExpectation(text, ignoreCase) { + return { type: "literal", text: text, ignoreCase: ignoreCase }; + } + + function peg$classExpectation(parts, inverted, ignoreCase) { + return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; + } + + function peg$anyExpectation() { + return { type: "any" }; + } + + function peg$endExpectation() { + return { type: "end" }; + } + + function peg$otherExpectation(description) { + return { type: "other", description: description }; + } + + function peg$computePosDetails(pos) { + var details = peg$posDetailsCache[pos]; + var p; + + if (details) { + return details; + } else { + p = pos - 1; + while (!peg$posDetailsCache[p]) { + p--; + } + + details = peg$posDetailsCache[p]; + details = { + line: details.line, + column: details.column + }; + + while (p < pos) { + if (input.charCodeAt(p) === 10) { + details.line++; + details.column = 1; + } else { + details.column++; + } + + p++; + } + + peg$posDetailsCache[pos] = details; + + return details; + } + } + + function peg$computeLocation(startPos, endPos) { + var startPosDetails = peg$computePosDetails(startPos); + var endPosDetails = peg$computePosDetails(endPos); + + return { + source: peg$source, + start: { + offset: startPos, + line: startPosDetails.line, + column: startPosDetails.column + }, + end: { + offset: endPos, + line: endPosDetails.line, + column: endPosDetails.column + } + }; + } + + function peg$fail(expected) { + if (peg$currPos < peg$maxFailPos) { return; } + + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + + peg$maxFailExpected.push(expected); + } + + function peg$buildSimpleError(message, location) { + return new peg$SyntaxError(message, null, null, location); + } + + function peg$buildStructuredError(expected, found, location) { + return new peg$SyntaxError( + peg$SyntaxError.buildMessage(expected, found), + expected, + found, + location + ); + } + + function peg$parsestart() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parseSpace(); + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parseSpace(); + } + s2 = peg$parseOrQuery(); + if (s2 === peg$FAILED) { + s2 = null; + } + s3 = peg$parseOptionalSpace(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f0(s2, s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseOrQuery() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseAndQuery(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = peg$parseOr(); + if (s4 !== peg$FAILED) { + s5 = peg$parseAndQuery(); + if (s5 !== peg$FAILED) { + peg$savedPos = s3; + s3 = peg$f1(s1, s5); + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$parseOr(); + if (s4 !== peg$FAILED) { + s5 = peg$parseAndQuery(); + if (s5 !== peg$FAILED) { + peg$savedPos = s3; + s3 = peg$f1(s1, s5); + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f2(s1, s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$parseAndQuery(); + } + + return s0; + } + + function peg$parseAndQuery() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseNotQuery(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = peg$parseAnd(); + if (s4 !== peg$FAILED) { + s5 = peg$parseNotQuery(); + if (s5 !== peg$FAILED) { + peg$savedPos = s3; + s3 = peg$f1(s1, s5); + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$parseAnd(); + if (s4 !== peg$FAILED) { + s5 = peg$parseNotQuery(); + if (s5 !== peg$FAILED) { + peg$savedPos = s3; + s3 = peg$f1(s1, s5); + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f3(s1, s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$parseNotQuery(); + } + + return s0; + } + + function peg$parseNotQuery() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = peg$parseNot(); + if (s1 !== peg$FAILED) { + s2 = peg$parseSubQuery(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f4(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$parseSubQuery(); + } + + return s0; + } + + function peg$parseSubQuery() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 40) { + s1 = peg$c0; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseSpace(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseSpace(); + } + s3 = peg$parseOrQuery(); + if (s3 !== peg$FAILED) { + s4 = peg$parseOptionalSpace(); + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 41) { + s5 = peg$c1; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f5(s3, s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$parseNestedQuery(); + } + + return s0; + } + + function peg$parseNestedQuery() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; + + s0 = peg$currPos; + s1 = peg$parseField(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseSpace(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseSpace(); + } + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c2; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e2); } + } + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseSpace(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseSpace(); + } + if (input.charCodeAt(peg$currPos) === 123) { + s5 = peg$c3; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e3); } + } + if (s5 !== peg$FAILED) { + s6 = []; + s7 = peg$parseSpace(); + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$parseSpace(); + } + s7 = peg$parseOrQuery(); + if (s7 !== peg$FAILED) { + s8 = peg$parseOptionalSpace(); + if (s8 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 125) { + s9 = peg$c4; + peg$currPos++; + } else { + s9 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e4); } + } + if (s9 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f6(s1, s7, s8); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$parseExpression(); + } + + return s0; + } + + function peg$parseExpression() { + var s0; + + s0 = peg$parseFieldRangeExpression(); + if (s0 === peg$FAILED) { + s0 = peg$parseFieldValueExpression(); + if (s0 === peg$FAILED) { + s0 = peg$parseValueExpression(); + } + } + + return s0; + } + + function peg$parseField() { + var s0, s1; + + peg$silentFails++; + s0 = peg$parseLiteral(); + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e5); } + } + + return s0; + } + + function peg$parseFieldRangeExpression() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseField(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseSpace(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseSpace(); + } + s3 = peg$parseRangeOperator(); + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseSpace(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseSpace(); + } + s5 = peg$parseLiteral(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f7(s1, s3, s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseFieldValueExpression() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseField(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseSpace(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseSpace(); + } + if (input.charCodeAt(peg$currPos) === 58) { + s3 = peg$c2; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e2); } + } + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseSpace(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseSpace(); + } + s5 = peg$parseListOfValues(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f8(s1, s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseValueExpression() { + var s0, s1; + + s0 = peg$currPos; + s1 = peg$parseValue(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f9(s1); + } + s0 = s1; + + return s0; + } + + function peg$parseListOfValues() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 40) { + s1 = peg$c0; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseSpace(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseSpace(); + } + s3 = peg$parseOrListOfValues(); + if (s3 !== peg$FAILED) { + s4 = peg$parseOptionalSpace(); + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 41) { + s5 = peg$c1; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f10(s3, s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$parseValue(); + } + + return s0; + } + + function peg$parseOrListOfValues() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseAndListOfValues(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = peg$parseOr(); + if (s4 !== peg$FAILED) { + s5 = peg$parseAndListOfValues(); + if (s5 !== peg$FAILED) { + peg$savedPos = s3; + s3 = peg$f11(s1, s5); + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$parseOr(); + if (s4 !== peg$FAILED) { + s5 = peg$parseAndListOfValues(); + if (s5 !== peg$FAILED) { + peg$savedPos = s3; + s3 = peg$f11(s1, s5); + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f12(s1, s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$parseAndListOfValues(); + } + + return s0; + } + + function peg$parseAndListOfValues() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parseNotListOfValues(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = peg$parseAnd(); + if (s4 !== peg$FAILED) { + s5 = peg$parseNotListOfValues(); + if (s5 !== peg$FAILED) { + peg$savedPos = s3; + s3 = peg$f11(s1, s5); + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$parseAnd(); + if (s4 !== peg$FAILED) { + s5 = peg$parseNotListOfValues(); + if (s5 !== peg$FAILED) { + peg$savedPos = s3; + s3 = peg$f11(s1, s5); + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f13(s1, s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$parseNotListOfValues(); + } + + return s0; + } + + function peg$parseNotListOfValues() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = peg$parseNot(); + if (s1 !== peg$FAILED) { + s2 = peg$parseListOfValues(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f14(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$parseListOfValues(); + } + + return s0; + } + + function peg$parseValue() { + var s0, s1; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parseQuotedString(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f15(s1); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseUnquotedLiteral(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f16(s1); + } + s0 = s1; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e6); } + } + + return s0; + } + + function peg$parseOr() { + var s0, s1, s2, s3, s4; + + peg$silentFails++; + s0 = peg$currPos; + s1 = []; + s2 = peg$parseSpace(); + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parseSpace(); + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c5) { + s2 = input.substr(peg$currPos, 2); + peg$currPos += 2; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e8); } + } + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$parseSpace(); + if (s4 !== peg$FAILED) { + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parseSpace(); + } + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e7); } + } + + return s0; + } + + function peg$parseAnd() { + var s0, s1, s2, s3, s4; + + peg$silentFails++; + s0 = peg$currPos; + s1 = []; + s2 = peg$parseSpace(); + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parseSpace(); + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + if (input.substr(peg$currPos, 3).toLowerCase() === peg$c6) { + s2 = input.substr(peg$currPos, 3); + peg$currPos += 3; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e10); } + } + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$parseSpace(); + if (s4 !== peg$FAILED) { + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parseSpace(); + } + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e9); } + } + + return s0; + } + + function peg$parseNot() { + var s0, s1, s2, s3; + + peg$silentFails++; + s0 = peg$currPos; + if (input.substr(peg$currPos, 3).toLowerCase() === peg$c7) { + s1 = input.substr(peg$currPos, 3); + peg$currPos += 3; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e12); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseSpace(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseSpace(); + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e11); } + } + + return s0; + } + + function peg$parseLiteral() { + var s0, s1; + + peg$silentFails++; + s0 = peg$parseQuotedString(); + if (s0 === peg$FAILED) { + s0 = peg$parseUnquotedLiteral(); + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e13); } + } + + return s0; + } + + function peg$parseQuotedString() { + var s0, s1, s2, s3, s4, s5, s6; + + s0 = peg$currPos; + peg$savedPos = peg$currPos; + s1 = peg$f17(); + if (s1) { + s1 = undefined; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 34) { + s2 = peg$c8; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e14); } + } + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$parseQuotedCharacter(); + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parseQuotedCharacter(); + } + s4 = peg$parseCursor(); + if (s4 !== peg$FAILED) { + s5 = []; + s6 = peg$parseQuotedCharacter(); + while (s6 !== peg$FAILED) { + s5.push(s6); + s6 = peg$parseQuotedCharacter(); + } + if (input.charCodeAt(peg$currPos) === 34) { + s6 = peg$c8; + peg$currPos++; + } else { + s6 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e14); } + } + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f18(s3, s4, s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 34) { + s1 = peg$c8; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e14); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseQuotedCharacter(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseQuotedCharacter(); + } + if (input.charCodeAt(peg$currPos) === 34) { + s3 = peg$c8; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e14); } + } + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f19(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + + return s0; + } + + function peg$parseQuotedCharacter() { + var s0, s1, s2; + + s0 = peg$parseEscapedWhitespace(); + if (s0 === peg$FAILED) { + s0 = peg$parseEscapedUnicodeSequence(); + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 92) { + s1 = peg$c9; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e15); } + } + if (s1 !== peg$FAILED) { + if (peg$r0.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e16); } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f20(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$currPos; + peg$silentFails++; + s2 = peg$parseCursor(); + peg$silentFails--; + if (s2 === peg$FAILED) { + s1 = undefined; + } else { + peg$currPos = s1; + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + if (peg$r1.test(input.charAt(peg$currPos))) { + s2 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e17); } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f20(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + } + } + + return s0; + } + + function peg$parseUnquotedLiteral() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + peg$savedPos = peg$currPos; + s1 = peg$f17(); + if (s1) { + s1 = undefined; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseUnquotedCharacter(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseUnquotedCharacter(); + } + s3 = peg$parseCursor(); + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseUnquotedCharacter(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseUnquotedCharacter(); + } + peg$savedPos = s0; + s0 = peg$f18(s2, s3, s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = []; + s2 = peg$parseUnquotedCharacter(); + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parseUnquotedCharacter(); + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f21(s1); + } + s0 = s1; + } + + return s0; + } + + function peg$parseUnquotedCharacter() { + var s0, s1, s2, s3, s4; + + s0 = peg$parseEscapedWhitespace(); + if (s0 === peg$FAILED) { + s0 = peg$parseEscapedSpecialCharacter(); + if (s0 === peg$FAILED) { + s0 = peg$parseEscapedUnicodeSequence(); + if (s0 === peg$FAILED) { + s0 = peg$parseEscapedKeyword(); + if (s0 === peg$FAILED) { + s0 = peg$parseWildcard(); + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$currPos; + peg$silentFails++; + s2 = peg$parseSpecialCharacter(); + peg$silentFails--; + if (s2 === peg$FAILED) { + s1 = undefined; + } else { + peg$currPos = s1; + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = peg$currPos; + peg$silentFails++; + s3 = peg$parseKeyword(); + peg$silentFails--; + if (s3 === peg$FAILED) { + s2 = undefined; + } else { + peg$currPos = s2; + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s3 = peg$currPos; + peg$silentFails++; + s4 = peg$parseCursor(); + peg$silentFails--; + if (s4 === peg$FAILED) { + s3 = undefined; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + if (input.length > peg$currPos) { + s4 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s4 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e18); } + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f20(s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + } + } + } + } + + return s0; + } + + function peg$parseWildcard() { + var s0, s1; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 42) { + s1 = peg$c10; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e19); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f22(); + } + s0 = s1; + + return s0; + } + + function peg$parseOptionalSpace() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + peg$savedPos = peg$currPos; + s1 = peg$f17(); + if (s1) { + s1 = undefined; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseSpace(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseSpace(); + } + s3 = peg$parseCursor(); + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parseSpace(); + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parseSpace(); + } + peg$savedPos = s0; + s0 = peg$f18(s2, s3, s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = []; + s1 = peg$parseSpace(); + while (s1 !== peg$FAILED) { + s0.push(s1); + s1 = peg$parseSpace(); + } + } + + return s0; + } + + function peg$parseEscapedWhitespace() { + var s0, s1; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 2) === peg$c11) { + s1 = peg$c11; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e20); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f23(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 2) === peg$c12) { + s1 = peg$c12; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e21); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f24(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 2) === peg$c13) { + s1 = peg$c13; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e22); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f25(); + } + s0 = s1; + } + } + + return s0; + } + + function peg$parseEscapedSpecialCharacter() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 92) { + s1 = peg$c9; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e15); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseSpecialCharacter(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f20(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseEscapedKeyword() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 92) { + s1 = peg$c9; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e15); } + } + if (s1 !== peg$FAILED) { + if (input.substr(peg$currPos, 2).toLowerCase() === peg$c5) { + s2 = input.substr(peg$currPos, 2); + peg$currPos += 2; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e8); } + } + if (s2 === peg$FAILED) { + if (input.substr(peg$currPos, 3).toLowerCase() === peg$c6) { + s2 = input.substr(peg$currPos, 3); + peg$currPos += 3; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e10); } + } + if (s2 === peg$FAILED) { + if (input.substr(peg$currPos, 3).toLowerCase() === peg$c7) { + s2 = input.substr(peg$currPos, 3); + peg$currPos += 3; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e12); } + } + } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f26(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseKeyword() { + var s0; + + s0 = peg$parseOr(); + if (s0 === peg$FAILED) { + s0 = peg$parseAnd(); + if (s0 === peg$FAILED) { + s0 = peg$parseNot(); + } + } + + return s0; + } + + function peg$parseSpecialCharacter() { + var s0; + + if (peg$r2.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e23); } + } + + return s0; + } + + function peg$parseEscapedUnicodeSequence() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 92) { + s1 = peg$c9; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e15); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parseUnicodeSequence(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f27(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseUnicodeSequence() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 117) { + s1 = peg$c14; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e24); } + } + if (s1 !== peg$FAILED) { + s2 = peg$currPos; + s3 = peg$currPos; + s4 = peg$parseHexDigit(); + if (s4 !== peg$FAILED) { + s5 = peg$parseHexDigit(); + if (s5 !== peg$FAILED) { + s6 = peg$parseHexDigit(); + if (s6 !== peg$FAILED) { + s7 = peg$parseHexDigit(); + if (s7 !== peg$FAILED) { + s4 = [s4, s5, s6, s7]; + s3 = s4; + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s2 = input.substring(s2, peg$currPos); + } else { + s2 = s3; + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f28(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseHexDigit() { + var s0; + + if (peg$r3.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e25); } + } + + return s0; + } + + function peg$parseRangeOperator() { + var s0, s1; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 2) === peg$c15) { + s1 = peg$c15; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e26); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f29(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.substr(peg$currPos, 2) === peg$c16) { + s1 = peg$c16; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e27); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f30(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 60) { + s1 = peg$c17; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e28); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f31(); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 62) { + s1 = peg$c18; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e29); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f32(); + } + s0 = s1; + } + } + } + + return s0; + } + + function peg$parseSpace() { + var s0, s1; + + peg$silentFails++; + if (peg$r4.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e31); } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e30); } + } + + return s0; + } + + function peg$parseCursor() { + var s0, s1, s2; + + s0 = peg$currPos; + peg$savedPos = peg$currPos; + s1 = peg$f17(); + if (s1) { + s1 = undefined; + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + if (input.substr(peg$currPos, 14) === peg$c19) { + s2 = peg$c19; + peg$currPos += 14; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e32); } + } + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f33(); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + + const { parseCursor, cursorSymbol, allowLeadingWildcards = true, helpers: { nodeTypes } } = options; + const buildFunctionNode = nodeTypes.function.buildNodeWithArgumentNodes; + const buildLiteralNode = nodeTypes.literal.buildNode; + const buildWildcardNode = nodeTypes.wildcard.buildNode; + const buildNamedArgNode = nodeTypes.namedArg.buildNode; + const { wildcardSymbol } = nodeTypes.wildcard; + + + peg$result = peg$startRuleFunction(); + + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail(peg$endExpectation()); + } + + throw peg$buildStructuredError( + peg$maxFailExpected, + peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, + peg$maxFailPos < input.length + ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) + : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) + ); + } +} + +module.exports = { + SyntaxError: peg$SyntaxError, + parse: peg$parse +}; diff --git a/packages/kbn-es-query/src/kuery/grammar/__mocks__/index.ts b/packages/kbn-es-query/src/kuery/grammar/__mocks__/index.ts new file mode 100644 index 0000000000000..9103c852c4845 --- /dev/null +++ b/packages/kbn-es-query/src/kuery/grammar/__mocks__/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +// @ts-expect-error +export { parse } from './grammar'; diff --git a/src/plugins/expression_reveal_image/common/i18n/expression_functions/index.ts b/packages/kbn-es-query/src/kuery/grammar/index.ts similarity index 83% rename from src/plugins/expression_reveal_image/common/i18n/expression_functions/index.ts rename to packages/kbn-es-query/src/kuery/grammar/index.ts index 3d36b123421f4..811fa723da3b8 100644 --- a/src/plugins/expression_reveal_image/common/i18n/expression_functions/index.ts +++ b/packages/kbn-es-query/src/kuery/grammar/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export * from './function_help'; -export * from './function_errors'; +// @ts-expect-error +export { parse } from '../../../grammar'; diff --git a/src/plugins/data/common/es_query/kuery/index.ts b/packages/kbn-es-query/src/kuery/index.ts similarity index 80% rename from src/plugins/data/common/es_query/kuery/index.ts rename to packages/kbn-es-query/src/kuery/index.ts index 72eecc09756b4..7796785f85394 100644 --- a/src/plugins/data/common/es_query/kuery/index.ts +++ b/packages/kbn-es-query/src/kuery/index.ts @@ -8,6 +8,5 @@ export { KQLSyntaxError } from './kuery_syntax_error'; export { nodeTypes, nodeBuilder } from './node_types'; -export * from './ast'; - -export * from './types'; +export { fromKueryExpression, toElasticsearchQuery } from './ast'; +export { DslQuery, KueryNode } from './types'; diff --git a/src/plugins/data/common/es_query/kuery/kuery_syntax_error.test.ts b/packages/kbn-es-query/src/kuery/kuery_syntax_error.test.ts similarity index 89% rename from src/plugins/data/common/es_query/kuery/kuery_syntax_error.test.ts rename to packages/kbn-es-query/src/kuery/kuery_syntax_error.test.ts index 6875bc3e5f74f..e04b1abdc8494 100644 --- a/src/plugins/data/common/es_query/kuery/kuery_syntax_error.test.ts +++ b/packages/kbn-es-query/src/kuery/kuery_syntax_error.test.ts @@ -8,6 +8,8 @@ import { fromKueryExpression } from './ast'; +jest.mock('./grammar'); + describe('kql syntax errors', () => { it('should throw an error for a field query missing a value', () => { expect(() => { @@ -47,7 +49,7 @@ describe('kql syntax errors', () => { ); }); - it('should throw an error for a NOT list missing a sub-query', () => { + it('should throw an error for a "missing a sub-query', () => { expect(() => { fromKueryExpression('response:(200 and not )'); }).toThrow( @@ -66,13 +68,17 @@ describe('kql syntax errors', () => { it('should throw an error for unescaped quotes in a quoted string', () => { expect(() => { fromKueryExpression('foo:"ba "r"'); - }).toThrow('Expected AND, OR, end of input but "r" found.\n' + 'foo:"ba "r"\n' + '---------^'); + }).toThrow( + 'Expected AND, OR, end of input, whitespace but "r" found.\n' + 'foo:"ba "r"\n' + '---------^' + ); }); it('should throw an error for unescaped special characters in literals', () => { expect(() => { fromKueryExpression('foo:ba:r'); - }).toThrow('Expected AND, OR, end of input but ":" found.\n' + 'foo:ba:r\n' + '------^'); + }).toThrow( + 'Expected AND, OR, end of input, whitespace but ":" found.\n' + 'foo:ba:r\n' + '------^' + ); }); it('should throw an error for range queries missing a value', () => { diff --git a/src/plugins/data/common/es_query/kuery/kuery_syntax_error.ts b/packages/kbn-es-query/src/kuery/kuery_syntax_error.ts similarity index 59% rename from src/plugins/data/common/es_query/kuery/kuery_syntax_error.ts rename to packages/kbn-es-query/src/kuery/kuery_syntax_error.ts index a9adbad4781b7..aa4440579eb49 100644 --- a/src/plugins/data/common/es_query/kuery/kuery_syntax_error.ts +++ b/packages/kbn-es-query/src/kuery/kuery_syntax_error.ts @@ -6,28 +6,40 @@ * Side Public License, v 1. */ -import { repeat } from 'lodash'; +import { repeat, uniq } from 'lodash'; import { i18n } from '@kbn/i18n'; -const endOfInputText = i18n.translate('data.common.kql.errors.endOfInputText', { +const endOfInputText = i18n.translate('esQuery.kql.errors.endOfInputText', { defaultMessage: 'end of input', }); const grammarRuleTranslations: Record = { - fieldName: i18n.translate('data.common.kql.errors.fieldNameText', { + fieldName: i18n.translate('esQuery.kql.errors.fieldNameText', { defaultMessage: 'field name', }), - value: i18n.translate('data.common.kql.errors.valueText', { + value: i18n.translate('esQuery.kql.errors.valueText', { defaultMessage: 'value', }), - literal: i18n.translate('data.common.kql.errors.literalText', { + literal: i18n.translate('esQuery.kql.errors.literalText', { defaultMessage: 'literal', }), - whitespace: i18n.translate('data.common.kql.errors.whitespaceText', { + whitespace: i18n.translate('esQuery.kql.errors.whitespaceText', { defaultMessage: 'whitespace', }), }; +const getItemText = (item: KQLSyntaxErrorExpected): string => { + if (item.type === 'other') { + return item.description!; + } else if (item.type === 'literal') { + return `"${item.text!}"`; + } else if (item.type === 'end') { + return 'end of input'; + } else { + return item.text || item.description || ''; + } +}; + interface KQLSyntaxErrorData extends Error { found: string; expected: KQLSyntaxErrorExpected[] | null; @@ -35,7 +47,9 @@ interface KQLSyntaxErrorData extends Error { } interface KQLSyntaxErrorExpected { - description: string; + description?: string; + text?: string; + type: string; } export class KQLSyntaxError extends Error { @@ -45,12 +59,16 @@ export class KQLSyntaxError extends Error { let message = error.message; if (error.expected) { const translatedExpectations = error.expected.map((expected) => { - return grammarRuleTranslations[expected.description] || expected.description; + const key = getItemText(expected); + return grammarRuleTranslations[key] || key; }); - const translatedExpectationText = translatedExpectations.join(', '); + const translatedExpectationText = uniq(translatedExpectations) + .filter((item) => item !== undefined) + .sort() + .join(', '); - message = i18n.translate('data.common.kql.errors.syntaxError', { + message = i18n.translate('esQuery.kql.errors.syntaxError', { defaultMessage: 'Expected {expectedList} but {foundInput} found.', values: { expectedList: translatedExpectationText, diff --git a/src/plugins/data/common/es_query/kuery/node_types/function.test.ts b/packages/kbn-es-query/src/kuery/node_types/function.test.ts similarity index 91% rename from src/plugins/data/common/es_query/kuery/node_types/function.test.ts rename to packages/kbn-es-query/src/kuery/node_types/function.test.ts index 42c06d7fdb603..5df6ba1916046 100644 --- a/src/plugins/data/common/es_query/kuery/node_types/function.test.ts +++ b/packages/kbn-es-query/src/kuery/node_types/function.test.ts @@ -6,22 +6,23 @@ * Side Public License, v 1. */ -import { fields } from '../../../index_patterns/mocks'; - import { nodeTypes } from './index'; -import { IIndexPattern } from '../../../index_patterns'; import { buildNode, buildNodeWithArgumentNodes, toElasticsearchQuery } from './function'; import { toElasticsearchQuery as isFunctionToElasticsearchQuery } from '../functions/is'; +import { IndexPatternBase } from '../../es_query'; +import { fields } from '../../filters/stubs/fields.mocks'; + +jest.mock('../grammar'); describe('kuery node types', () => { describe('function', () => { - let indexPattern: IIndexPattern; + let indexPattern: IndexPatternBase; beforeEach(() => { - indexPattern = ({ + indexPattern = { fields, - } as unknown) as IIndexPattern; + }; }); describe('buildNode', () => { diff --git a/src/plugins/data/common/es_query/kuery/node_types/function.ts b/packages/kbn-es-query/src/kuery/node_types/function.ts similarity index 96% rename from src/plugins/data/common/es_query/kuery/node_types/function.ts rename to packages/kbn-es-query/src/kuery/node_types/function.ts index 642089a101f31..e72f8a6b1e77a 100644 --- a/src/plugins/data/common/es_query/kuery/node_types/function.ts +++ b/packages/kbn-es-query/src/kuery/node_types/function.ts @@ -9,7 +9,7 @@ import _ from 'lodash'; import { functions } from '../functions'; -import { IndexPatternBase, KueryNode } from '../../..'; +import { IndexPatternBase, KueryNode } from '../..'; import { FunctionName, FunctionTypeBuildNode } from './types'; export function buildNode(functionName: FunctionName, ...args: any[]) { diff --git a/src/plugins/data/common/es_query/kuery/node_types/index.ts b/packages/kbn-es-query/src/kuery/node_types/index.ts similarity index 100% rename from src/plugins/data/common/es_query/kuery/node_types/index.ts rename to packages/kbn-es-query/src/kuery/node_types/index.ts diff --git a/src/plugins/data/common/es_query/kuery/node_types/literal.test.ts b/packages/kbn-es-query/src/kuery/node_types/literal.test.ts similarity index 97% rename from src/plugins/data/common/es_query/kuery/node_types/literal.test.ts rename to packages/kbn-es-query/src/kuery/node_types/literal.test.ts index c370292de38bc..7a36be704a609 100644 --- a/src/plugins/data/common/es_query/kuery/node_types/literal.test.ts +++ b/packages/kbn-es-query/src/kuery/node_types/literal.test.ts @@ -9,6 +9,8 @@ // @ts-ignore import { buildNode, toElasticsearchQuery } from './literal'; +jest.mock('../grammar'); + describe('kuery node types', () => { describe('literal', () => { describe('buildNode', () => { diff --git a/src/plugins/data/common/es_query/kuery/node_types/literal.ts b/packages/kbn-es-query/src/kuery/node_types/literal.ts similarity index 100% rename from src/plugins/data/common/es_query/kuery/node_types/literal.ts rename to packages/kbn-es-query/src/kuery/node_types/literal.ts diff --git a/src/plugins/data/common/es_query/kuery/node_types/named_arg.test.ts b/packages/kbn-es-query/src/kuery/node_types/named_arg.test.ts similarity index 98% rename from src/plugins/data/common/es_query/kuery/node_types/named_arg.test.ts rename to packages/kbn-es-query/src/kuery/node_types/named_arg.test.ts index 2c3fb43ee0f59..fa944660288d5 100644 --- a/src/plugins/data/common/es_query/kuery/node_types/named_arg.test.ts +++ b/packages/kbn-es-query/src/kuery/node_types/named_arg.test.ts @@ -7,10 +7,10 @@ */ import { nodeTypes } from './index'; - -// @ts-ignore import { buildNode, toElasticsearchQuery } from './named_arg'; +jest.mock('../grammar'); + describe('kuery node types', () => { describe('named arg', () => { describe('buildNode', () => { diff --git a/src/plugins/data/common/es_query/kuery/node_types/named_arg.ts b/packages/kbn-es-query/src/kuery/node_types/named_arg.ts similarity index 100% rename from src/plugins/data/common/es_query/kuery/node_types/named_arg.ts rename to packages/kbn-es-query/src/kuery/node_types/named_arg.ts diff --git a/src/plugins/data/common/es_query/kuery/node_types/node_builder.test.ts b/packages/kbn-es-query/src/kuery/node_types/node_builder.test.ts similarity index 99% rename from src/plugins/data/common/es_query/kuery/node_types/node_builder.test.ts rename to packages/kbn-es-query/src/kuery/node_types/node_builder.test.ts index d6439f8e7cc87..aefd40c6db3fb 100644 --- a/src/plugins/data/common/es_query/kuery/node_types/node_builder.test.ts +++ b/packages/kbn-es-query/src/kuery/node_types/node_builder.test.ts @@ -9,6 +9,8 @@ import { nodeBuilder } from './node_builder'; import { toElasticsearchQuery } from '../index'; +jest.mock('../grammar'); + describe('nodeBuilder', () => { describe('is method', () => { test('string value', () => { diff --git a/src/plugins/data/common/es_query/kuery/node_types/node_builder.ts b/packages/kbn-es-query/src/kuery/node_types/node_builder.ts similarity index 100% rename from src/plugins/data/common/es_query/kuery/node_types/node_builder.ts rename to packages/kbn-es-query/src/kuery/node_types/node_builder.ts diff --git a/src/plugins/data/common/es_query/kuery/node_types/types.ts b/packages/kbn-es-query/src/kuery/node_types/types.ts similarity index 100% rename from src/plugins/data/common/es_query/kuery/node_types/types.ts rename to packages/kbn-es-query/src/kuery/node_types/types.ts diff --git a/src/plugins/data/common/es_query/kuery/node_types/wildcard.test.ts b/packages/kbn-es-query/src/kuery/node_types/wildcard.test.ts similarity index 99% rename from src/plugins/data/common/es_query/kuery/node_types/wildcard.test.ts rename to packages/kbn-es-query/src/kuery/node_types/wildcard.test.ts index 8c20851cfe3f9..9f444eec82b69 100644 --- a/src/plugins/data/common/es_query/kuery/node_types/wildcard.test.ts +++ b/packages/kbn-es-query/src/kuery/node_types/wildcard.test.ts @@ -16,6 +16,8 @@ import { // @ts-ignore } from './wildcard'; +jest.mock('../grammar'); + describe('kuery node types', () => { describe('wildcard', () => { describe('buildNode', () => { diff --git a/src/plugins/data/common/es_query/kuery/node_types/wildcard.ts b/packages/kbn-es-query/src/kuery/node_types/wildcard.ts similarity index 100% rename from src/plugins/data/common/es_query/kuery/node_types/wildcard.ts rename to packages/kbn-es-query/src/kuery/node_types/wildcard.ts diff --git a/src/plugins/data/common/es_query/kuery/types.ts b/packages/kbn-es-query/src/kuery/types.ts similarity index 100% rename from src/plugins/data/common/es_query/kuery/types.ts rename to packages/kbn-es-query/src/kuery/types.ts diff --git a/src/plugins/data/common/es_query/utils.ts b/packages/kbn-es-query/src/utils.ts similarity index 100% rename from src/plugins/data/common/es_query/utils.ts rename to packages/kbn-es-query/src/utils.ts diff --git a/packages/kbn-es-query/tsconfig.browser.json b/packages/kbn-es-query/tsconfig.browser.json new file mode 100644 index 0000000000000..0a1c21cc8e05b --- /dev/null +++ b/packages/kbn-es-query/tsconfig.browser.json @@ -0,0 +1,21 @@ +{ + "extends": "../../tsconfig.browser.json", + "compilerOptions": { + "incremental": true, + "outDir": "./target_web", + "declaration": false, + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-es-query/src", + "types": [ + "jest", + "node" + ], + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "**/__fixtures__/**/*", + "**/__mocks__/**/*" + ] +} diff --git a/packages/kbn-es-query/tsconfig.json b/packages/kbn-es-query/tsconfig.json new file mode 100644 index 0000000000000..b48d90373e2cb --- /dev/null +++ b/packages/kbn-es-query/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "incremental": true, + "declarationDir": "./target_types", + "outDir": "./target_node", + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "sourceRoot": "../../../../packages/kbn-es-query/src", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "**/__fixtures__/**/*", + "**/__mocks__/**/grammar.js", + ] +} diff --git a/packages/kbn-es/BUILD.bazel b/packages/kbn-es/BUILD.bazel index 48f0fb58e983f..8d50d4e34e296 100644 --- a/packages/kbn-es/BUILD.bazel +++ b/packages/kbn-es/BUILD.bazel @@ -39,9 +39,7 @@ DEPS = [ "@npm//glob", "@npm//node-fetch", "@npm//simple-git", - "@npm//tar-fs", "@npm//tree-kill", - "@npm//yauzl", "@npm//zlib" ] diff --git a/packages/kbn-es/src/cluster.js b/packages/kbn-es/src/cluster.js index 52cfd0b71b8ba..32709fc608617 100644 --- a/packages/kbn-es/src/cluster.js +++ b/packages/kbn-es/src/cluster.js @@ -13,18 +13,12 @@ const chalk = require('chalk'); const path = require('path'); const { downloadSnapshot, installSnapshot, installSource, installArchive } = require('./install'); const { ES_BIN } = require('./paths'); -const { - log: defaultLog, - parseEsLog, - extractConfigFiles, - decompress, - NativeRealm, -} = require('./utils'); +const { log: defaultLog, parseEsLog, extractConfigFiles, NativeRealm } = require('./utils'); const { createCliError } = require('./errors'); const { promisify } = require('util'); const treeKillAsync = promisify(require('tree-kill')); const { parseSettings, SettingsFilter } = require('./settings'); -const { CA_CERT_PATH, ES_P12_PATH, ES_P12_PASSWORD } = require('@kbn/dev-utils'); +const { CA_CERT_PATH, ES_P12_PATH, ES_P12_PASSWORD, extract } = require('@kbn/dev-utils'); const readFile = util.promisify(fs.readFile); // listen to data on stream until map returns anything but undefined @@ -144,13 +138,17 @@ exports.Cluster = class Cluster { this._log.info(chalk.bold(`Extracting data directory`)); this._log.indent(4); - // decompress excludes the root directory as that is how our archives are + // stripComponents=1 excludes the root directory as that is how our archives are // structured. This works in our favor as we can explicitly extract into the data dir const extractPath = path.resolve(installPath, extractDirName); this._log.info(`Data archive: ${archivePath}`); this._log.info(`Extract path: ${extractPath}`); - await decompress(archivePath, extractPath); + await extract({ + archivePath, + targetDir: extractPath, + stripComponents: 1, + }); this._log.indent(-4); } diff --git a/packages/kbn-es/src/install/archive.js b/packages/kbn-es/src/install/archive.js index 84c694f6fa9f0..76db5a4427e6d 100644 --- a/packages/kbn-es/src/install/archive.js +++ b/packages/kbn-es/src/install/archive.js @@ -12,7 +12,8 @@ const chalk = require('chalk'); const execa = require('execa'); const del = require('del'); const url = require('url'); -const { log: defaultLog, decompress } = require('../utils'); +const { extract } = require('@kbn/dev-utils'); +const { log: defaultLog } = require('../utils'); const { BASE_PATH, ES_CONFIG, ES_KEYSTORE_BIN } = require('../paths'); const { Artifact } = require('../artifact'); const { parseSettings, SettingsFilter } = require('../settings'); @@ -50,7 +51,11 @@ exports.installArchive = async function installArchive(archive, options = {}) { } log.info('extracting %s', chalk.bold(dest)); - await decompress(dest, installPath); + await extract({ + archivePath: dest, + targetDir: installPath, + stripComponents: 1, + }); log.info('extracted to %s', chalk.bold(installPath)); const tmpdir = path.resolve(installPath, 'ES_TMPDIR'); diff --git a/packages/kbn-es/src/utils/decompress.js b/packages/kbn-es/src/utils/decompress.js deleted file mode 100644 index c895f2f798607..0000000000000 --- a/packages/kbn-es/src/utils/decompress.js +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const fs = require('fs'); -const path = require('path'); - -const yauzl = require('yauzl'); -const zlib = require('zlib'); -const tarFs = require('tar-fs'); - -function decompressTarball(archive, dirPath) { - return new Promise((resolve, reject) => { - fs.createReadStream(archive) - .on('error', reject) - .pipe(zlib.createGunzip()) - .on('error', reject) - .pipe(tarFs.extract(dirPath, { strip: true })) - .on('error', reject) - .on('finish', resolve); - }); -} - -function decompressZip(input, output) { - fs.mkdirSync(output, { recursive: true }); - return new Promise((resolve, reject) => { - yauzl.open(input, { lazyEntries: true }, (err, zipfile) => { - if (err) { - reject(err); - } - - zipfile.readEntry(); - - zipfile.on('close', () => { - resolve(); - }); - - zipfile.on('error', (err) => { - reject(err); - }); - - zipfile.on('entry', (entry) => { - const zipPath = entry.fileName.split(/\/|\\/).slice(1).join(path.sep); - const fileName = path.resolve(output, zipPath); - - if (/\/$/.test(entry.fileName)) { - fs.mkdirSync(fileName, { recursive: true }); - zipfile.readEntry(); - } else { - // file entry - zipfile.openReadStream(entry, (err, readStream) => { - if (err) { - reject(err); - } - - readStream.on('end', () => { - zipfile.readEntry(); - }); - - readStream.pipe(fs.createWriteStream(fileName)); - }); - } - }); - }); - }); -} - -exports.decompress = async function (input, output) { - const ext = path.extname(input); - - switch (path.extname(input)) { - case '.zip': - await decompressZip(input, output); - break; - case '.tar': - case '.gz': - await decompressTarball(input, output); - break; - default: - throw new Error(`unknown extension "${ext}"`); - } -}; diff --git a/packages/kbn-es/src/utils/decompress.test.js b/packages/kbn-es/src/utils/decompress.test.js deleted file mode 100644 index 0f9ac797e9207..0000000000000 --- a/packages/kbn-es/src/utils/decompress.test.js +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -const { decompress } = require('./decompress'); -const fs = require('fs'); -const path = require('path'); -const del = require('del'); -const os = require('os'); - -const fixturesFolder = path.resolve(__dirname, '__fixtures__'); -const randomDir = Math.random().toString(36); -const tmpFolder = path.resolve(os.tmpdir(), randomDir); -const dataFolder = path.resolve(tmpFolder, 'data'); -const esFolder = path.resolve(tmpFolder, '.es'); - -const zipSnapshot = path.resolve(dataFolder, 'snapshot.zip'); -const tarGzSnapshot = path.resolve(dataFolder, 'snapshot.tar.gz'); - -beforeEach(() => { - fs.mkdirSync(tmpFolder, { recursive: true }); - fs.mkdirSync(dataFolder, { recursive: true }); - fs.mkdirSync(esFolder, { recursive: true }); - - fs.copyFileSync(path.resolve(fixturesFolder, 'snapshot.zip'), zipSnapshot); - fs.copyFileSync(path.resolve(fixturesFolder, 'snapshot.tar.gz'), tarGzSnapshot); -}); - -afterEach(() => { - del.sync(tmpFolder, { force: true }); -}); - -test('zip strips root directory', async () => { - await decompress(zipSnapshot, path.resolve(esFolder, 'foo')); - expect(fs.readdirSync(path.resolve(esFolder, 'foo/bin'))).toContain('elasticsearch.bat'); -}); - -test('tar strips root directory', async () => { - await decompress(tarGzSnapshot, path.resolve(esFolder, 'foo')); - expect(fs.readdirSync(path.resolve(esFolder, 'foo/bin'))).toContain('elasticsearch'); -}); diff --git a/packages/kbn-es/src/utils/index.js b/packages/kbn-es/src/utils/index.js index 2715d7b675657..ed83495e5310a 100644 --- a/packages/kbn-es/src/utils/index.js +++ b/packages/kbn-es/src/utils/index.js @@ -11,7 +11,6 @@ exports.log = require('./log').log; exports.parseEsLog = require('./parse_es_log').parseEsLog; exports.findMostRecentlyChanged = require('./find_most_recently_changed').findMostRecentlyChanged; exports.extractConfigFiles = require('./extract_config_files').extractConfigFiles; -exports.decompress = require('./decompress').decompress; exports.NativeRealm = require('./native_realm').NativeRealm; exports.buildSnapshot = require('./build_snapshot').buildSnapshot; exports.archiveForPlatform = require('./build_snapshot').archiveForPlatform; diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 2f6765cd57b9e..7a2c9095e2011 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -107,10 +107,11 @@ pageLoadAssetSize: dataVisualizer: 27530 banners: 17946 mapsEms: 26072 - timelines: 251886 + timelines: 330000 screenshotMode: 17856 visTypePie: 35583 expressionRevealImage: 25675 cases: 144442 expressionError: 22127 userSetup: 18532 + expressionShape: 30033 diff --git a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts index 48d36b706b831..97a7f33be673d 100644 --- a/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts +++ b/packages/kbn-optimizer/src/integration_tests/basic_optimization.test.ts @@ -15,7 +15,7 @@ import cpy from 'cpy'; import del from 'del'; import { tap, filter } from 'rxjs/operators'; import { REPO_ROOT } from '@kbn/utils'; -import { ToolingLog } from '@kbn/dev-utils'; +import { ToolingLog, createReplaceSerializer } from '@kbn/dev-utils'; import { runOptimizer, OptimizerConfig, OptimizerUpdate, logOptimizerState } from '../index'; import { allValuesFrom } from '../common'; @@ -29,6 +29,8 @@ expect.addSnapshotSerializer({ test: (value: any) => typeof value === 'string' && value.includes(REPO_ROOT), }); +expect.addSnapshotSerializer(createReplaceSerializer(/\w+-fastbuild/, '-fastbuild')); + const log = new ToolingLog({ level: 'error', writeTo: { @@ -130,7 +132,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { expect(foo.cache.getModuleCount()).toBe(6); expect(foo.cache.getReferencedFiles()).toMatchInlineSnapshot(` Array [ - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts, + /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/bazel-out/-fastbuild/bin/packages/kbn-ui-shared-deps/target/public_path_module_creator.js, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/kibana.json, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/async_import.ts, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo/public/ext.ts, @@ -153,7 +155,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { /node_modules/@kbn/optimizer/postcss.config.js, /node_modules/css-loader/package.json, /node_modules/style-loader/package.json, - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts, + /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/bazel-out/-fastbuild/bin/packages/kbn-ui-shared-deps/target/public_path_module_creator.js, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/kibana.json, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.scss, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/bar/public/index.ts, @@ -173,7 +175,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { expect(baz.cache.getReferencedFiles()).toMatchInlineSnapshot(` Array [ - /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/packages/kbn-ui-shared-deps/src/public_path_module_creator.ts, + /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/bazel-out/-fastbuild/bin/packages/kbn-ui-shared-deps/target/public_path_module_creator.js, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/kibana.json, /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/x-pack/baz/public/index.ts, /packages/kbn-optimizer/src/worker/entry_point_creator.ts, diff --git a/packages/kbn-optimizer/src/optimizer/watcher.ts b/packages/kbn-optimizer/src/optimizer/watcher.ts index d0420d3c3699d..65958d6669f73 100644 --- a/packages/kbn-optimizer/src/optimizer/watcher.ts +++ b/packages/kbn-optimizer/src/optimizer/watcher.ts @@ -38,7 +38,6 @@ export class Watcher { private readonly watchpack = new Watchpack({ aggregateTimeout: 0, - ignored: /node_modules\/([^\/]+[\/])*(?!package.json)([^\/]+)$/, }); private readonly change$ = Rx.fromEvent<[string]>(this.watchpack, 'change').pipe(share()); diff --git a/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts b/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts index a3455d7ddf2b9..bc8418811e7ae 100644 --- a/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts +++ b/packages/kbn-optimizer/src/worker/populate_bundle_cache_plugin.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import Fs from 'fs'; import Path from 'path'; import { inspect } from 'util'; @@ -21,20 +22,6 @@ import { getModulePath, } from './webpack_helpers'; -function tryToResolveRewrittenPath(from: string, toResolve: string) { - try { - return require.resolve(toResolve); - } catch (error) { - if (error.code === 'MODULE_NOT_FOUND') { - throw new Error( - `attempted to rewrite bazel-out path [${from}] to [${toResolve}] but couldn't find the rewrite target` - ); - } - - throw error; - } -} - /** * sass-loader creates about a 40% overhead on the overall optimizer runtime, and * so this constant is used to indicate to assignBundlesToWorkers() that there is @@ -44,6 +31,20 @@ function tryToResolveRewrittenPath(from: string, toResolve: string) { */ const EXTRA_SCSS_WORK_UNITS = 100; +const isBazelPackageCache = new Map(); +function isBazelPackage(pkgJsonPath: string) { + const cached = isBazelPackageCache.get(pkgJsonPath); + if (typeof cached === 'boolean') { + return cached; + } + + const path = parseFilePath(Fs.realpathSync(pkgJsonPath, 'utf-8')); + const match = !!path.matchDirs('bazel-out', /-fastbuild$/, 'bin', 'packages'); + isBazelPackageCache.set(pkgJsonPath, match); + + return match; +} + export class PopulateBundleCachePlugin { constructor(private readonly workerConfig: WorkerConfig, private readonly bundle: Bundle) {} @@ -71,44 +72,16 @@ export class PopulateBundleCachePlugin { let path = getModulePath(module); let parsedPath = parseFilePath(path); - const bazelOut = parsedPath.matchDirs( - 'bazel-out', - /-fastbuild$/, - 'bin', - 'packages', - /.*/, - 'target' - ); - - // if the module is referenced from one of our packages and resolved to the `bazel-out` dir - // we should rewrite our reference to point to the source file so that we can track the - // modified time of that file rather than the built output which is rebuilt all the time - // without actually changing - if (bazelOut) { - const packageDir = parsedPath.dirs[bazelOut.endIndex - 1]; - const subDirs = parsedPath.dirs.slice(bazelOut.endIndex + 1); - path = tryToResolveRewrittenPath( - path, - Path.join( - workerConfig.repoRoot, - 'packages', - packageDir, - 'src', - ...subDirs, - parsedPath.filename - ? Path.basename(parsedPath.filename, Path.extname(parsedPath.filename)) - : '' - ) + const bazelOutIndex = parsedPath.dirs.indexOf('bazel-out'); + if (bazelOutIndex >= 0) { + path = Path.resolve( + this.workerConfig.repoRoot, + ...parsedPath.dirs.slice(bazelOutIndex), + parsedPath.filename ?? '' ); parsedPath = parseFilePath(path); } - if (parsedPath.matchDirs('bazel-out')) { - throw new Error( - `a bazel-out dir is being referenced by module [${path}] and not getting rewritten to its source location` - ); - } - if (!parsedPath.dirs.includes('node_modules')) { referencedFiles.add(path); @@ -125,13 +98,13 @@ export class PopulateBundleCachePlugin { const nmIndex = parsedPath.dirs.lastIndexOf('node_modules'); const isScoped = parsedPath.dirs[nmIndex + 1].startsWith('@'); - referencedFiles.add( - Path.join( - parsedPath.root, - ...parsedPath.dirs.slice(0, nmIndex + 1 + (isScoped ? 2 : 1)), - 'package.json' - ) + const pkgJsonPath = Path.join( + parsedPath.root, + ...parsedPath.dirs.slice(0, nmIndex + 1 + (isScoped ? 2 : 1)), + 'package.json' ); + + referencedFiles.add(isBazelPackage(pkgJsonPath) ? path : pkgJsonPath); continue; } diff --git a/packages/kbn-securitysolution-es-utils/src/read_privileges/index.ts b/packages/kbn-securitysolution-es-utils/src/read_privileges/index.ts index 8b11387a1d020..aab641367339c 100644 --- a/packages/kbn-securitysolution-es-utils/src/read_privileges/index.ts +++ b/packages/kbn-securitysolution-es-utils/src/read_privileges/index.ts @@ -6,89 +6,69 @@ * Side Public License, v 1. */ -/** - * Copied from src/core/server/elasticsearch/legacy/api_types.ts including its deprecation mentioned below - * TODO: Remove this and refactor the readPrivileges to utilize any newer client side ways rather than all this deprecated legacy stuff - */ -export interface LegacyCallAPIOptions { - /** - * Indicates whether `401 Unauthorized` errors returned from the Elasticsearch API - * should be wrapped into `Boom` error instances with properly set `WWW-Authenticate` - * header that could have been returned by the API itself. If API didn't specify that - * then `Basic realm="Authorization Required"` is used as `WWW-Authenticate`. - */ - wrap401Errors?: boolean; - /** - * A signal object that allows you to abort the request via an AbortController object. - */ - signal?: AbortSignal; -} - -type CallWithRequest, V> = ( - endpoint: string, - params: T, - options?: LegacyCallAPIOptions -) => Promise; +import { ElasticsearchClient } from '../elasticsearch_client'; export const readPrivileges = async ( - callWithRequest: CallWithRequest<{}, unknown>, + esClient: ElasticsearchClient, index: string ): Promise => { - return callWithRequest('transport.request', { - path: '/_security/user/_has_privileges', - method: 'POST', - body: { - cluster: [ - 'all', - 'create_snapshot', - 'manage', - 'manage_api_key', - 'manage_ccr', - 'manage_transform', - 'manage_ilm', - 'manage_index_templates', - 'manage_ingest_pipelines', - 'manage_ml', - 'manage_own_api_key', - 'manage_pipeline', - 'manage_rollup', - 'manage_saml', - 'manage_security', - 'manage_token', - 'manage_watcher', - 'monitor', - 'monitor_transform', - 'monitor_ml', - 'monitor_rollup', - 'monitor_watcher', - 'read_ccr', - 'read_ilm', - 'transport_client', - ], - index: [ - { - names: [index], - privileges: [ - 'all', - 'create', - 'create_doc', - 'create_index', - 'delete', - 'delete_index', - 'index', - 'manage', - 'maintenance', - 'manage_follow_index', - 'manage_ilm', - 'manage_leader_index', - 'monitor', - 'read', - 'read_cross_cluster', - 'view_index_metadata', - 'write', - ], - }, - ], - }, - }); + return ( + await esClient.transport.request({ + path: '/_security/user/_has_privileges', + method: 'POST', + body: { + cluster: [ + 'all', + 'create_snapshot', + 'manage', + 'manage_api_key', + 'manage_ccr', + 'manage_transform', + 'manage_ilm', + 'manage_index_templates', + 'manage_ingest_pipelines', + 'manage_ml', + 'manage_own_api_key', + 'manage_pipeline', + 'manage_rollup', + 'manage_saml', + 'manage_security', + 'manage_token', + 'manage_watcher', + 'monitor', + 'monitor_transform', + 'monitor_ml', + 'monitor_rollup', + 'monitor_watcher', + 'read_ccr', + 'read_ilm', + 'transport_client', + ], + index: [ + { + names: [index], + privileges: [ + 'all', + 'create', + 'create_doc', + 'create_index', + 'delete', + 'delete_index', + 'index', + 'manage', + 'maintenance', + 'manage_follow_index', + 'manage_ilm', + 'manage_leader_index', + 'monitor', + 'read', + 'read_cross_cluster', + 'view_index_metadata', + 'write', + ], + }, + ], + }, + }) + ).body; }; diff --git a/packages/kbn-test/src/jest/utils/get_url.ts b/packages/kbn-test/src/jest/utils/get_url.ts index 734e26c5199d7..e08695b334e1b 100644 --- a/packages/kbn-test/src/jest/utils/get_url.ts +++ b/packages/kbn-test/src/jest/utils/get_url.ts @@ -22,11 +22,6 @@ interface UrlParam { username?: string; } -interface App { - pathname?: string; - hash?: string; -} - /** * Converts a config and a pathname to a url * @param {object} config A url config @@ -46,11 +41,11 @@ interface App { * @return {string} */ -function getUrl(config: UrlParam, app: App) { +function getUrl(config: UrlParam, app: UrlParam) { return url.format(_.assign({}, config, app)); } -getUrl.noAuth = function getUrlNoAuth(config: UrlParam, app: App) { +getUrl.noAuth = function getUrlNoAuth(config: UrlParam, app: UrlParam) { config = _.pickBy(config, function (val, param) { return param !== 'auth'; }); diff --git a/packages/kbn-ui-shared-deps/BUILD.bazel b/packages/kbn-ui-shared-deps/BUILD.bazel index f92049292f373..426f8d0b7485a 100644 --- a/packages/kbn-ui-shared-deps/BUILD.bazel +++ b/packages/kbn-ui-shared-deps/BUILD.bazel @@ -44,9 +44,7 @@ SRC_DEPS = [ "@npm//abortcontroller-polyfill", "@npm//angular", "@npm//babel-loader", - "@npm//compression-webpack-plugin", "@npm//core-js", - "@npm//css-minimizer-webpack-plugin", "@npm//css-loader", "@npm//fflate", "@npm//jquery", @@ -67,7 +65,6 @@ SRC_DEPS = [ "@npm//rxjs", "@npm//styled-components", "@npm//symbol-observable", - "@npm//terser-webpack-plugin", "@npm//url-loader", "@npm//val-loader", "@npm//whatwg-fetch" diff --git a/packages/kbn-ui-shared-deps/webpack.config.js b/packages/kbn-ui-shared-deps/webpack.config.js index 9d18c8033ff67..9692f768cb3b4 100644 --- a/packages/kbn-ui-shared-deps/webpack.config.js +++ b/packages/kbn-ui-shared-deps/webpack.config.js @@ -7,20 +7,23 @@ */ const Path = require('path'); - const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); -const TerserPlugin = require('terser-webpack-plugin'); -const CompressionPlugin = require('compression-webpack-plugin'); const { REPO_ROOT } = require('@kbn/utils'); -const { RawSource } = require('webpack-sources'); const UiSharedDeps = require('./src/index'); const MOMENT_SRC = require.resolve('moment/min/moment-with-locales.js'); +const WEBPACK_SRC = require.resolve('webpack'); module.exports = { + node: { + child_process: 'empty', + fs: 'empty', + }, + externals: { + module: 'module', + }, mode: 'production', entry: { 'kbn-ui-shared-deps': './src/entry.js', @@ -30,8 +33,7 @@ module.exports = { 'kbn-ui-shared-deps.v8.light': ['@elastic/eui/dist/eui_theme_amsterdam_light.css'], }, context: __dirname, - // cheap-source-map should be used if needed - devtool: false, + devtool: 'cheap-source-map', output: { path: UiSharedDeps.distDir, filename: '[name].js', @@ -39,10 +41,11 @@ module.exports = { devtoolModuleFilenameTemplate: (info) => `kbn-ui-shared-deps/${Path.relative(REPO_ROOT, info.absoluteResourcePath)}`, library: '__kbnSharedDeps__', + futureEmitAssets: true, }, module: { - noParse: [MOMENT_SRC], + noParse: [MOMENT_SRC, WEBPACK_SRC], rules: [ { include: [require.resolve('./src/entry.js')], @@ -102,35 +105,17 @@ module.exports = { resolve: { alias: { moment: MOMENT_SRC, + // NOTE: Used to include react profiling on bundles + // https://gist.github.com/bvaughn/25e6233aeb1b4f0cdb8d8366e54a3977#webpack-4 + 'react-dom$': 'react-dom/profiling', + 'scheduler/tracing': 'scheduler/tracing-profiling', }, extensions: ['.js', '.ts'], symlinks: false, }, optimization: { - minimizer: [ - new CssMinimizerPlugin({ - parallel: false, - minimizerOptions: { - preset: [ - 'default', - { - discardComments: false, - }, - ], - }, - }), - new TerserPlugin({ - cache: false, - sourceMap: false, - extractComments: false, - parallel: false, - terserOptions: { - compress: true, - mangle: true, - }, - }), - ], + minimize: false, noEmitOnErrors: true, splitChunks: { cacheGroups: { @@ -155,44 +140,5 @@ module.exports = { new MiniCssExtractPlugin({ filename: '[name].css', }), - new CompressionPlugin({ - algorithm: 'brotliCompress', - filename: '[path].br', - test: /\.(js|css)$/, - cache: false, - }), - new CompressionPlugin({ - algorithm: 'gzip', - filename: '[path].gz', - test: /\.(js|css)$/, - cache: false, - }), - new (class MetricsPlugin { - apply(compiler) { - compiler.hooks.emit.tap('MetricsPlugin', (compilation) => { - const metrics = [ - { - group: 'page load bundle size', - id: 'kbnUiSharedDeps-js', - value: compilation.assets['kbn-ui-shared-deps.js'].size(), - }, - { - group: 'page load bundle size', - id: 'kbnUiSharedDeps-css', - value: - compilation.assets['kbn-ui-shared-deps.css'].size() + - compilation.assets['kbn-ui-shared-deps.v7.light.css'].size(), - }, - { - group: 'page load bundle size', - id: 'kbnUiSharedDeps-elastic', - value: compilation.assets['kbn-ui-shared-deps.@elastic.js'].size(), - }, - ]; - - compilation.emitAsset('metrics.json', new RawSource(JSON.stringify(metrics, null, 2))); - }); - } - })(), ], }; diff --git a/src/core/TESTING.md b/src/core/TESTING.md index 10ead1ea8cfe2..5e0913e7a9d20 100644 --- a/src/core/TESTING.md +++ b/src/core/TESTING.md @@ -337,6 +337,7 @@ describe('myPlugin', () => { let root: ReturnType; beforeAll(async () => { root = kbnTestServer.createRoot(); + await root.preboot(); await root.setup(); await root.start(); }, 30000); @@ -382,6 +383,7 @@ describe('myPlugin', () => { let root: ReturnType; beforeAll(async () => { root = kbnTestServer.createRoot(); + await root.preboot(); await root.setup(); await root.start(); }, 30000); diff --git a/src/core/public/execution_context/execution_context_container.ts b/src/core/public/execution_context/execution_context_container.ts index 9c8e3e269ec88..8e0670472c80d 100644 --- a/src/core/public/execution_context/execution_context_container.ts +++ b/src/core/public/execution_context/execution_context_container.ts @@ -30,6 +30,7 @@ function enforceMaxLength(header: string): string { */ export interface IExecutionContextContainer { toHeader: () => Record; + toJSON: () => Readonly; } export class ExecutionContextContainer implements IExecutionContextContainer { @@ -42,7 +43,12 @@ export class ExecutionContextContainer implements IExecutionContextContainer { // escape content as the description property might contain non-ASCII symbols return enforceMaxLength(encodeURIComponent(value)); } + toHeader() { return { [BAGGAGE_HEADER]: this.toString() }; } + + toJSON() { + return this.#context; + } } diff --git a/src/core/public/execution_context/execution_context_service.mock.ts b/src/core/public/execution_context/execution_context_service.mock.ts index d8148b0af807d..071e61f17c25c 100644 --- a/src/core/public/execution_context/execution_context_service.mock.ts +++ b/src/core/public/execution_context/execution_context_service.mock.ts @@ -13,6 +13,7 @@ import type { ExecutionContextContainer } from './execution_context_container'; const createContainerMock = () => { const mock: jest.Mocked> = { toHeader: jest.fn(), + toJSON: jest.fn(), }; return mock; }; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index b3dd3827352bd..40304d27580ca 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -186,16 +186,14 @@ export type { export type { DeprecationsServiceStart, ResolveDeprecationResponse } from './deprecations'; -export type { - IExecutionContextContainer, - ExecutionContextServiceStart, - KibanaExecutionContext, -} from './execution_context'; +export type { IExecutionContextContainer, ExecutionContextServiceStart } from './execution_context'; export type { MountPoint, UnmountCallback, PublicUiSettingsParams } from './types'; export { URL_MAX_LENGTH } from './core_app'; +export type { KibanaExecutionContext } from './execution_context'; + /** * Core services exposed to the `Plugin` setup lifecycle * diff --git a/src/core/public/plugins/plugin.test.ts b/src/core/public/plugins/plugin.test.ts index ef919018f120b..94c88f732f4e1 100644 --- a/src/core/public/plugins/plugin.test.ts +++ b/src/core/public/plugins/plugin.test.ts @@ -8,7 +8,7 @@ import { mockInitializer, mockPlugin, mockPluginReader } from './plugin.test.mocks'; -import { DiscoveredPlugin } from '../../server'; +import { DiscoveredPlugin, PluginType } from '../../server'; import { coreMock } from '../mocks'; import { PluginWrapper } from './plugin'; @@ -19,6 +19,7 @@ function createManifest( return { id, version: 'some-version', + type: PluginType.standard, configPath: ['path'], requiredPlugins: required, optionalPlugins: optional, diff --git a/src/core/public/plugins/plugins_service.test.ts b/src/core/public/plugins/plugins_service.test.ts index d62a4bcdd1e51..3f23889c57de6 100644 --- a/src/core/public/plugins/plugins_service.test.ts +++ b/src/core/public/plugins/plugins_service.test.ts @@ -13,7 +13,7 @@ import { mockPluginInitializerProvider, } from './plugins_service.test.mocks'; -import { PluginName } from 'src/core/server'; +import { PluginName, PluginType } from 'src/core/server'; import { coreMock } from '../mocks'; import { PluginsService, @@ -60,6 +60,7 @@ function createManifest( return { id, version: 'some-version', + type: PluginType.standard, configPath: ['path'], requiredPlugins: required, optionalPlugins: optional, diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index d23980ff55a2c..673cb2e7ab557 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -900,6 +900,8 @@ export interface IBasePath { export interface IExecutionContextContainer { // (undocumented) toHeader: () => Record; + // (undocumented) + toJSON: () => Readonly; } // @public diff --git a/src/core/server/bootstrap.ts b/src/core/server/bootstrap.ts index 18a5eceb1b2d3..5131defc93461 100644 --- a/src/core/server/bootstrap.ts +++ b/src/core/server/bootstrap.ts @@ -59,9 +59,9 @@ export async function bootstrap({ configs, cliArgs, applyConfigOverrides }: Boot reloadConfiguration(); }); - function reloadConfiguration() { + function reloadConfiguration(reason = 'SIGHUP signal received') { const cliLogger = root.logger.get('cli'); - cliLogger.info('Reloading Kibana configuration due to SIGHUP.', { tags: ['config'] }); + cliLogger.info(`Reloading Kibana configuration (reason: ${reason}).`, { tags: ['config'] }); try { rawConfigService.reloadConfig(); @@ -69,7 +69,7 @@ export async function bootstrap({ configs, cliArgs, applyConfigOverrides }: Boot return shutdown(err); } - cliLogger.info('Reloaded Kibana configuration due to SIGHUP.', { tags: ['config'] }); + cliLogger.info(`Reloaded Kibana configuration (reason: ${reason}).`, { tags: ['config'] }); } process.on('SIGINT', () => shutdown()); @@ -81,11 +81,28 @@ export async function bootstrap({ configs, cliArgs, applyConfigOverrides }: Boot } try { + const { preboot } = await root.preboot(); + + // If setup is on hold then preboot server is supposed to serve user requests and we can let + // dev parent process know that we are ready for dev mode. + const isSetupOnHold = preboot.isSetupOnHold(); + if (process.send && isSetupOnHold) { + process.send(['SERVER_LISTENING']); + } + + if (isSetupOnHold) { + root.logger.get().info('Holding setup until preboot stage is completed.'); + const { shouldReloadConfig } = await preboot.waitUntilCanSetup(); + if (shouldReloadConfig) { + await reloadConfiguration('configuration might have changed during preboot stage'); + } + } + await root.setup(); await root.start(); - // notify parent process know when we are ready for dev mode. - if (process.send) { + // Notify parent process if we haven't done that yet during preboot stage. + if (process.send && !isSetupOnHold) { process.send(['SERVER_LISTENING']); } } catch (err) { diff --git a/src/core/server/capabilities/capabilities_service.mock.ts b/src/core/server/capabilities/capabilities_service.mock.ts index 0ba3446a77270..1af10b3ad981e 100644 --- a/src/core/server/capabilities/capabilities_service.mock.ts +++ b/src/core/server/capabilities/capabilities_service.mock.ts @@ -36,6 +36,7 @@ const createCapabilitiesMock = (): Capabilities => { type CapabilitiesServiceContract = PublicMethodsOf; const createMock = () => { const mocked: jest.Mocked = { + preboot: jest.fn(), setup: jest.fn().mockReturnValue(createSetupContractMock()), start: jest.fn().mockReturnValue(createStartContractMock()), }; diff --git a/src/core/server/capabilities/capabilities_service.test.ts b/src/core/server/capabilities/capabilities_service.test.ts index 8f9627a64b082..0476f844d011f 100644 --- a/src/core/server/capabilities/capabilities_service.test.ts +++ b/src/core/server/capabilities/capabilities_service.test.ts @@ -6,7 +6,11 @@ * Side Public License, v 1. */ -import { httpServiceMock, InternalHttpServiceSetupMock } from '../http/http_service.mock'; +import { + httpServiceMock, + InternalHttpServicePrebootMock, + InternalHttpServiceSetupMock, +} from '../http/http_service.mock'; import { mockRouter, RouterMock } from '../http/router/router.mock'; import { CapabilitiesService, CapabilitiesSetup } from './capabilities_service'; import { mockCoreContext } from '../core_context.mock'; @@ -24,6 +28,31 @@ describe('CapabilitiesService', () => { service = new CapabilitiesService(mockCoreContext.create()); }); + describe('#preboot()', () => { + let httpPreboot: InternalHttpServicePrebootMock; + beforeEach(() => { + httpPreboot = httpServiceMock.createInternalPrebootContract(); + service.preboot({ http: httpPreboot }); + }); + + it('registers the capabilities routes', async () => { + expect(httpPreboot.registerRoutes).toHaveBeenCalledWith('', expect.any(Function)); + expect(httpPreboot.registerRoutes).toHaveBeenCalledTimes(1); + + const [[, callback]] = httpPreboot.registerRoutes.mock.calls; + callback(router); + + expect(router.post).toHaveBeenCalledTimes(1); + expect(router.post).toHaveBeenCalledWith( + expect.objectContaining({ + path: '/api/core/capabilities', + options: { authRequired: 'optional' }, + }), + expect.any(Function) + ); + }); + }); + describe('#setup()', () => { beforeEach(() => { setup = service.setup({ http }); diff --git a/src/core/server/capabilities/capabilities_service.ts b/src/core/server/capabilities/capabilities_service.ts index 6088ec29db998..1166c8f6b48c4 100644 --- a/src/core/server/capabilities/capabilities_service.ts +++ b/src/core/server/capabilities/capabilities_service.ts @@ -9,7 +9,7 @@ import { Capabilities, CapabilitiesProvider, CapabilitiesSwitcher } from './types'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; -import { InternalHttpServiceSetup, KibanaRequest } from '../http'; +import { InternalHttpServicePreboot, InternalHttpServiceSetup, KibanaRequest } from '../http'; import { mergeCapabilities } from './merge_capabilities'; import { getCapabilitiesResolver, CapabilitiesResolver } from './resolve_capabilities'; import { registerRoutes } from './routes'; @@ -120,6 +120,10 @@ export interface CapabilitiesStart { ): Promise; } +interface PrebootSetupDeps { + http: InternalHttpServicePreboot; +} + interface SetupDeps { http: InternalHttpServiceSetup; } @@ -149,10 +153,20 @@ export class CapabilitiesService { ); } + public preboot(prebootDeps: PrebootSetupDeps) { + this.logger.debug('Prebooting capabilities service'); + + // The preboot server has no need for real capabilities. + // Returning the un-augmented defaults is sufficient. + prebootDeps.http.registerRoutes('', (router) => { + registerRoutes(router, async () => defaultCapabilities); + }); + } + public setup(setupDeps: SetupDeps): CapabilitiesSetup { this.logger.debug('Setting up capabilities service'); - registerRoutes(setupDeps.http, this.resolveCapabilities); + registerRoutes(setupDeps.http.createRouter(''), this.resolveCapabilities); return { registerProvider: (provider: CapabilitiesProvider) => { diff --git a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts index ac793d960d03b..2e80fbb9d20c0 100644 --- a/src/core/server/capabilities/integration_tests/capabilities_service.test.ts +++ b/src/core/server/capabilities/integration_tests/capabilities_service.test.ts @@ -8,7 +8,7 @@ import supertest from 'supertest'; import { REPO_ROOT } from '@kbn/dev-utils'; -import { HttpService, InternalHttpServiceSetup } from '../../http'; +import { HttpService, InternalHttpServicePreboot, InternalHttpServiceSetup } from '../../http'; import { contextServiceMock } from '../../context/context_service.mock'; import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock'; import { loggingSystemMock } from '../../logging/logging_system.mock'; @@ -23,6 +23,7 @@ const env = Env.createDefault(REPO_ROOT, getEnvOptions()); describe('CapabilitiesService', () => { let server: HttpService; + let httpPreboot: InternalHttpServicePreboot; let httpSetup: InternalHttpServiceSetup; let service: CapabilitiesService; @@ -30,6 +31,7 @@ describe('CapabilitiesService', () => { beforeEach(async () => { server = createHttpServer(); + httpPreboot = await server.preboot({ context: contextServiceMock.createPrebootContract() }); httpSetup = await server.setup({ context: contextServiceMock.createSetupContract(), executionContext: executionContextServiceMock.createInternalSetupContract(), @@ -40,6 +42,7 @@ describe('CapabilitiesService', () => { logger: loggingSystemMock.create(), configService: {} as any, }); + await service.preboot({ http: httpPreboot }); serviceSetup = await service.setup({ http: httpSetup }); await server.start(); }); diff --git a/src/core/server/capabilities/routes/index.ts b/src/core/server/capabilities/routes/index.ts index a417dbfeb43a3..4140d75d1a1a1 100644 --- a/src/core/server/capabilities/routes/index.ts +++ b/src/core/server/capabilities/routes/index.ts @@ -7,10 +7,9 @@ */ import { CapabilitiesResolver } from '../resolve_capabilities'; -import { InternalHttpServiceSetup } from '../../http'; +import { IRouter } from '../../http'; import { registerCapabilitiesRoutes } from './resolve_capabilities'; -export function registerRoutes(http: InternalHttpServiceSetup, resolver: CapabilitiesResolver) { - const router = http.createRouter(''); +export function registerRoutes(router: IRouter, resolver: CapabilitiesResolver) { registerCapabilitiesRoutes(router, resolver); } diff --git a/src/core/server/config/ensure_valid_configuration.test.ts b/src/core/server/config/ensure_valid_configuration.test.ts index f1006f93dbc2d..372b9d4c0dfad 100644 --- a/src/core/server/config/ensure_valid_configuration.test.ts +++ b/src/core/server/config/ensure_valid_configuration.test.ts @@ -23,6 +23,20 @@ describe('ensureValidConfiguration', () => { it('returns normally when there is no unused keys and when the config validates', async () => { await expect(ensureValidConfiguration(configService as any)).resolves.toBeUndefined(); + + expect(configService.validate).toHaveBeenCalledWith(undefined); + }); + + it('forwards parameters to the `validate` method', async () => { + await expect( + ensureValidConfiguration(configService as any, { logDeprecations: false }) + ).resolves.toBeUndefined(); + expect(configService.validate).toHaveBeenCalledWith({ logDeprecations: false }); + + await expect( + ensureValidConfiguration(configService as any, { logDeprecations: true }) + ).resolves.toBeUndefined(); + expect(configService.validate).toHaveBeenCalledWith({ logDeprecations: true }); }); it('throws when config validation fails', async () => { diff --git a/src/core/server/config/ensure_valid_configuration.ts b/src/core/server/config/ensure_valid_configuration.ts index c7a4721b7d2ae..040cb23e07988 100644 --- a/src/core/server/config/ensure_valid_configuration.ts +++ b/src/core/server/config/ensure_valid_configuration.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ConfigService } from '@kbn/config'; +import { ConfigService, ConfigValidateParameters } from '@kbn/config'; import { CriticalError } from '../errors'; const ignoredPaths = ['dev.', 'elastic.apm.']; @@ -14,9 +14,12 @@ const ignoredPaths = ['dev.', 'elastic.apm.']; const invalidConfigExitCode = 78; const legacyInvalidConfigExitCode = 64; -export async function ensureValidConfiguration(configService: ConfigService) { +export async function ensureValidConfiguration( + configService: ConfigService, + params?: ConfigValidateParameters +) { try { - await configService.validate(); + await configService.validate(params); } catch (e) { throw new CriticalError(e.message, 'InvalidConfig', invalidConfigExitCode, e); } diff --git a/src/core/server/config/integration_tests/config_deprecation.test.ts b/src/core/server/config/integration_tests/config_deprecation.test.ts index 5b672774c515a..2d86281ce40d6 100644 --- a/src/core/server/config/integration_tests/config_deprecation.test.ts +++ b/src/core/server/config/integration_tests/config_deprecation.test.ts @@ -26,6 +26,7 @@ describe('configuration deprecations', () => { it('should not log deprecation warnings for default configuration that is not one of `logging.verbose`, `logging.quiet` or `logging.silent`', async () => { root = kbnTestServer.createRoot(); + await root.preboot(); await root.setup(); const logs = loggingSystemMock.collect(mockLoggingSystem); @@ -44,6 +45,7 @@ describe('configuration deprecations', () => { }, }); + await root.preboot(); await root.setup(); const logs = loggingSystemMock.collect(mockLoggingSystem); diff --git a/src/core/server/context/context_service.mock.ts b/src/core/server/context/context_service.mock.ts index e705fff2e35a5..c9b1e0e7692e5 100644 --- a/src/core/server/context/context_service.mock.ts +++ b/src/core/server/context/context_service.mock.ts @@ -8,9 +8,16 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; -import { ContextService, ContextSetup } from './context_service'; +import { ContextService, ContextSetup, InternalContextPreboot } from './context_service'; import { contextMock } from './container/context.mock'; +const createPrebootContractMock = (mockContext = {}) => { + const prebootContract: jest.Mocked = { + createContextContainer: jest.fn().mockImplementation(() => contextMock.create(mockContext)), + }; + return prebootContract; +}; + const createSetupContractMock = (mockContext = {}) => { const setupContract: jest.Mocked = { createContextContainer: jest.fn().mockImplementation(() => contextMock.create(mockContext)), @@ -21,13 +28,16 @@ const createSetupContractMock = (mockContext = {}) => { type ContextServiceContract = PublicMethodsOf; const createMock = () => { const mocked: jest.Mocked = { + preboot: jest.fn(), setup: jest.fn(), }; + mocked.preboot.mockReturnValue(createPrebootContractMock()); mocked.setup.mockReturnValue(createSetupContractMock()); return mocked; }; export const contextServiceMock = { create: createMock, + createPrebootContract: createPrebootContractMock, createSetupContract: createSetupContractMock, }; diff --git a/src/core/server/context/context_service.test.ts b/src/core/server/context/context_service.test.ts index 48dc485727251..8b5a784b1f8a1 100644 --- a/src/core/server/context/context_service.test.ts +++ b/src/core/server/context/context_service.test.ts @@ -14,10 +14,23 @@ import { CoreContext } from '../core_context'; const pluginDependencies = new Map(); describe('ContextService', () => { + describe('#preboot()', () => { + test('createContextContainer returns a new container configured with pluginDependencies', () => { + const coreId = Symbol(); + const service = new ContextService({ coreId } as CoreContext); + const preboot = service.preboot({ pluginDependencies }); + expect(preboot.createContextContainer()).toBeDefined(); + expect(MockContextConstructor).toHaveBeenCalledWith(pluginDependencies, coreId); + }); + }); + describe('#setup()', () => { test('createContextContainer returns a new container configured with pluginDependencies', () => { const coreId = Symbol(); const service = new ContextService({ coreId } as CoreContext); + + service.preboot({ pluginDependencies: new Map() }); + const setup = service.setup({ pluginDependencies }); expect(setup.createContextContainer()).toBeDefined(); expect(MockContextConstructor).toHaveBeenCalledWith(pluginDependencies, coreId); diff --git a/src/core/server/context/context_service.ts b/src/core/server/context/context_service.ts index 68a99ed3d4156..9e77786c1562c 100644 --- a/src/core/server/context/context_service.ts +++ b/src/core/server/context/context_service.ts @@ -10,6 +10,8 @@ import { PluginOpaqueId } from '../../server'; import { IContextContainer, ContextContainer } from './container'; import { CoreContext } from '../core_context'; +type PrebootDeps = SetupDeps; + interface SetupDeps { pluginDependencies: ReadonlyMap; } @@ -18,7 +20,17 @@ interface SetupDeps { export class ContextService { constructor(private readonly core: CoreContext) {} + public preboot({ pluginDependencies }: PrebootDeps): InternalContextPreboot { + return this.getContextContainerFactory(pluginDependencies); + } + public setup({ pluginDependencies }: SetupDeps): ContextSetup { + return this.getContextContainerFactory(pluginDependencies); + } + + private getContextContainerFactory( + pluginDependencies: ReadonlyMap + ) { return { createContextContainer: () => { return new ContextContainer(pluginDependencies, this.core.coreId); @@ -27,6 +39,9 @@ export class ContextService { } } +/** @internal */ +export type InternalContextPreboot = ContextSetup; + /** * {@inheritdoc IContextContainer} * diff --git a/src/core/server/context/index.ts b/src/core/server/context/index.ts index 84f7ad07da2c3..d12bafdef7a90 100644 --- a/src/core/server/context/index.ts +++ b/src/core/server/context/index.ts @@ -7,7 +7,7 @@ */ export { ContextService } from './context_service'; -export type { ContextSetup } from './context_service'; +export type { InternalContextPreboot, ContextSetup } from './context_service'; export type { IContextContainer, IContextProvider, diff --git a/src/core/server/core_app/core_app.test.ts b/src/core/server/core_app/core_app.test.ts index ad7af3ac8b84d..f6a9b653ec034 100644 --- a/src/core/server/core_app/core_app.test.ts +++ b/src/core/server/core_app/core_app.test.ts @@ -9,10 +9,13 @@ import { registerBundleRoutesMock } from './core_app.test.mocks'; import { mockCoreContext } from '../core_context.mock'; -import { coreMock } from '../mocks'; +import { coreMock, httpServerMock } from '../mocks'; import { httpResourcesMock } from '../http_resources/http_resources_service.mock'; import type { UiPlugins } from '../plugins'; +import { PluginType } from '../plugins'; import { CoreApp } from './core_app'; +import { mockRouter } from '../http/router/router.mock'; +import { RequestHandlerContext } from 'kibana/server'; const emptyPlugins = (): UiPlugins => ({ internal: new Map(), @@ -23,11 +26,23 @@ const emptyPlugins = (): UiPlugins => ({ describe('CoreApp', () => { let coreContext: ReturnType; let coreApp: CoreApp; + let internalCorePreboot: ReturnType; + let prebootHTTPResourcesRegistrar: ReturnType; let internalCoreSetup: ReturnType; let httpResourcesRegistrar: ReturnType; beforeEach(() => { coreContext = mockCoreContext.create(); + + internalCorePreboot = coreMock.createInternalPreboot(); + internalCorePreboot.http.registerRoutes.mockImplementation((path, callback) => + callback(mockRouter.create()) + ); + prebootHTTPResourcesRegistrar = httpResourcesMock.createRegistrar(); + internalCorePreboot.httpResources.createRegistrar.mockReturnValue( + prebootHTTPResourcesRegistrar + ); + internalCoreSetup = coreMock.createInternalSetup(); httpResourcesRegistrar = httpResourcesMock.createRegistrar(); internalCoreSetup.httpResources.createRegistrar.mockReturnValue(httpResourcesRegistrar); @@ -72,6 +87,60 @@ describe('CoreApp', () => { }); }); + describe('#preboot', () => { + let prebootUIPlugins: UiPlugins; + beforeEach(() => { + prebootUIPlugins = emptyPlugins(); + prebootUIPlugins.public.set('some-plugin', { + type: PluginType.preboot, + configPath: 'some-plugin', + id: 'some-plugin', + optionalPlugins: [], + requiredBundles: [], + requiredPlugins: [], + }); + }); + it('calls `registerBundleRoutes` with the correct options', () => { + coreApp.preboot(internalCorePreboot, prebootUIPlugins); + + expect(registerBundleRoutesMock).toHaveBeenCalledTimes(1); + expect(registerBundleRoutesMock).toHaveBeenCalledWith({ + uiPlugins: prebootUIPlugins, + router: expect.any(Object), + packageInfo: coreContext.env.packageInfo, + serverBasePath: internalCorePreboot.http.basePath.serverBasePath, + }); + }); + + it('does not call `registerBundleRoutes` if there are no `preboot` UI plugins', () => { + coreApp.preboot(internalCorePreboot, emptyPlugins()); + + expect(registerBundleRoutesMock).not.toHaveBeenCalled(); + }); + + it('main route handles core app rendering', () => { + coreApp.preboot(internalCorePreboot, prebootUIPlugins); + + expect(prebootHTTPResourcesRegistrar.register).toHaveBeenCalledWith( + { + path: '/{path*}', + validate: expect.any(Object), + }, + expect.any(Function) + ); + + const [[, handler]] = prebootHTTPResourcesRegistrar.register.mock.calls; + const mockResponseFactory = httpResourcesMock.createResponseFactory(); + handler( + ({} as unknown) as RequestHandlerContext, + httpServerMock.createKibanaRequest(), + mockResponseFactory + ); + + expect(mockResponseFactory.renderAnonymousCoreApp).toHaveBeenCalled(); + }); + }); + describe('`/app/{id}/{any*}` route', () => { it('is registered with the correct parameters', () => { coreApp.setup(internalCoreSetup, emptyPlugins()); @@ -89,7 +158,7 @@ describe('CoreApp', () => { }); }); - it('calls `registerBundleRoutes` with the correct options', () => { + it('`setup` calls `registerBundleRoutes` with the correct options', () => { const uiPlugins = emptyPlugins(); coreApp.setup(internalCoreSetup, uiPlugins); diff --git a/src/core/server/core_app/core_app.ts b/src/core/server/core_app/core_app.ts index e728cb0b82475..35a7c57b67610 100644 --- a/src/core/server/core_app/core_app.ts +++ b/src/core/server/core_app/core_app.ts @@ -12,12 +12,25 @@ import { Env } from '@kbn/config'; import { schema } from '@kbn/config-schema'; import { fromRoot } from '@kbn/utils'; -import { InternalCoreSetup } from '../internal_types'; +import { IRouter, IBasePath, IKibanaResponse, KibanaResponseFactory } from '../http'; +import { HttpResources, HttpResourcesServiceToolkit } from '../http_resources'; +import { InternalCorePreboot, InternalCoreSetup } from '../internal_types'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; import { registerBundleRoutes } from './bundle_routes'; import { UiPlugins } from '../plugins'; +/** @internal */ +interface CommonRoutesParams { + router: IRouter; + httpResources: HttpResources; + basePath: IBasePath; + uiPlugins: UiPlugins; + onResourceNotFound: ( + res: HttpResourcesServiceToolkit & KibanaResponseFactory + ) => Promise; +} + /** @internal */ export class CoreApp { private readonly logger: Logger; @@ -28,12 +41,34 @@ export class CoreApp { this.env = core.env; } + preboot(corePreboot: InternalCorePreboot, uiPlugins: UiPlugins) { + this.logger.debug('Prebooting core app.'); + + // We register app-serving routes only if there are `preboot` plugins that may need them. + if (uiPlugins.public.size > 0) { + this.registerPrebootDefaultRoutes(corePreboot, uiPlugins); + this.registerStaticDirs(corePreboot); + } + } + setup(coreSetup: InternalCoreSetup, uiPlugins: UiPlugins) { this.logger.debug('Setting up core app.'); this.registerDefaultRoutes(coreSetup, uiPlugins); this.registerStaticDirs(coreSetup); } + private registerPrebootDefaultRoutes(corePreboot: InternalCorePreboot, uiPlugins: UiPlugins) { + corePreboot.http.registerRoutes('', (router) => { + this.registerCommonDefaultRoutes({ + basePath: corePreboot.http.basePath, + httpResources: corePreboot.httpResources.createRegistrar(router), + router, + uiPlugins, + onResourceNotFound: (res) => res.renderAnonymousCoreApp(), + }); + }); + } + private registerDefaultRoutes(coreSetup: InternalCoreSetup, uiPlugins: UiPlugins) { const httpSetup = coreSetup.http; const router = httpSetup.createRouter(''); @@ -51,8 +86,55 @@ export class CoreApp { }); }); - // remove trailing slash catch-all - router.get( + this.registerCommonDefaultRoutes({ + basePath: coreSetup.http.basePath, + httpResources: resources, + router, + uiPlugins, + onResourceNotFound: async (res) => res.notFound(), + }); + + resources.register( + { + path: '/app/{id}/{any*}', + validate: false, + options: { + authRequired: true, + }, + }, + async (context, request, response) => { + return response.renderCoreApp(); + } + ); + + const anonymousStatusPage = coreSetup.status.isStatusPageAnonymous(); + resources.register( + { + path: '/status', + validate: false, + options: { + authRequired: !anonymousStatusPage, + }, + }, + async (context, request, response) => { + if (anonymousStatusPage) { + return response.renderAnonymousCoreApp(); + } else { + return response.renderCoreApp(); + } + } + ); + } + + private registerCommonDefaultRoutes({ + router, + basePath, + uiPlugins, + onResourceNotFound, + httpResources, + }: CommonRoutesParams) { + // catch-all route + httpResources.register( { path: '/{path*}', validate: { @@ -66,17 +148,18 @@ export class CoreApp { const { query, params } = req; const { path } = params; if (!path || !path.endsWith('/') || path.startsWith('/')) { - return res.notFound(); + return onResourceNotFound(res); } - const basePath = httpSetup.basePath.get(req); + // remove trailing slash + const requestBasePath = basePath.get(req); let rewrittenPath = path.slice(0, -1); - if (`/${path}`.startsWith(basePath)) { - rewrittenPath = rewrittenPath.substring(basePath.length); + if (`/${path}`.startsWith(requestBasePath)) { + rewrittenPath = rewrittenPath.substring(requestBasePath.length); } const querystring = query ? stringify(query) : undefined; - const url = `${basePath}/${rewrittenPath}${querystring ? `?${querystring}` : ''}`; + const url = `${requestBasePath}/${rewrittenPath}${querystring ? `?${querystring}` : ''}`; return res.redirected({ headers: { @@ -94,45 +177,14 @@ export class CoreApp { router, uiPlugins, packageInfo: this.env.packageInfo, - serverBasePath: coreSetup.http.basePath.serverBasePath, + serverBasePath: basePath.serverBasePath, }); - - resources.register( - { - path: '/app/{id}/{any*}', - validate: false, - options: { - authRequired: true, - }, - }, - async (context, request, response) => { - return response.renderCoreApp(); - } - ); - - const anonymousStatusPage = coreSetup.status.isStatusPageAnonymous(); - resources.register( - { - path: '/status', - validate: false, - options: { - authRequired: !anonymousStatusPage, - }, - }, - async (context, request, response) => { - if (anonymousStatusPage) { - return response.renderAnonymousCoreApp(); - } else { - return response.renderCoreApp(); - } - } - ); } - private registerStaticDirs(coreSetup: InternalCoreSetup) { - coreSetup.http.registerStaticDir('/ui/{path*}', Path.resolve(__dirname, './assets')); + private registerStaticDirs(core: InternalCoreSetup | InternalCorePreboot) { + core.http.registerStaticDir('/ui/{path*}', Path.resolve(__dirname, './assets')); - coreSetup.http.registerStaticDir( + core.http.registerStaticDir( '/node_modules/@kbn/ui-framework/dist/{path*}', fromRoot('node_modules/@kbn/ui-framework/dist') ); diff --git a/src/core/server/core_app/integration_tests/bundle_routes.test.ts b/src/core/server/core_app/integration_tests/bundle_routes.test.ts index 7c50e09b12468..6d37241c2a36f 100644 --- a/src/core/server/core_app/integration_tests/bundle_routes.test.ts +++ b/src/core/server/core_app/integration_tests/bundle_routes.test.ts @@ -26,12 +26,13 @@ describe('bundle routes', () => { let logger: ReturnType; let fileHashCache: FileHashCache; - beforeEach(() => { + beforeEach(async () => { contextSetup = contextServiceMock.createSetupContract(); logger = loggingSystemMock.create(); fileHashCache = new FileHashCache(); server = createHttpServer({ logger }); + await server.preboot({ context: contextServiceMock.createPrebootContract() }); }); afterEach(async () => { diff --git a/src/core/server/core_app/integration_tests/core_app_routes.test.ts b/src/core/server/core_app/integration_tests/core_app_routes.test.ts index faa1c905afa9d..a12e9e7d55188 100644 --- a/src/core/server/core_app/integration_tests/core_app_routes.test.ts +++ b/src/core/server/core_app/integration_tests/core_app_routes.test.ts @@ -20,6 +20,7 @@ describe('Core app routes', () => { }, }); + await root.preboot(); await root.setup(); await root.start(); }); diff --git a/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts b/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts index dcb623c1ffc74..2ffe3b9d1e4cc 100644 --- a/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts +++ b/src/core/server/core_app/integration_tests/default_route_provider_config.test.ts @@ -24,6 +24,7 @@ describe('default route provider', () => { }, }); + await root.preboot(); await root.setup(); await root.start(); }); diff --git a/src/core/server/core_app/integration_tests/static_assets.test.ts b/src/core/server/core_app/integration_tests/static_assets.test.ts index 1c7b7db305b7a..86da1d94d3fc6 100644 --- a/src/core/server/core_app/integration_tests/static_assets.test.ts +++ b/src/core/server/core_app/integration_tests/static_assets.test.ts @@ -15,6 +15,7 @@ describe('Platform assets', function () { beforeAll(async function () { root = kbnTestServer.createRoot({ plugins: { initialize: false } }); + await root.preboot(); await root.setup(); await root.start(); }); diff --git a/src/core/server/core_usage_data/core_usage_data_service.test.ts b/src/core/server/core_usage_data/core_usage_data_service.test.ts index 2395d6d1c1725..7ecfa37492242 100644 --- a/src/core/server/core_usage_data/core_usage_data_service.test.ts +++ b/src/core/server/core_usage_data/core_usage_data_service.test.ts @@ -122,6 +122,7 @@ describe('CoreUsageDataService', () => { hidden: true, namespaceType: 'agnostic', mappings: expect.anything(), + migrations: expect.anything(), }); }); }); diff --git a/src/core/server/core_usage_data/core_usage_stats.ts b/src/core/server/core_usage_data/core_usage_stats.ts index d02c92353d13e..8cdd83bcad2c9 100644 --- a/src/core/server/core_usage_data/core_usage_stats.ts +++ b/src/core/server/core_usage_data/core_usage_stats.ts @@ -8,6 +8,7 @@ import { SavedObjectsType } from '../saved_objects'; import { CORE_USAGE_STATS_TYPE } from './constants'; +import { migrateTo7141 } from './migrations'; /** @internal */ export const coreUsageStatsType: SavedObjectsType = { @@ -18,4 +19,7 @@ export const coreUsageStatsType: SavedObjectsType = { dynamic: false, // we aren't querying or aggregating over this data, so we don't need to specify any fields properties: {}, }, + migrations: { + '7.14.1': migrateTo7141, + }, }; diff --git a/src/core/server/core_usage_data/core_usage_stats_client.test.ts b/src/core/server/core_usage_data/core_usage_stats_client.test.ts index dc4a81adf5f8e..384e3d7b932c1 100644 --- a/src/core/server/core_usage_data/core_usage_stats_client.test.ts +++ b/src/core/server/core_usage_data/core_usage_stats_client.test.ts @@ -790,8 +790,14 @@ describe('CoreUsageStatsClient', () => { createNewCopies: true, overwrite: true, } as IncrementSavedObjectsImportOptions); - expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(1); - expect(repositoryMock.incrementCounter).toHaveBeenCalledWith( + await usageStatsClient.incrementSavedObjectsImport({ + request, + createNewCopies: false, + overwrite: true, + } as IncrementSavedObjectsImportOptions); + expect(repositoryMock.incrementCounter).toHaveBeenCalledTimes(2); + expect(repositoryMock.incrementCounter).toHaveBeenNthCalledWith( + 1, CORE_USAGE_STATS_TYPE, CORE_USAGE_STATS_ID, [ @@ -799,6 +805,19 @@ describe('CoreUsageStatsClient', () => { `${IMPORT_STATS_PREFIX}.namespace.default.total`, `${IMPORT_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, `${IMPORT_STATS_PREFIX}.createNewCopiesEnabled.yes`, + // excludes 'overwriteEnabled.yes' and 'overwriteEnabled.no' when createNewCopies is true + ], + incrementOptions + ); + expect(repositoryMock.incrementCounter).toHaveBeenNthCalledWith( + 2, + CORE_USAGE_STATS_TYPE, + CORE_USAGE_STATS_ID, + [ + `${IMPORT_STATS_PREFIX}.total`, + `${IMPORT_STATS_PREFIX}.namespace.default.total`, + `${IMPORT_STATS_PREFIX}.namespace.default.kibanaRequest.yes`, + `${IMPORT_STATS_PREFIX}.createNewCopiesEnabled.no`, `${IMPORT_STATS_PREFIX}.overwriteEnabled.yes`, ], incrementOptions diff --git a/src/core/server/core_usage_data/core_usage_stats_client.ts b/src/core/server/core_usage_data/core_usage_stats_client.ts index 3b73b475a30f4..29d6e875c7962 100644 --- a/src/core/server/core_usage_data/core_usage_stats_client.ts +++ b/src/core/server/core_usage_data/core_usage_stats_client.ts @@ -150,7 +150,7 @@ export class CoreUsageStatsClient { const { createNewCopies, overwrite } = options; const counterFieldNames = [ `createNewCopiesEnabled.${createNewCopies ? 'yes' : 'no'}`, - `overwriteEnabled.${overwrite ? 'yes' : 'no'}`, + ...(!createNewCopies ? [`overwriteEnabled.${overwrite ? 'yes' : 'no'}`] : []), // the overwrite option is ignored when createNewCopies is true ]; await this.updateUsageStats(counterFieldNames, IMPORT_STATS_PREFIX, options); } diff --git a/src/core/server/core_usage_data/migrations.test.ts b/src/core/server/core_usage_data/migrations.test.ts new file mode 100644 index 0000000000000..27ea745c3fab1 --- /dev/null +++ b/src/core/server/core_usage_data/migrations.test.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { SavedObjectUnsanitizedDoc } from '../saved_objects'; +import { migrateTo7141 } from './migrations'; +import type { CoreUsageStats } from './types'; + +const type = 'obj-type'; +const id = 'obj-id'; + +describe('#migrateTo7141', () => { + it('Resets targeted counter fields and leaves others unchanged', () => { + const doc = { + type, + id, + attributes: { + foo: 'bar', + 'apiCalls.savedObjectsImport.total': 10, + }, + } as SavedObjectUnsanitizedDoc; + + expect(migrateTo7141(doc)).toEqual({ + type, + id, + attributes: { + foo: 'bar', + 'apiCalls.savedObjectsImport.total': 0, + 'apiCalls.savedObjectsImport.namespace.default.total': 0, + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.yes': 0, + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.no': 0, + 'apiCalls.savedObjectsImport.namespace.custom.total': 0, + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.yes': 0, + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.no': 0, + 'apiCalls.savedObjectsImport.createNewCopiesEnabled.yes': 0, + 'apiCalls.savedObjectsImport.createNewCopiesEnabled.no': 0, + 'apiCalls.savedObjectsImport.overwriteEnabled.yes': 0, + 'apiCalls.savedObjectsImport.overwriteEnabled.no': 0, + }, + }); + }); +}); diff --git a/src/core/server/core_usage_data/migrations.ts b/src/core/server/core_usage_data/migrations.ts new file mode 100644 index 0000000000000..8cbb2a267f0c7 --- /dev/null +++ b/src/core/server/core_usage_data/migrations.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { cloneDeep } from 'lodash'; +import type { SavedObjectUnsanitizedDoc } from '../saved_objects'; +import type { CoreUsageStats } from './types'; + +export const migrateTo7141 = (doc: SavedObjectUnsanitizedDoc) => { + try { + return resetFields(doc, [ + // Prior to this, we were counting the `overwrite` option incorrectly; reset all import API counter fields so we get clean data + 'apiCalls.savedObjectsImport.total', + 'apiCalls.savedObjectsImport.namespace.default.total', + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.yes', + 'apiCalls.savedObjectsImport.namespace.default.kibanaRequest.no', + 'apiCalls.savedObjectsImport.namespace.custom.total', + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.yes', + 'apiCalls.savedObjectsImport.namespace.custom.kibanaRequest.no', + 'apiCalls.savedObjectsImport.createNewCopiesEnabled.yes', + 'apiCalls.savedObjectsImport.createNewCopiesEnabled.no', + 'apiCalls.savedObjectsImport.overwriteEnabled.yes', + 'apiCalls.savedObjectsImport.overwriteEnabled.no', + ]); + } catch (err) { + // fail-safe + } + return doc; +}; + +function resetFields( + doc: SavedObjectUnsanitizedDoc, + fieldsToReset: Array +) { + const newDoc = cloneDeep(doc); + const { attributes = {} } = newDoc; + for (const field of fieldsToReset) { + attributes[field] = 0; + } + return { ...newDoc, attributes }; +} diff --git a/src/core/server/elasticsearch/client/configure_client.test.ts b/src/core/server/elasticsearch/client/configure_client.test.ts index 924f1584c5f8f..ed23bf02d23ae 100644 --- a/src/core/server/elasticsearch/client/configure_client.test.ts +++ b/src/core/server/elasticsearch/client/configure_client.test.ts @@ -10,7 +10,11 @@ import { Buffer } from 'buffer'; import { Readable } from 'stream'; import { RequestEvent, errors } from '@elastic/elasticsearch'; -import { TransportRequestParams, RequestBody } from '@elastic/elasticsearch/lib/Transport'; +import type { + TransportRequestOptions, + TransportRequestParams, + RequestBody, +} from '@elastic/elasticsearch/lib/Transport'; import { parseClientOptionsMock, ClientMock } from './configure_client.test.mocks'; import { loggingSystemMock } from '../../logging/logging_system.mock'; @@ -39,12 +43,14 @@ const createApiResponse = ({ headers = {}, warnings = [], params, + requestOptions = {}, }: { body: T; statusCode?: number; headers?: Record; warnings?: string[]; params?: TransportRequestParams; + requestOptions?: TransportRequestOptions; }): RequestEvent => { return { body, @@ -54,6 +60,7 @@ const createApiResponse = ({ meta: { request: { params: params!, + options: requestOptions, } as any, } as any, }; @@ -146,6 +153,7 @@ describe('configureClient', () => { "200 GET /foo?hello=dolly {\\"seq_no_primary_term\\":true,\\"query\\":{\\"term\\":{\\"user\\":\\"kimchy\\"}}}", + undefined, ], ] `); @@ -170,6 +178,7 @@ describe('configureClient', () => { "200 GET /foo?hello=dolly {\\"seq_no_primary_term\\":true,\\"query\\":{\\"term\\":{\\"user\\":\\"kimchy\\"}}}", + undefined, ], ] `); @@ -196,6 +205,7 @@ describe('configureClient', () => { "200 GET /foo?hello=dolly [buffer]", + undefined, ], ] `); @@ -222,6 +232,7 @@ describe('configureClient', () => { "200 GET /foo?hello=dolly [stream]", + undefined, ], ] `); @@ -238,6 +249,7 @@ describe('configureClient', () => { Array [ "200 GET /foo?hello=dolly", + undefined, ], ] `); @@ -263,6 +275,7 @@ describe('configureClient', () => { Array [ "200 GET /foo?city=M%C3%BCnich", + undefined, ], ] `); @@ -298,6 +311,7 @@ describe('configureClient', () => { "500 GET /foo?hello=dolly {\\"seq_no_primary_term\\":true,\\"query\\":{\\"term\\":{\\"user\\":\\"kimchy\\"}}} [internal server error]: internal server error", + undefined, ], ] `); @@ -313,6 +327,7 @@ describe('configureClient', () => { Array [ Array [ "[TimeoutError]: message", + undefined, ], ] `); @@ -343,6 +358,7 @@ describe('configureClient', () => { Array [ "400 GET /_path?hello=dolly [illegal_argument_exception]: request [/_path] contains unrecognized parameter: [name]", + undefined, ], ] `); @@ -369,6 +385,7 @@ describe('configureClient', () => { Array [ "400 GET /_path [undefined]: Response Error", + undefined, ], ] `); @@ -391,10 +408,67 @@ describe('configureClient', () => { Array [ "400 GET /_path [undefined]: Response Error", + undefined, ], ] `); }); + + it('adds meta information to logs', () => { + const client = configureClient(createFakeConfig(), { logger, type: 'test', scoped: false }); + + let response = createApiResponse({ + statusCode: 400, + headers: {}, + params: { + method: 'GET', + path: '/_path', + }, + requestOptions: { + opaqueId: 'opaque-id', + }, + body: { + error: {}, + }, + }); + client.emit('response', null, response); + + expect(loggingSystemMock.collect(logger).debug[0][1]).toMatchInlineSnapshot(` + Object { + "http": Object { + "request": Object { + "id": "opaque-id", + }, + }, + } + `); + + logger.debug.mockClear(); + + response = createApiResponse({ + statusCode: 400, + headers: {}, + params: { + method: 'GET', + path: '/_path', + }, + requestOptions: { + opaqueId: 'opaque-id', + }, + body: {} as any, + }); + client.emit('response', new errors.ResponseError(response), response); + + expect(loggingSystemMock.collect(logger).debug[0][1]).toMatchInlineSnapshot(` + Object { + "http": Object { + "request": Object { + "id": "opaque-id", + }, + }, + } + `); + }); }); }); }); diff --git a/src/core/server/elasticsearch/client/configure_client.ts b/src/core/server/elasticsearch/client/configure_client.ts index ce4bd6fa2c59b..18c9724c2f8f4 100644 --- a/src/core/server/elasticsearch/client/configure_client.ts +++ b/src/core/server/elasticsearch/client/configure_client.ts @@ -98,10 +98,16 @@ function getResponseMessage(event: RequestEvent): string { const addLogging = (client: Client, logger: Logger) => { client.on('response', (error, event) => { if (event) { + const opaqueId = event.meta.request.options.opaqueId; + const meta = opaqueId + ? { + http: { request: { id: event.meta.request.options.opaqueId } }, + } + : undefined; // do not clutter logs if opaqueId is not present if (error) { - logger.debug(getErrorMessage(error, event)); + logger.debug(getErrorMessage(error, event), meta); } else { - logger.debug(getResponseMessage(event)); + logger.debug(getResponseMessage(event), meta); } } }); diff --git a/src/core/server/elasticsearch/elasticsearch_service.mock.ts b/src/core/server/elasticsearch/elasticsearch_service.mock.ts index 089028fc32976..0ccc0f51f6abd 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.mock.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.mock.ts @@ -19,10 +19,16 @@ import { ElasticsearchClientConfig } from './client'; import { legacyClientMock } from './legacy/mocks'; import { ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchService } from './elasticsearch_service'; -import { InternalElasticsearchServiceSetup, ElasticsearchStatusMeta } from './types'; +import { + InternalElasticsearchServiceSetup, + ElasticsearchStatusMeta, + ElasticsearchServicePreboot, +} from './types'; import { NodesVersionCompatibility } from './version_check/ensure_es_version'; import { ServiceStatus, ServiceStatusLevels } from '../status'; +type MockedElasticSearchServicePreboot = jest.Mocked; + export interface MockedElasticSearchServiceSetup { legacy: { config$: BehaviorSubject; @@ -38,6 +44,17 @@ type MockedElasticSearchServiceStart = MockedElasticSearchServiceSetup & { >; }; +const createPrebootContractMock = () => { + const prebootContract: MockedElasticSearchServicePreboot = { + config: { hosts: [], credentialsSpecified: false }, + createClient: jest.fn(), + }; + prebootContract.createClient.mockImplementation(() => + elasticsearchClientMock.createCustomClusterClient() + ); + return prebootContract; +}; + const createSetupContractMock = () => { const setupContract: MockedElasticSearchServiceSetup = { legacy: { @@ -73,7 +90,7 @@ const createStartContractMock = () => { return startContract; }; -const createInternalStartContractMock = createStartContractMock; +const createInternalPrebootContractMock = createPrebootContractMock; type MockedInternalElasticSearchServiceSetup = jest.Mocked< InternalElasticsearchServiceSetup & { @@ -102,13 +119,17 @@ const createInternalSetupContractMock = () => { return setupContract; }; +const createInternalStartContractMock = createStartContractMock; + type ElasticsearchServiceContract = PublicMethodsOf; const createMock = () => { const mocked: jest.Mocked = { + preboot: jest.fn(), setup: jest.fn(), start: jest.fn(), stop: jest.fn(), }; + mocked.preboot.mockResolvedValue(createInternalPrebootContractMock()); mocked.setup.mockResolvedValue(createInternalSetupContractMock()); mocked.start.mockResolvedValueOnce(createInternalStartContractMock()); mocked.stop.mockResolvedValue(); @@ -117,6 +138,8 @@ const createMock = () => { export const elasticsearchServiceMock = { create: createMock, + createInternalPreboot: createInternalPrebootContractMock, + createPreboot: createPrebootContractMock, createInternalSetup: createInternalSetupContractMock, createSetup: createSetupContractMock, createInternalStart: createInternalStartContractMock, diff --git a/src/core/server/elasticsearch/elasticsearch_service.test.ts b/src/core/server/elasticsearch/elasticsearch_service.test.ts index 791ae2ab7abaa..8932a4c73e1f2 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.test.ts @@ -16,7 +16,7 @@ import { CoreContext } from '../core_context'; import { loggingSystemMock } from '../logging/logging_system.mock'; import { httpServiceMock } from '../http/http_service.mock'; import { executionContextServiceMock } from '../execution_context/execution_context_service.mock'; -import { ElasticsearchConfig } from './elasticsearch_config'; +import { configSchema, ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchService } from './elasticsearch_service'; import { elasticsearchServiceMock } from './elasticsearch_service.mock'; import { elasticsearchClientMock } from './client/mocks'; @@ -31,17 +31,6 @@ const setupDeps = { http: httpServiceMock.createInternalSetupContract(), executionContext: executionContextServiceMock.createInternalSetupContract(), }; -configService.atPath.mockReturnValue( - new BehaviorSubject({ - hosts: ['http://1.2.3.4'], - healthCheck: { - delay: duration(10), - }, - ssl: { - verificationMode: 'none', - }, - } as any) -); let env: Env; let coreContext: CoreContext; @@ -51,10 +40,21 @@ let mockClusterClientInstance: ReturnType; - +let mockConfig$: BehaviorSubject; beforeEach(() => { env = Env.createDefault(REPO_ROOT, getEnvOptions()); + mockConfig$ = new BehaviorSubject({ + hosts: ['http://1.2.3.4'], + healthCheck: { + delay: duration(10), + }, + ssl: { + verificationMode: 'none', + }, + }); + configService.atPath.mockReturnValue(mockConfig$); + coreContext = { coreId: Symbol(), env, logger, configService: configService as any }; elasticsearchService = new ElasticsearchService(coreContext); @@ -69,6 +69,90 @@ beforeEach(() => { afterEach(() => jest.clearAllMocks()); +describe('#preboot', () => { + describe('#config', () => { + it('exposes `hosts`', async () => { + const prebootContract = await elasticsearchService.preboot(); + expect(prebootContract.config).toEqual({ + credentialsSpecified: false, + hosts: ['http://1.2.3.4'], + }); + }); + + it('set `credentialsSpecified` to `true` if `username` is specified', async () => { + mockConfig$.next(configSchema.validate({ username: 'kibana_system' })); + const prebootContract = await elasticsearchService.preboot(); + expect(prebootContract.config.credentialsSpecified).toBe(true); + }); + + it('set `credentialsSpecified` to `true` if `password` is specified', async () => { + mockConfig$.next(configSchema.validate({ password: 'changeme' })); + const prebootContract = await elasticsearchService.preboot(); + expect(prebootContract.config.credentialsSpecified).toBe(true); + }); + + it('set `credentialsSpecified` to `true` if `serviceAccountToken` is specified', async () => { + mockConfig$.next(configSchema.validate({ serviceAccountToken: 'xxxx' })); + const prebootContract = await elasticsearchService.preboot(); + expect(prebootContract.config.credentialsSpecified).toBe(true); + }); + }); + + describe('#createClient', () => { + it('allows to specify config properties', async () => { + const prebootContract = await elasticsearchService.preboot(); + const customConfig = { keepAlive: true }; + const clusterClient = prebootContract.createClient('custom-type', customConfig); + + expect(clusterClient).toBe(mockClusterClientInstance); + + expect(MockClusterClient).toHaveBeenCalledTimes(1); + expect(MockClusterClient.mock.calls[0][0]).toEqual(expect.objectContaining(customConfig)); + }); + + it('creates a new client on each call', async () => { + const prebootContract = await elasticsearchService.preboot(); + + const customConfig = { keepAlive: true }; + + prebootContract.createClient('custom-type', customConfig); + prebootContract.createClient('another-type', customConfig); + + expect(MockClusterClient).toHaveBeenCalledTimes(2); + }); + + it('falls back to elasticsearch default config values if property not specified', async () => { + const prebootContract = await elasticsearchService.preboot(); + + const customConfig = { + hosts: ['http://8.8.8.8'], + logQueries: true, + ssl: { certificate: 'certificate-value' }, + }; + + prebootContract.createClient('some-custom-type', customConfig); + const config = MockClusterClient.mock.calls[0][0]; + + expect(config).toMatchInlineSnapshot(` + Object { + "healthCheckDelay": "PT0.01S", + "hosts": Array [ + "http://8.8.8.8", + ], + "logQueries": true, + "requestHeadersWhitelist": Array [ + undefined, + ], + "ssl": Object { + "certificate": "certificate-value", + "verificationMode": "none", + }, + } + `); + }); + }); +}); + describe('#setup', () => { it('returns legacy Elasticsearch config as a part of the contract', async () => { const setupContract = await elasticsearchService.setup(setupDeps); @@ -249,7 +333,7 @@ describe('#setup', () => { describe('#start', () => { it('throws if called before `setup`', async () => { - expect(() => elasticsearchService.start()).rejects.toMatchInlineSnapshot( + await expect(() => elasticsearchService.start()).rejects.toMatchInlineSnapshot( `[Error: ElasticsearchService needs to be setup before calling start]` ); }); diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index deb2d49f70817..f983a8b77fe08 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.ts @@ -18,11 +18,15 @@ import { ILegacyCustomClusterClient, LegacyElasticsearchClientConfig, } from './legacy'; -import { ClusterClient, ICustomClusterClient, ElasticsearchClientConfig } from './client'; +import { ClusterClient, ElasticsearchClientConfig } from './client'; import { ElasticsearchConfig, ElasticsearchConfigType } from './elasticsearch_config'; -import type { InternalHttpServiceSetup, GetAuthHeaders } from '../http/'; +import type { InternalHttpServiceSetup, GetAuthHeaders } from '../http'; import type { InternalExecutionContextSetup, IExecutionContext } from '../execution_context'; -import { InternalElasticsearchServiceSetup, InternalElasticsearchServiceStart } from './types'; +import { + InternalElasticsearchServicePreboot, + InternalElasticsearchServiceSetup, + InternalElasticsearchServiceStart, +} from './types'; import { pollEsNodesVersion } from './version_check/ensure_es_version'; import { calculateStatus$ } from './status'; @@ -57,6 +61,22 @@ export class ElasticsearchService .pipe(map((rawConfig) => new ElasticsearchConfig(rawConfig))); } + public async preboot(): Promise { + this.log.debug('Prebooting elasticsearch service'); + + const config = await this.config$.pipe(first()).toPromise(); + return { + config: { + hosts: config.hosts, + credentialsSpecified: + config.username !== undefined || + config.password !== undefined || + config.serviceAccountToken !== undefined, + }, + createClient: (type, clientConfig) => this.createClusterClient(type, config, clientConfig), + }; + } + public async setup(deps: SetupDeps): Promise { this.log.debug('Setting up elasticsearch service'); @@ -96,18 +116,9 @@ export class ElasticsearchService } const config = await this.config$.pipe(first()).toPromise(); - - const createClient = ( - type: string, - clientConfig: Partial = {} - ): ICustomClusterClient => { - const finalConfig = merge({}, config, clientConfig); - return this.createClusterClient(type, finalConfig); - }; - return { client: this.client!, - createClient, + createClient: (type, clientConfig) => this.createClusterClient(type, config, clientConfig), legacy: { config$: this.config$, client: this.legacyClient, @@ -127,7 +138,12 @@ export class ElasticsearchService } } - private createClusterClient(type: string, config: ElasticsearchClientConfig) { + private createClusterClient( + type: string, + baseConfig: ElasticsearchConfig, + clientConfig?: Partial + ) { + const config = clientConfig ? merge({}, baseConfig, clientConfig) : baseConfig; return new ClusterClient( config, this.coreContext.logger.get('elasticsearch'), diff --git a/src/core/server/elasticsearch/index.ts b/src/core/server/elasticsearch/index.ts index 94dc10ff4e863..d97e3331c7cf5 100644 --- a/src/core/server/elasticsearch/index.ts +++ b/src/core/server/elasticsearch/index.ts @@ -11,13 +11,16 @@ export { config, configSchema } from './elasticsearch_config'; export { ElasticsearchConfig } from './elasticsearch_config'; export type { NodesVersionCompatibility } from './version_check/ensure_es_version'; export type { + ElasticsearchServicePreboot, ElasticsearchServiceSetup, ElasticsearchServiceStart, ElasticsearchStatusMeta, + InternalElasticsearchServicePreboot, InternalElasticsearchServiceSetup, InternalElasticsearchServiceStart, FakeRequest, ScopeableRequest, + ElasticsearchConfigPreboot, } from './types'; export * from './legacy'; export type { diff --git a/src/core/server/elasticsearch/types.ts b/src/core/server/elasticsearch/types.ts index 8bbf665cbc096..375c7015b16d7 100644 --- a/src/core/server/elasticsearch/types.ts +++ b/src/core/server/elasticsearch/types.ts @@ -19,6 +19,43 @@ import { IClusterClient, ICustomClusterClient, ElasticsearchClientConfig } from import { NodesVersionCompatibility } from './version_check/ensure_es_version'; import { ServiceStatus } from '../status'; +/** + * @public + */ +export interface ElasticsearchServicePreboot { + /** + * A limited set of Elasticsearch configuration entries. + * + * @example + * ```js + * const { hosts, credentialsSpecified } = core.elasticsearch.config; + * ``` + */ + readonly config: Readonly; + + /** + * Create application specific Elasticsearch cluster API client with customized config. See {@link IClusterClient}. + * + * @param type Unique identifier of the client + * @param clientConfig A config consists of Elasticsearch JS client options and + * valid sub-set of Elasticsearch service config. + * We fill all the missing properties in the `clientConfig` using the default + * Elasticsearch config so that we don't depend on default values set and + * controlled by underlying Elasticsearch JS client. + * We don't run validation against the passed config and expect it to be valid. + * + * @example + * ```js + * const client = elasticsearch.createClient('my-app-name', config); + * const data = await client.asInternalUser.search(); + * ``` + */ + readonly createClient: ( + type: string, + clientConfig?: Partial + ) => ICustomClusterClient; +} + /** * @public */ @@ -77,6 +114,9 @@ export interface ElasticsearchServiceSetup { }; } +/** @internal */ +export type InternalElasticsearchServicePreboot = ElasticsearchServicePreboot; + /** @internal */ export interface InternalElasticsearchServiceSetup extends ElasticsearchServiceSetup { esNodesCompatibility$: Observable; @@ -199,3 +239,21 @@ export interface FakeRequest { * See {@link KibanaRequest}. */ export type ScopeableRequest = KibanaRequest | LegacyRequest | FakeRequest; + +/** + * A limited set of Elasticsearch configuration entries exposed to the `preboot` plugins at `setup`. + * + * @public + */ +export interface ElasticsearchConfigPreboot { + /** + * Hosts that the client will connect to. If sniffing is enabled, this list will + * be used as seeds to discover the rest of your cluster. + */ + readonly hosts: string[]; + + /** + * Indicates whether Elasticsearch configuration includes credentials (`username`, `password` or `serviceAccountToken`). + */ + readonly credentialsSpecified: boolean; +} diff --git a/src/core/server/environment/environment_service.mock.ts b/src/core/server/environment/environment_service.mock.ts index 2bc5fa89b8e26..ab620c790a157 100644 --- a/src/core/server/environment/environment_service.mock.ts +++ b/src/core/server/environment/environment_service.mock.ts @@ -7,25 +7,39 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; -import type { EnvironmentService, InternalEnvironmentServiceSetup } from './environment_service'; +import type { + EnvironmentService, + InternalEnvironmentServicePreboot, + InternalEnvironmentServiceSetup, +} from './environment_service'; + +const createPrebootContractMock = () => { + const prebootContract: jest.Mocked = { + instanceUuid: 'uuid', + }; + return prebootContract; +}; const createSetupContractMock = () => { - const setupContract: jest.Mocked = { + const prebootContract: jest.Mocked = { instanceUuid: 'uuid', }; - return setupContract; + return prebootContract; }; type EnvironmentServiceContract = PublicMethodsOf; const createMock = () => { const mocked: jest.Mocked = { + preboot: jest.fn(), setup: jest.fn(), }; - mocked.setup.mockResolvedValue(createSetupContractMock()); + mocked.preboot.mockResolvedValue(createPrebootContractMock()); + mocked.setup.mockReturnValue(createSetupContractMock()); return mocked; }; export const environmentServiceMock = { create: createMock, + createPrebootContract: createPrebootContractMock, createSetupContract: createSetupContractMock, }; diff --git a/src/core/server/environment/environment_service.test.ts b/src/core/server/environment/environment_service.test.ts index fb3ddaa77b416..34647d090b995 100644 --- a/src/core/server/environment/environment_service.test.ts +++ b/src/core/server/environment/environment_service.test.ts @@ -76,9 +76,9 @@ describe('UuidService', () => { jest.clearAllMocks(); }); - describe('#setup()', () => { + describe('#preboot()', () => { it('calls resolveInstanceUuid with correct parameters', async () => { - await service.setup(); + await service.preboot(); expect(resolveInstanceUuid).toHaveBeenCalledTimes(1); expect(resolveInstanceUuid).toHaveBeenCalledWith({ @@ -89,7 +89,7 @@ describe('UuidService', () => { }); it('calls createDataFolder with correct parameters', async () => { - await service.setup(); + await service.preboot(); expect(createDataFolder).toHaveBeenCalledTimes(1); expect(createDataFolder).toHaveBeenCalledWith({ @@ -99,7 +99,7 @@ describe('UuidService', () => { }); it('calls writePidFile with correct parameters', async () => { - await service.setup(); + await service.preboot(); expect(writePidFile).toHaveBeenCalledTimes(1); expect(writePidFile).toHaveBeenCalledWith({ @@ -109,14 +109,14 @@ describe('UuidService', () => { }); it('returns the uuid resolved from resolveInstanceUuid', async () => { - const setup = await service.setup(); + const preboot = await service.preboot(); - expect(setup.instanceUuid).toEqual('SOME_UUID'); + expect(preboot.instanceUuid).toEqual('SOME_UUID'); }); describe('process warnings', () => { it('logs warnings coming from the process', async () => { - await service.setup(); + await service.preboot(); const warning = new Error('something went wrong'); process.emit('warning', warning); @@ -126,7 +126,7 @@ describe('UuidService', () => { }); it('does not log deprecation warnings', async () => { - await service.setup(); + await service.preboot(); const warning = new Error('something went wrong'); warning.name = 'DeprecationWarning'; @@ -136,4 +136,11 @@ describe('UuidService', () => { }); }); }); + + describe('#setup()', () => { + it('returns the uuid resolved from resolveInstanceUuid', async () => { + await expect(service.preboot()).resolves.toEqual({ instanceUuid: 'SOME_UUID' }); + expect(service.setup()).toEqual({ instanceUuid: 'SOME_UUID' }); + }); + }); }); diff --git a/src/core/server/environment/environment_service.ts b/src/core/server/environment/environment_service.ts index e652622049cfa..f96b616256577 100644 --- a/src/core/server/environment/environment_service.ts +++ b/src/core/server/environment/environment_service.ts @@ -20,13 +20,18 @@ import { writePidFile } from './write_pid_file'; /** * @internal */ -export interface InternalEnvironmentServiceSetup { +export interface InternalEnvironmentServicePreboot { /** * Retrieve the Kibana instance uuid. */ instanceUuid: string; } +/** + * @internal + */ +export type InternalEnvironmentServiceSetup = InternalEnvironmentServicePreboot; + /** @internal */ export class EnvironmentService { private readonly log: Logger; @@ -40,7 +45,9 @@ export class EnvironmentService { this.configService = core.configService; } - public async setup() { + public async preboot() { + // IMPORTANT: This code is based on the assumption that none of the configuration values used + // here is supposed to change during preboot phase and it's safe to read them only once. const [pathConfig, serverConfig, pidConfig] = await Promise.all([ this.configService.atPath(pathConfigDef.path).pipe(take(1)).toPromise(), this.configService.atPath(httpConfigDef.path).pipe(take(1)).toPromise(), @@ -73,4 +80,10 @@ export class EnvironmentService { instanceUuid: this.uuid, }; } + + public setup() { + return { + instanceUuid: this.uuid, + }; + } } diff --git a/src/core/server/environment/index.ts b/src/core/server/environment/index.ts index 01d5097887248..886c0f667fdb4 100644 --- a/src/core/server/environment/index.ts +++ b/src/core/server/environment/index.ts @@ -7,6 +7,9 @@ */ export { EnvironmentService } from './environment_service'; -export type { InternalEnvironmentServiceSetup } from './environment_service'; +export type { + InternalEnvironmentServicePreboot, + InternalEnvironmentServiceSetup, +} from './environment_service'; export { config } from './pid_config'; export type { PidConfigType } from './pid_config'; diff --git a/src/core/server/execution_context/execution_context_container.test.ts b/src/core/server/execution_context/execution_context_container.test.ts index 46a688c8abdf0..e8408d37f40f4 100644 --- a/src/core/server/execution_context/execution_context_container.test.ts +++ b/src/core/server/execution_context/execution_context_container.test.ts @@ -25,7 +25,7 @@ describe('KibanaExecutionContext', () => { }; const value = new ExecutionContextContainer(context).toString(); - expect(value).toMatchInlineSnapshot(`"1234-5678;kibana:test-type:42"`); + expect(value).toMatchInlineSnapshot(`"1234-5678;kibana:test-type:test-name:42"`); }); it('returns a limited representation if optional properties are omitted', () => { @@ -37,6 +37,20 @@ describe('KibanaExecutionContext', () => { expect(value).toMatchInlineSnapshot(`"1234-5678"`); }); + it('returns an escaped string representation of provided execution contextStringified', () => { + const context: KibanaServerExecutionContext = { + id: 'Visualization☺漢字', + type: 'test-type', + name: 'test-name', + requestId: '1234-5678', + }; + + const value = new ExecutionContextContainer(context).toString(); + expect(value).toMatchInlineSnapshot( + `"1234-5678;kibana:test-type:test-name:Visualization%E2%98%BA%E6%BC%A2%E5%AD%97"` + ); + }); + it('trims a string representation of provided execution context if it is bigger max allowed size', () => { expect( new Blob([ diff --git a/src/core/server/execution_context/execution_context_container.ts b/src/core/server/execution_context/execution_context_container.ts index 71bf4bb96e1b0..6c4a5606df152 100644 --- a/src/core/server/execution_context/execution_context_container.ts +++ b/src/core/server/execution_context/execution_context_container.ts @@ -57,7 +57,13 @@ export class ExecutionContextContainer implements IExecutionContextContainer { } toString(): string { const ctx = this.#context; - const contextStringified = ctx.type && ctx.id ? `kibana:${ctx.type}:${ctx.id}` : ''; + const contextStringified = + ctx.type && ctx.id && ctx.name + ? // id may contain non-ASCII symbols + `kibana:${encodeURIComponent(ctx.type)}:${encodeURIComponent( + ctx.name + )}:${encodeURIComponent(ctx.id)}` + : ''; const result = contextStringified ? `${ctx.requestId};${contextStringified}` : ctx.requestId; return enforceMaxLength(result); } diff --git a/src/core/server/execution_context/execution_context_service.test.ts b/src/core/server/execution_context/execution_context_service.test.ts index 9b9ab0f48bacb..0c213429e1951 100644 --- a/src/core/server/execution_context/execution_context_service.test.ts +++ b/src/core/server/execution_context/execution_context_service.test.ts @@ -11,14 +11,17 @@ import { InternalExecutionContextSetup, } from './execution_context_service'; import { mockCoreContext } from '../core_context.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; const delay = (ms: number = 100) => new Promise((resolve) => setTimeout(resolve, ms)); describe('ExecutionContextService', () => { describe('setup', () => { let service: InternalExecutionContextSetup; - const core = mockCoreContext.create(); - core.configService.atPath.mockReturnValue(new BehaviorSubject({ enabled: true })); + let core: ReturnType; + beforeEach(() => { + core = mockCoreContext.create(); + core.configService.atPath.mockReturnValue(new BehaviorSubject({ enabled: true })); service = new ExecutionContextService(core).setup(); }); @@ -82,6 +85,19 @@ describe('ExecutionContextService', () => { }, ]); }); + + it('emits context to the logs when "set" is called', async () => { + service.set({ + requestId: '0000', + }); + expect(loggingSystemMock.collect(core.logger).debug).toMatchInlineSnapshot(` + Array [ + Array [ + "stored the execution context: {\\"requestId\\":\\"0000\\"}", + ], + ] + `); + }); }); describe('config', () => { diff --git a/src/core/server/execution_context/execution_context_service.ts b/src/core/server/execution_context/execution_context_service.ts index 95a854f84d145..b187283e27e34 100644 --- a/src/core/server/execution_context/execution_context_service.ts +++ b/src/core/server/execution_context/execution_context_service.ts @@ -119,7 +119,7 @@ export class ExecutionContextService // we have to use enterWith since Hapi lifecycle model is built on event emitters. // therefore if we wrapped request handler in asyncLocalStorage.run(), we would lose context in other lifecycles. this.asyncLocalStorage.enterWith(contextContainer); - this.log.trace(`stored the execution context: ${contextContainer.toJSON()}`); + this.log.debug(`stored the execution context: ${JSON.stringify(contextContainer)}`); } private reset() { diff --git a/src/core/server/execution_context/integration_tests/tracing.test.ts b/src/core/server/execution_context/integration_tests/tracing.test.ts index c9de5fb98eb02..ade67d0dd2605 100644 --- a/src/core/server/execution_context/integration_tests/tracing.test.ts +++ b/src/core/server/execution_context/integration_tests/tracing.test.ts @@ -41,6 +41,7 @@ describe('trace', () => { }, }, }); + await root.preboot(); }, 30000); afterEach(async () => { @@ -167,6 +168,7 @@ describe('trace', () => { }, }, }); + await rootExecutionContextDisabled.preboot(); }, 30000); afterEach(async () => { @@ -435,7 +437,7 @@ describe('trace', () => { .expect(200); const header = response.body['x-opaque-id']; - expect(header).toContain('kibana:test-type:42'); + expect(header).toContain('kibana:test-type:test-name:42'); }); it('propagates context to Elasticsearch unscoped client', async () => { @@ -456,7 +458,7 @@ describe('trace', () => { .expect(200); const header = response.body['x-opaque-id']; - expect(header).toContain('kibana:test-type:42'); + expect(header).toContain('kibana:test-type:test-name:42'); }); it('a repeat call overwrites the old context', async () => { @@ -484,7 +486,7 @@ describe('trace', () => { .expect(200); const header = response.body['x-opaque-id']; - expect(header).toContain('kibana:new-type:41'); + expect(header).toContain('kibana:new-type:new-name:41'); }); it('does not affect "x-opaque-id" set by user', async () => { @@ -507,7 +509,7 @@ describe('trace', () => { .expect(200); const header = response.body['x-opaque-id']; - expect(header).toBe('my-opaque-id;kibana:test-type:42'); + expect(header).toBe('my-opaque-id;kibana:test-type:test-name:42'); }); it('does not break on non-ASCII characters within execution context', async () => { @@ -536,7 +538,7 @@ describe('trace', () => { .expect(200); const header = response.body['x-opaque-id']; - expect(header).toBe('my-opaque-id;kibana:test-type:42'); + expect(header).toBe('my-opaque-id;kibana:test-type:test-name:42'); }); }); }); diff --git a/src/core/server/http/__snapshots__/http_service.test.ts.snap b/src/core/server/http/__snapshots__/http_service.test.ts.snap index 04b78a84e818e..0377525c74126 100644 --- a/src/core/server/http/__snapshots__/http_service.test.ts.snap +++ b/src/core/server/http/__snapshots__/http_service.test.ts.snap @@ -8,7 +8,7 @@ Array [ ] `; -exports[`spins up notReady server until started if configured with \`autoListen:true\`: 503 response 1`] = ` +exports[`spins up \`preboot\` server until started if configured with \`autoListen:true\`: 503 response 1`] = ` Object { "body": Array [ Array [ diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index a589bc76d21fc..ef5e151083780 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -12,6 +12,8 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { CspConfig } from '../csp'; import { mockRouter, RouterMock } from './router/router.mock'; import { + InternalHttpServicePreboot, + HttpServicePreboot, InternalHttpServiceSetup, HttpServiceSetup, HttpServiceStart, @@ -31,6 +33,10 @@ import { ExternalUrlConfig } from '../external_url'; type BasePathMocked = jest.Mocked; type AuthMocked = jest.Mocked; +export type HttpServicePrebootMock = jest.Mocked; +export type InternalHttpServicePrebootMock = jest.Mocked< + Omit +> & { basePath: BasePathMocked }; export type HttpServiceSetupMock = jest.Mocked< Omit > & { @@ -72,6 +78,31 @@ const createAuthMock = () => { return mock; }; +const createInternalPrebootContractMock = () => { + const mock: InternalHttpServicePrebootMock = { + registerRoutes: jest.fn(), + // @ts-expect-error tsc cannot infer ContextName and uses never + registerRouteHandlerContext: jest.fn(), + registerStaticDir: jest.fn(), + basePath: createBasePathMock(), + csp: CspConfig.DEFAULT, + externalUrl: ExternalUrlConfig.DEFAULT, + auth: createAuthMock(), + }; + return mock; +}; + +const createPrebootContractMock = () => { + const internalMock = createInternalPrebootContractMock(); + + const mock: HttpServicePrebootMock = { + registerRoutes: internalMock.registerRoutes, + basePath: createBasePathMock(), + }; + + return mock; +}; + const createInternalSetupContractMock = () => { const mock: InternalHttpServiceSetupMock = { // we can mock other hapi server methods when we need it @@ -100,6 +131,7 @@ const createInternalSetupContractMock = () => { auth: createAuthMock(), getAuthHeaders: jest.fn(), getServerInfo: jest.fn(), + registerPrebootRoutes: jest.fn(), }; mock.createCookieSessionStorageFactory.mockResolvedValue(sessionStorageMock.createFactory()); mock.createRouter.mockImplementation(() => mockRouter.create()); @@ -165,11 +197,13 @@ type HttpServiceContract = PublicMethodsOf; const createHttpServiceMock = () => { const mocked: jest.Mocked = { + preboot: jest.fn(), setup: jest.fn(), getStartContract: jest.fn(), start: jest.fn(), stop: jest.fn(), }; + mocked.preboot.mockResolvedValue(createInternalPrebootContractMock()); mocked.setup.mockResolvedValue(createInternalSetupContractMock()); mocked.getStartContract.mockReturnValue(createInternalStartContractMock()); mocked.start.mockResolvedValue(createInternalStartContractMock()); @@ -204,6 +238,8 @@ export const httpServiceMock = { create: createHttpServiceMock, createBasePath: createBasePathMock, createAuth: createAuthMock, + createInternalPrebootContract: createInternalPrebootContractMock, + createPrebootContract: createPrebootContractMock, createInternalSetupContract: createInternalSetupContractMock, createSetupContract: createSetupContractMock, createInternalStartContract: createInternalStartContractMock, diff --git a/src/core/server/http/http_service.test.ts b/src/core/server/http/http_service.test.ts index d8a7b54275480..8d29e3221a2ca 100644 --- a/src/core/server/http/http_service.test.ts +++ b/src/core/server/http/http_service.test.ts @@ -20,7 +20,8 @@ import { loggingSystemMock } from '../logging/logging_system.mock'; import { contextServiceMock } from '../context/context_service.mock'; import { executionContextServiceMock } from '../execution_context/execution_context_service.mock'; import { config as cspConfig } from '../csp'; -import { config as externalUrlConfig } from '../external_url'; +import { config as externalUrlConfig, ExternalUrlConfig } from '../external_url'; +import { Router } from './router'; const logger = loggingSystemMock.create(); const env = Env.createDefault(REPO_ROOT, getEnvOptions()); @@ -42,8 +43,12 @@ const createConfigService = (value: Partial = {}) => { configService.setSchema(externalUrlConfig.path, externalUrlConfig.schema); return configService; }; +const contextPreboot = contextServiceMock.createPrebootContract(); const contextSetup = contextServiceMock.createSetupContract(); +const prebootDeps = { + context: contextPreboot, +}; const setupDeps = { context: contextSetup, executionContext: executionContextServiceMock.createInternalSetupContract(), @@ -70,35 +75,40 @@ test('creates and sets up http server', async () => { start: jest.fn(), stop: jest.fn(), }; - const notReadyHttpServer = { + const prebootHttpServer = { isListening: () => false, - setup: jest.fn().mockReturnValue({ server: fakeHapiServer }), + setup: jest.fn().mockReturnValue({ server: fakeHapiServer, registerStaticDir: jest.fn() }), start: jest.fn(), stop: jest.fn(), }; + mockHttpServer.mockImplementationOnce(() => prebootHttpServer); mockHttpServer.mockImplementationOnce(() => httpServer); - mockHttpServer.mockImplementationOnce(() => notReadyHttpServer); const service = new HttpService({ coreId, configService, env, logger }); - expect(mockHttpServer.mock.instances.length).toBe(1); + expect(mockHttpServer.mock.instances.length).toBe(2); expect(httpServer.setup).not.toHaveBeenCalled(); - expect(notReadyHttpServer.setup).not.toHaveBeenCalled(); + expect(prebootHttpServer.setup).not.toHaveBeenCalled(); + + await service.preboot(prebootDeps); + expect(httpServer.setup).not.toHaveBeenCalled(); + expect(httpServer.start).not.toHaveBeenCalled(); + + expect(prebootHttpServer.setup).toHaveBeenCalled(); + expect(prebootHttpServer.start).toHaveBeenCalled(); await service.setup(setupDeps); expect(httpServer.setup).toHaveBeenCalled(); expect(httpServer.start).not.toHaveBeenCalled(); - - expect(notReadyHttpServer.setup).toHaveBeenCalled(); - expect(notReadyHttpServer.start).toHaveBeenCalled(); + expect(prebootHttpServer.stop).not.toHaveBeenCalled(); await service.start(); expect(httpServer.start).toHaveBeenCalled(); - expect(notReadyHttpServer.stop).toHaveBeenCalled(); + expect(prebootHttpServer.stop).toHaveBeenCalled(); }); -test('spins up notReady server until started if configured with `autoListen:true`', async () => { +test('spins up `preboot` server until started if configured with `autoListen:true`', async () => { const configService = createConfigService(); const httpServer = { isListening: () => false, @@ -106,19 +116,19 @@ test('spins up notReady server until started if configured with `autoListen:true start: jest.fn(), stop: jest.fn(), }; - const notReadyHapiServer = { + const prebootHapiServer = { start: jest.fn(), stop: jest.fn(), route: jest.fn(), }; mockHttpServer - .mockImplementationOnce(() => httpServer) .mockImplementationOnce(() => ({ - setup: () => ({ server: notReadyHapiServer }), + setup: () => ({ server: prebootHapiServer, registerStaticDir: jest.fn() }), start: jest.fn(), - stop: jest.fn().mockImplementation(() => notReadyHapiServer.stop()), - })); + stop: jest.fn().mockImplementation(() => prebootHapiServer.stop()), + })) + .mockImplementationOnce(() => httpServer); const service = new HttpService({ coreId, @@ -127,7 +137,7 @@ test('spins up notReady server until started if configured with `autoListen:true logger, }); - await service.setup(setupDeps); + await service.preboot(prebootDeps); const mockResponse: any = { code: jest.fn().mockImplementation(() => mockResponse), @@ -137,7 +147,7 @@ test('spins up notReady server until started if configured with `autoListen:true response: jest.fn().mockReturnValue(mockResponse), }; - const [[{ handler }]] = notReadyHapiServer.route.mock.calls; + const [[{ handler }]] = prebootHapiServer.route.mock.calls; const response503 = await handler(httpServerMock.createRawRequest(), mockResponseToolkit); expect(response503).toBe(mockResponse); expect({ @@ -146,15 +156,25 @@ test('spins up notReady server until started if configured with `autoListen:true header: mockResponse.header.mock.calls, }).toMatchSnapshot('503 response'); + await service.setup(setupDeps); await service.start(); expect(httpServer.start).toBeCalledTimes(1); - expect(notReadyHapiServer.stop).toBeCalledTimes(1); + expect(prebootHapiServer.stop).toBeCalledTimes(1); }); test('logs error if already set up', async () => { const configService = createConfigService(); + mockHttpServer.mockImplementationOnce(() => ({ + setup: () => ({ + server: { start: jest.fn(), stop: jest.fn(), route: jest.fn() }, + registerStaticDir: jest.fn(), + }), + start: noop, + stop: noop, + })); + const httpServer = { isListening: () => true, setup: jest.fn().mockReturnValue({ server: fakeHapiServer }), @@ -165,6 +185,7 @@ test('logs error if already set up', async () => { const service = new HttpService({ coreId, configService, env, logger }); + await service.preboot(prebootDeps); await service.setup(setupDeps); expect(loggingSystemMock.collect(logger).warn).toMatchSnapshot(); @@ -179,29 +200,30 @@ test('stops http server', async () => { start: noop, stop: jest.fn(), }; - const notReadyHttpServer = { + const prebootHttpServer = { isListening: () => false, - setup: jest.fn().mockReturnValue({ server: fakeHapiServer }), + setup: jest.fn().mockReturnValue({ server: fakeHapiServer, registerStaticDir: jest.fn() }), start: noop, stop: jest.fn(), }; + mockHttpServer.mockImplementationOnce(() => prebootHttpServer); mockHttpServer.mockImplementationOnce(() => httpServer); - mockHttpServer.mockImplementationOnce(() => notReadyHttpServer); const service = new HttpService({ coreId, configService, env, logger }); + await service.preboot(prebootDeps); await service.setup(setupDeps); await service.start(); expect(httpServer.stop).toHaveBeenCalledTimes(0); - expect(notReadyHttpServer.stop).toHaveBeenCalledTimes(1); + expect(prebootHttpServer.stop).toHaveBeenCalledTimes(1); await service.stop(); expect(httpServer.stop).toHaveBeenCalledTimes(1); }); -test('stops not ready server if it is running', async () => { +test('stops `preboot` server if it is running', async () => { const configService = createConfigService(); const mockHapiServer = { start: jest.fn(), @@ -210,7 +232,7 @@ test('stops not ready server if it is running', async () => { }; const httpServer = { isListening: () => false, - setup: jest.fn().mockReturnValue({ server: mockHapiServer }), + setup: jest.fn().mockReturnValue({ server: mockHapiServer, registerStaticDir: jest.fn() }), start: noop, stop: jest.fn().mockImplementation(() => mockHapiServer.stop()), }; @@ -218,16 +240,61 @@ test('stops not ready server if it is running', async () => { const service = new HttpService({ coreId, configService, env, logger }); - await service.setup(setupDeps); + await service.preboot(prebootDeps); await service.stop(); expect(mockHapiServer.stop).toHaveBeenCalledTimes(2); }); +test('does not try to stop `preboot` server if it has been already stopped', async () => { + const prebootHttpServer = { + isListening: () => false, + setup: jest.fn().mockReturnValue({ server: fakeHapiServer, registerStaticDir: jest.fn() }), + start: noop, + stop: jest.fn(), + }; + const standardHttpServer = { + isListening: () => false, + setup: jest.fn().mockReturnValue({ server: fakeHapiServer }), + start: noop, + stop: jest.fn(), + }; + + mockHttpServer + .mockImplementationOnce(() => prebootHttpServer) + .mockImplementationOnce(() => standardHttpServer); + + const service = new HttpService({ coreId, configService: createConfigService(), env, logger }); + await service.preboot(prebootDeps); + await service.setup(setupDeps); + + expect(prebootHttpServer.stop).not.toHaveBeenCalled(); + expect(standardHttpServer.stop).not.toHaveBeenCalled(); + + await service.start(); + + expect(prebootHttpServer.stop).toHaveBeenCalledTimes(1); + expect(standardHttpServer.stop).not.toHaveBeenCalled(); + + await service.stop(); + + expect(prebootHttpServer.stop).toHaveBeenCalledTimes(1); + expect(standardHttpServer.stop).toHaveBeenCalledTimes(1); +}); + test('register route handler', async () => { const configService = createConfigService(); + mockHttpServer.mockImplementationOnce(() => ({ + setup: () => ({ + server: { start: jest.fn(), stop: jest.fn(), route: jest.fn() }, + registerStaticDir: jest.fn(), + }), + start: noop, + stop: noop, + })); + const registerRouterMock = jest.fn(); const httpServer = { isListening: () => false, @@ -241,6 +308,7 @@ test('register route handler', async () => { const service = new HttpService({ coreId, configService, env, logger }); + await service.preboot(prebootDeps); const { createRouter } = await service.setup(setupDeps); const router = createRouter('/foo'); @@ -248,10 +316,103 @@ test('register route handler', async () => { expect(registerRouterMock).toHaveBeenLastCalledWith(router); }); +test('register preboot route handler on preboot', async () => { + const registerRouterMock = jest.fn(); + mockHttpServer.mockImplementationOnce(() => ({ + setup: () => ({ + server: { start: jest.fn(), stop: jest.fn(), route: jest.fn() }, + registerStaticDir: jest.fn(), + registerRouterAfterListening: registerRouterMock, + }), + start: noop, + stop: noop, + })); + + const service = new HttpService({ coreId, configService: createConfigService(), env, logger }); + + const registerRoutesMock = jest.fn(); + const { registerRoutes } = await service.preboot(prebootDeps); + registerRoutes('some-path', registerRoutesMock); + + expect(registerRoutesMock).toHaveBeenCalledTimes(1); + expect(registerRoutesMock).toHaveBeenCalledWith(expect.any(Router)); + + const [[router]] = registerRoutesMock.mock.calls; + expect(registerRouterMock).toHaveBeenCalledTimes(1); + expect(registerRouterMock).toHaveBeenCalledWith(router); +}); + +test('register preboot route handler on setup', async () => { + const registerRouterMock = jest.fn(); + mockHttpServer + .mockImplementationOnce(() => ({ + setup: () => ({ + server: { start: jest.fn(), stop: jest.fn(), route: jest.fn() }, + registerStaticDir: jest.fn(), + registerRouterAfterListening: registerRouterMock, + }), + start: noop, + stop: noop, + })) + .mockImplementationOnce(() => ({ setup: () => ({ server: {} }), start: noop, stop: noop })); + + const service = new HttpService({ coreId, configService: createConfigService(), env, logger }); + await service.preboot(prebootDeps); + + const registerRoutesMock = jest.fn(); + const { registerPrebootRoutes } = await service.setup(setupDeps); + registerPrebootRoutes('some-path', registerRoutesMock); + + expect(registerRoutesMock).toHaveBeenCalledTimes(1); + expect(registerRoutesMock).toHaveBeenCalledWith(expect.any(Router)); + + const [[router]] = registerRoutesMock.mock.calls; + expect(registerRouterMock).toHaveBeenCalledTimes(1); + expect(registerRouterMock).toHaveBeenCalledWith(router); +}); + +test('returns `preboot` http server contract on preboot', async () => { + const configService = createConfigService(); + const httpServer = { + server: fakeHapiServer, + registerStaticDir: jest.fn(), + auth: Symbol('auth'), + basePath: Symbol('basePath'), + csp: Symbol('csp'), + }; + + mockHttpServer.mockImplementation(() => ({ + isListening: () => false, + setup: jest.fn().mockReturnValue(httpServer), + start: noop, + stop: noop, + })); + + const service = new HttpService({ coreId, configService, env, logger }); + await expect(service.preboot(prebootDeps)).resolves.toMatchObject({ + auth: httpServer.auth, + basePath: httpServer.basePath, + csp: httpServer.csp, + externalUrl: expect.any(ExternalUrlConfig), + registerRouteHandlerContext: expect.any(Function), + registerRoutes: expect.any(Function), + registerStaticDir: expect.any(Function), + }); +}); + test('returns http server contract on setup', async () => { const configService = createConfigService(); const httpServer = { server: fakeHapiServer, options: { someOption: true } }; + mockHttpServer.mockImplementationOnce(() => ({ + setup: () => ({ + server: { start: jest.fn(), stop: jest.fn(), route: jest.fn() }, + registerStaticDir: jest.fn(), + }), + start: noop, + stop: noop, + })); + mockHttpServer.mockImplementation(() => ({ isListening: () => false, setup: jest.fn().mockReturnValue(httpServer), @@ -260,10 +421,12 @@ test('returns http server contract on setup', async () => { })); const service = new HttpService({ coreId, configService, env, logger }); + await service.preboot(prebootDeps); const setupContract = await service.setup(setupDeps); expect(setupContract).toMatchObject(httpServer); expect(setupContract).toMatchObject({ createRouter: expect.any(Function), + registerPrebootRoutes: expect.any(Function), }); }); @@ -271,6 +434,14 @@ test('does not start http server if configured with `autoListen:false`', async ( const configService = createConfigService({ autoListen: false, }); + mockHttpServer.mockImplementationOnce(() => ({ + setup: () => ({ + server: { start: jest.fn(), stop: jest.fn(), route: jest.fn() }, + registerStaticDir: jest.fn(), + }), + start: noop, + stop: noop, + })); const httpServer = { isListening: () => false, setup: jest.fn().mockReturnValue({}), @@ -286,6 +457,7 @@ test('does not start http server if configured with `autoListen:false`', async ( logger, }); + await service.preboot(prebootDeps); await service.setup(setupDeps); await service.start(); diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index 0097aab82b21c..4b9e45e271be2 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -6,21 +6,21 @@ * Side Public License, v 1. */ -import { Observable, Subscription, combineLatest, of } from 'rxjs'; +import { Observable, Subscription, combineLatest } from 'rxjs'; import { first, map } from 'rxjs/operators'; import { pick } from '@kbn/std'; import type { RequestHandlerContext } from 'src/core/server'; import type { InternalExecutionContextSetup } from '../execution_context'; import { CoreService } from '../../types'; -import { Logger, LoggerFactory } from '../logging'; -import { ContextSetup } from '../context'; +import { Logger } from '../logging'; +import { ContextSetup, InternalContextPreboot } from '../context'; import { Env } from '../config'; import { CoreContext } from '../core_context'; import { PluginOpaqueId } from '../plugins'; import { CspConfigType, config as cspConfig } from '../csp'; -import { IRouter, Router } from './router'; +import { Router } from './router'; import { HttpConfig, HttpConfigType, config as httpConfig } from './http_config'; import { HttpServer } from './http_server'; import { HttpsRedirectServer } from './https_redirect_server'; @@ -28,9 +28,9 @@ import { HttpsRedirectServer } from './https_redirect_server'; import { RequestHandlerContextContainer, RequestHandlerContextProvider, + InternalHttpServicePreboot, InternalHttpServiceSetup, InternalHttpServiceStart, - InternalNotReadyHttpServiceSetup, } from './types'; import { registerCoreHandlers } from './lifecycle_handlers'; @@ -40,6 +40,10 @@ import { ExternalUrlConfig, } from '../external_url'; +interface PrebootDeps { + context: InternalContextPreboot; +} + interface SetupDeps { context: ContextSetup; executionContext: InternalExecutionContextSetup; @@ -48,22 +52,22 @@ interface SetupDeps { /** @internal */ export class HttpService implements CoreService { + private readonly prebootServer: HttpServer; + private isPrebootServerStopped = false; private readonly httpServer: HttpServer; private readonly httpsRedirectServer: HttpsRedirectServer; private readonly config$: Observable; private configSubscription?: Subscription; - private readonly logger: LoggerFactory; private readonly log: Logger; private readonly env: Env; - private notReadyServer?: HttpServer; + private internalPreboot?: InternalHttpServicePreboot; private internalSetup?: InternalHttpServiceSetup; private requestHandlerContext?: RequestHandlerContextContainer; constructor(private readonly coreContext: CoreContext) { const { logger, configService, env } = coreContext; - this.logger = logger; this.env = env; this.log = logger.get('http'); this.config$ = combineLatest([ @@ -72,10 +76,63 @@ export class HttpService configService.atPath(externalUrlConfig.path), ]).pipe(map(([http, csp, externalUrl]) => new HttpConfig(http, csp, externalUrl))); const shutdownTimeout$ = this.config$.pipe(map(({ shutdownTimeout }) => shutdownTimeout)); + this.prebootServer = new HttpServer(logger, 'Preboot', shutdownTimeout$); this.httpServer = new HttpServer(logger, 'Kibana', shutdownTimeout$); this.httpsRedirectServer = new HttpsRedirectServer(logger.get('http', 'redirect', 'server')); } + public async preboot(deps: PrebootDeps): Promise { + this.log.debug('setting up preboot server'); + const config = await this.config$.pipe(first()).toPromise(); + + const prebootSetup = await this.prebootServer.setup(config); + prebootSetup.server.route({ + path: '/{p*}', + method: '*', + handler: (req, responseToolkit) => { + this.log.debug(`Kibana server is not ready yet ${req.method}:${req.url.href}.`); + + // If server is not ready yet, because plugins or core can perform + // long running tasks (build assets, saved objects migrations etc.) + // we should let client know that and ask to retry after 30 seconds. + return responseToolkit + .response('Kibana server is not ready yet') + .code(503) + .header('Retry-After', '30'); + }, + }); + + if (this.shouldListen(config)) { + this.log.debug('starting preboot server'); + await this.prebootServer.start(); + } + + const prebootServerRequestHandlerContext = deps.context.createContextContainer(); + this.internalPreboot = { + externalUrl: new ExternalUrlConfig(config.externalUrl), + csp: prebootSetup.csp, + basePath: prebootSetup.basePath, + registerStaticDir: prebootSetup.registerStaticDir.bind(prebootSetup), + auth: prebootSetup.auth, + server: prebootSetup.server, + registerRouteHandlerContext: (pluginOpaqueId, contextName, provider) => + prebootServerRequestHandlerContext.registerContext(pluginOpaqueId, contextName, provider), + registerRoutes: (path, registerCallback) => { + const router = new Router( + path, + this.log, + prebootServerRequestHandlerContext.createHandler.bind(null, this.coreContext.coreId) + ); + + registerCallback(router); + + prebootSetup.registerRouterAfterListening(router); + }, + }; + + return this.internalPreboot; + } + public async setup(deps: SetupDeps) { this.requestHandlerContext = deps.context.createContextContainer(); this.configSubscription = this.config$.subscribe(() => { @@ -90,8 +147,6 @@ export class HttpService const config = await this.config$.pipe(first()).toPromise(); - const notReadyServer = await this.setupNotReadyService({ config, context: deps.context }); - const { registerRouter, ...serverContract } = await this.httpServer.setup( config, deps.executionContext @@ -102,8 +157,6 @@ export class HttpService this.internalSetup = { ...serverContract, - notReadyServer, - externalUrl: new ExternalUrlConfig(config.externalUrl), createRouter: ( @@ -124,6 +177,8 @@ export class HttpService contextName: ContextName, provider: RequestHandlerContextProvider ) => this.requestHandlerContext!.registerContext(pluginOpaqueId, contextName, provider), + + registerPrebootRoutes: this.internalPreboot!.registerRoutes, }; return this.internalSetup; @@ -141,11 +196,10 @@ export class HttpService public async start() { const config = await this.config$.pipe(first()).toPromise(); if (this.shouldListen(config)) { - if (this.notReadyServer) { - this.log.debug('stopping NotReady server'); - await this.notReadyServer.stop(); - this.notReadyServer = undefined; - } + this.log.debug('stopping preboot server'); + await this.prebootServer.stop(); + this.isPrebootServerStopped = true; + // If a redirect port is specified, we start an HTTP server at this port and // redirect all requests to the SSL port. if (config.ssl.enabled && config.ssl.redirectHttpFromPort !== undefined) { @@ -169,81 +223,15 @@ export class HttpService } public async stop() { - if (this.configSubscription === undefined) { - return; - } - this.configSubscription?.unsubscribe(); this.configSubscription = undefined; - if (this.notReadyServer) { - await this.notReadyServer.stop(); + if (!this.isPrebootServerStopped) { + this.isPrebootServerStopped = false; + await this.prebootServer.stop(); } + await this.httpServer.stop(); await this.httpsRedirectServer.stop(); } - - private async setupNotReadyService({ - config, - context, - }: { - config: HttpConfig; - context: ContextSetup; - }): Promise { - if (!this.shouldListen(config)) { - return; - } - - const notReadySetup = await this.runNotReadyServer(config); - - // We cannot use the real context container since the core services may not yet be ready - const fakeContext: RequestHandlerContextContainer = new Proxy( - context.createContextContainer(), - { - get: (target, property, receiver) => { - if (property === 'createHandler') { - return Reflect.get(target, property, receiver); - } - throw new Error(`Unexpected access from fake context: ${String(property)}`); - }, - } - ); - - return { - registerRoutes: (path: string, registerCallback: (router: IRouter) => void) => { - const router = new Router( - path, - this.log, - fakeContext.createHandler.bind(null, this.coreContext.coreId) - ); - - registerCallback(router); - notReadySetup.registerRouterAfterListening(router); - }, - }; - } - - private async runNotReadyServer(config: HttpConfig) { - this.log.debug('starting NotReady server'); - this.notReadyServer = new HttpServer(this.logger, 'NotReady', of(config.shutdownTimeout)); - const notReadySetup = await this.notReadyServer.setup(config); - notReadySetup.server.route({ - path: '/{p*}', - method: '*', - handler: (req, responseToolkit) => { - this.log.debug(`Kibana server is not ready yet ${req.method}:${req.url.href}.`); - - // If server is not ready yet, because plugins or core can perform - // long running tasks (build assets, saved objects migrations etc.) - // we should let client know that and ask to retry after 30 seconds. - return responseToolkit - .response('Kibana server is not ready yet') - .code(503) - .header('Retry-After', '30'); - }, - }); - await this.notReadyServer.start(); - - return notReadySetup; - } } diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts index 84fe5149c89c6..cad5a50dbc505 100644 --- a/src/core/server/http/index.ts +++ b/src/core/server/http/index.ts @@ -87,6 +87,8 @@ export type { RequestHandlerContextContainer, RequestHandlerContextProvider, HttpAuth, + HttpServicePreboot, + InternalHttpServicePreboot, HttpServiceSetup, InternalHttpServiceSetup, HttpServiceStart, diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts index 99b63fc73687a..e497f254e0632 100644 --- a/src/core/server/http/integration_tests/core_services.test.ts +++ b/src/core/server/http/integration_tests/core_services.test.ts @@ -43,6 +43,7 @@ describe('http service', () => { let root: ReturnType; beforeEach(async () => { root = kbnTestServer.createRoot({ plugins: { initialize: false } }); + await root.preboot(); }, 30000); afterEach(async () => { @@ -189,6 +190,7 @@ describe('http service', () => { let root: ReturnType; beforeEach(async () => { root = kbnTestServer.createRoot({ plugins: { initialize: false } }); + await root.preboot(); }, 30000); afterEach(async () => { @@ -282,6 +284,7 @@ describe('http service', () => { beforeEach(async () => { root = kbnTestServer.createRoot({ plugins: { initialize: false } }); + await root.preboot(); }, 30000); afterEach(async () => { diff --git a/src/core/server/http/integration_tests/http_auth.test.ts b/src/core/server/http/integration_tests/http_auth.test.ts index 0696deb9c07ae..9c923943118a0 100644 --- a/src/core/server/http/integration_tests/http_auth.test.ts +++ b/src/core/server/http/integration_tests/http_auth.test.ts @@ -15,6 +15,7 @@ describe('http auth', () => { beforeEach(async () => { root = kbnTestServer.createRoot({ plugins: { initialize: false } }); + await root.preboot(); }, 30000); afterEach(async () => { diff --git a/src/core/server/http/integration_tests/lifecycle.test.ts b/src/core/server/http/integration_tests/lifecycle.test.ts index da8abe55b6592..e883cd59c8c77 100644 --- a/src/core/server/http/integration_tests/lifecycle.test.ts +++ b/src/core/server/http/integration_tests/lifecycle.test.ts @@ -29,9 +29,10 @@ const setupDeps = { executionContext: executionContextServiceMock.createInternalSetupContract(), }; -beforeEach(() => { +beforeEach(async () => { logger = loggingSystemMock.create(); server = createHttpServer({ logger }); + await server.preboot({ context: contextServiceMock.createPrebootContract() }); }); afterEach(async () => { diff --git a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts index 077e2f6e9c485..c633db11edd7a 100644 --- a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts +++ b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts @@ -91,6 +91,7 @@ describe('core lifecycle handlers', () => { }); server = createHttpServer({ configService }); + await server.preboot({ context: contextServiceMock.createPrebootContract() }); const serverSetup = await server.setup(setupDeps); router = serverSetup.createRouter('/'); innerServer = serverSetup.server; diff --git a/src/core/server/http/integration_tests/logging.test.ts b/src/core/server/http/integration_tests/logging.test.ts index 62cb699bc49f6..f7eee9580d11a 100644 --- a/src/core/server/http/integration_tests/logging.test.ts +++ b/src/core/server/http/integration_tests/logging.test.ts @@ -28,6 +28,7 @@ describe('request logging', () => { describe('configuration', () => { it('does not log with a default config', async () => { const root = kbnTestServer.createRoot({ plugins: { initialize: false } }); + await root.preboot(); const { http } = await root.setup(); http @@ -69,6 +70,7 @@ describe('request logging', () => { initialize: false, }, }); + await root.preboot(); const { http } = await root.setup(); http @@ -125,6 +127,7 @@ describe('request logging', () => { }); it('handles a GET request', async () => { + await root.preboot(); const { http } = await root.setup(); http @@ -147,6 +150,7 @@ describe('request logging', () => { }); it('handles a POST request', async () => { + await root.preboot(); const { http } = await root.setup(); http.createRouter('/').post( @@ -178,6 +182,7 @@ describe('request logging', () => { }); it('handles an error response', async () => { + await root.preboot(); const { http } = await root.setup(); http @@ -198,6 +203,7 @@ describe('request logging', () => { }); it('handles query strings', async () => { + await root.preboot(); const { http } = await root.setup(); http @@ -216,6 +222,7 @@ describe('request logging', () => { }); it('correctly calculates response payload', async () => { + await root.preboot(); const { http } = await root.setup(); http @@ -234,6 +241,7 @@ describe('request logging', () => { describe('handles request/response headers', () => { it('includes request/response headers in log entry', async () => { + await root.preboot(); const { http } = await root.setup(); http @@ -252,6 +260,7 @@ describe('request logging', () => { }); it('filters sensitive request headers by default', async () => { + await root.preboot(); const { http } = await root.setup(); http.createRouter('/').post( @@ -319,6 +328,7 @@ describe('request logging', () => { initialize: false, }, }); + await root.preboot(); const { http } = await root.setup(); http.createRouter('/').post( @@ -351,6 +361,7 @@ describe('request logging', () => { }); it('filters sensitive response headers by defaut', async () => { + await root.preboot(); const { http } = await root.setup(); http.createRouter('/').post( @@ -416,6 +427,7 @@ describe('request logging', () => { initialize: false, }, }); + await root.preboot(); const { http } = await root.setup(); http.createRouter('/').post( @@ -449,6 +461,7 @@ describe('request logging', () => { }); it('handles user agent', async () => { + await root.preboot(); const { http } = await root.setup(); http diff --git a/src/core/server/http/integration_tests/preboot.test.ts b/src/core/server/http/integration_tests/preboot.test.ts new file mode 100644 index 0000000000000..7c2118bea725b --- /dev/null +++ b/src/core/server/http/integration_tests/preboot.test.ts @@ -0,0 +1,146 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import supertest from 'supertest'; + +import { contextServiceMock } from '../../context/context_service.mock'; +import { executionContextServiceMock } from '../../execution_context/execution_context_service.mock'; +import { loggingSystemMock } from '../../logging/logging_system.mock'; +import { createHttpServer } from '../test_utils'; +import { HttpService } from '../http_service'; + +let server: HttpService; +const prebootDeps = { + context: contextServiceMock.createPrebootContract(), +}; +const setupDeps = { + context: contextServiceMock.createSetupContract(), + executionContext: executionContextServiceMock.createInternalSetupContract(), +}; + +beforeEach(async () => { + server = createHttpServer({ logger: loggingSystemMock.create() }); +}); + +afterEach(async () => { + await server.stop(); +}); + +describe('Preboot HTTP server', () => { + it('accepts requests before `setup`', async () => { + const { server: innerPrebootServer, registerRoutes } = await server.preboot(prebootDeps); + registerRoutes('', (router) => { + router.get({ path: '/preboot-get', validate: false }, (context, req, res) => + res.ok({ body: 'hello-get' }) + ); + router.post({ path: '/preboot-post', validate: false }, (context, req, res) => + res.ok({ body: 'hello-post' }) + ); + }); + + // Preboot routes should work now. + await supertest(innerPrebootServer.listener).get('/preboot-get').expect(200, 'hello-get'); + await supertest(innerPrebootServer.listener).post('/preboot-post').expect(200, 'hello-post'); + + // All non-preboot routes should get `503` (e.g. if client tries to access any standard API). + await supertest(innerPrebootServer.listener) + .get('/standard-get') + .expect(503, 'Kibana server is not ready yet'); + await supertest(innerPrebootServer.listener) + .post('/standard-post') + .expect(503, 'Kibana server is not ready yet'); + }); + + it('accepts requests after `setup`, but before `start`', async () => { + const { server: innerPrebootServer, registerRoutes } = await server.preboot(prebootDeps); + registerRoutes('', (router) => { + router.get({ path: '/preboot-get', validate: false }, (context, req, res) => + res.ok({ body: 'hello-get' }) + ); + router.post({ path: '/preboot-post', validate: false }, (context, req, res) => + res.ok({ body: 'hello-post' }) + ); + }); + + const { createRouter, server: innerStandardServer } = await server.setup(setupDeps); + const standardRouter = createRouter(''); + standardRouter.get({ path: '/standard-get', validate: false }, (context, req, res) => + res.ok({ body: 'hello-get' }) + ); + standardRouter.post({ path: '/standard-post', validate: false }, (context, req, res) => + res.ok({ body: 'hello-post' }) + ); + + // Preboot routes should still work. + await supertest(innerPrebootServer.listener).get('/preboot-get').expect(200, 'hello-get'); + await supertest(innerPrebootServer.listener).post('/preboot-post').expect(200, 'hello-post'); + + // All non-preboot routes should still get `503` (e.g. if client tries to access any standard API). + await supertest(innerPrebootServer.listener) + .get('/standard-get') + .expect(503, 'Kibana server is not ready yet'); + await supertest(innerPrebootServer.listener) + .post('/standard-post') + .expect(503, 'Kibana server is not ready yet'); + + // Standard HTTP server isn't functional yet. + await supertest(innerStandardServer.listener) + .get('/standard-get') + .expect(404, { statusCode: 404, error: 'Not Found', message: 'Not Found' }); + await supertest(innerStandardServer.listener) + .post('/standard-post') + .expect(404, { statusCode: 404, error: 'Not Found', message: 'Not Found' }); + }); + + it('is not available after `start`', async () => { + const { server: innerPrebootServer, registerRoutes } = await server.preboot(prebootDeps); + registerRoutes('', (router) => { + router.get({ path: '/preboot-get', validate: false }, (context, req, res) => + res.ok({ body: 'hello-get' }) + ); + router.post({ path: '/preboot-post', validate: false }, (context, req, res) => + res.ok({ body: 'hello-post' }) + ); + }); + + const { createRouter, server: innerStandardServer } = await server.setup(setupDeps); + const standardRouter = createRouter(''); + standardRouter.get({ path: '/standard-get', validate: false }, (context, req, res) => + res.ok({ body: 'hello-get' }) + ); + standardRouter.post({ path: '/standard-post', validate: false }, (context, req, res) => + res.ok({ body: 'hello-post' }) + ); + + await server.start(); + + // Preboot routes should no longer work. + await supertest(innerPrebootServer.listener).get('/preboot-get').expect(503, { + statusCode: 503, + error: 'Service Unavailable', + message: 'Kibana is shutting down and not accepting new incoming requests', + }); + await supertest(innerPrebootServer.listener).post('/preboot-post').expect(503, { + statusCode: 503, + error: 'Service Unavailable', + message: 'Kibana is shutting down and not accepting new incoming requests', + }); + + // Preboot routes should simply become unknown routes for the standard server. + await supertest(innerStandardServer.listener) + .get('/preboot-get') + .expect(404, { statusCode: 404, error: 'Not Found', message: 'Not Found' }); + await supertest(innerStandardServer.listener) + .post('/preboot-post') + .expect(404, { statusCode: 404, error: 'Not Found', message: 'Not Found' }); + + // All non-preboot routes should finally function as expected (e.g. if client tries to access any standard API). + await supertest(innerStandardServer.listener).get('/standard-get').expect(200, 'hello-get'); + await supertest(innerStandardServer.listener).post('/standard-post').expect(200, 'hello-post'); + }); +}); diff --git a/src/core/server/http/integration_tests/request.test.ts b/src/core/server/http/integration_tests/request.test.ts index ecacbf0bfa0c2..0a30bfac85f5d 100644 --- a/src/core/server/http/integration_tests/request.test.ts +++ b/src/core/server/http/integration_tests/request.test.ts @@ -30,10 +30,11 @@ const setupDeps = { executionContext: executionContextServiceMock.createInternalSetupContract(), }; -beforeEach(() => { +beforeEach(async () => { logger = loggingSystemMock.create(); server = createHttpServer({ logger }); + await server.preboot({ context: contextServiceMock.createPrebootContract() }); }); afterEach(async () => { diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index 1b2b0b966d3a2..5bea371d479ae 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -28,9 +28,10 @@ const setupDeps = { executionContext: executionContextServiceMock.createInternalSetupContract(), }; -beforeEach(() => { +beforeEach(async () => { logger = loggingSystemMock.create(); server = createHttpServer({ logger }); + await server.preboot({ context: contextServiceMock.createPrebootContract() }); }); afterEach(async () => { diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index bbd296d6b1831..7353f48b47194 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -56,6 +56,109 @@ export interface HttpAuth { isAuthenticated: IsAuthenticated; } +/** + * Kibana HTTP Service provides an abstraction to work with the HTTP stack at the `preboot` stage. This functionality + * allows Kibana to serve user requests even before Kibana becomes fully operational. Only Core and `preboot` plugins + * can define HTTP routes at this stage. + * + * @example + * To handle an incoming request in your preboot plugin you should: + * - Use `@kbn/config-schema` package to create a schema to validate the request `params`, `query`, and `body`. Every incoming request will be validated against the created schema. If validation failed, the request is rejected with `400` status and `Bad request` error without calling the route's handler. + * To opt out of validating the request, specify `false`. + * ```ts + * import { schema, TypeOf } from '@kbn/config-schema'; + * const validate = { + * params: schema.object({ + * id: schema.string(), + * }), + * }; + * ``` + * + * - Declare a function to respond to incoming request. + * The function will receive `request` object containing request details: url, headers, matched route, as well as validated `params`, `query`, `body`. + * And `response` object instructing HTTP server to create HTTP response with information sent back to the client as the response body, headers, and HTTP status. + * Any exception raised during the handler call will generate `500 Server error` response and log error details for further investigation. See below for returning custom error responses. + * ```ts + * const handler = async (context: RequestHandlerContext, request: KibanaRequest, response: ResponseFactory) => { + * const data = await findObject(request.params.id); + * // creates a command to respond with 'not found' error + * if (!data) { + * return response.notFound(); + * } + * // creates a command to send found data to the client and set response headers + * return response.ok({ + * body: data, + * headers: { 'content-type': 'application/json' } + * }); + * } + * ``` + * * - Acquire `preboot` {@link IRouter} instance and register route handler for GET request to 'path/{id}' path. + * ```ts + * import { schema, TypeOf } from '@kbn/config-schema'; + * + * const validate = { + * params: schema.object({ + * id: schema.string(), + * }), + * }; + * + * httpPreboot.registerRoutes('my-plugin', (router) => { + * router.get({ path: 'path/{id}', validate }, async (context, request, response) => { + * const data = await findObject(request.params.id); + * if (!data) { + * return response.notFound(); + * } + * return response.ok({ + * body: data, + * headers: { 'content-type': 'application/json' } + * }); + * }); + * }); + * ``` + * @public + */ +export interface HttpServicePreboot { + /** + * Provides ability to acquire `preboot` {@link IRouter} instance for a particular top-level path and register handler + * functions for any number of nested routes. + * + * @remarks + * Each route can have only one handler function, which is executed when the route is matched. + * See the {@link IRouter} documentation for more information. + * + * @example + * ```ts + * registerRoutes('my-plugin', (router) => { + * // handler is called when '/my-plugin/path' resource is requested with `GET` method + * router.get({ path: '/path', validate: false }, (context, req, res) => res.ok({ content: 'ok' })); + * }); + * ``` + * @public + */ + registerRoutes(path: string, callback: (router: IRouter) => void): void; + + /** + * Access or manipulate the Kibana base path + * See {@link IBasePath}. + */ + basePath: IBasePath; +} + +/** @internal */ +export interface InternalHttpServicePreboot + extends Pick< + InternalHttpServiceSetup, + | 'auth' + | 'csp' + | 'basePath' + | 'externalUrl' + | 'registerStaticDir' + | 'registerRouteHandlerContext' + | 'server' + > { + registerRoutes(path: string, callback: (router: IRouter) => void): void; +} + /** * Kibana HTTP Service provides own abstraction for work with HTTP stack. * Plugins don't have direct access to `hapi` server and its primitives anymore. Moreover, @@ -277,11 +380,6 @@ export interface HttpServiceSetup { getServerInfo: () => HttpServerInfo; } -/** @internal */ -export interface InternalNotReadyHttpServiceSetup { - registerRoutes(path: string, callback: (router: IRouter) => void): void; -} - /** @internal */ export interface InternalHttpServiceSetup extends Omit { @@ -303,7 +401,7 @@ export interface InternalHttpServiceSetup contextName: ContextName, provider: RequestHandlerContextProvider ) => RequestHandlerContextContainer; - notReadyServer?: InternalNotReadyHttpServiceSetup; + registerPrebootRoutes(path: string, callback: (router: IRouter) => void): void; } /** @public */ diff --git a/src/core/server/http_resources/http_resources_service.mock.ts b/src/core/server/http_resources/http_resources_service.mock.ts index 3a94de15d14b9..a2ca0aa246582 100644 --- a/src/core/server/http_resources/http_resources_service.mock.ts +++ b/src/core/server/http_resources/http_resources_service.mock.ts @@ -13,12 +13,16 @@ const createHttpResourcesMock = (): jest.Mocked => ({ register: jest.fn(), }); -function createInternalHttpResourcesSetup() { +function createInternalHttpResourcesPreboot() { return { createRegistrar: jest.fn(() => createHttpResourcesMock()), }; } +function createInternalHttpResourcesSetup() { + return createInternalHttpResourcesPreboot(); +} + function createHttpResourcesResponseFactory() { const mocked: jest.Mocked = { renderCoreApp: jest.fn(), @@ -35,6 +39,7 @@ function createHttpResourcesResponseFactory() { export const httpResourcesMock = { createRegistrar: createHttpResourcesMock, + createPrebootContract: createInternalHttpResourcesPreboot, createSetupContract: createInternalHttpResourcesSetup, createResponseFactory: createHttpResourcesResponseFactory, }; diff --git a/src/core/server/http_resources/http_resources_service.test.ts b/src/core/server/http_resources/http_resources_service.test.ts index 8b24e05fc5bf4..33ee6cc4e3748 100644 --- a/src/core/server/http_resources/http_resources_service.test.ts +++ b/src/core/server/http_resources/http_resources_service.test.ts @@ -15,13 +15,15 @@ import { mockCoreContext } from '../core_context.mock'; import { httpServiceMock } from '../http/http_service.mock'; import { httpServerMock } from '../http/http_server.mocks'; import { renderingMock } from '../rendering/rendering_service.mock'; -import { HttpResourcesService, SetupDeps } from './http_resources_service'; +import { HttpResourcesService, PrebootDeps, SetupDeps } from './http_resources_service'; import { httpResourcesMock } from './http_resources_service.mock'; +import { HttpResources } from 'kibana/server'; const coreContext = mockCoreContext.create(); describe('HttpResources service', () => { let service: HttpResourcesService; + let prebootDeps: PrebootDeps; let setupDeps: SetupDeps; let router: jest.Mocked; const kibanaRequest = httpServerMock.createKibanaRequest(); @@ -34,6 +36,10 @@ describe('HttpResources service', () => { describe('#createRegistrar', () => { beforeEach(() => { + prebootDeps = { + http: httpServiceMock.createInternalPrebootContract(), + rendering: renderingMock.createPrebootContract(), + }; setupDeps = { http: httpServiceMock.createInternalSetupContract(), rendering: renderingMock.createSetupContract(), @@ -42,221 +48,228 @@ describe('HttpResources service', () => { router = httpServiceMock.createRouter(); }); - describe('register', () => { - describe('renderCoreApp', () => { - it('formats successful response', async () => { - const routeConfig: RouteConfig = { path: '/', validate: false }; - const { createRegistrar } = await service.setup(setupDeps); - const { register } = createRegistrar(router); - register(routeConfig, async (ctx, req, res) => { - return res.renderCoreApp(); - }); - const [[, routeHandler]] = router.get.mock.calls; - - const responseFactory = httpResourcesMock.createResponseFactory(); - await routeHandler(context, kibanaRequest, responseFactory); - expect(setupDeps.rendering.render).toHaveBeenCalledWith( - kibanaRequest, - context.core.uiSettings.client, - { - includeUserSettings: true, - vars: { - apmConfig, - }, - } - ); + function runRegisterTestSuite( + name: string, + initializer: () => Promise, + getDeps: () => PrebootDeps | SetupDeps + ) { + describe(`${name} register`, () => { + const routeConfig: RouteConfig = { path: '/', validate: false }; + let register: HttpResources['register']; + beforeEach(async () => { + register = await initializer(); }); - it('can attach headers, except the CSP header', async () => { - const routeConfig: RouteConfig = { path: '/', validate: false }; - const { createRegistrar } = await service.setup(setupDeps); - const { register } = createRegistrar(router); - register(routeConfig, async (ctx, req, res) => { - return res.renderCoreApp({ - headers: { - 'content-security-policy': "script-src 'unsafe-eval'", - 'x-kibana': '42', - }, + describe('renderCoreApp', () => { + it('formats successful response', async () => { + register(routeConfig, async (ctx, req, res) => { + return res.renderCoreApp(); }); - }); + const [[, routeHandler]] = router.get.mock.calls; - const [[, routeHandler]] = router.get.mock.calls; + const responseFactory = httpResourcesMock.createResponseFactory(); + await routeHandler(context, kibanaRequest, responseFactory); + expect(getDeps().rendering.render).toHaveBeenCalledWith( + kibanaRequest, + context.core.uiSettings.client, + { + includeUserSettings: true, + vars: { + apmConfig, + }, + } + ); + }); - const responseFactory = httpResourcesMock.createResponseFactory(); - await routeHandler(context, kibanaRequest, responseFactory); + it('can attach headers, except the CSP header', async () => { + register(routeConfig, async (ctx, req, res) => { + return res.renderCoreApp({ + headers: { + 'content-security-policy': "script-src 'unsafe-eval'", + 'x-kibana': '42', + }, + }); + }); - expect(responseFactory.ok).toHaveBeenCalledWith({ - body: '', - headers: { - 'x-kibana': '42', - 'content-security-policy': - "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", - }, - }); - }); - }); - describe('renderAnonymousCoreApp', () => { - it('formats successful response', async () => { - const routeConfig: RouteConfig = { path: '/', validate: false }; - const { createRegistrar } = await service.setup(setupDeps); - const { register } = createRegistrar(router); - register(routeConfig, async (ctx, req, res) => { - return res.renderAnonymousCoreApp(); - }); - const [[, routeHandler]] = router.get.mock.calls; + const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); - await routeHandler(context, kibanaRequest, responseFactory); - expect(setupDeps.rendering.render).toHaveBeenCalledWith( - kibanaRequest, - context.core.uiSettings.client, - { - includeUserSettings: false, - vars: { - apmConfig, - }, - } - ); - }); + const responseFactory = httpResourcesMock.createResponseFactory(); + await routeHandler(context, kibanaRequest, responseFactory); - it('can attach headers, except the CSP header', async () => { - const routeConfig: RouteConfig = { path: '/', validate: false }; - const { createRegistrar } = await service.setup(setupDeps); - const { register } = createRegistrar(router); - register(routeConfig, async (ctx, req, res) => { - return res.renderAnonymousCoreApp({ + expect(responseFactory.ok).toHaveBeenCalledWith({ + body: '', headers: { - 'content-security-policy': "script-src 'unsafe-eval'", 'x-kibana': '42', + 'content-security-policy': + "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", }, }); }); + }); + describe('renderAnonymousCoreApp', () => { + it('formats successful response', async () => { + register(routeConfig, async (ctx, req, res) => { + return res.renderAnonymousCoreApp(); + }); + const [[, routeHandler]] = router.get.mock.calls; - const [[, routeHandler]] = router.get.mock.calls; + const responseFactory = httpResourcesMock.createResponseFactory(); + await routeHandler(context, kibanaRequest, responseFactory); + expect(getDeps().rendering.render).toHaveBeenCalledWith( + kibanaRequest, + context.core.uiSettings.client, + { + includeUserSettings: false, + vars: { + apmConfig, + }, + } + ); + }); - const responseFactory = httpResourcesMock.createResponseFactory(); - await routeHandler(context, kibanaRequest, responseFactory); + it('can attach headers, except the CSP header', async () => { + register(routeConfig, async (ctx, req, res) => { + return res.renderAnonymousCoreApp({ + headers: { + 'content-security-policy': "script-src 'unsafe-eval'", + 'x-kibana': '42', + }, + }); + }); - expect(responseFactory.ok).toHaveBeenCalledWith({ - body: '', - headers: { - 'x-kibana': '42', - 'content-security-policy': - "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", - }, - }); - }); - }); - describe('renderHtml', () => { - it('formats successful response', async () => { - const htmlBody = ''; - const routeConfig: RouteConfig = { path: '/', validate: false }; - const { createRegistrar } = await service.setup(setupDeps); - const { register } = createRegistrar(router); - register(routeConfig, async (ctx, req, res) => { - return res.renderHtml({ body: htmlBody }); - }); - const [[, routeHandler]] = router.get.mock.calls; + const [[, routeHandler]] = router.get.mock.calls; + + const responseFactory = httpResourcesMock.createResponseFactory(); + await routeHandler(context, kibanaRequest, responseFactory); - const responseFactory = httpResourcesMock.createResponseFactory(); - await routeHandler(context, kibanaRequest, responseFactory); - expect(responseFactory.ok).toHaveBeenCalledWith({ - body: htmlBody, - headers: { - 'content-type': 'text/html', - 'content-security-policy': - "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", - }, + expect(responseFactory.ok).toHaveBeenCalledWith({ + body: '', + headers: { + 'x-kibana': '42', + 'content-security-policy': + "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", + }, + }); }); }); + describe('renderHtml', () => { + it('formats successful response', async () => { + const htmlBody = ''; + register(routeConfig, async (ctx, req, res) => { + return res.renderHtml({ body: htmlBody }); + }); + const [[, routeHandler]] = router.get.mock.calls; - it('can attach headers, except the CSP & "content-type" headers', async () => { - const htmlBody = ''; - const routeConfig: RouteConfig = { path: '/', validate: false }; - const { createRegistrar } = await service.setup(setupDeps); - const { register } = createRegistrar(router); - register(routeConfig, async (ctx, req, res) => { - return res.renderHtml({ + const responseFactory = httpResourcesMock.createResponseFactory(); + await routeHandler(context, kibanaRequest, responseFactory); + expect(responseFactory.ok).toHaveBeenCalledWith({ body: htmlBody, headers: { - 'content-type': 'text/html5', - 'content-security-policy': "script-src 'unsafe-eval'", - 'x-kibana': '42', + 'content-type': 'text/html', + 'content-security-policy': + "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", }, }); }); - const [[, routeHandler]] = router.get.mock.calls; + it('can attach headers, except the CSP & "content-type" headers', async () => { + const htmlBody = ''; + register(routeConfig, async (ctx, req, res) => { + return res.renderHtml({ + body: htmlBody, + headers: { + 'content-type': 'text/html5', + 'content-security-policy': "script-src 'unsafe-eval'", + 'x-kibana': '42', + }, + }); + }); - const responseFactory = httpResourcesMock.createResponseFactory(); - await routeHandler(context, kibanaRequest, responseFactory); + const [[, routeHandler]] = router.get.mock.calls; - expect(responseFactory.ok).toHaveBeenCalledWith({ - body: htmlBody, - headers: { - 'content-type': 'text/html', - 'x-kibana': '42', - 'content-security-policy': - "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", - }, - }); - }); - }); - describe('renderJs', () => { - it('formats successful response', async () => { - const jsBody = 'alert(1);'; - const routeConfig: RouteConfig = { path: '/', validate: false }; - const { createRegistrar } = await service.setup(setupDeps); - const { register } = createRegistrar(router); - register(routeConfig, async (ctx, req, res) => { - return res.renderJs({ body: jsBody }); - }); - const [[, routeHandler]] = router.get.mock.calls; + const responseFactory = httpResourcesMock.createResponseFactory(); + await routeHandler(context, kibanaRequest, responseFactory); - const responseFactory = httpResourcesMock.createResponseFactory(); - await routeHandler(context, kibanaRequest, responseFactory); - expect(responseFactory.ok).toHaveBeenCalledWith({ - body: jsBody, - headers: { - 'content-type': 'text/javascript', - 'content-security-policy': - "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", - }, + expect(responseFactory.ok).toHaveBeenCalledWith({ + body: htmlBody, + headers: { + 'content-type': 'text/html', + 'x-kibana': '42', + 'content-security-policy': + "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", + }, + }); }); }); + describe('renderJs', () => { + it('formats successful response', async () => { + const jsBody = 'alert(1);'; + register(routeConfig, async (ctx, req, res) => { + return res.renderJs({ body: jsBody }); + }); + const [[, routeHandler]] = router.get.mock.calls; - it('can attach headers, except the CSP & "content-type" headers', async () => { - const jsBody = 'alert(1);'; - const routeConfig: RouteConfig = { path: '/', validate: false }; - const { createRegistrar } = await service.setup(setupDeps); - const { register } = createRegistrar(router); - register(routeConfig, async (ctx, req, res) => { - return res.renderJs({ + const responseFactory = httpResourcesMock.createResponseFactory(); + await routeHandler(context, kibanaRequest, responseFactory); + expect(responseFactory.ok).toHaveBeenCalledWith({ body: jsBody, headers: { - 'content-type': 'text/html', - 'content-security-policy': "script-src 'unsafe-eval'", - 'x-kibana': '42', + 'content-type': 'text/javascript', + 'content-security-policy': + "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", }, }); }); - const [[, routeHandler]] = router.get.mock.calls; + it('can attach headers, except the CSP & "content-type" headers', async () => { + const jsBody = 'alert(1);'; + register(routeConfig, async (ctx, req, res) => { + return res.renderJs({ + body: jsBody, + headers: { + 'content-type': 'text/html', + 'content-security-policy': "script-src 'unsafe-eval'", + 'x-kibana': '42', + }, + }); + }); + + const [[, routeHandler]] = router.get.mock.calls; - const responseFactory = httpResourcesMock.createResponseFactory(); - await routeHandler(context, kibanaRequest, responseFactory); + const responseFactory = httpResourcesMock.createResponseFactory(); + await routeHandler(context, kibanaRequest, responseFactory); - expect(responseFactory.ok).toHaveBeenCalledWith({ - body: jsBody, - headers: { - 'content-type': 'text/javascript', - 'x-kibana': '42', - 'content-security-policy': - "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", - }, + expect(responseFactory.ok).toHaveBeenCalledWith({ + body: jsBody, + headers: { + 'content-type': 'text/javascript', + 'x-kibana': '42', + 'content-security-policy': + "script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'", + }, + }); }); }); }); - }); + } + + runRegisterTestSuite( + '#preboot', + async () => { + const { createRegistrar } = await service.preboot(prebootDeps); + return createRegistrar(router).register; + }, + () => prebootDeps + ); + + runRegisterTestSuite( + '#setup', + async () => { + await service.preboot(prebootDeps); + const { createRegistrar } = await service.setup(setupDeps); + return createRegistrar(router).register; + }, + () => setupDeps + ); }); }); diff --git a/src/core/server/http_resources/http_resources_service.ts b/src/core/server/http_resources/http_resources_service.ts index 44caa456e9955..6c295b152af3c 100644 --- a/src/core/server/http_resources/http_resources_service.ts +++ b/src/core/server/http_resources/http_resources_service.ts @@ -15,10 +15,11 @@ import { InternalHttpServiceSetup, KibanaRequest, KibanaResponseFactory, + InternalHttpServicePreboot, } from '../http'; import { Logger } from '../logging'; -import { InternalRenderingServiceSetup } from '../rendering'; +import { InternalRenderingServicePreboot, InternalRenderingServiceSetup } from '../rendering'; import { CoreService } from '../../types'; import { @@ -31,6 +32,11 @@ import { } from './types'; import { getApmConfig } from './get_apm_config'; +export interface PrebootDeps { + http: InternalHttpServicePreboot; + rendering: InternalRenderingServicePreboot; +} + export interface SetupDeps { http: InternalHttpServiceSetup; rendering: InternalRenderingServiceSetup; @@ -43,6 +49,13 @@ export class HttpResourcesService implements CoreService( route: RouteConfig, @@ -71,7 +84,7 @@ export class HttpResourcesService implements CoreService { }, plugins: { initialize: false }, }); + await root.preboot(); }, 30000); afterEach(async () => { diff --git a/src/core/server/http_resources/types.ts b/src/core/server/http_resources/types.ts index 3333574038ec7..1ec02272d151f 100644 --- a/src/core/server/http_resources/types.ts +++ b/src/core/server/http_resources/types.ts @@ -84,10 +84,16 @@ export type HttpResourcesRequestHandler< * Allows to configure HTTP response parameters * @internal */ -export interface InternalHttpResourcesSetup { +export interface InternalHttpResourcesPreboot { createRegistrar(router: IRouter): HttpResources; } +/** + * Allows to configure HTTP response parameters + * @internal + */ +export type InternalHttpResourcesSetup = InternalHttpResourcesPreboot; + /** * HttpResources service is responsible for serving static & dynamic assets for Kibana application via HTTP. * Provides API allowing plug-ins to respond with: diff --git a/src/core/server/i18n/i18n_service.mock.ts b/src/core/server/i18n/i18n_service.mock.ts index 29859e95b63b2..a199acd00eff5 100644 --- a/src/core/server/i18n/i18n_service.mock.ts +++ b/src/core/server/i18n/i18n_service.mock.ts @@ -25,6 +25,7 @@ type I18nServiceContract = PublicMethodsOf; const createMock = () => { const mock: jest.Mocked = { + preboot: jest.fn(), setup: jest.fn(), }; diff --git a/src/core/server/i18n/i18n_service.test.ts b/src/core/server/i18n/i18n_service.test.ts index 913d5ee4aaa99..ad87b371aca33 100644 --- a/src/core/server/i18n/i18n_service.test.ts +++ b/src/core/server/i18n/i18n_service.test.ts @@ -17,7 +17,7 @@ import { I18nService } from './i18n_service'; import { configServiceMock } from '../config/mocks'; import { mockCoreContext } from '../core_context.mock'; -import { httpServiceMock } from '../http/http_service.mock'; +import { httpServiceMock } from '../mocks'; const getConfigService = (locale = 'en') => { const configService = configServiceMock.create(); @@ -35,7 +35,8 @@ const getConfigService = (locale = 'en') => { describe('I18nService', () => { let service: I18nService; let configService: ReturnType; - let http: ReturnType; + let httpPreboot: ReturnType; + let httpSetup: ReturnType; beforeEach(() => { jest.clearAllMocks(); @@ -44,15 +45,60 @@ describe('I18nService', () => { const coreContext = mockCoreContext.create({ configService }); service = new I18nService(coreContext); - http = httpServiceMock.createInternalSetupContract(); + httpPreboot = httpServiceMock.createInternalPrebootContract(); + httpPreboot.registerRoutes.mockImplementation((type, callback) => + callback(httpServiceMock.createRouter()) + ); + httpSetup = httpServiceMock.createInternalSetupContract(); + }); + + describe('#preboot', () => { + it('calls `getKibanaTranslationFiles` with the correct parameters', async () => { + getKibanaTranslationFilesMock.mockResolvedValue([]); + + const pluginPaths = ['/pathA', '/pathB']; + await service.preboot({ pluginPaths, http: httpPreboot }); + + expect(getKibanaTranslationFilesMock).toHaveBeenCalledTimes(1); + expect(getKibanaTranslationFilesMock).toHaveBeenCalledWith('en', pluginPaths); + }); + + it('calls `initTranslations` with the correct parameters', async () => { + const translationFiles = ['/path/to/file', 'path/to/another/file']; + getKibanaTranslationFilesMock.mockResolvedValue(translationFiles); + + await service.preboot({ pluginPaths: [], http: httpPreboot }); + + expect(initTranslationsMock).toHaveBeenCalledTimes(1); + expect(initTranslationsMock).toHaveBeenCalledWith('en', translationFiles); + }); + + it('calls `registerRoutesMock` with the correct parameters', async () => { + await service.preboot({ pluginPaths: [], http: httpPreboot }); + + expect(registerRoutesMock).toHaveBeenCalledTimes(1); + expect(registerRoutesMock).toHaveBeenCalledWith({ + locale: 'en', + router: expect.any(Object), + }); + }); }); describe('#setup', () => { + beforeEach(async () => { + await service.preboot({ pluginPaths: ['/pathPrebootA'], http: httpPreboot }); + + // Reset mocks that were used in the `preboot`. + getKibanaTranslationFilesMock.mockClear(); + initTranslationsMock.mockClear(); + registerRoutesMock.mockClear(); + }); + it('calls `getKibanaTranslationFiles` with the correct parameters', async () => { getKibanaTranslationFilesMock.mockResolvedValue([]); const pluginPaths = ['/pathA', '/pathB']; - await service.setup({ pluginPaths, http }); + await service.setup({ pluginPaths, http: httpSetup }); expect(getKibanaTranslationFilesMock).toHaveBeenCalledTimes(1); expect(getKibanaTranslationFilesMock).toHaveBeenCalledWith('en', pluginPaths); @@ -62,14 +108,14 @@ describe('I18nService', () => { const translationFiles = ['/path/to/file', 'path/to/another/file']; getKibanaTranslationFilesMock.mockResolvedValue(translationFiles); - await service.setup({ pluginPaths: [], http }); + await service.setup({ pluginPaths: [], http: httpSetup }); expect(initTranslationsMock).toHaveBeenCalledTimes(1); expect(initTranslationsMock).toHaveBeenCalledWith('en', translationFiles); }); it('calls `registerRoutesMock` with the correct parameters', async () => { - await service.setup({ pluginPaths: [], http }); + await service.setup({ pluginPaths: [], http: httpSetup }); expect(registerRoutesMock).toHaveBeenCalledTimes(1); expect(registerRoutesMock).toHaveBeenCalledWith({ @@ -82,7 +128,10 @@ describe('I18nService', () => { const translationFiles = ['/path/to/file', 'path/to/another/file']; getKibanaTranslationFilesMock.mockResolvedValue(translationFiles); - const { getLocale, getTranslationFiles } = await service.setup({ pluginPaths: [], http }); + const { getLocale, getTranslationFiles } = await service.setup({ + pluginPaths: [], + http: httpSetup, + }); expect(getLocale()).toEqual('en'); expect(getTranslationFiles()).toEqual(translationFiles); diff --git a/src/core/server/i18n/i18n_service.ts b/src/core/server/i18n/i18n_service.ts index 0dffd8934a8ca..02a809b3c2c36 100644 --- a/src/core/server/i18n/i18n_service.ts +++ b/src/core/server/i18n/i18n_service.ts @@ -10,12 +10,17 @@ import { take } from 'rxjs/operators'; import { Logger } from '../logging'; import { IConfigService } from '../config'; import { CoreContext } from '../core_context'; -import { InternalHttpServiceSetup } from '../http'; +import { InternalHttpServicePreboot, InternalHttpServiceSetup } from '../http'; import { config as i18nConfigDef, I18nConfigType } from './i18n_config'; import { getKibanaTranslationFiles } from './get_kibana_translation_files'; import { initTranslations } from './init_translations'; import { registerRoutes } from './routes'; +interface PrebootDeps { + http: InternalHttpServicePreboot; + pluginPaths: string[]; +} + interface SetupDeps { http: InternalHttpServiceSetup; pluginPaths: string[]; @@ -45,7 +50,24 @@ export class I18nService { this.configService = coreContext.configService; } + public async preboot({ pluginPaths, http }: PrebootDeps) { + const { locale } = await this.initTranslations(pluginPaths); + http.registerRoutes('', (router) => registerRoutes({ router, locale })); + } + public async setup({ pluginPaths, http }: SetupDeps): Promise { + const { locale, translationFiles } = await this.initTranslations(pluginPaths); + + const router = http.createRouter(''); + registerRoutes({ router, locale }); + + return { + getLocale: () => locale, + getTranslationFiles: () => translationFiles, + }; + } + + private async initTranslations(pluginPaths: string[]) { const i18nConfig = await this.configService .atPath(i18nConfigDef.path) .pipe(take(1)) @@ -59,12 +81,6 @@ export class I18nService { this.log.debug(`Using translation files: [${translationFiles.join(', ')}]`); await initTranslations(locale, translationFiles); - const router = http.createRouter(''); - registerRoutes({ router, locale }); - - return { - getLocale: () => locale, - getTranslationFiles: () => translationFiles, - }; + return { locale, translationFiles }; } } diff --git a/src/core/server/index.ts b/src/core/server/index.ts index d2a4b4bff3390..c77a3a967364c 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -35,8 +35,9 @@ import { configSchema as elasticsearchConfigSchema, ElasticsearchServiceStart, IScopedClusterClient, + ElasticsearchServicePreboot, } from './elasticsearch'; -import { HttpServiceSetup, HttpServiceStart } from './http'; +import { HttpServicePreboot, HttpServiceSetup, HttpServiceStart } from './http'; import { HttpResources } from './http_resources'; import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plugins'; @@ -58,7 +59,7 @@ import { AppenderConfigType, appendersSchema, LoggingServiceSetup } from './logg import { CoreUsageDataStart } from './core_usage_data'; import { I18nServiceSetup } from './i18n'; import { DeprecationsServiceSetup } from './deprecations'; -// Because of #79265 we need to explicity import, then export these types for +// Because of #79265 we need to explicitly import, then export these types for // scripts/telemetry_check.js to work as expected import { CoreUsageStats, @@ -68,6 +69,9 @@ import { CoreEnvironmentUsageData, CoreServicesUsageData, } from './core_usage_data'; +import { PrebootServicePreboot } from './preboot'; + +export type { PrebootServicePreboot } from './preboot'; export type { CoreUsageStats, @@ -81,8 +85,6 @@ export type { import type { ExecutionContextSetup, ExecutionContextStart } from './execution_context'; export type { - ExecutionContextSetup, - ExecutionContextStart, IExecutionContextContainer, KibanaServerExecutionContext, KibanaExecutionContext, @@ -127,6 +129,7 @@ export type { LegacyElasticsearchClientConfig, LegacyElasticsearchError, LegacyElasticsearchErrorHelpers, + ElasticsearchServicePreboot, ElasticsearchServiceSetup, ElasticsearchServiceStart, ElasticsearchStatusMeta, @@ -145,6 +148,7 @@ export type { ShardsResponse, GetResponse, DeleteDocumentResponse, + ElasticsearchConfigPreboot, } from './elasticsearch'; export type { @@ -181,6 +185,7 @@ export type { HttpResponseOptions, HttpResponsePayload, HttpServerInfo, + HttpServicePreboot, HttpServiceSetup, HttpServiceStart, ErrorHttpResponseOptions, @@ -262,8 +267,11 @@ export type { AppenderConfigType, } from './logging'; +export { PluginType } from './plugins'; + export type { DiscoveredPlugin, + PrebootPlugin, Plugin, AsyncPlugin, PluginConfigDescriptor, @@ -470,7 +478,20 @@ export interface RequestHandlerContext { } /** - * Context passed to the plugins `setup` method. + * Context passed to the `setup` method of `preboot` plugins. + * @public + */ +export interface CorePreboot { + /** {@link ElasticsearchServicePreboot} */ + elasticsearch: ElasticsearchServicePreboot; + /** {@link HttpServicePreboot} */ + http: HttpServicePreboot; + /** {@link PrebootServicePreboot} */ + preboot: PrebootServicePreboot; +} + +/** + * Context passed to the `setup` method of `standard` plugins. * * @typeParam TPluginsStart - the type of the consuming plugin's start dependencies. Should be the same * as the consuming {@link Plugin}'s `TPluginsStart` type. Used by `getStartServices`. @@ -551,6 +572,8 @@ export type { CapabilitiesSetup, CapabilitiesStart, ContextSetup, + ExecutionContextSetup, + ExecutionContextStart, HttpResources, PluginsServiceSetup, PluginsServiceStart, diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index f3253e32aa08e..540670274f416 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -10,23 +10,32 @@ import { Type } from '@kbn/config-schema'; import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; import { ConfigDeprecationProvider } from './config'; -import { ContextSetup } from './context'; +import { InternalContextPreboot, ContextSetup } from './context'; import { + InternalElasticsearchServicePreboot, InternalElasticsearchServiceSetup, InternalElasticsearchServiceStart, } from './elasticsearch'; -import { InternalHttpServiceSetup, InternalHttpServiceStart } from './http'; +import { + InternalHttpServicePreboot, + InternalHttpServiceSetup, + InternalHttpServiceStart, +} from './http'; import { InternalSavedObjectsServiceSetup, InternalSavedObjectsServiceStart, } from './saved_objects'; -import { InternalUiSettingsServiceSetup, InternalUiSettingsServiceStart } from './ui_settings'; +import { + InternalUiSettingsServicePreboot, + InternalUiSettingsServiceSetup, + InternalUiSettingsServiceStart, +} from './ui_settings'; import { InternalEnvironmentServiceSetup } from './environment'; import { InternalMetricsServiceSetup, InternalMetricsServiceStart } from './metrics'; import { InternalRenderingServiceSetup } from './rendering'; -import { InternalHttpResourcesSetup } from './http_resources'; +import { InternalHttpResourcesPreboot, InternalHttpResourcesSetup } from './http_resources'; import { InternalStatusServiceSetup } from './status'; -import { InternalLoggingServiceSetup } from './logging'; +import { InternalLoggingServicePreboot, InternalLoggingServiceSetup } from './logging'; import { CoreUsageDataStart } from './core_usage_data'; import { I18nServiceSetup } from './i18n'; import { InternalDeprecationsServiceSetup } from './deprecations'; @@ -34,6 +43,18 @@ import type { InternalExecutionContextSetup, InternalExecutionContextStart, } from './execution_context'; +import { InternalPrebootServicePreboot } from './preboot'; + +/** @internal */ +export interface InternalCorePreboot { + context: InternalContextPreboot; + http: InternalHttpServicePreboot; + elasticsearch: InternalElasticsearchServicePreboot; + uiSettings: InternalUiSettingsServicePreboot; + httpResources: InternalHttpResourcesPreboot; + logging: InternalLoggingServicePreboot; + preboot: InternalPrebootServicePreboot; +} /** @internal */ export interface InternalCoreSetup { diff --git a/src/core/server/legacy/integration_tests/logging.test.ts b/src/core/server/legacy/integration_tests/logging.test.ts index 88c45962ce4a6..d8f9f035f44be 100644 --- a/src/core/server/legacy/integration_tests/logging.test.ts +++ b/src/core/server/legacy/integration_tests/logging.test.ts @@ -67,6 +67,7 @@ describe('logging service', () => { beforeAll(async () => { root = createRoot(); + await root.preboot(); await root.setup(); await root.start(); }, 30000); @@ -119,6 +120,7 @@ describe('logging service', () => { it('"silent": true', async () => { root = createRoot({ silent: true }); + await root.preboot(); await root.setup(); await root.start(); @@ -150,6 +152,7 @@ describe('logging service', () => { it('"quiet": true', async () => { root = createRoot({ quiet: true }); + await root.preboot(); await root.setup(); await root.start(); @@ -187,6 +190,7 @@ describe('logging service', () => { it('"verbose": true', async () => { root = createRoot({ verbose: true }); + await root.preboot(); await root.setup(); await root.start(); diff --git a/src/core/server/logging/index.ts b/src/core/server/logging/index.ts index 9d17b289bfa4c..ba6aec5f128c1 100644 --- a/src/core/server/logging/index.ts +++ b/src/core/server/logging/index.ts @@ -32,6 +32,10 @@ export type { export { LoggingSystem } from './logging_system'; export type { ILoggingSystem } from './logging_system'; export { LoggingService } from './logging_service'; -export type { InternalLoggingServiceSetup, LoggingServiceSetup } from './logging_service'; +export type { + InternalLoggingServicePreboot, + InternalLoggingServiceSetup, + LoggingServiceSetup, +} from './logging_service'; export { appendersSchema } from './appenders/appenders'; export type { AppenderConfigType } from './appenders/appenders'; diff --git a/src/core/server/logging/integration_tests/logging.test.ts b/src/core/server/logging/integration_tests/logging.test.ts index b4eb98546de21..ade10fc1c0257 100644 --- a/src/core/server/logging/integration_tests/logging.test.ts +++ b/src/core/server/logging/integration_tests/logging.test.ts @@ -49,6 +49,7 @@ describe('logging service', () => { mockConsoleLog = jest.spyOn(global.console, 'log'); root = createRoot(); + await root.preboot(); await root.setup(); }, 30000); @@ -151,6 +152,7 @@ describe('logging service', () => { mockConsoleLog = jest.spyOn(global.console, 'log'); root = kbnTestServer.createRoot(); + await root.preboot(); setup = await root.setup(); setup.logging.configure(['plugins', 'myplugin'], loggingConfig$); }, 30000); diff --git a/src/core/server/logging/integration_tests/rolling_file_appender.test.ts b/src/core/server/logging/integration_tests/rolling_file_appender.test.ts index b40ce7a4e7b0e..b560748026ace 100644 --- a/src/core/server/logging/integration_tests/rolling_file_appender.test.ts +++ b/src/core/server/logging/integration_tests/rolling_file_appender.test.ts @@ -79,6 +79,7 @@ describe('RollingFileAppender', () => { pattern: '.%i', }, }); + await root.preboot(); await root.setup(); const logger = root.logger.get('test.rolling.file'); @@ -124,6 +125,7 @@ describe('RollingFileAppender', () => { pattern: '-%i', }, }); + await root.preboot(); await root.setup(); const logger = root.logger.get('test.rolling.file'); @@ -174,6 +176,7 @@ describe('RollingFileAppender', () => { pattern: '-%i', }, }); + await root.preboot(); await root.setup(); const logger = root.logger.get('test.rolling.file'); diff --git a/src/core/server/logging/logging_service.mock.ts b/src/core/server/logging/logging_service.mock.ts index 5f91c7b8734b8..75d358c8cbe6e 100644 --- a/src/core/server/logging/logging_service.mock.ts +++ b/src/core/server/logging/logging_service.mock.ts @@ -12,8 +12,13 @@ import { LoggingService, LoggingServiceSetup, InternalLoggingServiceSetup, + InternalLoggingServicePreboot, } from './logging_service'; +const createInternalPrebootMock = (): jest.Mocked => ({ + configure: jest.fn(), +}); + const createInternalSetupMock = (): jest.Mocked => ({ configure: jest.fn(), }); @@ -25,11 +30,13 @@ const createSetupMock = (): jest.Mocked => ({ type LoggingServiceContract = PublicMethodsOf; const createMock = (): jest.Mocked => { const service: jest.Mocked = { + preboot: jest.fn(), setup: jest.fn(), start: jest.fn(), stop: jest.fn(), }; + service.preboot.mockReturnValue(createInternalPrebootMock()); service.setup.mockReturnValue(createInternalSetupMock()); return service; @@ -38,5 +45,6 @@ const createMock = (): jest.Mocked => { export const loggingServiceMock = { create: createMock, createSetupContract: createSetupMock, + createInternalPrebootContract: createInternalPrebootMock, createInternalSetupContract: createInternalSetupMock, }; diff --git a/src/core/server/logging/logging_service.test.ts b/src/core/server/logging/logging_service.test.ts index 341a04736b87a..9817f08c59bf8 100644 --- a/src/core/server/logging/logging_service.test.ts +++ b/src/core/server/logging/logging_service.test.ts @@ -8,83 +8,102 @@ import { of, Subject } from 'rxjs'; -import { LoggingService, InternalLoggingServiceSetup } from './logging_service'; +import { + LoggingService, + InternalLoggingServiceSetup, + InternalLoggingServicePreboot, +} from './logging_service'; import { loggingSystemMock } from './logging_system.mock'; import { LoggerContextConfigType } from './logging_config'; describe('LoggingService', () => { let loggingSystem: ReturnType; let service: LoggingService; - let setup: InternalLoggingServiceSetup; + let preboot: InternalLoggingServicePreboot; beforeEach(() => { loggingSystem = loggingSystemMock.create(); service = new LoggingService({ logger: loggingSystem.asLoggerFactory() } as any); - setup = service.setup({ loggingSystem }); + preboot = service.preboot({ loggingSystem }); }); afterEach(() => { service.stop(); }); - describe('setup', () => { - it('forwards configuration changes to logging system', () => { - const config1: LoggerContextConfigType = { - appenders: new Map(), - loggers: [{ name: 'subcontext', appenders: ['console'], level: 'warn' }], - }; - const config2: LoggerContextConfigType = { - appenders: new Map(), - loggers: [{ name: 'subcontext', appenders: ['default'], level: 'all' }], - }; + function runTestSuite( + testSuiteName: string, + getContract: () => InternalLoggingServicePreboot | InternalLoggingServiceSetup + ) { + describe(testSuiteName, () => { + it('forwards configuration changes to logging system', async () => { + const config1: LoggerContextConfigType = { + appenders: new Map(), + loggers: [{ name: 'subcontext', appenders: ['console'], level: 'warn' }], + }; + const config2: LoggerContextConfigType = { + appenders: new Map(), + loggers: [{ name: 'subcontext', appenders: ['default'], level: 'all' }], + }; - setup.configure(['test', 'context'], of(config1, config2)); - expect(loggingSystem.setContextConfig).toHaveBeenNthCalledWith( - 1, - ['test', 'context'], - config1 - ); - expect(loggingSystem.setContextConfig).toHaveBeenNthCalledWith( - 2, - ['test', 'context'], - config2 - ); - }); + getContract().configure(['test', 'context'], of(config1, config2)); + expect(loggingSystem.setContextConfig).toHaveBeenNthCalledWith( + 1, + ['test', 'context'], + config1 + ); + expect(loggingSystem.setContextConfig).toHaveBeenNthCalledWith( + 2, + ['test', 'context'], + config2 + ); + }); - it('stops forwarding first observable when called a second time', () => { - const updates$ = new Subject(); - const config1: LoggerContextConfigType = { - appenders: new Map(), - loggers: [{ name: 'subcontext', appenders: ['console'], level: 'warn' }], - }; - const config2: LoggerContextConfigType = { - appenders: new Map(), - loggers: [{ name: 'subcontext', appenders: ['default'], level: 'all' }], - }; + it('stops forwarding first observable when called a second time', () => { + const updates$ = new Subject(); + const config1: LoggerContextConfigType = { + appenders: new Map(), + loggers: [{ name: 'subcontext', appenders: ['console'], level: 'warn' }], + }; + const config2: LoggerContextConfigType = { + appenders: new Map(), + loggers: [{ name: 'subcontext', appenders: ['default'], level: 'all' }], + }; - setup.configure(['test', 'context'], updates$); - setup.configure(['test', 'context'], of(config1)); - updates$.next(config2); - expect(loggingSystem.setContextConfig).toHaveBeenNthCalledWith( - 1, - ['test', 'context'], - config1 - ); - expect(loggingSystem.setContextConfig).not.toHaveBeenCalledWith(['test', 'context'], config2); + const contract = getContract(); + contract.configure(['test', 'context'], updates$); + contract.configure(['test', 'context'], of(config1)); + updates$.next(config2); + expect(loggingSystem.setContextConfig).toHaveBeenNthCalledWith( + 1, + ['test', 'context'], + config1 + ); + expect(loggingSystem.setContextConfig).not.toHaveBeenCalledWith( + ['test', 'context'], + config2 + ); + }); }); - }); - describe('stop', () => { - it('stops forwarding updates to logging system', () => { - const updates$ = new Subject(); - const config1: LoggerContextConfigType = { - appenders: new Map(), - loggers: [{ name: 'subcontext', appenders: ['console'], level: 'warn' }], - }; + describe(`stop after ${testSuiteName}`, () => { + it('stops forwarding updates to logging system', () => { + const updates$ = new Subject(); + const config1: LoggerContextConfigType = { + appenders: new Map(), + loggers: [{ name: 'subcontext', appenders: ['console'], level: 'warn' }], + }; - setup.configure(['test', 'context'], updates$); - service.stop(); - updates$.next(config1); - expect(loggingSystem.setContextConfig).not.toHaveBeenCalledWith(['test', 'context'], config1); + getContract().configure(['test', 'context'], updates$); + service.stop(); + updates$.next(config1); + expect(loggingSystem.setContextConfig).not.toHaveBeenCalledWith( + ['test', 'context'], + config1 + ); + }); }); - }); + } + + runTestSuite('preboot', () => preboot); + runTestSuite('setup', () => service.setup()); }); diff --git a/src/core/server/logging/logging_service.ts b/src/core/server/logging/logging_service.ts index f5a4717fdbfaf..6c3eee4981725 100644 --- a/src/core/server/logging/logging_service.ts +++ b/src/core/server/logging/logging_service.ts @@ -42,11 +42,14 @@ export interface LoggingServiceSetup { } /** @internal */ -export interface InternalLoggingServiceSetup { +export interface InternalLoggingServicePreboot { configure(contextParts: string[], config$: Observable): void; } -interface SetupDeps { +/** @internal */ +export type InternalLoggingServiceSetup = InternalLoggingServicePreboot; + +interface PrebootDeps { loggingSystem: ILoggingSystem; } @@ -54,13 +57,14 @@ interface SetupDeps { export class LoggingService implements CoreService { private readonly subscriptions = new Map(); private readonly log: Logger; + private internalPreboot?: InternalLoggingServicePreboot; constructor(coreContext: CoreContext) { this.log = coreContext.logger.get('logging'); } - public setup({ loggingSystem }: SetupDeps) { - return { + public preboot({ loggingSystem }: PrebootDeps) { + this.internalPreboot = { configure: (contextParts: string[], config$: Observable) => { const contextName = LoggingConfig.getLoggerContext(contextParts); this.log.debug(`Setting custom config for context [${contextName}]`); @@ -80,6 +84,14 @@ export class LoggingService implements CoreService ); }, }; + + return this.internalPreboot; + } + + public setup() { + return { + configure: this.internalPreboot!.configure, + }; } public start() {} diff --git a/src/core/server/metrics/integration_tests/server_collector.test.ts b/src/core/server/metrics/integration_tests/server_collector.test.ts index 19e3e6a6c68f9..93589648ca0ae 100644 --- a/src/core/server/metrics/integration_tests/server_collector.test.ts +++ b/src/core/server/metrics/integration_tests/server_collector.test.ts @@ -29,6 +29,7 @@ describe('ServerMetricsCollector', () => { beforeEach(async () => { server = createHttpServer(); + await server.preboot({ context: contextServiceMock.createPrebootContract() }); const contextSetup = contextServiceMock.createSetupContract(); const httpSetup = await server.setup({ context: contextSetup, diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index ff844f44aede0..f423e40fbcaf9 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -10,7 +10,13 @@ import { of } from 'rxjs'; import { duration } from 'moment'; import { ByteSizeValue } from '@kbn/config-schema'; import type { MockedKeys } from '@kbn/utility-types/jest'; -import { PluginInitializerContext, CoreSetup, CoreStart, StartServicesAccessor } from '.'; +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + StartServicesAccessor, + CorePreboot, +} from '.'; import { loggingSystemMock } from './logging/logging_system.mock'; import { loggingServiceMock } from './logging/logging_service.mock'; import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; @@ -31,6 +37,7 @@ import { coreUsageDataServiceMock } from './core_usage_data/core_usage_data_serv import { i18nServiceMock } from './i18n/i18n_service.mock'; import { deprecationsServiceMock } from './deprecations/deprecations_service.mock'; import { executionContextServiceMock } from './execution_context/execution_context_service.mock'; +import { prebootServiceMock } from './preboot/preboot_service.mock'; export { configServiceMock } from './config/mocks'; export { httpServerMock } from './http/http_server.mocks'; @@ -106,6 +113,7 @@ function pluginInitializerContextMock(config: T = {} as T) { dist: false, }, instanceUuid: 'instance-uuid', + configs: ['/some/path/to/config/kibana.yml'], }, config: pluginInitializerContextConfigMock(config), }; @@ -113,6 +121,20 @@ function pluginInitializerContextMock(config: T = {} as T) { return mock; } +type CorePrebootMockType = MockedKeys & { + elasticsearch: ReturnType; +}; + +function createCorePrebootMock() { + const mock: CorePrebootMockType = { + elasticsearch: elasticsearchServiceMock.createPreboot(), + http: httpServiceMock.createPrebootContract(), + preboot: prebootServiceMock.createPrebootContract(), + }; + + return mock; +} + type CoreSetupMockType = MockedKeys & { elasticsearch: ReturnType; getStartServices: jest.MockedFunction>; @@ -170,6 +192,19 @@ function createCoreStartMock() { return mock; } +function createInternalCorePrebootMock() { + const prebootDeps = { + context: contextServiceMock.createPrebootContract(), + elasticsearch: elasticsearchServiceMock.createInternalPreboot(), + http: httpServiceMock.createInternalPrebootContract(), + httpResources: httpResourcesMock.createPrebootContract(), + uiSettings: uiSettingsServiceMock.createPrebootContract(), + logging: loggingServiceMock.createInternalPrebootContract(), + preboot: prebootServiceMock.createInternalPrebootContract(), + }; + return prebootDeps; +} + function createInternalCoreSetupMock() { const setupDeps = { capabilities: capabilitiesServiceMock.createSetupContract(), @@ -227,8 +262,10 @@ function createCoreRequestHandlerContextMock() { } export const coreMock = { + createPreboot: createCorePrebootMock, createSetup: createCoreSetupMock, createStart: createCoreStartMock, + createInternalPreboot: createInternalCorePrebootMock, createInternalSetup: createInternalCoreSetupMock, createInternalStart: createInternalCoreStartMock, createPluginInitializerContext: pluginInitializerContextMock, diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts index f3a92c896b014..3e410e4b19c0e 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts @@ -226,6 +226,29 @@ test('return error when manifest contains unrecognized properties', async () => }); }); +test('returns error when manifest contains unrecognized `type`', async () => { + mockReadFile.mockImplementation((path, cb) => { + cb( + null, + Buffer.from( + JSON.stringify({ + id: 'someId', + version: '7.0.0', + kibanaVersion: '7.0.0', + type: 'unknown', + server: true, + }) + ) + ); + }); + + await expect(parseManifest(pluginPath, packageInfo)).rejects.toMatchObject({ + message: `The "type" in manifest for plugin "someId" is set to "unknown", but it should either be "standard" or "preboot". (invalid-manifest, ${pluginManifestPath})`, + type: PluginDiscoveryErrorType.InvalidManifest, + path: pluginManifestPath, + }); +}); + describe('configPath', () => { test('falls back to plugin id if not specified', async () => { mockReadFile.mockImplementation((path, cb) => { @@ -284,6 +307,7 @@ test('set defaults for all missing optional fields', async () => { configPath: 'some_id', version: '7.0.0', kibanaVersion: '7.0.0', + type: 'standard', optionalPlugins: [], requiredPlugins: [], requiredBundles: [], @@ -302,6 +326,7 @@ test('return all set optional fields as they are in manifest', async () => { configPath: ['some', 'path'], version: 'some-version', kibanaVersion: '7.0.0', + type: 'preboot', requiredPlugins: ['some-required-plugin', 'some-required-plugin-2'], optionalPlugins: ['some-optional-plugin'], ui: true, @@ -315,6 +340,7 @@ test('return all set optional fields as they are in manifest', async () => { configPath: ['some', 'path'], version: 'some-version', kibanaVersion: '7.0.0', + type: 'preboot', optionalPlugins: ['some-optional-plugin'], requiredBundles: [], requiredPlugins: ['some-required-plugin', 'some-required-plugin-2'], @@ -345,6 +371,7 @@ test('return manifest when plugin expected Kibana version matches actual version configPath: 'some-path', version: 'some-version', kibanaVersion: '7.0.0-alpha2', + type: 'standard', optionalPlugins: [], requiredPlugins: ['some-required-plugin'], requiredBundles: [], @@ -375,6 +402,7 @@ test('return manifest when plugin expected Kibana version is `kibana`', async () configPath: 'some_id', version: 'some-version', kibanaVersion: 'kibana', + type: 'standard', optionalPlugins: [], requiredPlugins: ['some-required-plugin'], requiredBundles: [], diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.ts index b59418a67219e..57640ec6acc0a 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.ts @@ -12,7 +12,7 @@ import { coerce } from 'semver'; import { promisify } from 'util'; import { snakeCase } from 'lodash'; import { isConfigPath, PackageInfo } from '../../config'; -import { PluginManifest } from '../types'; +import { PluginManifest, PluginType } from '../types'; import { PluginDiscoveryError } from './plugin_discovery_error'; import { isCamelCase } from './is_camel_case'; @@ -39,6 +39,7 @@ const KNOWN_MANIFEST_FIELDS = (() => { const manifestFields: { [P in keyof PluginManifest]: boolean } = { id: true, kibanaVersion: true, + type: true, version: true, configPath: true, requiredPlugins: true, @@ -178,10 +179,21 @@ export async function parseManifest( ); } + const type = manifest.type ?? PluginType.standard; + if (type !== PluginType.preboot && type !== PluginType.standard) { + throw PluginDiscoveryError.invalidManifest( + manifestPath, + new Error( + `The "type" in manifest for plugin "${manifest.id}" is set to "${type}", but it should either be "standard" or "preboot".` + ) + ); + } + return { id: manifest.id, version: manifest.version, kibanaVersion: expectedKibanaVersion, + type, configPath: manifest.configPath || snakeCase(manifest.id), requiredPlugins: Array.isArray(manifest.requiredPlugins) ? manifest.requiredPlugins : [], optionalPlugins: Array.isArray(manifest.optionalPlugins) ? manifest.optionalPlugins : [], diff --git a/src/core/server/plugins/discovery/plugins_discovery.test.ts b/src/core/server/plugins/discovery/plugins_discovery.test.ts index f6028fa8b099d..28f2ab799e092 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.test.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.test.ts @@ -21,6 +21,7 @@ import { PluginsConfig, PluginsConfigType, config } from '../plugins_config'; import type { InstanceInfo } from '../plugin_context'; import { discover } from './plugins_discovery'; import { CoreContext } from '../../core_context'; +import { PluginType } from '../types'; const KIBANA_ROOT = process.cwd(); @@ -34,6 +35,15 @@ const Plugins = { incompatible: () => ({ 'kibana.json': JSON.stringify({ id: 'plugin', version: '1' }), }), + incompatibleType: (id: string) => ({ + 'kibana.json': JSON.stringify({ + id, + version: '1', + kibanaVersion: '1.2.3', + type: 'evenEarlierThanPreboot', + server: true, + }), + }), missingManifest: () => ({}), inaccessibleManifest: () => ({ 'kibana.json': mockFs.file({ @@ -52,6 +62,18 @@ const Plugins = { server: true, }), }), + validPreboot: (id: string) => ({ + 'kibana.json': JSON.stringify({ + id, + configPath: ['plugins', id], + version: '1', + kibanaVersion: '1.2.3', + type: PluginType.preboot, + requiredPlugins: [], + optionalPlugins: [], + server: true, + }), + }), }; const packageMock = { @@ -132,6 +154,7 @@ describe('plugins discovery system', () => { [`${KIBANA_ROOT}/src/plugins/plugin_a`]: Plugins.valid('pluginA'), [`${KIBANA_ROOT}/plugins/plugin_b`]: Plugins.valid('pluginB'), [`${KIBANA_ROOT}/x-pack/plugins/plugin_c`]: Plugins.valid('pluginC'), + [`${KIBANA_ROOT}/src/plugins/plugin_d`]: Plugins.validPreboot('pluginD'), }, { createCwd: false } ); @@ -139,8 +162,10 @@ describe('plugins discovery system', () => { const plugins = await plugin$.pipe(toArray()).toPromise(); const pluginNames = plugins.map((plugin) => plugin.name); - expect(pluginNames).toHaveLength(3); - expect(pluginNames).toEqual(expect.arrayContaining(['pluginA', 'pluginB', 'pluginC'])); + expect(pluginNames).toHaveLength(4); + expect(pluginNames).toEqual( + expect.arrayContaining(['pluginA', 'pluginB', 'pluginC', 'pluginD']) + ); }); it('return errors when the manifest is invalid or incompatible', async () => { @@ -155,6 +180,7 @@ describe('plugins discovery system', () => { [`${KIBANA_ROOT}/src/plugins/plugin_a`]: Plugins.invalid(), [`${KIBANA_ROOT}/src/plugins/plugin_b`]: Plugins.incomplete(), [`${KIBANA_ROOT}/src/plugins/plugin_c`]: Plugins.incompatible(), + [`${KIBANA_ROOT}/src/plugins/plugin_d`]: Plugins.incompatibleType('pluginD'), [`${KIBANA_ROOT}/src/plugins/plugin_ad`]: Plugins.missingManifest(), }, { createCwd: false } @@ -181,6 +207,9 @@ describe('plugins discovery system', () => { `Error: Plugin "plugin" is only compatible with Kibana version "1", but used Kibana version is "1.2.3". (incompatible-version, ${manifestPath( 'plugin_c' )})`, + `Error: The "type" in manifest for plugin "pluginD" is set to "evenEarlierThanPreboot", but it should either be "standard" or "preboot". (invalid-manifest, ${manifestPath( + 'plugin_d' + )})`, ]) ); }); @@ -271,7 +300,8 @@ describe('plugins discovery system', () => { [`${KIBANA_ROOT}/src/plugins/plugin_a`]: Plugins.valid('pluginA'), [`${KIBANA_ROOT}/src/plugins/sub1/plugin_b`]: Plugins.valid('pluginB'), [`${KIBANA_ROOT}/src/plugins/sub1/sub2/plugin_c`]: Plugins.valid('pluginC'), - [`${KIBANA_ROOT}/src/plugins/sub1/sub2/plugin_d`]: Plugins.incomplete(), + [`${KIBANA_ROOT}/src/plugins/sub1/sub2/plugin_d`]: Plugins.validPreboot('pluginD'), + [`${KIBANA_ROOT}/src/plugins/sub1/sub2/plugin_e`]: Plugins.incomplete(), }, { createCwd: false } ); @@ -279,8 +309,10 @@ describe('plugins discovery system', () => { const plugins = await plugin$.pipe(toArray()).toPromise(); const pluginNames = plugins.map((plugin) => plugin.name); - expect(pluginNames).toHaveLength(3); - expect(pluginNames).toEqual(expect.arrayContaining(['pluginA', 'pluginB', 'pluginC'])); + expect(pluginNames).toHaveLength(4); + expect(pluginNames).toEqual( + expect.arrayContaining(['pluginA', 'pluginB', 'pluginC', 'pluginD']) + ); const errors = await error$ .pipe( @@ -294,7 +326,7 @@ describe('plugins discovery system', () => { `Error: Plugin manifest must contain an "id" property. (invalid-manifest, ${manifestPath( 'sub1', 'sub2', - 'plugin_d' + 'plugin_e' )})`, ]) ); @@ -358,6 +390,7 @@ describe('plugins discovery system', () => { [pluginFolder]: { plugin_a: Plugins.valid('pluginA'), plugin_b: Plugins.valid('pluginB'), + plugin_c: Plugins.validPreboot('pluginC'), }, }, { createCwd: false } @@ -366,8 +399,8 @@ describe('plugins discovery system', () => { const plugins = await plugin$.pipe(toArray()).toPromise(); const pluginNames = plugins.map((plugin) => plugin.name); - expect(pluginNames).toHaveLength(2); - expect(pluginNames).toEqual(expect.arrayContaining(['pluginA', 'pluginB'])); + expect(pluginNames).toHaveLength(3); + expect(pluginNames).toEqual(expect.arrayContaining(['pluginA', 'pluginB', 'pluginC'])); }); it('logs a warning about --plugin-path when used in development', async () => { diff --git a/src/core/server/plugins/index.ts b/src/core/server/plugins/index.ts index a71df00b39c5c..1b655ccd8bd98 100644 --- a/src/core/server/plugins/index.ts +++ b/src/core/server/plugins/index.ts @@ -7,7 +7,12 @@ */ export { PluginsService } from './plugins_service'; -export type { PluginsServiceSetup, PluginsServiceStart, UiPlugins } from './plugins_service'; +export type { + PluginsServiceSetup, + PluginsServiceStart, + UiPlugins, + DiscoveredPlugins, +} from './plugins_service'; export { config } from './plugins_config'; /** @internal */ export { isNewPlatformPlugin } from './discovery'; diff --git a/src/core/server/plugins/integration_tests/plugins_service.test.ts b/src/core/server/plugins/integration_tests/plugins_service.test.ts index a29fb01fbc009..1b0caf7bf6255 100644 --- a/src/core/server/plugins/integration_tests/plugins_service.test.ts +++ b/src/core/server/plugins/integration_tests/plugins_service.test.ts @@ -20,12 +20,12 @@ import { config } from '../plugins_config'; import { loggingSystemMock } from '../../logging/logging_system.mock'; import { environmentServiceMock } from '../../environment/environment_service.mock'; import { coreMock } from '../../mocks'; -import { AsyncPlugin } from '../types'; +import { AsyncPlugin, PluginType } from '../types'; import { PluginWrapper } from '../plugin'; describe('PluginsService', () => { const logger = loggingSystemMock.create(); - const environmentSetup = environmentServiceMock.createSetupContract(); + const environmentPreboot = environmentServiceMock.createPrebootContract(); let pluginsService: PluginsService; const createPlugin = ( @@ -38,6 +38,7 @@ describe('PluginsService', () => { requiredBundles = [], optionalPlugins = [], kibanaVersion = '7.0.0', + type = PluginType.standard, configPath = [path], server = true, ui = true, @@ -49,6 +50,7 @@ describe('PluginsService', () => { requiredBundles?: string[]; optionalPlugins?: string[]; kibanaVersion?: string; + type?: PluginType; configPath?: ConfigPath; server?: boolean; ui?: boolean; @@ -61,6 +63,7 @@ describe('PluginsService', () => { version, configPath: `${configPath}${disabled ? '-disabled' : ''}`, kibanaVersion, + type, requiredPlugins, requiredBundles, optionalPlugins, @@ -150,7 +153,10 @@ describe('PluginsService', () => { } ); - await pluginsService.discover({ environment: environmentSetup }); + await pluginsService.discover({ environment: environmentPreboot }); + + const prebootDeps = coreMock.createInternalPreboot(); + await pluginsService.preboot(prebootDeps); const setupDeps = coreMock.createInternalSetup(); await pluginsService.setup(setupDeps); diff --git a/src/core/server/plugins/plugin.test.ts b/src/core/server/plugins/plugin.test.ts index c90d2e804225c..610bc1cac5a3b 100644 --- a/src/core/server/plugins/plugin.test.ts +++ b/src/core/server/plugins/plugin.test.ts @@ -15,10 +15,10 @@ import { Env } from '../config'; import { CoreContext } from '../core_context'; import { coreMock } from '../mocks'; import { loggingSystemMock } from '../logging/logging_system.mock'; -import { getEnvOptions, configServiceMock } from '../config/mocks'; +import { configServiceMock, getEnvOptions } from '../config/mocks'; import { PluginWrapper } from './plugin'; -import { PluginManifest } from './types'; +import { PluginManifest, PluginType } from './types'; import { createPluginInitializerContext, createPluginSetupContext, @@ -45,6 +45,7 @@ function createPluginManifest(manifestProps: Partial = {}): Plug version: 'some-version', configPath: 'path', kibanaVersion: '7.0.0', + type: PluginType.standard, requiredPlugins: ['some-required-dep'], optionalPlugins: ['some-optional-dep'], requiredBundles: [], @@ -243,6 +244,31 @@ test('`start` fails if setup is not called first', () => { ); }); +test('`start` fails invoked for the `preboot` plugin', async () => { + const manifest = createPluginManifest({ type: PluginType.preboot }); + const opaqueId = Symbol(); + const plugin = new PluginWrapper({ + path: 'plugin-with-initializer-path', + manifest, + opaqueId, + initializerContext: createPluginInitializerContext( + coreContext, + opaqueId, + manifest, + instanceInfo + ), + }); + + const mockPluginInstance = { setup: jest.fn() }; + mockPluginInitializer.mockReturnValue(mockPluginInstance); + + await plugin.setup({} as any, {} as any); + + expect(() => plugin.start({} as any, {} as any)).toThrowErrorMatchingInlineSnapshot( + `"Plugin \\"some-plugin-id\\" is a preboot plugin and cannot be started."` + ); +}); + test('`start` calls plugin.start with context and dependencies', async () => { const manifest = createPluginManifest(); const opaqueId = Symbol(); diff --git a/src/core/server/plugins/plugin.ts b/src/core/server/plugins/plugin.ts index ca7f11e28de75..76ed4f8f24b3d 100644 --- a/src/core/server/plugins/plugin.ts +++ b/src/core/server/plugins/plugin.ts @@ -15,15 +15,17 @@ import { isConfigSchema } from '@kbn/config-schema'; import { Logger } from '../logging'; import { - Plugin, AsyncPlugin, + Plugin, + PluginConfigDescriptor, + PluginInitializer, PluginInitializerContext, PluginManifest, - PluginInitializer, PluginOpaqueId, - PluginConfigDescriptor, + PluginType, + PrebootPlugin, } from './types'; -import { CoreSetup, CoreStart } from '..'; +import { CorePreboot, CoreSetup, CoreStart } from '..'; /** * Lightweight wrapper around discovered plugin that is responsible for instantiating @@ -53,6 +55,7 @@ export class PluginWrapper< private instance?: | Plugin + | PrebootPlugin | AsyncPlugin; private readonly startDependencies$ = new Subject<[CoreStart, TPluginsStart, TStart]>(); @@ -88,11 +91,16 @@ export class PluginWrapper< * is the contract returned by the dependency's `setup` function. */ public setup( - setupContext: CoreSetup, + setupContext: CoreSetup | CorePreboot, plugins: TPluginsSetup ): TSetup | Promise { this.instance = this.createPluginInstance(); - return this.instance.setup(setupContext, plugins); + + if (this.isPrebootPluginInstance(this.instance)) { + return this.instance.setup(setupContext as CorePreboot, plugins); + } + + return this.instance.setup(setupContext as CoreSetup, plugins); } /** @@ -107,6 +115,10 @@ export class PluginWrapper< throw new Error(`Plugin "${this.name}" can't be started since it isn't set up.`); } + if (this.isPrebootPluginInstance(this.instance)) { + throw new Error(`Plugin "${this.name}" is a preboot plugin and cannot be started.`); + } + const startContract = this.instance.start(startContext, plugins); if (isPromise(startContract)) { return startContract.then((resolvedContract) => { @@ -185,4 +197,10 @@ export class PluginWrapper< return instance; } + + private isPrebootPluginInstance( + instance: PluginWrapper['instance'] + ): instance is PrebootPlugin { + return this.manifest.type === PluginType.preboot; + } } diff --git a/src/core/server/plugins/plugin_context.test.ts b/src/core/server/plugins/plugin_context.test.ts index 4ba34ccb2149f..7913bad3cad17 100644 --- a/src/core/server/plugins/plugin_context.test.ts +++ b/src/core/server/plugins/plugin_context.test.ts @@ -10,15 +10,21 @@ import { duration } from 'moment'; import { first } from 'rxjs/operators'; import { REPO_ROOT } from '@kbn/dev-utils'; import { fromRoot } from '@kbn/utils'; -import { createPluginInitializerContext, InstanceInfo } from './plugin_context'; +import { + createPluginInitializerContext, + createPluginPrebootSetupContext, + InstanceInfo, +} from './plugin_context'; import { CoreContext } from '../core_context'; import { Env } from '../config'; import { loggingSystemMock } from '../logging/logging_system.mock'; -import { rawConfigServiceMock, getEnvOptions } from '../config/mocks'; -import { PluginManifest } from './types'; +import { rawConfigServiceMock, getEnvOptions, configServiceMock } from '../config/mocks'; +import { PluginManifest, PluginType } from './types'; import { Server } from '../server'; import { schema, ByteSizeValue } from '@kbn/config-schema'; import { ConfigService } from '@kbn/config'; +import { PluginWrapper } from './plugin'; +import { coreMock } from '../mocks'; function createPluginManifest(manifestProps: Partial = {}): PluginManifest { return { @@ -26,6 +32,7 @@ function createPluginManifest(manifestProps: Partial = {}): Plug version: 'some-version', configPath: 'path', kibanaVersion: '7.0.0', + type: PluginType.standard, requiredPlugins: ['some-required-dep'], requiredBundles: [], optionalPlugins: ['some-optional-dep'], @@ -141,5 +148,67 @@ describe('createPluginInitializerContext', () => { ); expect(pluginInitializerContext.env.instanceUuid).toBe('kibana-uuid'); }); + + it('should expose paths to the config files', () => { + coreContext = { + ...coreContext, + env: Env.createDefault( + REPO_ROOT, + getEnvOptions({ + configs: ['/home/kibana/config/kibana.yml', '/home/kibana/config/kibana.dev.yml'], + }) + ), + }; + const pluginInitializerContext = createPluginInitializerContext( + coreContext, + opaqueId, + createPluginManifest(), + instanceInfo + ); + expect(pluginInitializerContext.env.configs).toEqual([ + '/home/kibana/config/kibana.yml', + '/home/kibana/config/kibana.dev.yml', + ]); + }); + }); +}); + +describe('createPluginPrebootSetupContext', () => { + let coreContext: CoreContext; + let opaqueId: symbol; + + beforeEach(async () => { + opaqueId = Symbol(); + coreContext = { + coreId: Symbol('core'), + env: Env.createDefault(REPO_ROOT, getEnvOptions()), + logger: loggingSystemMock.create(), + configService: configServiceMock.create(), + }; + }); + + it('`holdSetupUntilResolved` captures plugin.name', () => { + const manifest = createPluginManifest(); + const plugin = new PluginWrapper({ + path: 'some-path', + manifest, + opaqueId, + initializerContext: createPluginInitializerContext(coreContext, opaqueId, manifest, { + uuid: 'instance-uuid', + }), + }); + + const corePreboot = coreMock.createInternalPreboot(); + const prebootSetupContext = createPluginPrebootSetupContext(coreContext, corePreboot, plugin); + + const holdSetupPromise = Promise.resolve(undefined); + prebootSetupContext.preboot.holdSetupUntilResolved('some-reason', holdSetupPromise); + + expect(corePreboot.preboot.holdSetupUntilResolved).toHaveBeenCalledTimes(1); + expect(corePreboot.preboot.holdSetupUntilResolved).toHaveBeenCalledWith( + 'some-plugin-id', + 'some-reason', + holdSetupPromise + ); }); }); diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 70fd1c60efa61..29194b1e8fc62 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -10,11 +10,15 @@ import { shareReplay } from 'rxjs/operators'; import type { RequestHandlerContext } from 'src/core/server'; import { CoreContext } from '../core_context'; import { PluginWrapper } from './plugin'; -import { PluginsServiceSetupDeps, PluginsServiceStartDeps } from './plugins_service'; +import { + PluginsServicePrebootSetupDeps, + PluginsServiceSetupDeps, + PluginsServiceStartDeps, +} from './plugins_service'; import { PluginInitializerContext, PluginManifest, PluginOpaqueId } from './types'; import { IRouter, RequestHandlerContextProvider } from '../http'; import { getGlobalConfig, getGlobalConfig$ } from './legacy_config'; -import { CoreSetup, CoreStart } from '..'; +import { CorePreboot, CoreSetup, CoreStart } from '..'; export interface InstanceInfo { uuid: string; @@ -49,6 +53,7 @@ export function createPluginInitializerContext( mode: coreContext.env.mode, packageInfo: coreContext.env.packageInfo, instanceUuid: instanceInfo.uuid, + configs: coreContext.env.configs, }, /** @@ -83,6 +88,42 @@ export function createPluginInitializerContext( }; } +/** + * Provides `CorePreboot` contract that will be exposed to the `preboot` plugin `setup` method. + * This contract should be safe to use only within `setup` itself. + * + * This is called for each `preboot` plugin when it's set up, so each plugin gets its own + * version of these values. + * + * We should aim to be restrictive and specific in the APIs that we expose. + * + * @param coreContext Kibana core context + * @param deps Dependencies that Plugins services gets during setup. + * @param plugin The plugin we're building these values for. + * @internal + */ +export function createPluginPrebootSetupContext( + coreContext: CoreContext, + deps: PluginsServicePrebootSetupDeps, + plugin: PluginWrapper +): CorePreboot { + return { + elasticsearch: { + config: deps.elasticsearch.config, + createClient: deps.elasticsearch.createClient, + }, + http: { + registerRoutes: deps.http.registerRoutes, + basePath: deps.http.basePath, + }, + preboot: { + isSetupOnHold: deps.preboot.isSetupOnHold, + holdSetupUntilResolved: (reason, promise) => + deps.preboot.holdSetupUntilResolved(plugin.name, reason, promise), + }, + }; +} + /** * This returns a facade for `CoreContext` that will be exposed to the plugin `setup` method. * This facade should be safe to use only within `setup` itself. diff --git a/src/core/server/plugins/plugins_service.mock.ts b/src/core/server/plugins/plugins_service.mock.ts index f4f2263a1bdb0..ee7b35a412e80 100644 --- a/src/core/server/plugins/plugins_service.mock.ts +++ b/src/core/server/plugins/plugins_service.mock.ts @@ -20,6 +20,7 @@ const createStartContractMock = () => ({ contracts: new Map() }); const createServiceMock = (): PluginsServiceMock => ({ discover: jest.fn(), getExposedPluginConfigsToUsage: jest.fn(), + preboot: jest.fn(), setup: jest.fn().mockResolvedValue(createSetupContractMock()), start: jest.fn().mockResolvedValue(createStartContractMock()), stop: jest.fn(), diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts index 5c50df07dc697..3cd645cb74ac0 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/src/core/server/plugins/plugins_service.test.ts @@ -24,26 +24,31 @@ import { PluginsService } from './plugins_service'; import { PluginsSystem } from './plugins_system'; import { config } from './plugins_config'; import { take } from 'rxjs/operators'; -import { DiscoveredPlugin } from './types'; +import { DiscoveredPlugin, PluginType } from './types'; -const MockPluginsSystem: jest.Mock = PluginsSystem as any; +const MockPluginsSystem: jest.Mock> = PluginsSystem as any; let pluginsService: PluginsService; let config$: BehaviorSubject>; let configService: ConfigService; let coreId: symbol; let env: Env; -let mockPluginSystem: jest.Mocked; -let environmentSetup: ReturnType; +let prebootMockPluginSystem: jest.Mocked>; +let standardMockPluginSystem: jest.Mocked>; +let environmentPreboot: ReturnType; +const prebootDeps = coreMock.createInternalPreboot(); const setupDeps = coreMock.createInternalSetup(); +const startDeps = coreMock.createInternalStart(); const logger = loggingSystemMock.create(); expect.addSnapshotSerializer(createAbsolutePathSerializer()); ['path-1', 'path-2', 'path-3', 'path-4', 'path-5', 'path-6', 'path-7', 'path-8'].forEach((path) => { - jest.doMock(join(path, 'server'), () => ({}), { - virtual: true, + [PluginType.preboot, PluginType.standard].forEach((type) => { + jest.doMock(join(`${path}-${type}`, 'server'), () => ({}), { + virtual: true, + }); }); }); @@ -53,6 +58,7 @@ const createPlugin = ( path = id, disabled = false, version = 'some-version', + type = PluginType.standard, requiredPlugins = [], requiredBundles = [], optionalPlugins = [], @@ -64,6 +70,7 @@ const createPlugin = ( path?: string; disabled?: boolean; version?: string; + type?: PluginType; requiredPlugins?: string[]; requiredBundles?: string[]; optionalPlugins?: string[]; @@ -80,6 +87,7 @@ const createPlugin = ( version, configPath: disabled ? configPath.concat('-disabled') : configPath, kibanaVersion, + type, requiredPlugins, requiredBundles, optionalPlugins, @@ -111,11 +119,13 @@ async function testSetup() { await configService.setSchema(config.path, config.schema); pluginsService = new PluginsService({ coreId, env, logger, configService }); - [mockPluginSystem] = MockPluginsSystem.mock.instances as any; - mockPluginSystem.uiPlugins.mockReturnValue(new Map()); - mockPluginSystem.getPlugins.mockReturnValue([]); + [prebootMockPluginSystem, standardMockPluginSystem] = MockPluginsSystem.mock.instances as any; + prebootMockPluginSystem.uiPlugins.mockReturnValue(new Map()); + prebootMockPluginSystem.getPlugins.mockReturnValue([]); + standardMockPluginSystem.uiPlugins.mockReturnValue(new Map()); + standardMockPluginSystem.getPlugins.mockReturnValue([]); - environmentSetup = environmentServiceMock.createSetupContract(); + environmentPreboot = environmentServiceMock.createPrebootContract(); } afterEach(() => { @@ -134,7 +144,7 @@ describe('PluginsService', () => { plugin$: from([]), }); - await expect(pluginsService.discover({ environment: environmentSetup })).rejects + await expect(pluginsService.discover({ environment: environmentPreboot })).rejects .toMatchInlineSnapshot(` [Error: Failed to initialize plugins: Invalid JSON (invalid-manifest, path-1)] @@ -156,7 +166,7 @@ describe('PluginsService', () => { plugin$: from([]), }); - await expect(pluginsService.discover({ environment: environmentSetup })).rejects + await expect(pluginsService.discover({ environment: environmentPreboot })).rejects .toMatchInlineSnapshot(` [Error: Failed to initialize plugins: Incompatible version (incompatible-version, path-3)] @@ -175,14 +185,14 @@ describe('PluginsService', () => { error$: from([]), plugin$: from([ createPlugin('conflicting-id', { - path: 'path-4', + path: 'path-4-standard', version: 'some-version', configPath: 'path', requiredPlugins: ['some-required-plugin', 'some-required-plugin-2'], optionalPlugins: ['some-optional-plugin'], }), createPlugin('conflicting-id', { - path: 'path-4', + path: 'path-4-standard', version: 'some-version', configPath: 'path', requiredPlugins: ['some-required-plugin', 'some-required-plugin-2'], @@ -192,13 +202,49 @@ describe('PluginsService', () => { }); await expect( - pluginsService.discover({ environment: environmentSetup }) + pluginsService.discover({ environment: environmentPreboot }) ).rejects.toMatchInlineSnapshot( `[Error: Plugin with id "conflicting-id" is already registered!]` ); - expect(mockPluginSystem.addPlugin).not.toHaveBeenCalled(); - expect(mockPluginSystem.setupPlugins).not.toHaveBeenCalled(); + expect(prebootMockPluginSystem.addPlugin).not.toHaveBeenCalled(); + expect(prebootMockPluginSystem.setupPlugins).not.toHaveBeenCalled(); + expect(standardMockPluginSystem.addPlugin).not.toHaveBeenCalled(); + expect(standardMockPluginSystem.setupPlugins).not.toHaveBeenCalled(); + }); + + it('throws if discovered standard and preboot plugins with conflicting names', async () => { + mockDiscover.mockReturnValue({ + error$: from([]), + plugin$: from([ + createPlugin('conflicting-id', { + type: PluginType.preboot, + path: 'path-4-preboot', + version: 'some-version', + configPath: 'path', + requiredPlugins: ['some-required-plugin', 'some-required-plugin-2'], + optionalPlugins: ['some-optional-plugin'], + }), + createPlugin('conflicting-id', { + path: 'path-4-standard', + version: 'some-version', + configPath: 'path', + requiredPlugins: ['some-required-plugin', 'some-required-plugin-2'], + optionalPlugins: ['some-optional-plugin'], + }), + ]), + }); + + await expect( + pluginsService.discover({ environment: environmentPreboot }) + ).rejects.toMatchInlineSnapshot( + `[Error: Plugin with id "conflicting-id" is already registered!]` + ); + + expect(prebootMockPluginSystem.addPlugin).not.toHaveBeenCalled(); + expect(prebootMockPluginSystem.setupPlugins).not.toHaveBeenCalled(); + expect(standardMockPluginSystem.addPlugin).not.toHaveBeenCalled(); + expect(standardMockPluginSystem.setupPlugins).not.toHaveBeenCalled(); }); it('properly detects plugins that should be disabled.', async () => { @@ -206,160 +252,356 @@ describe('PluginsService', () => { .spyOn(configService, 'isEnabledAtPath') .mockImplementation((path) => Promise.resolve(!path.includes('disabled'))); - mockPluginSystem.setupPlugins.mockResolvedValue(new Map()); + prebootMockPluginSystem.setupPlugins.mockResolvedValue(new Map()); + standardMockPluginSystem.setupPlugins.mockResolvedValue(new Map()); mockDiscover.mockReturnValue({ error$: from([]), plugin$: from([ - createPlugin('explicitly-disabled-plugin', { + createPlugin('explicitly-disabled-plugin-preboot', { + type: PluginType.preboot, + disabled: true, + path: 'path-1-preboot', + configPath: 'path-1-preboot', + }), + createPlugin('explicitly-disabled-plugin-standard', { disabled: true, - path: 'path-1', - configPath: 'path-1', + path: 'path-1-standard', + configPath: 'path-1-standard', + }), + createPlugin('plugin-with-missing-required-deps-preboot', { + type: PluginType.preboot, + path: 'path-2-preboot', + configPath: 'path-2-preboot', + requiredPlugins: ['missing-plugin-preboot'], }), - createPlugin('plugin-with-missing-required-deps', { - path: 'path-2', - configPath: 'path-2', - requiredPlugins: ['missing-plugin'], + createPlugin('plugin-with-missing-required-deps-standard', { + path: 'path-2-standard', + configPath: 'path-2-standard', + requiredPlugins: ['missing-plugin-standard'], }), - createPlugin('plugin-with-disabled-transitive-dep', { - path: 'path-3', - configPath: 'path-3', - requiredPlugins: ['another-explicitly-disabled-plugin'], + createPlugin('plugin-with-disabled-transitive-dep-preboot', { + type: PluginType.preboot, + path: 'path-3-preboot', + configPath: 'path-3-preboot', + requiredPlugins: ['another-explicitly-disabled-plugin-preboot'], }), - createPlugin('another-explicitly-disabled-plugin', { + createPlugin('plugin-with-disabled-transitive-dep-standard', { + path: 'path-3-standard', + configPath: 'path-3-standard', + requiredPlugins: ['another-explicitly-disabled-plugin-standard'], + }), + createPlugin('another-explicitly-disabled-plugin-preboot', { + type: PluginType.preboot, + disabled: true, + path: 'path-4-preboot', + configPath: 'path-4-disabled-preboot', + }), + createPlugin('another-explicitly-disabled-plugin-standard', { disabled: true, - path: 'path-4', - configPath: 'path-4-disabled', + path: 'path-4-standard', + configPath: 'path-4-disabled-standard', + }), + createPlugin('plugin-with-disabled-optional-dep-preboot', { + type: PluginType.preboot, + path: 'path-5-preboot', + configPath: 'path-5-preboot', + optionalPlugins: ['explicitly-disabled-plugin-preboot'], }), - createPlugin('plugin-with-disabled-optional-dep', { - path: 'path-5', - configPath: 'path-5', - optionalPlugins: ['explicitly-disabled-plugin'], + createPlugin('plugin-with-disabled-optional-dep-standard', { + path: 'path-5-standard', + configPath: 'path-5-standard', + optionalPlugins: ['explicitly-disabled-plugin-standard'], }), - createPlugin('plugin-with-missing-optional-dep', { - path: 'path-6', - configPath: 'path-6', - optionalPlugins: ['missing-plugin'], + createPlugin('plugin-with-missing-optional-dep-preboot', { + type: PluginType.preboot, + path: 'path-6-preboot', + configPath: 'path-6-preboot', + optionalPlugins: ['missing-plugin-preboot'], }), - createPlugin('plugin-with-disabled-nested-transitive-dep', { - path: 'path-7', - configPath: 'path-7', - requiredPlugins: ['plugin-with-disabled-transitive-dep'], + createPlugin('plugin-with-missing-optional-dep-standard', { + path: 'path-6-standard', + configPath: 'path-6-standard', + optionalPlugins: ['missing-plugin-standard'], }), - createPlugin('plugin-with-missing-nested-dep', { - path: 'path-8', - configPath: 'path-8', - requiredPlugins: ['plugin-with-missing-required-deps'], + createPlugin('plugin-with-disabled-nested-transitive-dep-preboot', { + type: PluginType.preboot, + path: 'path-7-preboot', + configPath: 'path-7-preboot', + requiredPlugins: ['plugin-with-disabled-transitive-dep-preboot'], + }), + createPlugin('plugin-with-disabled-nested-transitive-dep-standard', { + path: 'path-7-standard', + configPath: 'path-7-standard', + requiredPlugins: ['plugin-with-disabled-transitive-dep-standard'], + }), + createPlugin('plugin-with-missing-nested-dep-preboot', { + type: PluginType.preboot, + path: 'path-8-preboot', + configPath: 'path-8-preboot', + requiredPlugins: ['plugin-with-missing-required-deps-preboot'], + }), + createPlugin('plugin-with-missing-nested-dep-standard', { + path: 'path-8-standard', + configPath: 'path-8-standard', + requiredPlugins: ['plugin-with-missing-required-deps-standard'], }), ]), }); - await pluginsService.discover({ environment: environmentSetup }); + await pluginsService.discover({ environment: environmentPreboot }); + await pluginsService.preboot(prebootDeps); const setup = await pluginsService.setup(setupDeps); expect(setup.contracts).toBeInstanceOf(Map); - expect(mockPluginSystem.addPlugin).toHaveBeenCalledTimes(2); - expect(mockPluginSystem.setupPlugins).toHaveBeenCalledTimes(1); - expect(mockPluginSystem.setupPlugins).toHaveBeenCalledWith(setupDeps); + + expect(prebootMockPluginSystem.addPlugin).toHaveBeenCalledTimes(2); + expect(standardMockPluginSystem.addPlugin).toHaveBeenCalledTimes(2); + + expect(prebootMockPluginSystem.setupPlugins).toHaveBeenCalledTimes(1); + expect(standardMockPluginSystem.setupPlugins).toHaveBeenCalledTimes(1); + + expect(prebootMockPluginSystem.setupPlugins).toHaveBeenCalledWith(prebootDeps); + expect(standardMockPluginSystem.setupPlugins).toHaveBeenCalledWith(setupDeps); expect(loggingSystemMock.collect(logger).info).toMatchInlineSnapshot(` Array [ Array [ - "Plugin \\"explicitly-disabled-plugin\\" is disabled.", + "Plugin \\"explicitly-disabled-plugin-preboot\\" is disabled.", + ], + Array [ + "Plugin \\"explicitly-disabled-plugin-standard\\" is disabled.", + ], + Array [ + "Plugin \\"plugin-with-missing-required-deps-preboot\\" has been disabled since the following direct or transitive dependencies are missing, disabled, or have incompatible types: [missing-plugin-preboot]", + ], + Array [ + "Plugin \\"plugin-with-missing-required-deps-standard\\" has been disabled since the following direct or transitive dependencies are missing, disabled, or have incompatible types: [missing-plugin-standard]", + ], + Array [ + "Plugin \\"plugin-with-disabled-transitive-dep-preboot\\" has been disabled since the following direct or transitive dependencies are missing, disabled, or have incompatible types: [another-explicitly-disabled-plugin-preboot]", + ], + Array [ + "Plugin \\"plugin-with-disabled-transitive-dep-standard\\" has been disabled since the following direct or transitive dependencies are missing, disabled, or have incompatible types: [another-explicitly-disabled-plugin-standard]", ], Array [ - "Plugin \\"plugin-with-missing-required-deps\\" has been disabled since the following direct or transitive dependencies are missing or disabled: [missing-plugin]", + "Plugin \\"another-explicitly-disabled-plugin-preboot\\" is disabled.", ], Array [ - "Plugin \\"plugin-with-disabled-transitive-dep\\" has been disabled since the following direct or transitive dependencies are missing or disabled: [another-explicitly-disabled-plugin]", + "Plugin \\"another-explicitly-disabled-plugin-standard\\" is disabled.", ], Array [ - "Plugin \\"another-explicitly-disabled-plugin\\" is disabled.", + "Plugin \\"plugin-with-disabled-nested-transitive-dep-preboot\\" has been disabled since the following direct or transitive dependencies are missing, disabled, or have incompatible types: [plugin-with-disabled-transitive-dep-preboot]", ], Array [ - "Plugin \\"plugin-with-disabled-nested-transitive-dep\\" has been disabled since the following direct or transitive dependencies are missing or disabled: [plugin-with-disabled-transitive-dep]", + "Plugin \\"plugin-with-disabled-nested-transitive-dep-standard\\" has been disabled since the following direct or transitive dependencies are missing, disabled, or have incompatible types: [plugin-with-disabled-transitive-dep-standard]", ], Array [ - "Plugin \\"plugin-with-missing-nested-dep\\" has been disabled since the following direct or transitive dependencies are missing or disabled: [plugin-with-missing-required-deps]", + "Plugin \\"plugin-with-missing-nested-dep-preboot\\" has been disabled since the following direct or transitive dependencies are missing, disabled, or have incompatible types: [plugin-with-missing-required-deps-preboot]", + ], + Array [ + "Plugin \\"plugin-with-missing-nested-dep-standard\\" has been disabled since the following direct or transitive dependencies are missing, disabled, or have incompatible types: [plugin-with-missing-required-deps-standard]", ], ] `); }); it('does not throw in case of mutual plugin dependencies', async () => { - const firstPlugin = createPlugin('first-plugin', { - path: 'path-1', - requiredPlugins: ['second-plugin'], + const prebootPlugins = [ + createPlugin('first-plugin-preboot', { + type: PluginType.preboot, + path: 'path-1-preboot', + requiredPlugins: ['second-plugin-preboot'], + }), + createPlugin('second-plugin-preboot', { + type: PluginType.preboot, + path: 'path-2-preboot', + requiredPlugins: ['first-plugin-preboot'], + }), + ]; + const standardPlugins = [ + createPlugin('first-plugin-standard', { + path: 'path-1-standard', + requiredPlugins: ['second-plugin-standard'], + }), + createPlugin('second-plugin-standard', { + path: 'path-2-standard', + requiredPlugins: ['first-plugin-standard'], + }), + ]; + + mockDiscover.mockReturnValue({ + error$: from([]), + plugin$: from([...prebootPlugins, ...standardPlugins]), }); - const secondPlugin = createPlugin('second-plugin', { - path: 'path-2', - requiredPlugins: ['first-plugin'], + + const { preboot, standard } = await pluginsService.discover({ + environment: environmentPreboot, }); + expect(mockDiscover).toHaveBeenCalledTimes(1); + + expect(preboot.pluginTree).toBeUndefined(); + for (const plugin of prebootPlugins) { + expect(prebootMockPluginSystem.addPlugin).toHaveBeenCalledWith(plugin); + } + expect(standard.pluginTree).toBeUndefined(); + for (const plugin of standardPlugins) { + expect(standardMockPluginSystem.addPlugin).toHaveBeenCalledWith(plugin); + } + }); + + it('does not throw in case of mutual plugin dependencies between preboot and standard plugins', async () => { mockDiscover.mockReturnValue({ error$: from([]), - plugin$: from([firstPlugin, secondPlugin]), + plugin$: from([ + createPlugin('first-plugin-preboot', { + type: PluginType.preboot, + path: 'path-1-preboot', + requiredPlugins: ['second-plugin-standard'], + }), + createPlugin('first-plugin-standard', { + path: 'path-1-standard', + requiredPlugins: ['second-plugin-preboot'], + }), + createPlugin('second-plugin-preboot', { + type: PluginType.preboot, + path: 'path-2-preboot', + requiredPlugins: ['first-plugin-standard'], + }), + createPlugin('second-plugin-standard', { + path: 'path-2-standard', + requiredPlugins: ['first-plugin-preboot'], + }), + ]), }); - const { pluginTree } = await pluginsService.discover({ environment: environmentSetup }); - expect(pluginTree).toBeUndefined(); + const { preboot, standard } = await pluginsService.discover({ + environment: environmentPreboot, + }); + expect(preboot.pluginTree).toBeUndefined(); + expect(standard.pluginTree).toBeUndefined(); expect(mockDiscover).toHaveBeenCalledTimes(1); - expect(mockPluginSystem.addPlugin).toHaveBeenCalledTimes(2); - expect(mockPluginSystem.addPlugin).toHaveBeenCalledWith(firstPlugin); - expect(mockPluginSystem.addPlugin).toHaveBeenCalledWith(secondPlugin); + expect(prebootMockPluginSystem.addPlugin).not.toHaveBeenCalled(); + expect(standardMockPluginSystem.addPlugin).not.toHaveBeenCalled(); }); it('does not throw in case of cyclic plugin dependencies', async () => { - const firstPlugin = createPlugin('first-plugin', { - path: 'path-1', - requiredPlugins: ['second-plugin'], - }); - const secondPlugin = createPlugin('second-plugin', { - path: 'path-2', - requiredPlugins: ['third-plugin', 'last-plugin'], - }); - const thirdPlugin = createPlugin('third-plugin', { - path: 'path-3', - requiredPlugins: ['last-plugin', 'first-plugin'], - }); - const lastPlugin = createPlugin('last-plugin', { - path: 'path-4', - requiredPlugins: ['first-plugin'], - }); - const missingDepsPlugin = createPlugin('missing-deps-plugin', { - path: 'path-5', - requiredPlugins: ['not-a-plugin'], - }); + const prebootPlugins = [ + createPlugin('first-plugin-preboot', { + type: PluginType.preboot, + path: 'path-1-preboot', + requiredPlugins: ['second-plugin-preboot'], + }), + createPlugin('second-plugin-preboot', { + type: PluginType.preboot, + path: 'path-2-preboot', + requiredPlugins: ['third-plugin-preboot', 'last-plugin-preboot'], + }), + createPlugin('third-plugin-preboot', { + type: PluginType.preboot, + path: 'path-3-preboot', + requiredPlugins: ['last-plugin-preboot', 'first-plugin-preboot'], + }), + createPlugin('last-plugin-preboot', { + type: PluginType.preboot, + path: 'path-4-preboot', + requiredPlugins: ['first-plugin-preboot'], + }), + createPlugin('missing-deps-plugin-preboot', { + type: PluginType.preboot, + path: 'path-5-preboot', + requiredPlugins: ['not-a-plugin-preboot'], + }), + ]; + + const standardPlugins = [ + createPlugin('first-plugin-standard', { + path: 'path-1-standard', + requiredPlugins: ['second-plugin-standard'], + }), + createPlugin('second-plugin-standard', { + path: 'path-2-standard', + requiredPlugins: ['third-plugin-standard', 'last-plugin-standard'], + }), + createPlugin('third-plugin-standard', { + path: 'path-3-standard', + requiredPlugins: ['last-plugin-standard', 'first-plugin-standard'], + }), + createPlugin('last-plugin-standard', { + path: 'path-4-standard', + requiredPlugins: ['first-plugin-standard'], + }), + createPlugin('missing-deps-plugin-standard', { + path: 'path-5-standard', + requiredPlugins: ['not-a-plugin-standard'], + }), + ]; mockDiscover.mockReturnValue({ error$: from([]), - plugin$: from([firstPlugin, secondPlugin, thirdPlugin, lastPlugin, missingDepsPlugin]), + plugin$: from([...prebootPlugins, ...standardPlugins]), }); - const { pluginTree } = await pluginsService.discover({ environment: environmentSetup }); - expect(pluginTree).toBeUndefined(); - + const { standard, preboot } = await pluginsService.discover({ + environment: environmentPreboot, + }); expect(mockDiscover).toHaveBeenCalledTimes(1); - expect(mockPluginSystem.addPlugin).toHaveBeenCalledTimes(4); - expect(mockPluginSystem.addPlugin).toHaveBeenCalledWith(firstPlugin); - expect(mockPluginSystem.addPlugin).toHaveBeenCalledWith(secondPlugin); - expect(mockPluginSystem.addPlugin).toHaveBeenCalledWith(thirdPlugin); - expect(mockPluginSystem.addPlugin).toHaveBeenCalledWith(lastPlugin); + + expect(preboot.pluginTree).toBeUndefined(); + expect(prebootMockPluginSystem.addPlugin).toHaveBeenCalledTimes(4); + for (const plugin of prebootPlugins) { + if (plugin.name.startsWith('missing-deps')) { + expect(prebootMockPluginSystem.addPlugin).not.toHaveBeenCalledWith(plugin); + } else { + expect(prebootMockPluginSystem.addPlugin).toHaveBeenCalledWith(plugin); + } + } + + expect(standard.pluginTree).toBeUndefined(); + expect(standardMockPluginSystem.addPlugin).toHaveBeenCalledTimes(4); + for (const plugin of standardPlugins) { + if (plugin.name.startsWith('missing-deps')) { + expect(standardMockPluginSystem.addPlugin).not.toHaveBeenCalledWith(plugin); + } else { + expect(standardMockPluginSystem.addPlugin).toHaveBeenCalledWith(plugin); + } + } }); it('properly invokes plugin discovery and ignores non-critical errors.', async () => { - const firstPlugin = createPlugin('some-id', { - path: 'path-1', - configPath: 'path', - requiredPlugins: ['some-other-id'], - optionalPlugins: ['missing-optional-dep'], - }); - const secondPlugin = createPlugin('some-other-id', { - path: 'path-2', - version: 'some-other-version', - configPath: ['plugin', 'path'], - }); + const prebootPlugins = [ + createPlugin('some-id-preboot', { + type: PluginType.preboot, + path: 'path-1-preboot', + configPath: 'path-preboot', + requiredPlugins: ['some-other-id-preboot'], + optionalPlugins: ['missing-optional-dep'], + }), + createPlugin('some-other-id-preboot', { + type: PluginType.preboot, + path: 'path-2-preboot', + version: 'some-other-version', + configPath: ['plugin-other-preboot', 'path'], + }), + ]; + + const standardPlugins = [ + createPlugin('some-id-standard', { + type: PluginType.standard, + path: 'path-1-standard', + configPath: 'path-standard', + requiredPlugins: ['some-other-id-standard'], + optionalPlugins: ['missing-optional-dep'], + }), + createPlugin('some-other-id-standard', { + type: PluginType.standard, + path: 'path-2-standard', + version: 'some-other-version', + configPath: ['plugin-other-standard', 'path'], + }), + ]; mockDiscover.mockReturnValue({ error$: from([ @@ -367,13 +609,20 @@ describe('PluginsService', () => { PluginDiscoveryError.invalidSearchPath('dir-1', new Error('No dir')), PluginDiscoveryError.invalidPluginPath('path4-1', new Error('No path')), ]), - plugin$: from([firstPlugin, secondPlugin]), + plugin$: from([...prebootPlugins, ...standardPlugins]), }); - await pluginsService.discover({ environment: environmentSetup }); - expect(mockPluginSystem.addPlugin).toHaveBeenCalledTimes(2); - expect(mockPluginSystem.addPlugin).toHaveBeenCalledWith(firstPlugin); - expect(mockPluginSystem.addPlugin).toHaveBeenCalledWith(secondPlugin); + await pluginsService.discover({ environment: environmentPreboot }); + expect(prebootMockPluginSystem.addPlugin).toHaveBeenCalledTimes(2); + for (const plugin of prebootPlugins) { + expect(prebootMockPluginSystem.addPlugin).toHaveBeenCalledWith(plugin); + } + + expect(standardMockPluginSystem.addPlugin).toHaveBeenCalledTimes(2); + for (const plugin of standardPlugins) { + expect(standardMockPluginSystem.addPlugin).toHaveBeenCalledWith(plugin); + } + expect(mockDiscover).toHaveBeenCalledTimes(1); expect(mockDiscover).toHaveBeenCalledWith( { @@ -399,27 +648,33 @@ describe('PluginsService', () => { const configSchema = schema.string(); jest.spyOn(configService, 'setSchema').mockImplementation(() => Promise.resolve()); jest.doMock( - join('path-with-schema', 'server'), - () => ({ - config: { - schema: configSchema, - }, - }), - { - virtual: true, - } + join('path-with-schema-preboot', 'server'), + () => ({ config: { schema: configSchema } }), + { virtual: true } + ); + jest.doMock( + join('path-with-schema-standard', 'server'), + () => ({ config: { schema: configSchema } }), + { virtual: true } ); + mockDiscover.mockReturnValue({ error$: from([]), plugin$: from([ - createPlugin('some-id', { - path: 'path-with-schema', - configPath: 'path', + createPlugin('some-id-preboot', { + type: PluginType.preboot, + path: 'path-with-schema-preboot', + configPath: 'path-preboot', + }), + createPlugin('some-id-standard', { + path: 'path-with-schema-standard', + configPath: 'path-standard', }), ]), }); - await pluginsService.discover({ environment: environmentSetup }); - expect(configService.setSchema).toBeCalledWith('path', configSchema); + await pluginsService.discover({ environment: environmentPreboot }); + expect(configService.setSchema).toBeCalledWith('path-preboot', configSchema); + expect(configService.setSchema).toBeCalledWith('path-standard', configSchema); }); it('registers plugin config deprecation provider in config service', async () => { @@ -427,127 +682,183 @@ describe('PluginsService', () => { jest.spyOn(configService, 'setSchema').mockImplementation(() => Promise.resolve()); jest.spyOn(configService, 'addDeprecationProvider'); - const deprecationProvider = () => []; + const prebootDeprecationProvider = () => []; jest.doMock( - join('path-with-provider', 'server'), - () => ({ - config: { - schema: configSchema, - deprecations: deprecationProvider, - }, - }), - { - virtual: true, - } + join('path-with-provider-preboot', 'server'), + () => ({ config: { schema: configSchema, deprecations: prebootDeprecationProvider } }), + { virtual: true } + ); + + const standardDeprecationProvider = () => []; + jest.doMock( + join('path-with-provider-standard', 'server'), + () => ({ config: { schema: configSchema, deprecations: standardDeprecationProvider } }), + { virtual: true } ); + mockDiscover.mockReturnValue({ error$: from([]), plugin$: from([ - createPlugin('some-id', { - path: 'path-with-provider', - configPath: 'config-path', + createPlugin('some-id-preboot', { + type: PluginType.preboot, + path: 'path-with-provider-preboot', + configPath: 'config-path-preboot', + }), + createPlugin('some-id-standard', { + path: 'path-with-provider-standard', + configPath: 'config-path-standard', }), ]), }); - await pluginsService.discover({ environment: environmentSetup }); + await pluginsService.discover({ environment: environmentPreboot }); + expect(configService.addDeprecationProvider).toBeCalledWith( + 'config-path-preboot', + prebootDeprecationProvider + ); expect(configService.addDeprecationProvider).toBeCalledWith( - 'config-path', - deprecationProvider + 'config-path-standard', + standardDeprecationProvider ); }); it('returns the paths of the plugins', async () => { - const pluginA = createPlugin('A', { path: '/plugin-A-path', configPath: 'pathA' }); - const pluginB = createPlugin('B', { path: '/plugin-B-path', configPath: 'pathB' }); - mockDiscover.mockReturnValue({ error$: from([]), plugin$: from([]), }); - mockPluginSystem.getPlugins.mockReturnValue([pluginA, pluginB]); - - const { pluginPaths } = await pluginsService.discover({ environment: environmentSetup }); + prebootMockPluginSystem.getPlugins.mockImplementation(() => [ + createPlugin('A-preboot', { + type: PluginType.preboot, + path: '/plugin-A-path-preboot', + configPath: 'pathA-preboot', + }), + createPlugin('B-preboot', { + type: PluginType.preboot, + path: '/plugin-B-path-preboot', + configPath: 'pathB-preboot', + }), + ]); - expect(pluginPaths).toEqual(['/plugin-A-path', '/plugin-B-path']); - }); + standardMockPluginSystem.getPlugins.mockImplementation(() => [ + createPlugin('A-standard', { + path: '/plugin-A-path-standard', + configPath: 'pathA-standard', + }), + createPlugin('B-standard', { + path: '/plugin-B-path-standard', + configPath: 'pathB-standard', + }), + ]); - it('ppopulates pluginConfigUsageDescriptors with plugins exposeToUsage property', async () => { - const pluginA = createPlugin('plugin-with-expose-usage', { - path: 'plugin-with-expose-usage', - configPath: 'pathA', + const { preboot, standard } = await pluginsService.discover({ + environment: environmentPreboot, }); - jest.doMock( - join('plugin-with-expose-usage', 'server'), - () => ({ - config: { - exposeToUsage: { - test: true, - nested: { - prop: true, - }, + expect(preboot.pluginPaths).toEqual(['/plugin-A-path-preboot', '/plugin-B-path-preboot']); + expect(standard.pluginPaths).toEqual(['/plugin-A-path-standard', '/plugin-B-path-standard']); + }); + + it('populates pluginConfigUsageDescriptors with plugins exposeToUsage property', async () => { + const pluginsWithExposeUsage = [ + createPlugin('plugin-with-expose-usage-preboot', { + type: PluginType.preboot, + path: 'plugin-with-expose-usage-preboot', + configPath: 'pathA-preboot', + }), + createPlugin('plugin-with-expose-usage-standard', { + path: 'plugin-with-expose-usage-standard', + configPath: 'pathA-standard', + }), + ]; + for (const plugin of pluginsWithExposeUsage) { + jest.doMock( + join(plugin.path, 'server'), + () => ({ + config: { + exposeToUsage: { test: true, nested: { prop: true } }, + schema: schema.maybe(schema.any()), }, - schema: schema.maybe(schema.any()), - }, + }), + { virtual: true } + ); + } + + const pluginsWithArrayConfigPath = [ + createPlugin('plugin-with-array-configPath-preboot', { + type: PluginType.preboot, + path: 'plugin-with-array-configPath-preboot', + version: 'some-other-version', + configPath: ['plugin-preboot', 'pathB'], }), - { - virtual: true, - } - ); - - const pluginB = createPlugin('plugin-with-array-configPath', { - path: 'plugin-with-array-configPath', - configPath: ['plugin', 'pathB'], - }); - - jest.doMock( - join('plugin-with-array-configPath', 'server'), - () => ({ - config: { - exposeToUsage: { - test: true, + createPlugin('plugin-with-array-configPath-standard', { + path: 'plugin-with-array-configPath-standard', + version: 'some-other-version', + configPath: ['plugin-standard', 'pathB'], + }), + ]; + for (const plugin of pluginsWithArrayConfigPath) { + jest.doMock( + join(plugin.path, 'server'), + () => ({ + config: { + exposeToUsage: { test: true }, + schema: schema.maybe(schema.any()), }, - schema: schema.maybe(schema.any()), - }, + }), + { virtual: true } + ); + } + + const pluginsWithoutExpose = [ + createPlugin('plugin-without-expose-preboot', { + type: PluginType.preboot, + path: 'plugin-without-expose-preboot', + configPath: 'pathC-preboot', }), - { - virtual: true, - } - ); - - jest.doMock( - join('plugin-without-expose', 'server'), - () => ({ - config: { - schema: schema.maybe(schema.any()), - }, + createPlugin('plugin-without-expose-standard', { + path: 'plugin-without-expose-standard', + configPath: 'pathC-standard', }), - { - virtual: true, - } - ); - - const pluginC = createPlugin('plugin-without-expose', { - path: 'plugin-without-expose', - configPath: 'pathC', - }); + ]; + for (const plugin of pluginsWithoutExpose) { + jest.doMock( + join(plugin.path, 'server'), + () => ({ + config: { + schema: schema.maybe(schema.any()), + }, + }), + { virtual: true } + ); + } mockDiscover.mockReturnValue({ error$: from([]), - plugin$: from([pluginA, pluginB, pluginC]), + plugin$: from([ + ...pluginsWithExposeUsage, + ...pluginsWithArrayConfigPath, + ...pluginsWithoutExpose, + ]), }); - await pluginsService.discover({ environment: environmentSetup }); + await pluginsService.discover({ environment: environmentPreboot }); // eslint-disable-next-line dot-notation expect(pluginsService['pluginConfigUsageDescriptors']).toMatchInlineSnapshot(` Map { - "pathA" => Object { + "pathA-preboot" => Object { "nested.prop": true, "test": true, }, - "plugin.pathB" => Object { + "pathA-standard" => Object { + "nested.prop": true, + "test": true, + }, + "plugin-preboot.pathB" => Object { + "test": true, + }, + "plugin-standard.pathB" => Object { "test": true, }, } @@ -560,6 +871,7 @@ describe('PluginsService', () => { plugin.name, { id: plugin.name, + type: plugin.manifest.type, configPath: plugin.manifest.configPath, requiredPlugins: [], requiredBundles: [], @@ -568,140 +880,246 @@ describe('PluginsService', () => { ]; it('properly generates client configs for plugins according to `exposeToBrowser`', async () => { - jest.doMock( - join('plugin-with-expose', 'server'), - () => ({ - config: { - exposeToBrowser: { - sharedProp: true, - }, - schema: schema.object({ - serverProp: schema.string({ defaultValue: 'serverProp default value' }), - sharedProp: schema.string({ defaultValue: 'sharedProp default value' }), - }), - }, - }), - { - virtual: true, - } - ); - const plugin = createPlugin('plugin-with-expose', { - path: 'plugin-with-expose', - configPath: 'path', + const prebootPlugin = createPlugin('plugin-with-expose-preboot', { + type: PluginType.preboot, + path: 'plugin-with-expose-preboot', + configPath: 'path-preboot', }); + const standardPlugin = createPlugin('plugin-with-expose-standard', { + path: 'plugin-with-expose-standard', + configPath: 'path-standard', + }); + for (const plugin of [prebootPlugin, standardPlugin]) { + jest.doMock( + join(plugin.path, 'server'), + () => ({ + config: { + exposeToBrowser: { + sharedProp: true, + }, + schema: schema.object({ + serverProp: schema.string({ + defaultValue: `serverProp default value ${plugin.name}`, + }), + sharedProp: schema.string({ + defaultValue: `sharedProp default value ${plugin.name}`, + }), + }), + }, + }), + { virtual: true } + ); + } + mockDiscover.mockReturnValue({ error$: from([]), - plugin$: from([plugin]), + plugin$: from([prebootPlugin, standardPlugin]), + }); + prebootMockPluginSystem.uiPlugins.mockReturnValue( + new Map([pluginToDiscoveredEntry(prebootPlugin)]) + ); + standardMockPluginSystem.uiPlugins.mockReturnValue( + new Map([pluginToDiscoveredEntry(standardPlugin)]) + ); + + const { preboot, standard } = await pluginsService.discover({ + environment: environmentPreboot, }); - mockPluginSystem.uiPlugins.mockReturnValue(new Map([pluginToDiscoveredEntry(plugin)])); - const { uiPlugins } = await pluginsService.discover({ environment: environmentSetup }); - const uiConfig$ = uiPlugins.browserConfigs.get('plugin-with-expose'); - expect(uiConfig$).toBeDefined(); + const prebootUIConfig$ = preboot.uiPlugins.browserConfigs.get('plugin-with-expose-preboot')!; + await expect(prebootUIConfig$.pipe(take(1)).toPromise()).resolves.toEqual({ + sharedProp: 'sharedProp default value plugin-with-expose-preboot', + }); - const uiConfig = await uiConfig$!.pipe(take(1)).toPromise(); - expect(uiConfig).toMatchInlineSnapshot(` - Object { - "sharedProp": "sharedProp default value", - } - `); + const standardUIConfig$ = standard.uiPlugins.browserConfigs.get( + 'plugin-with-expose-standard' + )!; + await expect(standardUIConfig$.pipe(take(1)).toPromise()).resolves.toEqual({ + sharedProp: 'sharedProp default value plugin-with-expose-standard', + }); }); it('does not generate config for plugins not exposing to client', async () => { - jest.doMock( - join('plugin-without-expose', 'server'), - () => ({ - config: { - schema: schema.object({ - serverProp: schema.string({ defaultValue: 'serverProp default value' }), - }), - }, - }), - { - virtual: true, - } - ); - const plugin = createPlugin('plugin-without-expose', { - path: 'plugin-without-expose', - configPath: 'path', + const prebootPlugin = createPlugin('plugin-without-expose-preboot', { + type: PluginType.preboot, + path: 'plugin-without-expose-preboot', + configPath: 'path-preboot', + }); + const standardPlugin = createPlugin('plugin-without-expose-standard', { + path: 'plugin-without-expose-standard', + configPath: 'path-standard', }); + for (const plugin of [prebootPlugin, standardPlugin]) { + jest.doMock( + join(plugin.path, 'server'), + () => ({ + config: { + schema: schema.object({ + serverProp: schema.string({ defaultValue: 'serverProp default value' }), + }), + }, + }), + { virtual: true } + ); + } + mockDiscover.mockReturnValue({ error$: from([]), - plugin$: from([plugin]), + plugin$: from([prebootPlugin, standardPlugin]), }); - mockPluginSystem.uiPlugins.mockReturnValue(new Map([pluginToDiscoveredEntry(plugin)])); + prebootMockPluginSystem.uiPlugins.mockReturnValue( + new Map([pluginToDiscoveredEntry(prebootPlugin)]) + ); + standardMockPluginSystem.uiPlugins.mockReturnValue( + new Map([pluginToDiscoveredEntry(standardPlugin)]) + ); - const { uiPlugins } = await pluginsService.discover({ environment: environmentSetup }); - expect([...uiPlugins.browserConfigs.entries()]).toHaveLength(0); + const { preboot, standard } = await pluginsService.discover({ + environment: environmentPreboot, + }); + expect(preboot.uiPlugins.browserConfigs.size).toBe(0); + expect(standard.uiPlugins.browserConfigs.size).toBe(0); }); }); - describe('#setup()', () => { + describe('plugin initialization', () => { beforeEach(() => { mockDiscover.mockReturnValue({ error$: from([]), plugin$: from([ - createPlugin('plugin-1', { - path: 'path-1', + createPlugin('plugin-1-preboot', { + type: PluginType.preboot, + path: 'path-1-preboot', version: 'version-1', - configPath: 'plugin1', + configPath: 'plugin1_preboot', }), - createPlugin('plugin-2', { - path: 'path-2', + createPlugin('plugin-1-standard', { + path: 'path-1-standard', + version: 'version-1', + configPath: 'plugin1_standard', + }), + createPlugin('plugin-2-preboot', { + type: PluginType.preboot, + path: 'path-2-preboot', version: 'version-2', - configPath: 'plugin2', + configPath: 'plugin2_preboot', + }), + createPlugin('plugin-2-standard', { + path: 'path-2-standard', + version: 'version-2', + configPath: 'plugin2_standard', }), ]), }); - mockPluginSystem.uiPlugins.mockReturnValue(new Map()); + prebootMockPluginSystem.uiPlugins.mockReturnValue(new Map()); + standardMockPluginSystem.uiPlugins.mockReturnValue(new Map()); }); - describe('uiPlugins.internal', () => { - it('contains internal properties for plugins', async () => { - config$.next({ plugins: { initialize: true }, plugin1: { enabled: false } }); - const { uiPlugins } = await pluginsService.discover({ environment: environmentSetup }); - expect(uiPlugins.internal).toMatchInlineSnapshot(` - Map { - "plugin-1" => Object { - "publicAssetsDir": /path-1/public/assets, - "publicTargetDir": /path-1/target/public, - "requiredBundles": Array [], - "version": "version-1", - }, - "plugin-2" => Object { - "publicAssetsDir": /path-2/public/assets, - "publicTargetDir": /path-2/target/public, - "requiredBundles": Array [], - "version": "version-2", - }, - } - `); + it('`uiPlugins.internal` contains internal properties for plugins', async () => { + config$.next({ + plugins: { initialize: true }, + plugin1_preboot: { enabled: false }, + plugin1_standard: { enabled: false }, }); - - it('includes disabled plugins', async () => { - config$.next({ plugins: { initialize: true }, plugin1: { enabled: false } }); - const { uiPlugins } = await pluginsService.discover({ environment: environmentSetup }); - expect([...uiPlugins.internal.keys()].sort()).toEqual(['plugin-1', 'plugin-2']); + const { preboot, standard } = await pluginsService.discover({ + environment: environmentPreboot, }); + expect(preboot.uiPlugins.internal).toMatchInlineSnapshot(` + Map { + "plugin-1-preboot" => Object { + "publicAssetsDir": /path-1-preboot/public/assets, + "publicTargetDir": /path-1-preboot/target/public, + "requiredBundles": Array [], + "version": "version-1", + }, + "plugin-2-preboot" => Object { + "publicAssetsDir": /path-2-preboot/public/assets, + "publicTargetDir": /path-2-preboot/target/public, + "requiredBundles": Array [], + "version": "version-2", + }, + } + `); + expect(standard.uiPlugins.internal).toMatchInlineSnapshot(` + Map { + "plugin-1-standard" => Object { + "publicAssetsDir": /path-1-standard/public/assets, + "publicTargetDir": /path-1-standard/target/public, + "requiredBundles": Array [], + "version": "version-1", + }, + "plugin-2-standard" => Object { + "publicAssetsDir": /path-2-standard/public/assets, + "publicTargetDir": /path-2-standard/target/public, + "requiredBundles": Array [], + "version": "version-2", + }, + } + `); }); - describe('plugin initialization', () => { - it('does initialize if plugins.initialize is true', async () => { - config$.next({ plugins: { initialize: true } }); - await pluginsService.discover({ environment: environmentSetup }); - const { initialized } = await pluginsService.setup(setupDeps); - expect(mockPluginSystem.setupPlugins).toHaveBeenCalled(); - expect(initialized).toBe(true); + it('`uiPlugins.internal` includes disabled plugins', async () => { + config$.next({ + plugins: { initialize: true }, + plugin1_preboot: { enabled: false }, + plugin1_standard: { enabled: false }, }); - - it('does not initialize if plugins.initialize is false', async () => { - config$.next({ plugins: { initialize: false } }); - await pluginsService.discover({ environment: environmentSetup }); - const { initialized } = await pluginsService.setup(setupDeps); - expect(mockPluginSystem.setupPlugins).not.toHaveBeenCalled(); - expect(initialized).toBe(false); + const { preboot, standard } = await pluginsService.discover({ + environment: environmentPreboot, }); + expect([...preboot.uiPlugins.internal.keys()].sort()).toMatchInlineSnapshot(` + Array [ + "plugin-1-preboot", + "plugin-2-preboot", + ] + `); + expect([...standard.uiPlugins.internal.keys()].sort()).toMatchInlineSnapshot(` + Array [ + "plugin-1-standard", + "plugin-2-standard", + ] + `); + }); + + it('#preboot does initialize `preboot` plugins if plugins.initialize is true', async () => { + config$.next({ plugins: { initialize: true } }); + await pluginsService.discover({ environment: environmentPreboot }); + await pluginsService.preboot(prebootDeps); + + expect(prebootMockPluginSystem.setupPlugins).toHaveBeenCalledTimes(1); + expect(prebootMockPluginSystem.setupPlugins).toHaveBeenCalledWith(prebootDeps); + expect(standardMockPluginSystem.setupPlugins).not.toHaveBeenCalled(); + }); + + it('#preboot does not initialize `preboot` plugins if plugins.initialize is false', async () => { + config$.next({ plugins: { initialize: false } }); + await pluginsService.discover({ environment: environmentPreboot }); + await pluginsService.preboot(prebootDeps); + + expect(prebootMockPluginSystem.setupPlugins).not.toHaveBeenCalled(); + expect(standardMockPluginSystem.setupPlugins).not.toHaveBeenCalled(); + }); + + it('#setup does initialize `standard` plugins if plugins.initialize is true', async () => { + config$.next({ plugins: { initialize: true } }); + await pluginsService.discover({ environment: environmentPreboot }); + await pluginsService.preboot(prebootDeps); + + const { initialized } = await pluginsService.setup(setupDeps); + expect(standardMockPluginSystem.setupPlugins).toHaveBeenCalledTimes(1); + expect(standardMockPluginSystem.setupPlugins).toHaveBeenCalledWith(setupDeps); + expect(initialized).toBe(true); + }); + + it('#setup does not initialize `standard` plugins if plugins.initialize is false', async () => { + config$.next({ plugins: { initialize: false } }); + await pluginsService.discover({ environment: environmentPreboot }); + await pluginsService.preboot(prebootDeps); + const { initialized } = await pluginsService.setup(setupDeps); + expect(standardMockPluginSystem.setupPlugins).not.toHaveBeenCalled(); + expect(prebootMockPluginSystem.setupPlugins).not.toHaveBeenCalled(); + expect(initialized).toBe(false); }); }); @@ -719,10 +1137,74 @@ describe('PluginsService', () => { }); }); + describe('#start()', () => { + beforeEach(() => { + mockDiscover.mockReturnValue({ + error$: from([]), + plugin$: from([ + createPlugin('plugin-1-preboot', { type: PluginType.preboot, path: 'path-1-preboot' }), + createPlugin('plugin-1-standard', { path: 'path-1-standard' }), + ]), + }); + }); + + it('does not try to stop `preboot` plugins and start `standard` ones if plugins.initialize is `false`', async () => { + config$.next({ plugins: { initialize: false } }); + + await pluginsService.discover({ environment: environmentPreboot }); + await pluginsService.preboot(prebootDeps); + await pluginsService.setup(setupDeps); + + const { contracts } = await pluginsService.start(startDeps); + expect(contracts).toBeInstanceOf(Map); + expect(contracts.size).toBe(0); + + expect(prebootMockPluginSystem.stopPlugins).not.toHaveBeenCalled(); + expect(standardMockPluginSystem.startPlugins).not.toHaveBeenCalled(); + }); + + it('stops `preboot` plugins and starts `standard` ones', async () => { + await pluginsService.discover({ environment: environmentPreboot }); + await pluginsService.preboot(prebootDeps); + await pluginsService.setup(setupDeps); + + expect(prebootMockPluginSystem.stopPlugins).not.toHaveBeenCalled(); + expect(standardMockPluginSystem.startPlugins).not.toHaveBeenCalled(); + + await pluginsService.start(startDeps); + + expect(prebootMockPluginSystem.stopPlugins).toHaveBeenCalledTimes(1); + expect(standardMockPluginSystem.stopPlugins).not.toHaveBeenCalled(); + + expect(standardMockPluginSystem.startPlugins).toHaveBeenCalledTimes(1); + expect(standardMockPluginSystem.startPlugins).toHaveBeenCalledWith(startDeps); + expect(prebootMockPluginSystem.startPlugins).not.toHaveBeenCalled(); + }); + }); + describe('#stop()', () => { it('`stop` stops plugins system', async () => { await pluginsService.stop(); - expect(mockPluginSystem.stopPlugins).toHaveBeenCalledTimes(1); + expect(standardMockPluginSystem.stopPlugins).toHaveBeenCalledTimes(1); + expect(prebootMockPluginSystem.stopPlugins).toHaveBeenCalledTimes(1); + }); + + it('`stop` does not try to stop preboot plugins system if it was stopped during `start`.', async () => { + await pluginsService.preboot(prebootDeps); + await pluginsService.setup(setupDeps); + + expect(prebootMockPluginSystem.stopPlugins).not.toHaveBeenCalled(); + expect(standardMockPluginSystem.stopPlugins).not.toHaveBeenCalled(); + + await pluginsService.start(startDeps); + + expect(prebootMockPluginSystem.stopPlugins).toHaveBeenCalledTimes(1); + expect(standardMockPluginSystem.stopPlugins).not.toHaveBeenCalled(); + + await pluginsService.stop(); + + expect(prebootMockPluginSystem.stopPlugins).toHaveBeenCalledTimes(1); + expect(standardMockPluginSystem.stopPlugins).toHaveBeenCalledTimes(1); }); }); }); diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index 99a9aaaddcb0b..05bb60fb22c6d 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -8,20 +8,36 @@ import Path from 'path'; import { Observable } from 'rxjs'; -import { filter, first, map, concatMap, tap, toArray } from 'rxjs/operators'; -import { pick, getFlattenedObject } from '@kbn/std'; +import { concatMap, filter, first, map, tap, toArray } from 'rxjs/operators'; +import { getFlattenedObject, pick } from '@kbn/std'; import { CoreService } from '../../types'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; import { discover, PluginDiscoveryError, PluginDiscoveryErrorType } from './discovery'; import { PluginWrapper } from './plugin'; -import { DiscoveredPlugin, PluginConfigDescriptor, PluginName, InternalPluginInfo } from './types'; +import { + DiscoveredPlugin, + InternalPluginInfo, + PluginConfigDescriptor, + PluginDependencies, + PluginName, + PluginType, +} from './types'; import { PluginsConfig, PluginsConfigType } from './plugins_config'; import { PluginsSystem } from './plugins_system'; -import { InternalCoreSetup, InternalCoreStart } from '../internal_types'; +import { InternalCorePreboot, InternalCoreSetup, InternalCoreStart } from '../internal_types'; import { IConfigService } from '../config'; -import { InternalEnvironmentServiceSetup } from '../environment'; +import { InternalEnvironmentServicePreboot } from '../environment'; + +/** @internal */ +export type DiscoveredPlugins = { + [key in PluginType]: { + pluginTree: PluginDependencies; + pluginPaths: string[]; + uiPlugins: UiPlugins; + }; +}; /** @internal */ export interface PluginsServiceSetup { @@ -56,6 +72,9 @@ export interface PluginsServiceStart { contracts: Map; } +/** @internal */ +export type PluginsServicePrebootSetupDeps = InternalCorePreboot; + /** @internal */ export type PluginsServiceSetupDeps = InternalCoreSetup; @@ -64,29 +83,31 @@ export type PluginsServiceStartDeps = InternalCoreStart; /** @internal */ export interface PluginsServiceDiscoverDeps { - environment: InternalEnvironmentServiceSetup; + environment: InternalEnvironmentServicePreboot; } /** @internal */ export class PluginsService implements CoreService { private readonly log: Logger; - private readonly pluginsSystem: PluginsSystem; + private readonly prebootPluginsSystem = new PluginsSystem(this.coreContext, PluginType.preboot); + private arePrebootPluginsStopped = false; + private readonly prebootUiPluginInternalInfo = new Map(); + private readonly standardPluginsSystem = new PluginsSystem(this.coreContext, PluginType.standard); + private readonly standardUiPluginInternalInfo = new Map(); private readonly configService: IConfigService; private readonly config$: Observable; private readonly pluginConfigDescriptors = new Map(); - private readonly uiPluginInternalInfo = new Map(); private readonly pluginConfigUsageDescriptors = new Map>(); constructor(private readonly coreContext: CoreContext) { this.log = coreContext.logger.get('plugins-service'); - this.pluginsSystem = new PluginsSystem(coreContext); this.configService = coreContext.configService; this.config$ = coreContext.configService .atPath('plugins') .pipe(map((rawConfig) => new PluginsConfig(rawConfig, coreContext.env))); } - public async discover({ environment }: PluginsServiceDiscoverDeps) { + public async discover({ environment }: PluginsServiceDiscoverDeps): Promise { const config = await this.config$.pipe(first()).toPromise(); const { error$, plugin$ } = discover(config, this.coreContext, { @@ -96,16 +117,26 @@ export class PluginsService implements CoreService plugin.path), - uiPlugins: { - internal: this.uiPluginInternalInfo, - public: uiPlugins, - browserConfigs: this.generateUiPluginsConfigs(uiPlugins), + preboot: { + pluginPaths: this.prebootPluginsSystem.getPlugins().map((plugin) => plugin.path), + pluginTree: this.prebootPluginsSystem.getPluginDependencies(), + uiPlugins: { + internal: this.prebootUiPluginInternalInfo, + public: prebootUiPlugins, + browserConfigs: this.generateUiPluginsConfigs(prebootUiPlugins), + }, + }, + standard: { + pluginPaths: this.standardPluginsSystem.getPlugins().map((plugin) => plugin.path), + pluginTree: this.standardPluginsSystem.getPluginDependencies(), + uiPlugins: { + internal: this.standardUiPluginInternalInfo, + public: standardUiPlugins, + browserConfigs: this.generateUiPluginsConfigs(standardUiPlugins), + }, }, }; } @@ -114,6 +145,20 @@ export class PluginsService implements CoreService(); if (config.initialize) { - contracts = await this.pluginsSystem.setupPlugins(deps); - this.registerPluginStaticDirs(deps); + contracts = await this.standardPluginsSystem.setupPlugins(deps); + this.registerPluginStaticDirs(deps, this.standardUiPluginInternalInfo); } else { - this.log.info('Plugin initialization disabled.'); + this.log.info( + 'Skipping `setup` for `standard` plugins since plugin initialization is disabled.' + ); } return { @@ -135,13 +182,31 @@ export class PluginsService implements CoreService, parents: PluginName[] = [] - ): { enabled: true } | { enabled: false; missingDependencies: string[] } { + ): { enabled: true } | { enabled: false; missingOrIncompatibleDependencies: string[] } { const pluginInfo = pluginEnableStatuses.get(pluginName); if (pluginInfo === undefined || !pluginInfo.isEnabled) { return { enabled: false, - missingDependencies: [], + missingOrIncompatibleDependencies: [], }; } - const missingDependencies = pluginInfo.plugin.requiredPlugins + const missingOrIncompatibleDependencies = pluginInfo.plugin.requiredPlugins .filter((dep) => !parents.includes(dep)) .filter( (dependencyName) => + pluginEnableStatuses.get(dependencyName)?.plugin.manifest.type !== + pluginInfo.plugin.manifest.type || !this.shouldEnablePlugin(dependencyName, pluginEnableStatuses, [...parents, pluginName]) .enabled ); - if (missingDependencies.length === 0) { + if (missingOrIncompatibleDependencies.length === 0) { return { enabled: true, }; @@ -308,12 +390,15 @@ export class PluginsService implements CoreService + ) { + for (const [pluginName, pluginInfo] of uiPluginInternalInfo) { deps.http.registerStaticDir( `/plugins/${pluginName}/assets/{path*}`, pluginInfo.publicAssetsDir diff --git a/src/core/server/plugins/plugins_system.test.mocks.ts b/src/core/server/plugins/plugins_system.test.mocks.ts index 26175ab2b68a9..1e1264ba76a80 100644 --- a/src/core/server/plugins/plugins_system.test.mocks.ts +++ b/src/core/server/plugins/plugins_system.test.mocks.ts @@ -6,9 +6,11 @@ * Side Public License, v 1. */ +export const mockCreatePluginPrebootSetupContext = jest.fn(); export const mockCreatePluginSetupContext = jest.fn(); export const mockCreatePluginStartContext = jest.fn(); jest.mock('./plugin_context', () => ({ + createPluginPrebootSetupContext: mockCreatePluginPrebootSetupContext, createPluginSetupContext: mockCreatePluginSetupContext, createPluginStartContext: mockCreatePluginStartContext, })); diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index abcd00f4e2daf..e61c9c2002a12 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -7,6 +7,7 @@ */ import { + mockCreatePluginPrebootSetupContext, mockCreatePluginSetupContext, mockCreatePluginStartContext, } from './plugins_system.test.mocks'; @@ -20,7 +21,7 @@ import { CoreContext } from '../core_context'; import { loggingSystemMock } from '../logging/logging_system.mock'; import { PluginWrapper } from './plugin'; -import { PluginName } from './types'; +import { PluginName, PluginType } from './types'; import { PluginsSystem } from './plugins_system'; import { coreMock } from '../mocks'; import { Logger } from '../logging'; @@ -32,7 +33,14 @@ function createPlugin( optional = [], server = true, ui = true, - }: { required?: string[]; optional?: string[]; server?: boolean; ui?: boolean } = {} + type = PluginType.standard, + }: { + required?: string[]; + optional?: string[]; + server?: boolean; + ui?: boolean; + type?: PluginType; + } = {} ): PluginWrapper { return new PluginWrapper({ path: 'some-path', @@ -41,6 +49,7 @@ function createPlugin( version: 'some-version', configPath: 'path', kibanaVersion: '7.0.0', + type, requiredPlugins: required, optionalPlugins: optional, requiredBundles: [], @@ -52,10 +61,11 @@ function createPlugin( }); } +const prebootDeps = coreMock.createInternalPreboot(); const setupDeps = coreMock.createInternalSetup(); const startDeps = coreMock.createInternalStart(); -let pluginsSystem: PluginsSystem; +let pluginsSystem: PluginsSystem; let configService: ReturnType; let logger: ReturnType; let env: Env; @@ -70,7 +80,7 @@ beforeEach(() => { coreContext = { coreId: Symbol(), env, logger, configService: configService as any }; - pluginsSystem = new PluginsSystem(coreContext); + pluginsSystem = new PluginsSystem(coreContext, PluginType.standard); }); test('can be setup even without plugins', async () => { @@ -80,6 +90,26 @@ test('can be setup even without plugins', async () => { expect(pluginsSetup.size).toBe(0); }); +test('throws if adding plugin with incompatible type', () => { + const prebootPlugin = createPlugin('plugin-preboot', { type: PluginType.preboot }); + const standardPlugin = createPlugin('plugin-standard'); + + const prebootPluginSystem = new PluginsSystem(coreContext, PluginType.preboot); + const standardPluginSystem = new PluginsSystem(coreContext, PluginType.standard); + + prebootPluginSystem.addPlugin(prebootPlugin); + expect(() => prebootPluginSystem.addPlugin(standardPlugin)).toThrowErrorMatchingInlineSnapshot( + `"Cannot add plugin with type \\"standard\\" to plugin system with type \\"preboot\\"."` + ); + expect(prebootPluginSystem.getPlugins()).toEqual([prebootPlugin]); + + standardPluginSystem.addPlugin(standardPlugin); + expect(() => standardPluginSystem.addPlugin(prebootPlugin)).toThrowErrorMatchingInlineSnapshot( + `"Cannot add plugin with type \\"preboot\\" to plugin system with type \\"standard\\"."` + ); + expect(standardPluginSystem.getPlugins()).toEqual([standardPlugin]); +}); + test('getPlugins returns the list of plugins', () => { const pluginA = createPlugin('plugin-a'); const pluginB = createPlugin('plugin-b'); @@ -293,6 +323,83 @@ test('correctly orders plugins and returns exposed values for "setup" and "start } }); +test('correctly orders preboot plugins and returns exposed values for "setup"', async () => { + const prebootPluginSystem = new PluginsSystem(coreContext, PluginType.preboot); + const plugins = new Map([ + [ + createPlugin('order-4', { type: PluginType.preboot, required: ['order-2'] }), + { 'order-2': 'added-as-2' }, + ], + [createPlugin('order-0', { type: PluginType.preboot }), {}], + [ + createPlugin('order-2', { + type: PluginType.preboot, + required: ['order-1'], + optional: ['order-0'], + }), + { 'order-1': 'added-as-3', 'order-0': 'added-as-1' }, + ], + [ + createPlugin('order-1', { type: PluginType.preboot, required: ['order-0'] }), + { 'order-0': 'added-as-1' }, + ], + [ + createPlugin('order-3', { + type: PluginType.preboot, + required: ['order-2'], + optional: ['missing-dep'], + }), + { 'order-2': 'added-as-2' }, + ], + ] as Array<[PluginWrapper, Record]>); + + const setupContextMap = new Map(); + [...plugins.keys()].forEach((plugin, index) => { + jest.spyOn(plugin, 'setup').mockResolvedValue(`added-as-${index}`); + setupContextMap.set(plugin.name, `setup-for-${plugin.name}`); + prebootPluginSystem.addPlugin(plugin); + }); + + mockCreatePluginPrebootSetupContext.mockImplementation((context, deps, plugin) => + setupContextMap.get(plugin.name) + ); + + expect([...(await prebootPluginSystem.setupPlugins(prebootDeps))]).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", + ], + ] + `); + + for (const [plugin, deps] of plugins) { + expect(mockCreatePluginPrebootSetupContext).toHaveBeenCalledWith( + coreContext, + prebootDeps, + plugin + ); + expect(plugin.setup).toHaveBeenCalledTimes(1); + expect(plugin.setup).toHaveBeenCalledWith(setupContextMap.get(plugin.name), deps); + } +}); + test('`setupPlugins` only setups plugins that have server side', async () => { const firstPluginToRun = createPlugin('order-0'); const secondPluginNotToRun = createPlugin('order-not-run', { server: false }); @@ -399,6 +506,21 @@ test('can start without plugins', async () => { expect(pluginsStart.size).toBe(0); }); +test('cannot start preboot plugins', async () => { + const prebootPlugin = createPlugin('order-0', { type: PluginType.preboot }); + jest.spyOn(prebootPlugin, 'setup').mockResolvedValue({}); + jest.spyOn(prebootPlugin, 'start').mockResolvedValue({}); + + const prebootPluginSystem = new PluginsSystem(coreContext, PluginType.preboot); + prebootPluginSystem.addPlugin(prebootPlugin); + await prebootPluginSystem.setupPlugins(prebootDeps); + + await expect( + prebootPluginSystem.startPlugins(startDeps) + ).rejects.toThrowErrorMatchingInlineSnapshot(`"Preboot plugins cannot be started."`); + expect(prebootPlugin.start).not.toHaveBeenCalled(); +}); + test('`startPlugins` only starts plugins that were setup', async () => { const firstPluginToRun = createPlugin('order-0'); const secondPluginNotToRun = createPlugin('order-not-run', { server: false }); @@ -525,7 +647,7 @@ describe('asynchronous plugins', () => { }) ); coreContext = { coreId: Symbol(), env, logger, configService: configService as any }; - pluginsSystem = new PluginsSystem(coreContext); + pluginsSystem = new PluginsSystem(coreContext, PluginType.standard); const syncPlugin = createPlugin('sync-plugin'); jest.spyOn(syncPlugin, 'setup').mockReturnValue('setup-sync'); diff --git a/src/core/server/plugins/plugins_system.ts b/src/core/server/plugins/plugins_system.ts index f6327d4eabf43..4a156c5fbb976 100644 --- a/src/core/server/plugins/plugins_system.ts +++ b/src/core/server/plugins/plugins_system.ts @@ -10,25 +10,38 @@ import { withTimeout, isPromise } from '@kbn/std'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; import { PluginWrapper } from './plugin'; -import { DiscoveredPlugin, PluginName } from './types'; -import { createPluginSetupContext, createPluginStartContext } from './plugin_context'; -import { PluginsServiceSetupDeps, PluginsServiceStartDeps } from './plugins_service'; -import { PluginDependencies } from '.'; +import { DiscoveredPlugin, PluginDependencies, PluginName, PluginType } from './types'; +import { + createPluginPrebootSetupContext, + createPluginSetupContext, + createPluginStartContext, +} from './plugin_context'; +import { + PluginsServicePrebootSetupDeps, + PluginsServiceSetupDeps, + PluginsServiceStartDeps, +} from './plugins_service'; const Sec = 1000; /** @internal */ -export class PluginsSystem { +export class PluginsSystem { private readonly plugins = new Map(); private readonly log: Logger; // `satup`, the past-tense version of the noun `setup`. private readonly satupPlugins: PluginName[] = []; - constructor(private readonly coreContext: CoreContext) { - this.log = coreContext.logger.get('plugins-system'); + constructor(private readonly coreContext: CoreContext, public readonly type: T) { + this.log = coreContext.logger.get('plugins-system', this.type); } public addPlugin(plugin: PluginWrapper) { + if (plugin.manifest.type !== this.type) { + throw new Error( + `Cannot add plugin with type "${plugin.manifest.type}" to plugin system with type "${this.type}".` + ); + } + this.plugins.set(plugin.name, plugin); } @@ -67,7 +80,9 @@ export class PluginsSystem { return { asNames, asOpaqueIds }; } - public async setupPlugins(deps: PluginsServiceSetupDeps) { + public async setupPlugins( + deps: T extends PluginType.preboot ? PluginsServicePrebootSetupDeps : PluginsServiceSetupDeps + ): Promise> { const contracts = new Map(); if (this.plugins.size === 0) { return contracts; @@ -95,11 +110,23 @@ export class PluginsSystem { return depContracts; }, {} as Record); + let pluginSetupContext; + if (this.type === PluginType.preboot) { + pluginSetupContext = createPluginPrebootSetupContext( + this.coreContext, + deps as PluginsServicePrebootSetupDeps, + plugin + ); + } else { + pluginSetupContext = createPluginSetupContext( + this.coreContext, + deps as PluginsServiceSetupDeps, + plugin + ); + } + let contract: unknown; - const contractOrPromise = plugin.setup( - createPluginSetupContext(this.coreContext, deps, plugin), - pluginDepContracts - ); + const contractOrPromise = plugin.setup(pluginSetupContext, pluginDepContracts); if (isPromise(contractOrPromise)) { if (this.coreContext.env.mode.dev) { this.log.warn( @@ -130,6 +157,10 @@ export class PluginsSystem { } public async startPlugins(deps: PluginsServiceStartDeps) { + if (this.type === PluginType.preboot) { + throw new Error('Preboot plugins cannot be started.'); + } + const contracts = new Map(); if (this.satupPlugins.length === 0) { return contracts; @@ -222,6 +253,7 @@ export class PluginsSystem { pluginName, { id: pluginName, + type: plugin.manifest.type, configPath: plugin.manifest.configPath, requiredPlugins: plugin.manifest.requiredPlugins.filter((p) => uiPluginNames.includes(p) diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index 0cdc806e997ef..b0edcbdfd8677 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -16,7 +16,7 @@ import { LoggerFactory } from '../logging'; import { KibanaConfigType } from '../kibana_config'; import { ElasticsearchConfigType } from '../elasticsearch/elasticsearch_config'; import { SavedObjectsConfigType } from '../saved_objects/saved_objects_config'; -import { CoreSetup, CoreStart } from '..'; +import { CorePreboot, CoreSetup, CoreStart } from '..'; type Maybe = T | undefined; @@ -116,6 +116,18 @@ export type PluginName = string; /** @public */ export type PluginOpaqueId = symbol; +/** @public */ +export enum PluginType { + /** + * Preboot plugins are special-purpose plugins that only function during preboot stage. + */ + preboot = 'preboot', + /** + * Standard plugins are plugins that start to function as soon as Kibana is fully booted and are active until it shuts down. + */ + standard = 'standard', +} + /** @internal */ export interface PluginDependencies { asNames: ReadonlyMap; @@ -149,6 +161,11 @@ export interface PluginManifest { */ readonly kibanaVersion: string; + /** + * Type of the plugin, defaults to `standard`. + */ + readonly type: PluginType; + /** * Root {@link ConfigPath | configuration path} used by the plugin, defaults * to "id" in snake_case format. @@ -247,6 +264,11 @@ export interface DiscoveredPlugin { */ readonly configPath: ConfigPath; + /** + * Type of the plugin, defaults to `standard`. + */ + readonly type: PluginType; + /** * An optional list of the other plugins that **must be** installed and enabled * for this plugin to function properly. @@ -296,7 +318,18 @@ export interface InternalPluginInfo { } /** - * The interface that should be returned by a `PluginInitializer`. + * The interface that should be returned by a `PluginInitializer` for a `preboot` plugin. + * + * @public + */ +export interface PrebootPlugin { + setup(core: CorePreboot, plugins: TPluginsSetup): TSetup; + + stop?(): void; +} + +/** + * The interface that should be returned by a `PluginInitializer` for a `standard` plugin. * * @public */ @@ -361,6 +394,7 @@ export interface PluginInitializerContext { mode: EnvironmentMode; packageInfo: Readonly; instanceUuid: string; + configs: readonly string[]; }; /** * {@link LoggerFactory | logger factory} instance already bound to the plugin's logging context @@ -471,4 +505,5 @@ export type PluginInitializer< core: PluginInitializerContext ) => | Plugin + | PrebootPlugin | AsyncPlugin; diff --git a/src/core/server/preboot/index.ts b/src/core/server/preboot/index.ts new file mode 100644 index 0000000000000..2b7f25538dcb1 --- /dev/null +++ b/src/core/server/preboot/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { PrebootService } from './preboot_service'; +export type { InternalPrebootServicePreboot, PrebootServicePreboot } from './types'; diff --git a/src/core/server/preboot/preboot_service.mock.ts b/src/core/server/preboot/preboot_service.mock.ts new file mode 100644 index 0000000000000..acdd9458a462d --- /dev/null +++ b/src/core/server/preboot/preboot_service.mock.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { PublicMethodsOf } from '@kbn/utility-types'; +import type { InternalPrebootServicePreboot, PrebootServicePreboot } from './types'; +import { PrebootService } from './preboot_service'; + +export type InternalPrebootServicePrebootMock = jest.Mocked; +export type PrebootServicePrebootMock = jest.Mocked; + +const createInternalPrebootContractMock = () => { + const mock: InternalPrebootServicePrebootMock = { + isSetupOnHold: jest.fn(), + holdSetupUntilResolved: jest.fn(), + waitUntilCanSetup: jest.fn(), + }; + return mock; +}; + +const createPrebootContractMock = () => { + const mock: PrebootServicePrebootMock = { + isSetupOnHold: jest.fn(), + holdSetupUntilResolved: jest.fn(), + }; + + return mock; +}; + +type PrebootServiceContract = PublicMethodsOf; + +const createPrebootServiceMock = () => { + const mocked: jest.Mocked = { + preboot: jest.fn(), + stop: jest.fn(), + }; + mocked.preboot.mockReturnValue(createInternalPrebootContractMock()); + return mocked; +}; + +export const prebootServiceMock = { + create: createPrebootServiceMock, + createInternalPrebootContract: createInternalPrebootContractMock, + createPrebootContract: createPrebootContractMock, +}; diff --git a/src/core/server/preboot/preboot_service.test.ts b/src/core/server/preboot/preboot_service.test.ts new file mode 100644 index 0000000000000..dd4b1cb7d1df0 --- /dev/null +++ b/src/core/server/preboot/preboot_service.test.ts @@ -0,0 +1,191 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { nextTick } from '@kbn/test/jest'; +import { REPO_ROOT } from '@kbn/dev-utils'; +import { LoggerFactory } from '@kbn/logging'; +import { Env } from '@kbn/config'; +import { getEnvOptions } from '../config/mocks'; +import { configServiceMock, loggingSystemMock } from '../mocks'; + +import { PrebootService } from './preboot_service'; + +describe('PrebootService', () => { + describe('#preboot()', () => { + let service: PrebootService; + let logger: LoggerFactory; + beforeEach(() => { + logger = loggingSystemMock.create(); + service = new PrebootService({ + configService: configServiceMock.create(), + env: Env.createDefault(REPO_ROOT, getEnvOptions()), + logger, + coreId: Symbol(), + }); + }); + + it('returns a proper contract', () => { + expect(service.preboot()).toMatchInlineSnapshot(` + Object { + "holdSetupUntilResolved": [Function], + "isSetupOnHold": [Function], + "waitUntilCanSetup": [Function], + } + `); + }); + + it('#isSetupOnHold correctly determines if `setup` is on hold', async () => { + const preboot = service.preboot(); + + expect(preboot.isSetupOnHold()).toBe(false); + + let resolveFirstPromise: (value?: { shouldReloadConfig: boolean }) => void; + preboot.holdSetupUntilResolved( + 'some-plugin', + 'some-reason', + new Promise<{ shouldReloadConfig: boolean } | undefined>((resolve) => { + resolveFirstPromise = resolve; + }) + ); + + let resolveSecondPromise: (value?: { shouldReloadConfig: boolean }) => void; + preboot.holdSetupUntilResolved( + 'some-other-plugin', + 'some-other-reason', + new Promise<{ shouldReloadConfig: boolean } | undefined>((resolve) => { + resolveSecondPromise = resolve; + }) + ); + + expect(preboot.isSetupOnHold()).toBe(true); + const waitUntilPromise = preboot.waitUntilCanSetup(); + + resolveFirstPromise!(); + await nextTick(); + expect(preboot.isSetupOnHold()).toBe(true); + + resolveSecondPromise!(); + await nextTick(); + expect(preboot.isSetupOnHold()).toBe(false); + + await expect(waitUntilPromise).resolves.toEqual({ shouldReloadConfig: false }); + }); + + it('#holdSetupUntilResolved logs a reason', async () => { + const preboot = service.preboot(); + + preboot.holdSetupUntilResolved( + 'some-plugin', + 'some-reason', + Promise.resolve({ shouldReloadConfig: true }) + ); + preboot.holdSetupUntilResolved( + 'some-other-plugin', + 'some-other-reason', + Promise.resolve(undefined) + ); + + expect(loggingSystemMock.collect(logger).info).toMatchInlineSnapshot(` + Array [ + Array [ + "\\"some-plugin\\" plugin is holding setup: some-reason", + ], + Array [ + "\\"some-other-plugin\\" plugin is holding setup: some-other-reason", + ], + ] + `); + + await expect(preboot.waitUntilCanSetup()).resolves.toEqual({ shouldReloadConfig: true }); + }); + + it('#holdSetupUntilResolved does not allow to register new promises after #waitUntilCanSetup is called', async () => { + const preboot = service.preboot(); + + preboot.holdSetupUntilResolved( + 'some-plugin', + 'some-reason', + Promise.resolve({ shouldReloadConfig: true }) + ); + + const waitUntilPromise = preboot.waitUntilCanSetup(); + + expect(() => + preboot.holdSetupUntilResolved( + 'some-other-plugin', + 'some-other-reason', + Promise.resolve(undefined) + ) + ).toThrowErrorMatchingInlineSnapshot(`"Cannot hold boot at this stage."`); + + expect(loggingSystemMock.collect(logger).info).toMatchInlineSnapshot(` + Array [ + Array [ + "\\"some-plugin\\" plugin is holding setup: some-reason", + ], + ] + `); + + await expect(waitUntilPromise).resolves.toEqual({ shouldReloadConfig: true }); + }); + + it('#waitUntilCanSetup returns `shouldReloadConfig` set to `true` if at least one promise did it', async () => { + const preboot = service.preboot(); + + expect(preboot.isSetupOnHold()).toBe(false); + + let resolveFirstPromise: (value?: { shouldReloadConfig: boolean }) => void; + preboot.holdSetupUntilResolved( + 'some-plugin', + 'some-reason', + new Promise<{ shouldReloadConfig: boolean } | undefined>((resolve) => { + resolveFirstPromise = resolve; + }) + ); + + let resolveSecondPromise: (value?: { shouldReloadConfig: boolean }) => void; + preboot.holdSetupUntilResolved( + 'some-other-plugin', + 'some-other-reason', + new Promise<{ shouldReloadConfig: boolean } | undefined>((resolve) => { + resolveSecondPromise = resolve; + }) + ); + + expect(preboot.isSetupOnHold()).toBe(true); + const waitUntilPromise = preboot.waitUntilCanSetup(); + + resolveFirstPromise!({ shouldReloadConfig: true }); + await nextTick(); + expect(preboot.isSetupOnHold()).toBe(true); + + resolveSecondPromise!({ shouldReloadConfig: false }); + await nextTick(); + expect(preboot.isSetupOnHold()).toBe(false); + + await expect(waitUntilPromise).resolves.toEqual({ shouldReloadConfig: true }); + }); + + it('#waitUntilCanSetup is rejected if at least one promise is rejected', async () => { + const preboot = service.preboot(); + + preboot.holdSetupUntilResolved( + 'some-plugin', + 'some-reason', + Promise.resolve({ shouldReloadConfig: true }) + ); + preboot.holdSetupUntilResolved( + 'some-other-plugin', + 'some-other-reason', + Promise.reject('Uh oh!') + ); + + await expect(preboot.waitUntilCanSetup()).rejects.toBe('Uh oh!'); + }); + }); +}); diff --git a/src/core/server/preboot/preboot_service.ts b/src/core/server/preboot/preboot_service.ts new file mode 100644 index 0000000000000..4313541ef91d3 --- /dev/null +++ b/src/core/server/preboot/preboot_service.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreContext } from '../core_context'; +import { InternalPrebootServicePreboot } from './types'; + +/** @internal */ +export class PrebootService { + private readonly promiseList: Array> = []; + private waitUntilCanSetupPromise?: Promise<{ shouldReloadConfig: boolean }>; + private isSetupOnHold = false; + private readonly log = this.core.logger.get('preboot'); + + constructor(private readonly core: CoreContext) {} + + public preboot(): InternalPrebootServicePreboot { + return { + isSetupOnHold: () => this.isSetupOnHold, + holdSetupUntilResolved: (pluginName, reason, promise) => { + if (this.waitUntilCanSetupPromise) { + throw new Error('Cannot hold boot at this stage.'); + } + + this.log.info(`"${pluginName}" plugin is holding setup: ${reason}`); + + this.isSetupOnHold = true; + + this.promiseList.push(promise); + }, + waitUntilCanSetup: () => { + if (!this.waitUntilCanSetupPromise) { + this.waitUntilCanSetupPromise = Promise.all(this.promiseList) + .then((results) => ({ + shouldReloadConfig: results.some((result) => result?.shouldReloadConfig), + })) + .catch((err) => { + this.log.error(err); + throw err; + }) + .finally(() => (this.isSetupOnHold = false)); + } + + return this.waitUntilCanSetupPromise; + }, + }; + } + + public stop() { + this.isSetupOnHold = false; + this.promiseList.length = 0; + this.waitUntilCanSetupPromise = undefined; + } +} diff --git a/src/core/server/preboot/types.ts b/src/core/server/preboot/types.ts new file mode 100644 index 0000000000000..61abc327c9ddb --- /dev/null +++ b/src/core/server/preboot/types.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PluginName } from '..'; + +/** @internal */ +export interface InternalPrebootServicePreboot { + /** + * Indicates whether Kibana is currently on hold and cannot proceed to `setup` yet. + */ + readonly isSetupOnHold: () => boolean; + + /** + * Registers a `Promise` as a precondition before Kibana can proceed to `setup`. This method can be invoked multiple + * times and from multiple `preboot` plugins. Kibana will proceed to `setup` only when all registered `Promise` are + * resolved, or it will shut down if any of them are rejected. + * @param pluginName Name of the plugin that needs to hold `setup`. + * @param reason A string that explains the reason why this promise should hold `setup`. It's supposed to be a human + * readable string that will be recorded in the logs or standard output. + * @param promise A `Promise` that should resolved before Kibana can proceed to `setup`. + */ + readonly holdSetupUntilResolved: ( + pluginName: PluginName, + reason: string, + promise: Promise<{ shouldReloadConfig: boolean } | undefined> + ) => void; + + /** + * Returns a `Promise` that is resolved only when all `Promise` instances registered with {@link holdSetupUntilResolved} + * are resolved, or rejected if any of them are rejected. If the supplied `Promise` resolves to an object with the + * `shouldReloadConfig` property set to `true`, it indicates that Kibana configuration might have changed and Kibana + * needs to reload it from the disk. + */ + readonly waitUntilCanSetup: () => Promise<{ shouldReloadConfig: boolean }>; +} + +/** + * Kibana Preboot Service allows to control the boot flow of Kibana. Preboot plugins can use it to hold the boot until certain condition is met. + * + * @example + * A plugin can supply a `Promise` to a `holdSetupUntilResolved` method to signal Kibana to initialize and start `standard` plugins only after this + * `Promise` is resolved. If `Promise` is rejected, Kibana will shut down. + * ```ts + * core.preboot.holdSetupUntilResolved('Just waiting for 5 seconds', + * new Promise((resolve) => { + * setTimeout(resolve, 5000); + * }) + * ); + * ``` + * + * If the supplied `Promise` resolves to an object with the `shouldReloadConfig` property set to `true`, Kibana will also reload its configuration from disk. + * ```ts + * let completeSetup: (result: { shouldReloadConfig: boolean }) => void; + * core.preboot.holdSetupUntilResolved('Just waiting for 5 seconds before reloading configuration', + * new Promise<{ shouldReloadConfig: boolean }>((resolve) => { + * setTimeout(() => resolve({ shouldReloadConfig: true }), 5000); + * }) + * ); + * ``` + * @public + */ +export interface PrebootServicePreboot { + /** + * Indicates whether Kibana is currently on hold and cannot proceed to `setup` yet. + */ + readonly isSetupOnHold: () => boolean; + + /** + * Registers a `Promise` as a precondition before Kibana can proceed to `setup`. This method can be invoked multiple + * times and from multiple `preboot` plugins. Kibana will proceed to `setup` only when all registered `Promises` + * instances are resolved, or it will shut down if any of them is rejected. + * @param reason A string that explains the reason why this promise should hold `setup`. It's supposed to be a human + * readable string that will be recorded in the logs or standard output. + * @param promise A `Promise` that should resolved before Kibana can proceed to `setup`. + */ + readonly holdSetupUntilResolved: ( + reason: string, + promise: Promise<{ shouldReloadConfig: boolean } | undefined> + ) => void; +} diff --git a/src/core/server/preboot_core_route_handler_context.test.ts b/src/core/server/preboot_core_route_handler_context.test.ts new file mode 100644 index 0000000000000..8d090d8644637 --- /dev/null +++ b/src/core/server/preboot_core_route_handler_context.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { PrebootCoreRouteHandlerContext } from './preboot_core_route_handler_context'; +import { coreMock } from './mocks'; + +describe('#uiSettings', () => { + describe('#client', () => { + test('returns the results of corePreboot.uiSettings.createDefaultsClient', () => { + const corePreboot = coreMock.createInternalPreboot(); + const context = new PrebootCoreRouteHandlerContext(corePreboot); + + const client = context.uiSettings.client; + const [{ value: mockResult }] = corePreboot.uiSettings.createDefaultsClient.mock.results; + expect(client).toBe(mockResult); + }); + + test('only creates one instance', () => { + const corePreboot = coreMock.createInternalPreboot(); + const context = new PrebootCoreRouteHandlerContext(corePreboot); + + const client1 = context.uiSettings.client; + const client2 = context.uiSettings.client; + + expect(corePreboot.uiSettings.createDefaultsClient).toHaveBeenCalledTimes(1); + const [{ value: mockResult }] = corePreboot.uiSettings.createDefaultsClient.mock.results; + expect(client1).toBe(mockResult); + expect(client2).toBe(mockResult); + }); + }); +}); diff --git a/src/core/server/preboot_core_route_handler_context.ts b/src/core/server/preboot_core_route_handler_context.ts new file mode 100644 index 0000000000000..63378046e8050 --- /dev/null +++ b/src/core/server/preboot_core_route_handler_context.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// eslint-disable-next-line max-classes-per-file +import { InternalCorePreboot } from './internal_types'; +import { IUiSettingsClient } from './ui_settings'; + +class PrebootCoreUiSettingsRouteHandlerContext { + constructor(public readonly client: IUiSettingsClient) {} +} + +export class PrebootCoreRouteHandlerContext { + readonly uiSettings: PrebootCoreUiSettingsRouteHandlerContext; + + constructor(private readonly corePreboot: InternalCorePreboot) { + this.uiSettings = new PrebootCoreUiSettingsRouteHandlerContext( + this.corePreboot.uiSettings.createDefaultsClient() + ); + } +} diff --git a/src/core/server/rendering/__mocks__/params.ts b/src/core/server/rendering/__mocks__/params.ts index d790e4a679988..091d185cceefc 100644 --- a/src/core/server/rendering/__mocks__/params.ts +++ b/src/core/server/rendering/__mocks__/params.ts @@ -12,13 +12,17 @@ import { pluginServiceMock } from '../../plugins/plugins_service.mock'; import { statusServiceMock } from '../../status/status_service.mock'; const context = mockCoreContext.create(); -const http = httpServiceMock.createInternalSetupContract(); -const uiPlugins = pluginServiceMock.createUiPlugins(); +const httpPreboot = httpServiceMock.createInternalPrebootContract(); +const httpSetup = httpServiceMock.createInternalSetupContract(); const status = statusServiceMock.createInternalSetupContract(); export const mockRenderingServiceParams = context; +export const mockRenderingPrebootDeps = { + http: httpPreboot, + uiPlugins: pluginServiceMock.createUiPlugins(), +}; export const mockRenderingSetupDeps = { - http, - uiPlugins, + http: httpSetup, + uiPlugins: pluginServiceMock.createUiPlugins(), status, }; diff --git a/src/core/server/rendering/__mocks__/rendering_service.ts b/src/core/server/rendering/__mocks__/rendering_service.ts index 56131d77b23eb..76558a999a374 100644 --- a/src/core/server/rendering/__mocks__/rendering_service.ts +++ b/src/core/server/rendering/__mocks__/rendering_service.ts @@ -8,17 +8,22 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import type { RenderingService as Service } from '../rendering_service'; -import type { InternalRenderingServiceSetup } from '../types'; +import type { InternalRenderingServicePreboot, InternalRenderingServiceSetup } from '../types'; import { mockRenderingServiceParams } from './params'; type IRenderingService = PublicMethodsOf; +export const prebootMock: jest.Mocked = { + render: jest.fn(), +}; export const setupMock: jest.Mocked = { render: jest.fn(), }; +export const mockPreboot = jest.fn().mockResolvedValue(prebootMock); export const mockSetup = jest.fn().mockResolvedValue(setupMock); export const mockStop = jest.fn(); export const mockRenderingService: jest.Mocked = { + preboot: mockPreboot, setup: mockSetup, stop: mockStop, }; diff --git a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap index f6b39ea24262b..495a38a1af5bf 100644 --- a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap +++ b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap @@ -1,5 +1,264 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`RenderingService preboot() render() renders "core" from legacy request 1`] = ` +Object { + "anonymousStatusPage": false, + "basePath": "/mock-server-basepath", + "branch": Any, + "buildNumber": Any, + "csp": Object { + "warnLegacyBrowsers": true, + }, + "env": Object { + "mode": Object { + "dev": Any, + "name": Any, + "prod": Any, + }, + "packageInfo": Object { + "branch": Any, + "buildNum": Any, + "buildSha": Any, + "dist": Any, + "version": Any, + }, + }, + "externalUrl": Object { + "policy": Array [ + Object { + "allow": true, + }, + ], + }, + "i18n": Object { + "translationsUrl": "/mock-server-basepath/translations/en.json", + }, + "legacyMetadata": Object { + "uiSettings": Object { + "defaults": Object { + "registered": Object { + "name": "title", + }, + }, + "user": Object {}, + }, + }, + "publicBaseUrl": "http://myhost.com/mock-server-basepath", + "serverBasePath": "/mock-server-basepath", + "uiPlugins": Array [], + "vars": Object {}, + "version": Any, +} +`; + +exports[`RenderingService preboot() render() renders "core" page 1`] = ` +Object { + "anonymousStatusPage": false, + "basePath": "/mock-server-basepath", + "branch": Any, + "buildNumber": Any, + "csp": Object { + "warnLegacyBrowsers": true, + }, + "env": Object { + "mode": Object { + "dev": Any, + "name": Any, + "prod": Any, + }, + "packageInfo": Object { + "branch": Any, + "buildNum": Any, + "buildSha": Any, + "dist": Any, + "version": Any, + }, + }, + "externalUrl": Object { + "policy": Array [ + Object { + "allow": true, + }, + ], + }, + "i18n": Object { + "translationsUrl": "/mock-server-basepath/translations/en.json", + }, + "legacyMetadata": Object { + "uiSettings": Object { + "defaults": Object { + "registered": Object { + "name": "title", + }, + }, + "user": Object {}, + }, + }, + "publicBaseUrl": "http://myhost.com/mock-server-basepath", + "serverBasePath": "/mock-server-basepath", + "uiPlugins": Array [], + "vars": Object {}, + "version": Any, +} +`; + +exports[`RenderingService preboot() render() renders "core" page driven by settings 1`] = ` +Object { + "anonymousStatusPage": false, + "basePath": "/mock-server-basepath", + "branch": Any, + "buildNumber": Any, + "csp": Object { + "warnLegacyBrowsers": true, + }, + "env": Object { + "mode": Object { + "dev": Any, + "name": Any, + "prod": Any, + }, + "packageInfo": Object { + "branch": Any, + "buildNum": Any, + "buildSha": Any, + "dist": Any, + "version": Any, + }, + }, + "externalUrl": Object { + "policy": Array [ + Object { + "allow": true, + }, + ], + }, + "i18n": Object { + "translationsUrl": "/mock-server-basepath/translations/en.json", + }, + "legacyMetadata": Object { + "uiSettings": Object { + "defaults": Object { + "registered": Object { + "name": "title", + }, + }, + "user": Object { + "theme:darkMode": Object { + "userValue": true, + }, + }, + }, + }, + "publicBaseUrl": "http://myhost.com/mock-server-basepath", + "serverBasePath": "/mock-server-basepath", + "uiPlugins": Array [], + "vars": Object {}, + "version": Any, +} +`; + +exports[`RenderingService preboot() render() renders "core" page for blank basepath 1`] = ` +Object { + "anonymousStatusPage": false, + "basePath": "", + "branch": Any, + "buildNumber": Any, + "csp": Object { + "warnLegacyBrowsers": true, + }, + "env": Object { + "mode": Object { + "dev": Any, + "name": Any, + "prod": Any, + }, + "packageInfo": Object { + "branch": Any, + "buildNum": Any, + "buildSha": Any, + "dist": Any, + "version": Any, + }, + }, + "externalUrl": Object { + "policy": Array [ + Object { + "allow": true, + }, + ], + }, + "i18n": Object { + "translationsUrl": "/translations/en.json", + }, + "legacyMetadata": Object { + "uiSettings": Object { + "defaults": Object { + "registered": Object { + "name": "title", + }, + }, + "user": Object {}, + }, + }, + "publicBaseUrl": "http://myhost.com/mock-server-basepath", + "serverBasePath": "/mock-server-basepath", + "uiPlugins": Array [], + "vars": Object {}, + "version": Any, +} +`; + +exports[`RenderingService preboot() render() renders "core" with excluded user settings 1`] = ` +Object { + "anonymousStatusPage": false, + "basePath": "/mock-server-basepath", + "branch": Any, + "buildNumber": Any, + "csp": Object { + "warnLegacyBrowsers": true, + }, + "env": Object { + "mode": Object { + "dev": Any, + "name": Any, + "prod": Any, + }, + "packageInfo": Object { + "branch": Any, + "buildNum": Any, + "buildSha": Any, + "dist": Any, + "version": Any, + }, + }, + "externalUrl": Object { + "policy": Array [ + Object { + "allow": true, + }, + ], + }, + "i18n": Object { + "translationsUrl": "/mock-server-basepath/translations/en.json", + }, + "legacyMetadata": Object { + "uiSettings": Object { + "defaults": Object { + "registered": Object { + "name": "title", + }, + }, + "user": Object {}, + }, + }, + "publicBaseUrl": "http://myhost.com/mock-server-basepath", + "serverBasePath": "/mock-server-basepath", + "uiPlugins": Array [], + "vars": Object {}, + "version": Any, +} +`; + exports[`RenderingService setup() render() renders "core" from legacy request 1`] = ` Object { "anonymousStatusPage": false, diff --git a/src/core/server/rendering/bootstrap/get_plugin_bundle_paths.test.ts b/src/core/server/rendering/bootstrap/get_plugin_bundle_paths.test.ts index 0abd8fd5a0057..67f8507b9b700 100644 --- a/src/core/server/rendering/bootstrap/get_plugin_bundle_paths.test.ts +++ b/src/core/server/rendering/bootstrap/get_plugin_bundle_paths.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { InternalPluginInfo, UiPlugins } from '../../plugins'; +import { InternalPluginInfo, PluginType, UiPlugins } from '../../plugins'; import { getPluginsBundlePaths } from './get_plugin_bundle_paths'; const createUiPlugins = (pluginDeps: Record) => { @@ -26,6 +26,7 @@ const createUiPlugins = (pluginDeps: Record) => { uiPlugins.public.set(pluginId, { id: pluginId, configPath: 'config-path', + type: PluginType.standard, optionalPlugins: [], requiredPlugins: [], requiredBundles: deps, diff --git a/src/core/server/rendering/rendering_service.mock.ts b/src/core/server/rendering/rendering_service.mock.ts index 81418c58175f5..3d8213da62c6d 100644 --- a/src/core/server/rendering/rendering_service.mock.ts +++ b/src/core/server/rendering/rendering_service.mock.ts @@ -6,7 +6,14 @@ * Side Public License, v 1. */ -import { InternalRenderingServiceSetup } from './types'; +import { InternalRenderingServicePreboot, InternalRenderingServiceSetup } from './types'; + +function createRenderingPreboot() { + const mocked: jest.Mocked = { + render: jest.fn().mockResolvedValue(''), + }; + return mocked; +} function createRenderingSetup() { const mocked: jest.Mocked = { @@ -16,5 +23,6 @@ function createRenderingSetup() { } export const renderingMock = { + createPrebootContract: createRenderingPreboot, createSetupContract: createRenderingSetup, }; diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts index bba0dc6fd8a67..de7d21add6c6c 100644 --- a/src/core/server/rendering/rendering_service.test.ts +++ b/src/core/server/rendering/rendering_service.test.ts @@ -16,9 +16,14 @@ import { import { load } from 'cheerio'; import { httpServerMock } from '../http/http_server.mocks'; +import { mockRouter } from '../http/router/router.mock'; import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; -import { mockRenderingServiceParams, mockRenderingSetupDeps } from './__mocks__/params'; -import { InternalRenderingServiceSetup } from './types'; +import { + mockRenderingServiceParams, + mockRenderingPrebootDeps, + mockRenderingSetupDeps, +} from './__mocks__/params'; +import { InternalRenderingServicePreboot, InternalRenderingServiceSetup } from './types'; import { RenderingService } from './rendering_service'; const INJECTED_METADATA = { @@ -43,106 +48,145 @@ const INJECTED_METADATA = { const { createKibanaRequest, createRawRequest } = httpServerMock; -describe('RenderingService', () => { - let service: RenderingService; +function renderTestCases( + getRender: () => Promise< + [ + InternalRenderingServicePreboot['render'] | InternalRenderingServiceSetup['render'], + typeof mockRenderingPrebootDeps | typeof mockRenderingSetupDeps + ] + > +) { + describe('render()', () => { + let uiSettings: ReturnType; + + beforeEach(async () => { + uiSettings = uiSettingsServiceMock.createClient(); + uiSettings.getRegistered.mockReturnValue({ + registered: { name: 'title' }, + }); + }); - beforeEach(() => { - jest.clearAllMocks(); - service = new RenderingService(mockRenderingServiceParams); + it('renders "core" page', async () => { + const [render] = await getRender(); + const content = await render(createKibanaRequest(), uiSettings); + const dom = load(content); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); - getSettingValueMock.mockImplementation((settingName: string) => settingName); - getStylesheetPathsMock.mockReturnValue(['/style-1.css', '/style-2.css']); - }); + expect(data).toMatchSnapshot(INJECTED_METADATA); + }); - describe('setup()', () => { - it('calls `registerBootstrapRoute` with the correct parameters', async () => { - await service.setup(mockRenderingSetupDeps); + it('renders "core" page for blank basepath', async () => { + const [render, deps] = await getRender(); + deps.http.basePath.get.mockReturnValueOnce(''); - expect(registerBootstrapRouteMock).toHaveBeenCalledTimes(1); - expect(registerBootstrapRouteMock).toHaveBeenCalledWith({ - router: expect.any(Object), - renderer: bootstrapRendererMock, - }); - }); + const content = await render(createKibanaRequest(), uiSettings); + const dom = load(content); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); - describe('render()', () => { - let uiSettings: ReturnType; - let render: InternalRenderingServiceSetup['render']; + expect(data).toMatchSnapshot(INJECTED_METADATA); + }); - beforeEach(async () => { - uiSettings = uiSettingsServiceMock.createClient(); - uiSettings.getRegistered.mockReturnValue({ - registered: { name: 'title' }, - }); - render = (await service.setup(mockRenderingSetupDeps)).render; - }); + it('renders "core" page driven by settings', async () => { + uiSettings.getUserProvided.mockResolvedValue({ 'theme:darkMode': { userValue: true } }); + const [render] = await getRender(); + const content = await render(createKibanaRequest(), uiSettings); + const dom = load(content); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); - it('renders "core" page', async () => { - const content = await render(createKibanaRequest(), uiSettings); - const dom = load(content); - const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); + expect(data).toMatchSnapshot(INJECTED_METADATA); + }); - expect(data).toMatchSnapshot(INJECTED_METADATA); + it('renders "core" with excluded user settings', async () => { + const [render] = await getRender(); + const content = await render(createKibanaRequest(), uiSettings, { + includeUserSettings: false, }); + const dom = load(content); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); - it('renders "core" page for blank basepath', async () => { - mockRenderingSetupDeps.http.basePath.get.mockReturnValueOnce(''); + expect(data).toMatchSnapshot(INJECTED_METADATA); + }); - const content = await render(createKibanaRequest(), uiSettings); - const dom = load(content); - const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); + it('renders "core" from legacy request', async () => { + const [render] = await getRender(); + const content = await render(createRawRequest(), uiSettings); + const dom = load(content); + const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); - expect(data).toMatchSnapshot(INJECTED_METADATA); + expect(data).toMatchSnapshot(INJECTED_METADATA); + }); + + it('calls `getStylesheetPaths` with the correct parameters', async () => { + getSettingValueMock.mockImplementation((settingName: string) => { + if (settingName === 'theme:darkMode') { + return true; + } + if (settingName === 'theme:version') { + return 'v8'; + } + return settingName; }); - it('renders "core" page driven by settings', async () => { - uiSettings.getUserProvided.mockResolvedValue({ 'theme:darkMode': { userValue: true } }); - const content = await render(createKibanaRequest(), uiSettings); - const dom = load(content); - const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); + const [render] = await getRender(); + await render(createKibanaRequest(), uiSettings); - expect(data).toMatchSnapshot(INJECTED_METADATA); + expect(getStylesheetPathsMock).toHaveBeenCalledTimes(1); + expect(getStylesheetPathsMock).toHaveBeenCalledWith({ + darkMode: true, + themeVersion: 'v8', + basePath: '/mock-server-basepath', + buildNum: expect.any(Number), }); + }); + }); +} - it('renders "core" with excluded user settings', async () => { - const content = await render(createKibanaRequest(), uiSettings, { - includeUserSettings: false, - }); - const dom = load(content); - const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); +describe('RenderingService', () => { + let service: RenderingService; - expect(data).toMatchSnapshot(INJECTED_METADATA); - }); + beforeEach(() => { + jest.clearAllMocks(); + service = new RenderingService(mockRenderingServiceParams); - it('renders "core" from legacy request', async () => { - const content = await render(createRawRequest(), uiSettings); - const dom = load(content); - const data = JSON.parse(dom('kbn-injected-metadata').attr('data') ?? '""'); + getSettingValueMock.mockImplementation((settingName: string) => settingName); + getStylesheetPathsMock.mockReturnValue(['/style-1.css', '/style-2.css']); + }); + + describe('preboot()', () => { + it('calls `registerBootstrapRoute` with the correct parameters', async () => { + const routerMock = mockRouter.create(); + mockRenderingPrebootDeps.http.registerRoutes.mockImplementation((path, callback) => + callback(routerMock) + ); + + await service.preboot(mockRenderingPrebootDeps); - expect(data).toMatchSnapshot(INJECTED_METADATA); + expect(registerBootstrapRouteMock).toHaveBeenCalledTimes(1); + expect(registerBootstrapRouteMock).toHaveBeenCalledWith({ + router: routerMock, + renderer: bootstrapRendererMock, }); + }); + + renderTestCases(async () => { + return [(await service.preboot(mockRenderingPrebootDeps)).render, mockRenderingPrebootDeps]; + }); + }); + + describe('setup()', () => { + it('calls `registerBootstrapRoute` with the correct parameters', async () => { + await service.setup(mockRenderingSetupDeps); - it('calls `getStylesheetPaths` with the correct parameters', async () => { - getSettingValueMock.mockImplementation((settingName: string) => { - if (settingName === 'theme:darkMode') { - return true; - } - if (settingName === 'theme:version') { - return 'v8'; - } - return settingName; - }); - - await render(createKibanaRequest(), uiSettings); - - expect(getStylesheetPathsMock).toHaveBeenCalledTimes(1); - expect(getStylesheetPathsMock).toHaveBeenCalledWith({ - darkMode: true, - themeVersion: 'v8', - basePath: '/mock-server-basepath', - buildNum: expect.any(Number), - }); + expect(registerBootstrapRouteMock).toHaveBeenCalledTimes(1); + expect(registerBootstrapRouteMock).toHaveBeenCalledWith({ + router: expect.any(Object), + renderer: bootstrapRendererMock, }); }); + + renderTestCases(async () => { + await service.preboot(mockRenderingPrebootDeps); + return [(await service.setup(mockRenderingSetupDeps)).render, mockRenderingSetupDeps]; + }); }); }); diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index fd4e1140d68b4..2d95822d92219 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -16,100 +16,130 @@ import { CoreContext } from '../core_context'; import { Template } from './views'; import { IRenderOptions, + RenderingPrebootDeps, RenderingSetupDeps, + InternalRenderingServicePreboot, InternalRenderingServiceSetup, RenderingMetadata, } from './types'; import { registerBootstrapRoute, bootstrapRendererFactory } from './bootstrap'; import { getSettingValue, getStylesheetPaths } from './render_utils'; +import { KibanaRequest, LegacyRequest } from '../http'; +import { IUiSettingsClient } from '../ui_settings'; + +type RenderOptions = (RenderingPrebootDeps & { status?: never }) | RenderingSetupDeps; /** @internal */ export class RenderingService { constructor(private readonly coreContext: CoreContext) {} + public async preboot({ + http, + uiPlugins, + }: RenderingPrebootDeps): Promise { + http.registerRoutes('', (router) => { + registerBootstrapRoute({ + router, + renderer: bootstrapRendererFactory({ + uiPlugins, + serverBasePath: http.basePath.serverBasePath, + packageInfo: this.coreContext.env.packageInfo, + auth: http.auth, + }), + }); + }); + + return { + render: this.render.bind(this, { http, uiPlugins }), + }; + } + public async setup({ http, status, uiPlugins, }: RenderingSetupDeps): Promise { - const router = http.createRouter(''); - - const bootstrapRenderer = bootstrapRendererFactory({ - uiPlugins, - serverBasePath: http.basePath.serverBasePath, - packageInfo: this.coreContext.env.packageInfo, - auth: http.auth, + registerBootstrapRoute({ + router: http.createRouter(''), + renderer: bootstrapRendererFactory({ + uiPlugins, + serverBasePath: http.basePath.serverBasePath, + packageInfo: this.coreContext.env.packageInfo, + auth: http.auth, + }), }); - registerBootstrapRoute({ router, renderer: bootstrapRenderer }); return { - render: async ( - request, - uiSettings, - { includeUserSettings = true, vars }: IRenderOptions = {} - ) => { - const env = { - mode: this.coreContext.env.mode, - packageInfo: this.coreContext.env.packageInfo, - }; - const buildNum = env.packageInfo.buildNum; - const basePath = http.basePath.get(request); - const { serverBasePath, publicBaseUrl } = http.basePath; - const settings = { - defaults: uiSettings.getRegistered(), - user: includeUserSettings ? await uiSettings.getUserProvided() : {}, - }; + render: this.render.bind(this, { http, uiPlugins, status }), + }; + } - const darkMode = getSettingValue('theme:darkMode', settings, Boolean); - const themeVersion = getSettingValue('theme:version', settings, String); + private async render( + { http, uiPlugins, status }: RenderOptions, + request: KibanaRequest | LegacyRequest, + uiSettings: IUiSettingsClient, + { includeUserSettings = true, vars }: IRenderOptions = {} + ) { + const env = { + mode: this.coreContext.env.mode, + packageInfo: this.coreContext.env.packageInfo, + }; + const buildNum = env.packageInfo.buildNum; + const basePath = http.basePath.get(request); + const { serverBasePath, publicBaseUrl } = http.basePath; + const settings = { + defaults: uiSettings.getRegistered() ?? {}, + user: includeUserSettings ? await uiSettings.getUserProvided() : {}, + }; - const stylesheetPaths = getStylesheetPaths({ - darkMode, - themeVersion, - basePath: serverBasePath, - buildNum, - }); + const darkMode = getSettingValue('theme:darkMode', settings, Boolean); + const themeVersion = getSettingValue('theme:version', settings, String); - const metadata: RenderingMetadata = { - strictCsp: http.csp.strict, - uiPublicUrl: `${basePath}/ui`, - bootstrapScriptUrl: `${basePath}/bootstrap.js`, - i18n: i18n.translate, - locale: i18n.getLocale(), - darkMode, - stylesheetPaths, - themeVersion, - injectedMetadata: { - version: env.packageInfo.version, - buildNumber: env.packageInfo.buildNum, - branch: env.packageInfo.branch, - basePath, - serverBasePath, - publicBaseUrl, - env, - anonymousStatusPage: status.isStatusPageAnonymous(), - i18n: { - translationsUrl: `${basePath}/translations/${i18n.getLocale()}.json`, - }, - csp: { warnLegacyBrowsers: http.csp.warnLegacyBrowsers }, - externalUrl: http.externalUrl, - vars: vars ?? {}, - uiPlugins: await Promise.all( - [...uiPlugins.public].map(async ([id, plugin]) => ({ - id, - plugin, - config: await getUiConfig(uiPlugins, id), - })) - ), - legacyMetadata: { - uiSettings: settings, - }, - }, - }; + const stylesheetPaths = getStylesheetPaths({ + darkMode, + themeVersion, + basePath: serverBasePath, + buildNum, + }); - return `${renderToStaticMarkup(