diff --git a/.ci/Jenkinsfile_coverage b/.ci/Jenkinsfile_coverage index 650ef94e1d3da..bd55bd73966ff 100644 --- a/.ci/Jenkinsfile_coverage +++ b/.ci/Jenkinsfile_coverage @@ -28,13 +28,13 @@ def handleIngestion(timestamp) { kibanaCoverage.collectVcsInfo("### Collect VCS Info") kibanaCoverage.generateReports("### Merge coverage reports") kibanaCoverage.uploadCombinedReports() - kibanaCoverage.ingest(timestamp, '### Injest && Upload') + kibanaCoverage.ingest(env.JOB_NAME, BUILD_NUMBER, BUILD_URL, timestamp, '### Ingest && Upload') kibanaCoverage.uploadCoverageStaticSite(timestamp) } def handleFail() { def buildStatus = buildUtils.getBuildStatus() - if(params.NOTIFY_ON_FAILURE && buildStatus != 'SUCCESS' && buildStatus != 'ABORTED') { + if(params.NOTIFY_ON_FAILURE && buildStatus != 'SUCCESS' && buildStatus != 'ABORTED' && buildStatus != 'UNSTABLE') { slackNotifications.sendFailedBuild( channel: '#kibana-qa', username: 'Kibana QA' diff --git a/.eslintrc.js b/.eslintrc.js index 8d5b4525d51ba..8d979dc0f8645 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -64,63 +64,63 @@ module.exports = { * Temporarily disable some react rules for specific plugins, remove in separate PRs */ { - files: ['packages/kbn-ui-framework/**/*.{js,ts,tsx}'], + files: ['packages/kbn-ui-framework/**/*.{js,mjs,ts,tsx}'], rules: { 'jsx-a11y/no-onchange': 'off', }, }, { - files: ['src/plugins/es_ui_shared/**/*.{js,ts,tsx}'], + files: ['src/plugins/es_ui_shared/**/*.{js,mjs,ts,tsx}'], rules: { 'react-hooks/exhaustive-deps': 'off', }, }, { - files: ['src/plugins/kibana_react/**/*.{js,ts,tsx}'], + files: ['src/plugins/kibana_react/**/*.{js,mjs,ts,tsx}'], rules: { 'react-hooks/rules-of-hooks': 'off', 'react-hooks/exhaustive-deps': 'off', }, }, { - files: ['src/plugins/kibana_utils/**/*.{js,ts,tsx}'], + files: ['src/plugins/kibana_utils/**/*.{js,mjs,ts,tsx}'], rules: { 'react-hooks/exhaustive-deps': 'off', }, }, { - files: ['x-pack/plugins/canvas/**/*.{js,ts,tsx}'], + files: ['x-pack/plugins/canvas/**/*.{js,mjs,ts,tsx}'], rules: { 'jsx-a11y/click-events-have-key-events': 'off', }, }, { - files: ['x-pack/plugins/cross_cluster_replication/**/*.{js,ts,tsx}'], + files: ['x-pack/plugins/cross_cluster_replication/**/*.{js,mjs,ts,tsx}'], rules: { 'jsx-a11y/click-events-have-key-events': 'off', }, }, { - files: ['x-pack/legacy/plugins/index_management/**/*.{js,ts,tsx}'], + files: ['x-pack/legacy/plugins/index_management/**/*.{js,mjs,ts,tsx}'], rules: { 'react-hooks/exhaustive-deps': 'off', 'react-hooks/rules-of-hooks': 'off', }, }, { - files: ['x-pack/plugins/lens/**/*.{js,ts,tsx}'], + files: ['x-pack/plugins/lens/**/*.{js,mjs,ts,tsx}'], rules: { 'react-hooks/exhaustive-deps': 'off', }, }, { - files: ['x-pack/plugins/ml/**/*.{js,ts,tsx}'], + files: ['x-pack/plugins/ml/**/*.{js,mjs,ts,tsx}'], rules: { 'react-hooks/exhaustive-deps': 'off', }, }, { - files: ['x-pack/legacy/plugins/snapshot_restore/**/*.{js,ts,tsx}'], + files: ['x-pack/legacy/plugins/snapshot_restore/**/*.{js,mjs,ts,tsx}'], rules: { 'react-hooks/exhaustive-deps': 'off', }, @@ -132,7 +132,7 @@ module.exports = { * Licence headers */ { - files: ['**/*.{js,ts,tsx}', '!plugins/**/*'], + files: ['**/*.{js,mjs,ts,tsx}', '!plugins/**/*'], rules: { '@kbn/eslint/require-license-header': [ 'error', @@ -153,7 +153,7 @@ module.exports = { * New Platform client-side */ { - files: ['{src,x-pack}/plugins/*/public/**/*.{js,ts,tsx}'], + files: ['{src,x-pack}/plugins/*/public/**/*.{js,mjs,ts,tsx}'], rules: { 'import/no-commonjs': 'error', }, @@ -163,7 +163,7 @@ module.exports = { * Files that require Elastic license headers instead of Apache 2.0 header */ { - files: ['x-pack/**/*.{js,ts,tsx}'], + files: ['x-pack/**/*.{js,mjs,ts,tsx}'], rules: { '@kbn/eslint/require-license-header': [ 'error', @@ -184,7 +184,7 @@ module.exports = { * Restricted paths */ { - files: ['**/*.{js,ts,tsx}'], + files: ['**/*.{js,mjs,ts,tsx}'], rules: { '@kbn/eslint/no-restricted-paths': [ 'error', @@ -251,8 +251,8 @@ module.exports = { ], from: [ '(src|x-pack)/plugins/**/(public|server)/**/*', - '!(src|x-pack)/plugins/**/(public|server)/mocks/index.{js,ts}', - '!(src|x-pack)/plugins/**/(public|server)/(index|mocks).{js,ts,tsx}', + '!(src|x-pack)/plugins/**/(public|server)/mocks/index.{js,mjs,ts}', + '!(src|x-pack)/plugins/**/(public|server)/(index|mocks).{js,mjs,ts,tsx}', ], allowSameFolder: true, errorMessage: 'Plugins may only import from top-level public and server modules.', @@ -264,11 +264,11 @@ module.exports = { 'src/legacy/core_plugins/**/*', '!src/legacy/core_plugins/**/server/**/*', - '!src/legacy/core_plugins/**/index.{js,ts,tsx}', + '!src/legacy/core_plugins/**/index.{js,mjs,ts,tsx}', 'x-pack/legacy/plugins/**/*', '!x-pack/legacy/plugins/**/server/**/*', - '!x-pack/legacy/plugins/**/index.{js,ts,tsx}', + '!x-pack/legacy/plugins/**/index.{js,mjs,ts,tsx}', 'examples/**/*', '!examples/**/server/**/*', @@ -334,6 +334,7 @@ module.exports = { */ { files: [ + 'x-pack/test/apm_api_integration/**/*.ts', 'x-pack/test/functional/apps/**/*.js', 'x-pack/plugins/apm/**/*.js', 'test/*/config.ts', @@ -530,7 +531,7 @@ module.exports = { * Jest specific rules */ { - files: ['**/*.test.{js,ts,tsx}'], + files: ['**/*.test.{js,mjs,ts,tsx}'], rules: { 'jest/valid-describe': 'error', }, @@ -595,8 +596,8 @@ module.exports = { { // front end and common typescript and javascript files only files: [ - 'x-pack/plugins/security_solution/public/**/*.{js,ts,tsx}', - 'x-pack/plugins/security_solution/common/**/*.{js,ts,tsx}', + 'x-pack/plugins/security_solution/public/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/security_solution/common/**/*.{js,mjs,ts,tsx}', ], rules: { 'import/no-nodejs-modules': 'error', @@ -646,7 +647,7 @@ module.exports = { // { // // will introduced after the other warns are fixed // // typescript and javascript for front end react performance - // files: ['x-pack/plugins/security_solution/public/**/!(*.test).{js,ts,tsx}'], + // files: ['x-pack/plugins/security_solution/public/**/!(*.test).{js,mjs,ts,tsx}'], // plugins: ['react-perf'], // rules: { // // 'react-perf/jsx-no-new-object-as-prop': 'error', @@ -657,7 +658,7 @@ module.exports = { // }, { // typescript and javascript for front and back end - files: ['x-pack/{,legacy/}plugins/security_solution/**/*.{js,ts,tsx}'], + files: ['x-pack/{,legacy/}plugins/security_solution/**/*.{js,mjs,ts,tsx}'], plugins: ['eslint-plugin-node', 'react'], env: { mocha: true, @@ -776,8 +777,8 @@ module.exports = { { // front end and common typescript and javascript files only files: [ - 'x-pack/plugins/lists/public/**/*.{js,ts,tsx}', - 'x-pack/plugins/lists/common/**/*.{js,ts,tsx}', + 'x-pack/plugins/lists/public/**/*.{js,mjs,ts,tsx}', + 'x-pack/plugins/lists/common/**/*.{js,mjs,ts,tsx}', ], rules: { 'import/no-nodejs-modules': 'error', @@ -792,7 +793,7 @@ module.exports = { }, { // typescript and javascript for front and back end - files: ['x-pack/plugins/lists/**/*.{js,ts,tsx}'], + files: ['x-pack/plugins/lists/**/*.{js,mjs,ts,tsx}'], plugins: ['eslint-plugin-node'], env: { mocha: true, @@ -888,7 +889,7 @@ module.exports = { { // typescript only for front and back end files: [ - 'x-pack/{,legacy/}plugins/{alerting,alerting_builtins,actions,task_manager,event_log}/**/*.{ts,tsx}', + 'x-pack/{,legacy/}plugins/{alerts,alerting_builtins,actions,task_manager,event_log}/**/*.{ts,tsx}', ], rules: { '@typescript-eslint/no-explicit-any': 'error', @@ -1020,8 +1021,8 @@ module.exports = { */ { files: [ - 'src/plugins/vis_type_timeseries/**/*.{js,ts,tsx}', - 'src/legacy/core_plugins/vis_type_timeseries/**/*.{js,ts,tsx}', + 'src/plugins/vis_type_timeseries/**/*.{js,mjs,ts,tsx}', + 'src/legacy/core_plugins/vis_type_timeseries/**/*.{js,mjs,ts,tsx}', ], rules: { 'import/no-default-export': 'error', @@ -1039,5 +1040,22 @@ module.exports = { ...require('eslint-config-prettier/@typescript-eslint').rules, }, }, + + { + files: [ + // platform-team owned code + 'src/core/**', + 'x-pack/plugins/features/**', + 'x-pack/plugins/licensing/**', + 'x-pack/plugins/global_search/**', + 'x-pack/plugins/cloud/**', + 'packages/kbn-config-schema', + 'src/plugins/status_page/**', + 'src/plugins/saved_objects_management/**', + ], + rules: { + '@typescript-eslint/prefer-ts-expect-error': 'error', + }, + }, ], }; diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e6f6e83253c8b..bec0a0a33bad2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -84,7 +84,7 @@ /x-pack/plugins/infra/ @elastic/logs-metrics-ui /x-pack/plugins/ingest_manager/ @elastic/ingest-management /x-pack/legacy/plugins/ingest_manager/ @elastic/ingest-management -/x-pack/plugins/observability/ @elastic/logs-metrics-ui @elastic/apm-ui @elastic/uptime @elastic/ingest-management +/x-pack/plugins/observability/ @elastic/observability-ui /x-pack/legacy/plugins/monitoring/ @elastic/stack-monitoring-ui /x-pack/plugins/monitoring/ @elastic/stack-monitoring-ui /x-pack/plugins/uptime @elastic/uptime @@ -132,6 +132,7 @@ # Quality Assurance /src/dev/code_coverage @elastic/kibana-qa +/vars/*Coverage.groovy @elastic/kibana-qa /test/functional/services/common @elastic/kibana-qa /test/functional/services/lib @elastic/kibana-qa /test/functional/services/remote @elastic/kibana-qa @@ -170,6 +171,7 @@ # Kibana Telemetry /packages/kbn-analytics/ @elastic/kibana-telemetry +/packages/kbn-telemetry-tools/ @elastic/kibana-telemetry /src/plugins/kibana_usage_collection/ @elastic/kibana-telemetry /src/plugins/newsfeed/ @elastic/kibana-telemetry /src/plugins/telemetry/ @elastic/kibana-telemetry @@ -177,6 +179,11 @@ /src/plugins/telemetry_management_section/ @elastic/kibana-telemetry /src/plugins/usage_collection/ @elastic/kibana-telemetry /x-pack/plugins/telemetry_collection_xpack/ @elastic/kibana-telemetry +/.telemetryrc.json @elastic/kibana-telemetry +/x-pack/.telemetryrc.json @elastic/kibana-telemetry +src/plugins/telemetry/schema/legacy_oss_plugins.json @elastic/kibana-telemetry +src/plugins/telemetry/schema/oss_plugins.json @elastic/kibana-telemetry +x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kibana-telemetry # Kibana Alerting Services /x-pack/plugins/alerts/ @elastic/kibana-alerting-services diff --git a/.telemetryrc.json b/.telemetryrc.json new file mode 100644 index 0000000000000..30643a104c1cd --- /dev/null +++ b/.telemetryrc.json @@ -0,0 +1,25 @@ +[ + { + "output": "src/plugins/telemetry/schema/legacy_oss_plugins.json", + "root": "src/legacy/core_plugins/", + "exclude": [ + "src/legacy/core_plugins/testbed", + "src/legacy/core_plugins/elasticsearch", + "src/legacy/core_plugins/tests_bundle" + ] + }, + { + "output": "src/plugins/telemetry/schema/oss_plugins.json", + "root": "src/plugins/", + "exclude": [ + "src/plugins/kibana_react/", + "src/plugins/testbed/", + "src/plugins/kibana_utils/", + "src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts", + "src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts", + "src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts", + "src/plugins/kibana_usage_collection/server/collectors/ui_metric/telemetry_ui_metric_collector.ts", + "src/plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts" + ] + } +] diff --git a/docs/apm/apm-alerts.asciidoc b/docs/apm/apm-alerts.asciidoc index 75ce5f56c96c6..bc5e1ccc1dd55 100644 --- a/docs/apm/apm-alerts.asciidoc +++ b/docs/apm/apm-alerts.asciidoc @@ -15,15 +15,20 @@ and enables central management of all alerts from <>. + +The APM app supports two different types of threshold alerts: transaction duration, and error rate. Below, we'll create one of each. [float] [[apm-create-transaction-alert]] === Create a transaction duration alert -This guide creates an alert for the `opbeans-java` service based on the following criteria: +Transaction duration alerts trigger when the duration of a specific transaction type in a service exceeds a defined threshold. +This guide will create an alert for the `opbeans-java` service based on the following criteria: +* Environment: Production * Transaction type: `transaction.type:request` * Average request is above `1500ms` for the last 5 minutes * Check every 10 minutes, and repeat the alert every 30 minutes @@ -52,14 +57,22 @@ Enter a name for the connector, and paste the webhook URL. See Slack's webhook documentation if you need to create one. +Add a message body in markdown format. +You can use the https://mustache.github.io/[Mustache] template syntax, i.e., `{{variable}}` +to pass alert values at the time a condition is detected to an action. +A list of available variables can be accessed by selecting the +**add variable** button image:apm/images/add-variable.png[add variable button]. + Select **Save**. The alert has been created and is now active! [float] [[apm-create-error-alert]] === Create an error rate alert +Error rate alerts trigger when the number of errors in a service exceeds a defined threshold. This guide creates an alert for the `opbeans-python` service based on the following criteria: +* Environment: Production * Error rate is above 25 for the last minute * Check every 1 minute, and repeat the alert every 10 minutes * Send the alert via email to the `opbeans-python` team @@ -81,6 +94,12 @@ Based on the alert criteria, define the following alert details: Select the **Email** action type and click **Create a connector**. Fill out the required details: sender, host, port, etc., and click **save**. +Add a message body in markdown format. +You can use the https://mustache.github.io/[Mustache] template syntax, i.e., `{{variable}}` +to pass alert values at the time a condition is detected to an action. +A list of available variables can be accessed by selecting the +**add variable** button image:apm/images/add-variable.png[add variable button]. + Select **Save**. The alert has been created and is now active! [float] diff --git a/docs/apm/images/add-variable.png b/docs/apm/images/add-variable.png new file mode 100644 index 0000000000000..860ab66f22f4e Binary files /dev/null and b/docs/apm/images/add-variable.png differ diff --git a/docs/apm/troubleshooting.asciidoc b/docs/apm/troubleshooting.asciidoc index eb4fb790afd7f..65f7a378ec244 100644 --- a/docs/apm/troubleshooting.asciidoc +++ b/docs/apm/troubleshooting.asciidoc @@ -10,6 +10,11 @@ your proposed changes at https://github.com/elastic/kibana. Also, check out the https://discuss.elastic.co/c/apm[APM discussion forum]. +* <> +* <> +* <> +* <> + [float] [[no-apm-data-found]] === No APM data found @@ -58,6 +63,66 @@ Navigate to *APM* > *Settings* > *Indices*, and change all `apm_oss.*Pattern` va include the new index pattern. For example: `customIndexName-*`. [float] +[[troubleshooting-too-many-transactions]] +=== Too many unique transaction names + +Transaction names are defined in each APM Agent; when an Agent supports a framework, +it includes logic for naming the transactions that the framework creates. +In some cases though, like when using an Agent's API to create custom transactions, +it is up to the user to define a pattern for transaction naming. +When transactions are named incorrectly, each unique URL can be associated with a unique transaction group—causing +an explosion in the number of transaction groups per service, and leading to inaccuracies in the APM app. + +To fix a large number of unique transaction names, +you need to change how you are using the Agent API to name your transactions. +To do this, ensure you are **not** naming based on parameters that can change. +For example, user ids, product ids, order numbers, query parameters, etc., +should be stripped away, and commonality should be found between your unique URLs. + +Let's look at an example from the RUM Agent documentation. Here are a few URLs you might find on Elastic.co: + +[source,yml] +---- +// Blog Posts +https://www.elastic.co/blog/reflections-on-three-years-in-the-elastic-public-sector +https://www.elastic.co/blog/say-heya-to-the-elastic-search-awards +https://www.elastic.co/blog/and-the-winner-of-the-elasticon-2018-training-subscription-drawing-is + +// Documentation +https://www.elastic.co/guide/en/elastic-stack/current/index.html +https://www.elastic.co/guide/en/apm/get-started/current/index.html +https://www.elastic.co/guide/en/infrastructure/guide/current/index.html +---- + +These URLs, like most, include unique names. +If we named transactions based on each unique URL, we'd end up with the problem described above—a +very large number of different transaction names. +Instead, we should strip away the unique information and group our transactions based on common information. +In this case, that means naming all blog transactions, `/blog`, and all documentation transactions, `/guide`. + +If you feel like you'd be losing valuable information by following this naming convention, don't fret! +You can always add additional metadata to your transactions using {apm-overview-ref-v}/metadata.html#labels-fields[labels] (indexed) or +{apm-overview-ref-v}/metadata.html#custom-fields[custom context] (non-indexed). + +After ensuring you've correctly named your transactions, +you might still see an error in the APM app related to too many transaction names. +If this is the case, you can increase the default number of transaction groups displayed in the APM app by configuring +<>. + +**More information** + +While this can happen with any APM Agent, it typically occurs with the RUM Agent. +For more information on how to correctly set `transaction.name` in the RUM Agent, +see {apm-rum-ref}/custom-transaction-name.html[custom initial page load transaction names]. + +The RUM Agent can also set the `transaction.name` when observing for transaction events. +See {apm-rum-ref}/agent-api.html#observe[`apm.observe()`] for more information. + +If your problem is occurring in a different Agent, the tips above still apply. +See the relevant {apm-agents-ref}[Agent API documentation] to adjust how you're naming your transactions. + +[float] +[[troubleshooting-unknown-route]] === Unknown route The {apm-app-ref}/transactions.html[transaction overview] will only display helpful information @@ -78,6 +143,7 @@ Specifically, view the Agent's supported technologies page. You can also use the Agent's public API to manually set a name for the transaction. [float] +[[troubleshooting-fields-unsearchable]] === Fields are not searchable In Elasticsearch, index templates are used to define settings and mappings that determine how fields should be analyzed. diff --git a/docs/developer/core/development-unit-tests.asciidoc b/docs/developer/core/development-unit-tests.asciidoc index a738e2cf372d9..04cce0dfec901 100644 --- a/docs/developer/core/development-unit-tests.asciidoc +++ b/docs/developer/core/development-unit-tests.asciidoc @@ -22,7 +22,7 @@ yarn test:mocha [float] ==== Jest -Jest tests are stored in the same directory as source code files with the `.test.{js,ts,tsx}` suffix. +Jest tests are stored in the same directory as source code files with the `.test.{js,mjs,ts,tsx}` suffix. *Running Jest Unit Tests* diff --git a/docs/development/core/public/kibana-plugin-core-public.md b/docs/development/core/public/kibana-plugin-core-public.md index b0612ff4d5b65..8f2bde3856019 100644 --- a/docs/development/core/public/kibana-plugin-core-public.md +++ b/docs/development/core/public/kibana-plugin-core-public.md @@ -172,7 +172,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PublicAppInfo](./kibana-plugin-core-public.publicappinfo.md) | Public information about a registered [application](./kibana-plugin-core-public.app.md) | | [PublicLegacyAppInfo](./kibana-plugin-core-public.publiclegacyappinfo.md) | Information about a registered [legacy application](./kibana-plugin-core-public.legacyapp.md) | | [PublicUiSettingsParams](./kibana-plugin-core-public.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) exposed to the client-side. | -| [RecursiveReadonly](./kibana-plugin-core-public.recursivereadonly.md) | | | [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | | [SavedObjectAttributeSingle](./kibana-plugin-core-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-core-public.savedobjectattribute.md) | | [SavedObjectsClientContract](./kibana-plugin-core-public.savedobjectsclientcontract.md) | SavedObjectsClientContract as implemented by the [SavedObjectsClient](./kibana-plugin-core-public.savedobjectsclient.md) | diff --git a/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md index c70f3a97a8882..4b3b103c92731 100644 --- a/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md +++ b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md @@ -11,5 +11,8 @@ Public information about a registered [application](./kibana-plugin-core-public. ```typescript export declare type PublicAppInfo = Omit & { legacy: false; + status: AppStatus; + navLinkStatus: AppNavLinkStatus; + appRoute: string; }; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md b/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md index cc3e9de3193cb..051638daabd12 100644 --- a/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md +++ b/docs/development/core/public/kibana-plugin-core-public.publiclegacyappinfo.md @@ -11,5 +11,7 @@ Information about a registered [legacy application](./kibana-plugin-core-public. ```typescript export declare type PublicLegacyAppInfo = Omit & { legacy: true; + status: AppStatus; + navLinkStatus: AppNavLinkStatus; }; ``` diff --git a/docs/development/core/public/kibana-plugin-core-public.recursivereadonly.md b/docs/development/core/public/kibana-plugin-core-public.recursivereadonly.md deleted file mode 100644 index 2f47ef1086d71..0000000000000 --- a/docs/development/core/public/kibana-plugin-core-public.recursivereadonly.md +++ /dev/null @@ -1,14 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [RecursiveReadonly](./kibana-plugin-core-public.recursivereadonly.md) - -## RecursiveReadonly type - - -Signature: - -```typescript -export declare type RecursiveReadonly = T extends (...args: any[]) => any ? T : T extends any[] ? RecursiveReadonlyArray : T extends object ? Readonly<{ - [K in keyof T]: RecursiveReadonly; -}> : T; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.callapioptions.md b/docs/development/core/server/kibana-plugin-core-server.callapioptions.md deleted file mode 100644 index 03f74424acf99..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.callapioptions.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CallAPIOptions](./kibana-plugin-core-server.callapioptions.md) - -## CallAPIOptions interface - -The set of options that defines how API call should be made and result be processed. - -Signature: - -```typescript -export interface CallAPIOptions -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [signal](./kibana-plugin-core-server.callapioptions.signal.md) | AbortSignal | A signal object that allows you to abort the request via an AbortController object. | -| [wrap401Errors](./kibana-plugin-core-server.callapioptions.wrap401errors.md) | boolean | 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. | - diff --git a/docs/development/core/server/kibana-plugin-core-server.clusterclient._constructor_.md b/docs/development/core/server/kibana-plugin-core-server.clusterclient._constructor_.md deleted file mode 100644 index d4b2f80443acb..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.clusterclient._constructor_.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ClusterClient](./kibana-plugin-core-server.clusterclient.md) > [(constructor)](./kibana-plugin-core-server.clusterclient._constructor_.md) - -## ClusterClient.(constructor) - -Constructs a new instance of the `ClusterClient` class - -Signature: - -```typescript -constructor(config: ElasticsearchClientConfig, log: Logger, getAuthHeaders?: GetAuthHeaders); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| config | ElasticsearchClientConfig | | -| log | Logger | | -| getAuthHeaders | GetAuthHeaders | | - diff --git a/docs/development/core/server/kibana-plugin-core-server.clusterclient.asscoped.md b/docs/development/core/server/kibana-plugin-core-server.clusterclient.asscoped.md deleted file mode 100644 index dd1dcffc5a969..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.clusterclient.asscoped.md +++ /dev/null @@ -1,24 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ClusterClient](./kibana-plugin-core-server.clusterclient.md) > [asScoped](./kibana-plugin-core-server.clusterclient.asscoped.md) - -## ClusterClient.asScoped() method - -Creates an instance of [IScopedClusterClient](./kibana-plugin-core-server.iscopedclusterclient.md) based on the configuration the current cluster client that exposes additional `callAsCurrentUser` method scoped to the provided req. Consumers shouldn't worry about closing scoped client instances, these will be automatically closed as soon as the original cluster client isn't needed anymore and closed. - -Signature: - -```typescript -asScoped(request?: ScopeableRequest): IScopedClusterClient; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| request | ScopeableRequest | Request the IScopedClusterClient instance will be scoped to. Supports request optionality, Legacy.Request & FakeRequest for BWC with LegacyPlatform | - -Returns: - -`IScopedClusterClient` - diff --git a/docs/development/core/server/kibana-plugin-core-server.clusterclient.callasinternaluser.md b/docs/development/core/server/kibana-plugin-core-server.clusterclient.callasinternaluser.md deleted file mode 100644 index ae9a9b46b52b6..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.clusterclient.callasinternaluser.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ClusterClient](./kibana-plugin-core-server.clusterclient.md) > [callAsInternalUser](./kibana-plugin-core-server.clusterclient.callasinternaluser.md) - -## ClusterClient.callAsInternalUser property - -Calls specified endpoint with provided clientParams on behalf of the Kibana internal user. See [APICaller](./kibana-plugin-core-server.apicaller.md). - -Signature: - -```typescript -callAsInternalUser: APICaller; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.clusterclient.md b/docs/development/core/server/kibana-plugin-core-server.clusterclient.md deleted file mode 100644 index 0f6b2512a6d94..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.clusterclient.md +++ /dev/null @@ -1,35 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ClusterClient](./kibana-plugin-core-server.clusterclient.md) - -## ClusterClient class - -Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via `asScoped(...)`). - -See [ClusterClient](./kibana-plugin-core-server.clusterclient.md). - -Signature: - -```typescript -export declare class ClusterClient implements IClusterClient -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(config, log, getAuthHeaders)](./kibana-plugin-core-server.clusterclient._constructor_.md) | | Constructs a new instance of the ClusterClient class | - -## Properties - -| Property | Modifiers | Type | Description | -| --- | --- | --- | --- | -| [callAsInternalUser](./kibana-plugin-core-server.clusterclient.callasinternaluser.md) | | APICaller | Calls specified endpoint with provided clientParams on behalf of the Kibana internal user. See [APICaller](./kibana-plugin-core-server.apicaller.md). | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [asScoped(request)](./kibana-plugin-core-server.clusterclient.asscoped.md) | | Creates an instance of [IScopedClusterClient](./kibana-plugin-core-server.iscopedclusterclient.md) based on the configuration the current cluster client that exposes additional callAsCurrentUser method scoped to the provided req. Consumers shouldn't worry about closing scoped client instances, these will be automatically closed as soon as the original cluster client isn't needed anymore and closed. | -| [close()](./kibana-plugin-core-server.clusterclient.close.md) | | Closes the cluster client. After that client cannot be used and one should create a new client instance to be able to interact with Elasticsearch API. | - diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearcherror._code_.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearcherror._code_.md deleted file mode 100644 index d2b07e8d116ba..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearcherror._code_.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchError](./kibana-plugin-core-server.elasticsearcherror.md) > [\[code\]](./kibana-plugin-core-server.elasticsearcherror._code_.md) - -## ElasticsearchError.\[code\] property - -Signature: - -```typescript -[code]?: string; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearcherror.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearcherror.md deleted file mode 100644 index 42079c02689c5..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearcherror.md +++ /dev/null @@ -1,19 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchError](./kibana-plugin-core-server.elasticsearcherror.md) - -## ElasticsearchError interface - - -Signature: - -```typescript -export interface ElasticsearchError extends Boom -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [\[code\]](./kibana-plugin-core-server.elasticsearcherror._code_.md) | string | | - diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearcherrorhelpers.isnotauthorizederror.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearcherrorhelpers.isnotauthorizederror.md deleted file mode 100644 index d85ae4f9ab530..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearcherrorhelpers.isnotauthorizederror.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchErrorHelpers](./kibana-plugin-core-server.elasticsearcherrorhelpers.md) > [isNotAuthorizedError](./kibana-plugin-core-server.elasticsearcherrorhelpers.isnotauthorizederror.md) - -## ElasticsearchErrorHelpers.isNotAuthorizedError() method - -Signature: - -```typescript -static isNotAuthorizedError(error: any): error is ElasticsearchError; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| error | any | | - -Returns: - -`error is ElasticsearchError` - diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md index e8c4c63dc6a96..c683f0ba33189 100644 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.legacy.md @@ -13,7 +13,7 @@ ```typescript legacy: { - readonly createClient: (type: string, clientConfig?: Partial) => ICustomClusterClient; - readonly client: IClusterClient; + readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient; + readonly client: ILegacyClusterClient; }; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md index c1e23527e9516..0dd41a6154a1e 100644 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicesetup.md @@ -15,5 +15,5 @@ export interface ElasticsearchServiceSetup | Property | Type | Description | | --- | --- | --- | -| [legacy](./kibana-plugin-core-server.elasticsearchservicesetup.legacy.md) | {
readonly createClient: (type: string, clientConfig?: Partial<ElasticsearchClientConfig>) => ICustomClusterClient;
readonly client: IClusterClient;
} | | +| [legacy](./kibana-plugin-core-server.elasticsearchservicesetup.legacy.md) | {
readonly createClient: (type: string, clientConfig?: Partial<LegacyElasticsearchClientConfig>) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
} | | diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md index 667a36091f232..5f346d7887c2a 100644 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.legacy.md @@ -13,7 +13,7 @@ ```typescript legacy: { - readonly createClient: (type: string, clientConfig?: Partial) => ICustomClusterClient; - readonly client: IClusterClient; + readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient; + readonly client: ILegacyClusterClient; }; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md index 39c794af2c881..e059acdbd52fa 100644 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md +++ b/docs/development/core/server/kibana-plugin-core-server.elasticsearchservicestart.md @@ -15,5 +15,5 @@ export interface ElasticsearchServiceStart | Property | Type | Description | | --- | --- | --- | -| [legacy](./kibana-plugin-core-server.elasticsearchservicestart.legacy.md) | {
readonly createClient: (type: string, clientConfig?: Partial<ElasticsearchClientConfig>) => ICustomClusterClient;
readonly client: IClusterClient;
} | | +| [legacy](./kibana-plugin-core-server.elasticsearchservicestart.legacy.md) | {
readonly createClient: (type: string, clientConfig?: Partial<LegacyElasticsearchClientConfig>) => ILegacyCustomClusterClient;
readonly client: ILegacyClusterClient;
} | | diff --git a/docs/development/core/server/kibana-plugin-core-server.iclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.ilegacyclusterclient.md similarity index 56% rename from docs/development/core/server/kibana-plugin-core-server.iclusterclient.md rename to docs/development/core/server/kibana-plugin-core-server.ilegacyclusterclient.md index 28fbdf17cf31c..c70a5ac07c6ad 100644 --- a/docs/development/core/server/kibana-plugin-core-server.iclusterclient.md +++ b/docs/development/core/server/kibana-plugin-core-server.ilegacyclusterclient.md @@ -1,15 +1,15 @@ -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IClusterClient](./kibana-plugin-core-server.iclusterclient.md) +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ILegacyClusterClient](./kibana-plugin-core-server.ilegacyclusterclient.md) -## IClusterClient type +## ILegacyClusterClient type Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via `asScoped(...)`). -See [ClusterClient](./kibana-plugin-core-server.clusterclient.md). +See [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md). Signature: ```typescript -export declare type IClusterClient = Pick; +export declare type ILegacyClusterClient = Pick; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.icustomclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.ilegacycustomclusterclient.md similarity index 53% rename from docs/development/core/server/kibana-plugin-core-server.icustomclusterclient.md rename to docs/development/core/server/kibana-plugin-core-server.ilegacycustomclusterclient.md index 3d379e1231cb1..a3cb8f1315021 100644 --- a/docs/development/core/server/kibana-plugin-core-server.icustomclusterclient.md +++ b/docs/development/core/server/kibana-plugin-core-server.ilegacycustomclusterclient.md @@ -1,15 +1,15 @@ -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ICustomClusterClient](./kibana-plugin-core-server.icustomclusterclient.md) +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ILegacyCustomClusterClient](./kibana-plugin-core-server.ilegacycustomclusterclient.md) -## ICustomClusterClient type +## ILegacyCustomClusterClient type Represents an Elasticsearch cluster API client created by a plugin. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via `asScoped(...)`). -See [ClusterClient](./kibana-plugin-core-server.clusterclient.md). +See [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md). Signature: ```typescript -export declare type ICustomClusterClient = Pick; +export declare type ILegacyCustomClusterClient = Pick; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.ilegacyscopedclusterclient.md similarity index 56% rename from docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md rename to docs/development/core/server/kibana-plugin-core-server.ilegacyscopedclusterclient.md index 378836ee461f3..1263b85acb498 100644 --- a/docs/development/core/server/kibana-plugin-core-server.iscopedclusterclient.md +++ b/docs/development/core/server/kibana-plugin-core-server.ilegacyscopedclusterclient.md @@ -1,15 +1,15 @@ -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IScopedClusterClient](./kibana-plugin-core-server.iscopedclusterclient.md) +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ILegacyScopedClusterClient](./kibana-plugin-core-server.ilegacyscopedclusterclient.md) -## IScopedClusterClient type +## ILegacyScopedClusterClient type Serves the same purpose as "normal" `ClusterClient` but exposes additional `callAsCurrentUser` method that doesn't use credentials of the Kibana internal user (as `callAsInternalUser` does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API. -See [ScopedClusterClient](./kibana-plugin-core-server.scopedclusterclient.md). +See [LegacyScopedClusterClient](./kibana-plugin-core-server.legacyscopedclusterclient.md). Signature: ```typescript -export declare type IScopedClusterClient = Pick; +export declare type ILegacyScopedClusterClient = Pick; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.apicaller.md b/docs/development/core/server/kibana-plugin-core-server.legacyapicaller.md similarity index 54% rename from docs/development/core/server/kibana-plugin-core-server.apicaller.md rename to docs/development/core/server/kibana-plugin-core-server.legacyapicaller.md index 4da23b3f821c5..e6c2878d2b355 100644 --- a/docs/development/core/server/kibana-plugin-core-server.apicaller.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyapicaller.md @@ -1,12 +1,12 @@ -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [APICaller](./kibana-plugin-core-server.apicaller.md) +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md) -## APICaller interface +## LegacyAPICaller interface Signature: ```typescript -export interface APICaller +export interface LegacyAPICaller ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.md b/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.md new file mode 100644 index 0000000000000..9ebe2fc57a54b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyCallAPIOptions](./kibana-plugin-core-server.legacycallapioptions.md) + +## LegacyCallAPIOptions interface + +The set of options that defines how API call should be made and result be processed. + +Signature: + +```typescript +export interface LegacyCallAPIOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [signal](./kibana-plugin-core-server.legacycallapioptions.signal.md) | AbortSignal | A signal object that allows you to abort the request via an AbortController object. | +| [wrap401Errors](./kibana-plugin-core-server.legacycallapioptions.wrap401errors.md) | boolean | 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. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.callapioptions.signal.md b/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.signal.md similarity index 57% rename from docs/development/core/server/kibana-plugin-core-server.callapioptions.signal.md rename to docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.signal.md index 8b35924826f15..7d795a59e41a5 100644 --- a/docs/development/core/server/kibana-plugin-core-server.callapioptions.signal.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.signal.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CallAPIOptions](./kibana-plugin-core-server.callapioptions.md) > [signal](./kibana-plugin-core-server.callapioptions.signal.md) +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyCallAPIOptions](./kibana-plugin-core-server.legacycallapioptions.md) > [signal](./kibana-plugin-core-server.legacycallapioptions.signal.md) -## CallAPIOptions.signal property +## LegacyCallAPIOptions.signal property A signal object that allows you to abort the request via an AbortController object. diff --git a/docs/development/core/server/kibana-plugin-core-server.callapioptions.wrap401errors.md b/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.wrap401errors.md similarity index 69% rename from docs/development/core/server/kibana-plugin-core-server.callapioptions.wrap401errors.md rename to docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.wrap401errors.md index 2a5b8054819a3..38fac54db77a4 100644 --- a/docs/development/core/server/kibana-plugin-core-server.callapioptions.wrap401errors.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacycallapioptions.wrap401errors.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CallAPIOptions](./kibana-plugin-core-server.callapioptions.md) > [wrap401Errors](./kibana-plugin-core-server.callapioptions.wrap401errors.md) +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyCallAPIOptions](./kibana-plugin-core-server.legacycallapioptions.md) > [wrap401Errors](./kibana-plugin-core-server.legacycallapioptions.wrap401errors.md) -## CallAPIOptions.wrap401Errors property +## LegacyCallAPIOptions.wrap401Errors property 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`. diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient._constructor_.md b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient._constructor_.md new file mode 100644 index 0000000000000..823f34bd7dd23 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient._constructor_.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md) > [(constructor)](./kibana-plugin-core-server.legacyclusterclient._constructor_.md) + +## LegacyClusterClient.(constructor) + +Constructs a new instance of the `LegacyClusterClient` class + +Signature: + +```typescript +constructor(config: LegacyElasticsearchClientConfig, log: Logger, getAuthHeaders?: GetAuthHeaders); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| config | LegacyElasticsearchClientConfig | | +| log | Logger | | +| getAuthHeaders | GetAuthHeaders | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.asscoped.md b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.asscoped.md new file mode 100644 index 0000000000000..1c25fc1d072b6 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.asscoped.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md) > [asScoped](./kibana-plugin-core-server.legacyclusterclient.asscoped.md) + +## LegacyClusterClient.asScoped() method + +Creates an instance of [ILegacyScopedClusterClient](./kibana-plugin-core-server.ilegacyscopedclusterclient.md) based on the configuration the current cluster client that exposes additional `callAsCurrentUser` method scoped to the provided req. Consumers shouldn't worry about closing scoped client instances, these will be automatically closed as soon as the original cluster client isn't needed anymore and closed. + +Signature: + +```typescript +asScoped(request?: ScopeableRequest): ILegacyScopedClusterClient; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| request | ScopeableRequest | Request the IScopedClusterClient instance will be scoped to. Supports request optionality, Legacy.Request & FakeRequest for BWC with LegacyPlatform | + +Returns: + +`ILegacyScopedClusterClient` + diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.callasinternaluser.md b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.callasinternaluser.md new file mode 100644 index 0000000000000..2e235485711c6 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.callasinternaluser.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md) > [callAsInternalUser](./kibana-plugin-core-server.legacyclusterclient.callasinternaluser.md) + +## LegacyClusterClient.callAsInternalUser property + +Calls specified endpoint with provided clientParams on behalf of the Kibana internal user. See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). + +Signature: + +```typescript +callAsInternalUser: LegacyAPICaller; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.clusterclient.close.md b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.close.md similarity index 64% rename from docs/development/core/server/kibana-plugin-core-server.clusterclient.close.md rename to docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.close.md index 68a1bd4b9d7cf..88a5ffce5bb17 100644 --- a/docs/development/core/server/kibana-plugin-core-server.clusterclient.close.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.close.md @@ -1,8 +1,8 @@ -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ClusterClient](./kibana-plugin-core-server.clusterclient.md) > [close](./kibana-plugin-core-server.clusterclient.close.md) +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md) > [close](./kibana-plugin-core-server.legacyclusterclient.close.md) -## ClusterClient.close() method +## LegacyClusterClient.close() method Closes the cluster client. After that client cannot be used and one should create a new client instance to be able to interact with Elasticsearch API. diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.md new file mode 100644 index 0000000000000..4f218ae552c99 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.legacyclusterclient.md @@ -0,0 +1,32 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md) + +## LegacyClusterClient class + + +Signature: + +```typescript +export declare class LegacyClusterClient implements ILegacyClusterClient +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(config, log, getAuthHeaders)](./kibana-plugin-core-server.legacyclusterclient._constructor_.md) | | Constructs a new instance of the LegacyClusterClient class | + +## Properties + +| Property | Modifiers | Type | Description | +| --- | --- | --- | --- | +| [callAsInternalUser](./kibana-plugin-core-server.legacyclusterclient.callasinternaluser.md) | | LegacyAPICaller | Calls specified endpoint with provided clientParams on behalf of the Kibana internal user. See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [asScoped(request)](./kibana-plugin-core-server.legacyclusterclient.asscoped.md) | | Creates an instance of [ILegacyScopedClusterClient](./kibana-plugin-core-server.ilegacyscopedclusterclient.md) based on the configuration the current cluster client that exposes additional callAsCurrentUser method scoped to the provided req. Consumers shouldn't worry about closing scoped client instances, these will be automatically closed as soon as the original cluster client isn't needed anymore and closed. | +| [close()](./kibana-plugin-core-server.legacyclusterclient.close.md) | | Closes the cluster client. After that client cannot be used and one should create a new client instance to be able to interact with Elasticsearch API. | + diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearchclientconfig.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearchclientconfig.md similarity index 52% rename from docs/development/core/server/kibana-plugin-core-server.elasticsearchclientconfig.md rename to docs/development/core/server/kibana-plugin-core-server.legacyelasticsearchclientconfig.md index 703b07808aca3..62b0f216c863c 100644 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearchclientconfig.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearchclientconfig.md @@ -1,14 +1,14 @@ -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchClientConfig](./kibana-plugin-core-server.elasticsearchclientconfig.md) +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyElasticsearchClientConfig](./kibana-plugin-core-server.legacyelasticsearchclientconfig.md) -## ElasticsearchClientConfig type +## LegacyElasticsearchClientConfig type Signature: ```typescript -export declare type ElasticsearchClientConfig = Pick & Pick & { +export declare type LegacyElasticsearchClientConfig = Pick & Pick & { pingTimeout?: ElasticsearchConfig['pingTimeout'] | ConfigOptions['pingTimeout']; requestTimeout?: ElasticsearchConfig['requestTimeout'] | ConfigOptions['requestTimeout']; sniffInterval?: ElasticsearchConfig['sniffInterval'] | ConfigOptions['sniffInterval']; diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror._code_.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror._code_.md new file mode 100644 index 0000000000000..05530ceb0d568 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror._code_.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyElasticsearchError](./kibana-plugin-core-server.legacyelasticsearcherror.md) > [\[code\]](./kibana-plugin-core-server.legacyelasticsearcherror._code_.md) + +## LegacyElasticsearchError.\[code\] property + +Signature: + +```typescript +[code]?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md new file mode 100644 index 0000000000000..f760780504e55 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyElasticsearchError](./kibana-plugin-core-server.legacyelasticsearcherror.md) + +## LegacyElasticsearchError interface + + +Signature: + +```typescript +export interface LegacyElasticsearchError extends Boom +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [\[code\]](./kibana-plugin-core-server.legacyelasticsearcherror._code_.md) | string | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearcherrorhelpers.decoratenotauthorizederror.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.decoratenotauthorizederror.md similarity index 52% rename from docs/development/core/server/kibana-plugin-core-server.elasticsearcherrorhelpers.decoratenotauthorizederror.md rename to docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.decoratenotauthorizederror.md index 2b2eeceb4e3c9..bd802a39e9339 100644 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearcherrorhelpers.decoratenotauthorizederror.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.decoratenotauthorizederror.md @@ -1,13 +1,13 @@ -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchErrorHelpers](./kibana-plugin-core-server.elasticsearcherrorhelpers.md) > [decorateNotAuthorizedError](./kibana-plugin-core-server.elasticsearcherrorhelpers.decoratenotauthorizederror.md) +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyElasticsearchErrorHelpers](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.md) > [decorateNotAuthorizedError](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.decoratenotauthorizederror.md) -## ElasticsearchErrorHelpers.decorateNotAuthorizedError() method +## LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError() method Signature: ```typescript -static decorateNotAuthorizedError(error: Error, reason?: string): ElasticsearchError; +static decorateNotAuthorizedError(error: Error, reason?: string): LegacyElasticsearchError; ``` ## Parameters @@ -19,5 +19,5 @@ static decorateNotAuthorizedError(error: Error, reason?: string): ElasticsearchE Returns: -`ElasticsearchError` +`LegacyElasticsearchError` diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.isnotauthorizederror.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.isnotauthorizederror.md new file mode 100644 index 0000000000000..f647916149458 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.isnotauthorizederror.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyElasticsearchErrorHelpers](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.md) > [isNotAuthorizedError](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.isnotauthorizederror.md) + +## LegacyElasticsearchErrorHelpers.isNotAuthorizedError() method + +Signature: + +```typescript +static isNotAuthorizedError(error: any): error is LegacyElasticsearchError; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| error | any | | + +Returns: + +`error is LegacyElasticsearchError` + diff --git a/docs/development/core/server/kibana-plugin-core-server.elasticsearcherrorhelpers.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.md similarity index 65% rename from docs/development/core/server/kibana-plugin-core-server.elasticsearcherrorhelpers.md rename to docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.md index e9c205e3dfa2c..e20dcd4ed253e 100644 --- a/docs/development/core/server/kibana-plugin-core-server.elasticsearcherrorhelpers.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherrorhelpers.md @@ -1,15 +1,15 @@ -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ElasticsearchErrorHelpers](./kibana-plugin-core-server.elasticsearcherrorhelpers.md) +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyElasticsearchErrorHelpers](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.md) -## ElasticsearchErrorHelpers class +## LegacyElasticsearchErrorHelpers class Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as `body.error.header[WWW-Authenticate]` Signature: ```typescript -export declare class ElasticsearchErrorHelpers +export declare class LegacyElasticsearchErrorHelpers ``` ## Example @@ -30,6 +30,6 @@ try { | Method | Modifiers | Description | | --- | --- | --- | -| [decorateNotAuthorizedError(error, reason)](./kibana-plugin-core-server.elasticsearcherrorhelpers.decoratenotauthorizederror.md) | static | | -| [isNotAuthorizedError(error)](./kibana-plugin-core-server.elasticsearcherrorhelpers.isnotauthorizederror.md) | static | | +| [decorateNotAuthorizedError(error, reason)](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.decoratenotauthorizederror.md) | static | | +| [isNotAuthorizedError(error)](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.isnotauthorizederror.md) | static | | diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient._constructor_.md b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient._constructor_.md new file mode 100644 index 0000000000000..bd1cd1e9f3d9b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient._constructor_.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyScopedClusterClient](./kibana-plugin-core-server.legacyscopedclusterclient.md) > [(constructor)](./kibana-plugin-core-server.legacyscopedclusterclient._constructor_.md) + +## LegacyScopedClusterClient.(constructor) + +Constructs a new instance of the `LegacyScopedClusterClient` class + +Signature: + +```typescript +constructor(internalAPICaller: LegacyAPICaller, scopedAPICaller: LegacyAPICaller, headers?: Headers | undefined); +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| internalAPICaller | LegacyAPICaller | | +| scopedAPICaller | LegacyAPICaller | | +| headers | Headers | undefined | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.scopedclusterclient.callascurrentuser.md b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md similarity index 58% rename from docs/development/core/server/kibana-plugin-core-server.scopedclusterclient.callascurrentuser.md rename to docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md index 68c0f8e6be1be..9130f9adde76e 100644 --- a/docs/development/core/server/kibana-plugin-core-server.scopedclusterclient.callascurrentuser.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md @@ -1,15 +1,15 @@ -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ScopedClusterClient](./kibana-plugin-core-server.scopedclusterclient.md) > [callAsCurrentUser](./kibana-plugin-core-server.scopedclusterclient.callascurrentuser.md) +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyScopedClusterClient](./kibana-plugin-core-server.legacyscopedclusterclient.md) > [callAsCurrentUser](./kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md) -## ScopedClusterClient.callAsCurrentUser() method +## LegacyScopedClusterClient.callAsCurrentUser() method -Calls specified `endpoint` with provided `clientParams` on behalf of the user initiated request to the Kibana server (via HTTP request headers). See [APICaller](./kibana-plugin-core-server.apicaller.md). +Calls specified `endpoint` with provided `clientParams` on behalf of the user initiated request to the Kibana server (via HTTP request headers). See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). Signature: ```typescript -callAsCurrentUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; +callAsCurrentUser(endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; ``` ## Parameters @@ -18,7 +18,7 @@ callAsCurrentUser(endpoint: string, clientParams?: Record, options? | --- | --- | --- | | endpoint | string | String descriptor of the endpoint e.g. cluster.getSettings or ping. | | clientParams | Record<string, any> | A dictionary of parameters that will be passed directly to the Elasticsearch JS client. | -| options | CallAPIOptions | Options that affect the way we call the API and process the result. | +| options | LegacyCallAPIOptions | Options that affect the way we call the API and process the result. | Returns: diff --git a/docs/development/core/server/kibana-plugin-core-server.scopedclusterclient.callasinternaluser.md b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md similarity index 55% rename from docs/development/core/server/kibana-plugin-core-server.scopedclusterclient.callasinternaluser.md rename to docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md index e769fd692a4d1..bf95782299e72 100644 --- a/docs/development/core/server/kibana-plugin-core-server.scopedclusterclient.callasinternaluser.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md @@ -1,15 +1,15 @@ -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ScopedClusterClient](./kibana-plugin-core-server.scopedclusterclient.md) > [callAsInternalUser](./kibana-plugin-core-server.scopedclusterclient.callasinternaluser.md) +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyScopedClusterClient](./kibana-plugin-core-server.legacyscopedclusterclient.md) > [callAsInternalUser](./kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md) -## ScopedClusterClient.callAsInternalUser() method +## LegacyScopedClusterClient.callAsInternalUser() method -Calls specified `endpoint` with provided `clientParams` on behalf of the Kibana internal user. See [APICaller](./kibana-plugin-core-server.apicaller.md). +Calls specified `endpoint` with provided `clientParams` on behalf of the Kibana internal user. See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). Signature: ```typescript -callAsInternalUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; +callAsInternalUser(endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; ``` ## Parameters @@ -18,7 +18,7 @@ callAsInternalUser(endpoint: string, clientParams?: Record, options | --- | --- | --- | | endpoint | string | String descriptor of the endpoint e.g. cluster.getSettings or ping. | | clientParams | Record<string, any> | A dictionary of parameters that will be passed directly to the Elasticsearch JS client. | -| options | CallAPIOptions | Options that affect the way we call the API and process the result. | +| options | LegacyCallAPIOptions | Options that affect the way we call the API and process the result. | Returns: diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.md new file mode 100644 index 0000000000000..f3d8a69b8ed05 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.legacyscopedclusterclient.md @@ -0,0 +1,26 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [LegacyScopedClusterClient](./kibana-plugin-core-server.legacyscopedclusterclient.md) + +## LegacyScopedClusterClient class + + +Signature: + +```typescript +export declare class LegacyScopedClusterClient implements ILegacyScopedClusterClient +``` + +## Constructors + +| Constructor | Modifiers | Description | +| --- | --- | --- | +| [(constructor)(internalAPICaller, scopedAPICaller, headers)](./kibana-plugin-core-server.legacyscopedclusterclient._constructor_.md) | | Constructs a new instance of the LegacyScopedClusterClient class | + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [callAsCurrentUser(endpoint, clientParams, options)](./kibana-plugin-core-server.legacyscopedclusterclient.callascurrentuser.md) | | Calls specified endpoint with provided clientParams on behalf of the user initiated request to the Kibana server (via HTTP request headers). See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). | +| [callAsInternalUser(endpoint, clientParams, options)](./kibana-plugin-core-server.legacyscopedclusterclient.callasinternaluser.md) | | Calls specified endpoint with provided clientParams on behalf of the Kibana internal user. See [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md). | + diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 74422c82fc9ea..f73595ea0a8ff 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -17,18 +17,18 @@ The plugin integrates with the core system via lifecycle events: `setup` | Class | Description | | --- | --- | | [BasePath](./kibana-plugin-core-server.basepath.md) | Access or manipulate the Kibana base path | -| [ClusterClient](./kibana-plugin-core-server.clusterclient.md) | Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-core-server.clusterclient.md). | | [CspConfig](./kibana-plugin-core-server.cspconfig.md) | CSP configuration for use in Kibana. | | [ElasticsearchConfig](./kibana-plugin-core-server.elasticsearchconfig.md) | Wrapper of config schema. | -| [ElasticsearchErrorHelpers](./kibana-plugin-core-server.elasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | | [KibanaRequest](./kibana-plugin-core-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | +| [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md) | | +| [LegacyElasticsearchErrorHelpers](./kibana-plugin-core-server.legacyelasticsearcherrorhelpers.md) | Helpers for working with errors returned from the Elasticsearch service.Since the internal data of errors are subject to change, consumers of the Elasticsearch service should always use these helpers to classify errors instead of checking error internals such as body.error.header[WWW-Authenticate] | +| [LegacyScopedClusterClient](./kibana-plugin-core-server.legacyscopedclusterclient.md) | | | [RouteValidationError](./kibana-plugin-core-server.routevalidationerror.md) | Error to return when the validation is not successful. | | [SavedObjectsClient](./kibana-plugin-core-server.savedobjectsclient.md) | | | [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) | | | [SavedObjectsRepository](./kibana-plugin-core-server.savedobjectsrepository.md) | | | [SavedObjectsSerializer](./kibana-plugin-core-server.savedobjectsserializer.md) | A serializer that can be used to manually convert [raw](./kibana-plugin-core-server.savedobjectsrawdoc.md) or [sanitized](./kibana-plugin-core-server.savedobjectsanitizeddoc.md) documents to the other kind. | | [SavedObjectTypeRegistry](./kibana-plugin-core-server.savedobjecttyperegistry.md) | Registry holding information about all the registered [saved object types](./kibana-plugin-core-server.savedobjectstype.md). | -| [ScopedClusterClient](./kibana-plugin-core-server.scopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-core-server.scopedclusterclient.md). | ## Enumerations @@ -54,7 +54,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | Interface | Description | | --- | --- | -| [APICaller](./kibana-plugin-core-server.apicaller.md) | | | [AssistanceAPIResponse](./kibana-plugin-core-server.assistanceapiresponse.md) | | | [AssistantAPIClientParams](./kibana-plugin-core-server.assistantapiclientparams.md) | | | [Authenticated](./kibana-plugin-core-server.authenticated.md) | | @@ -63,7 +62,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [AuthRedirectedParams](./kibana-plugin-core-server.authredirectedparams.md) | Result of auth redirection. | | [AuthResultParams](./kibana-plugin-core-server.authresultparams.md) | Result of successful authentication. | | [AuthToolkit](./kibana-plugin-core-server.authtoolkit.md) | A tool set defining an outcome of Auth interceptor for incoming request. | -| [CallAPIOptions](./kibana-plugin-core-server.callapioptions.md) | The set of options that defines how API call should be made and result be processed. | | [Capabilities](./kibana-plugin-core-server.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | | [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). | @@ -78,7 +76,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [DeprecationInfo](./kibana-plugin-core-server.deprecationinfo.md) | | | [DeprecationSettings](./kibana-plugin-core-server.deprecationsettings.md) | UiSettings deprecation field options. | | [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. | -| [ElasticsearchError](./kibana-plugin-core-server.elasticsearcherror.md) | | | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | | | [ElasticsearchServiceStart](./kibana-plugin-core-server.elasticsearchservicestart.md) | | | [ElasticsearchStatusMeta](./kibana-plugin-core-server.elasticsearchstatusmeta.md) | | @@ -104,6 +101,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IUiSettingsClient](./kibana-plugin-core-server.iuisettingsclient.md) | Server-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. | | [KibanaRequestEvents](./kibana-plugin-core-server.kibanarequestevents.md) | Request events. | | [KibanaRequestRoute](./kibana-plugin-core-server.kibanarequestroute.md) | Request specific route information exposed to a handler. | +| [LegacyAPICaller](./kibana-plugin-core-server.legacyapicaller.md) | | +| [LegacyCallAPIOptions](./kibana-plugin-core-server.legacycallapioptions.md) | The set of options that defines how API call should be made and result be processed. | +| [LegacyElasticsearchError](./kibana-plugin-core-server.legacyelasticsearcherror.md) | | | [LegacyRequest](./kibana-plugin-core-server.legacyrequest.md) | | | [LegacyServiceSetupDeps](./kibana-plugin-core-server.legacyservicesetupdeps.md) | | | [LegacyServiceStartDeps](./kibana-plugin-core-server.legacyservicestartdeps.md) | | @@ -128,7 +128,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [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. | -| [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.legacy.client](./kibana-plugin-core-server.scopedclusterclient.md) - 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 | +| [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.legacy.client](./kibana-plugin-core-server.legacyscopedclusterclient.md) - 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 | | [RouteConfig](./kibana-plugin-core-server.routeconfig.md) | Route specific configuration. | | [RouteConfigOptions](./kibana-plugin-core-server.routeconfigoptions.md) | Additional route options. | | [RouteConfigOptionsBody](./kibana-plugin-core-server.routeconfigoptionsbody.md) | Additional body options for a route | @@ -222,7 +222,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ConfigDeprecationProvider](./kibana-plugin-core-server.configdeprecationprovider.md) | A provider that should returns a list of [ConfigDeprecation](./kibana-plugin-core-server.configdeprecation.md).See [ConfigDeprecationFactory](./kibana-plugin-core-server.configdeprecationfactory.md) for more usage examples. | | [ConfigPath](./kibana-plugin-core-server.configpath.md) | | | [DestructiveRouteMethod](./kibana-plugin-core-server.destructiveroutemethod.md) | Set of HTTP methods changing the state of the server. | -| [ElasticsearchClientConfig](./kibana-plugin-core-server.elasticsearchclientconfig.md) | | | [Freezable](./kibana-plugin-core-server.freezable.md) | | | [GetAuthHeaders](./kibana-plugin-core-server.getauthheaders.md) | Get headers to authenticate a user against Elasticsearch. | | [GetAuthState](./kibana-plugin-core-server.getauthstate.md) | Gets authentication state for a request. Returned by auth interceptor. | @@ -234,16 +233,17 @@ The plugin integrates with the core system via lifecycle events: `setup` | [HttpResourcesResponseOptions](./kibana-plugin-core-server.httpresourcesresponseoptions.md) | HTTP Resources response parameters | | [HttpResponsePayload](./kibana-plugin-core-server.httpresponsepayload.md) | Data send to the client as a response payload. | | [IBasePath](./kibana-plugin-core-server.ibasepath.md) | Access or manipulate the Kibana base path[BasePath](./kibana-plugin-core-server.basepath.md) | -| [IClusterClient](./kibana-plugin-core-server.iclusterclient.md) | Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-core-server.clusterclient.md). | | [IContextProvider](./kibana-plugin-core-server.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | -| [ICustomClusterClient](./kibana-plugin-core-server.icustomclusterclient.md) | Represents an Elasticsearch cluster API client created by a plugin. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-core-server.clusterclient.md). | +| [ILegacyClusterClient](./kibana-plugin-core-server.ilegacyclusterclient.md) | Represents an Elasticsearch cluster API client created by the platform. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md). | +| [ILegacyCustomClusterClient](./kibana-plugin-core-server.ilegacycustomclusterclient.md) | Represents an Elasticsearch cluster API client created by a plugin. It allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [LegacyClusterClient](./kibana-plugin-core-server.legacyclusterclient.md). | +| [ILegacyScopedClusterClient](./kibana-plugin-core-server.ilegacyscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [LegacyScopedClusterClient](./kibana-plugin-core-server.legacyscopedclusterclient.md). | | [IsAuthenticated](./kibana-plugin-core-server.isauthenticated.md) | Returns authentication status for a request. | | [ISavedObjectsRepository](./kibana-plugin-core-server.isavedobjectsrepository.md) | See [SavedObjectsRepository](./kibana-plugin-core-server.savedobjectsrepository.md) | | [ISavedObjectTypeRegistry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) | See [SavedObjectTypeRegistry](./kibana-plugin-core-server.savedobjecttyperegistry.md) for documentation. | -| [IScopedClusterClient](./kibana-plugin-core-server.iscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-core-server.scopedclusterclient.md). | | [KibanaRequestRouteOptions](./kibana-plugin-core-server.kibanarequestrouteoptions.md) | Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. | | [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. | | [KnownHeaders](./kibana-plugin-core-server.knownheaders.md) | Set of well-known HTTP headers. | +| [LegacyElasticsearchClientConfig](./kibana-plugin-core-server.legacyelasticsearchclientconfig.md) | | | [LifecycleResponseFactory](./kibana-plugin-core-server.lifecycleresponsefactory.md) | Creates an object containing redirection or error response with error details, HTTP headers, and other data transmitted to the client. | | [LoggerConfigType](./kibana-plugin-core-server.loggerconfigtype.md) | | | [MIGRATION\_ASSISTANCE\_INDEX\_ACTION](./kibana-plugin-core-server.migration_assistance_index_action.md) | | @@ -257,7 +257,6 @@ The plugin integrates with the core system via lifecycle events: `setup` | [PluginName](./kibana-plugin-core-server.pluginname.md) | Dedicated type for plugin name/id that is supposed to make Map/Set/Arrays that use it as a key or value more obvious. | | [PluginOpaqueId](./kibana-plugin-core-server.pluginopaqueid.md) | | | [PublicUiSettingsParams](./kibana-plugin-core-server.publicuisettingsparams.md) | A sub-set of [UiSettingsParams](./kibana-plugin-core-server.uisettingsparams.md) exposed to the client-side. | -| [RecursiveReadonly](./kibana-plugin-core-server.recursivereadonly.md) | | | [RedirectResponseOptions](./kibana-plugin-core-server.redirectresponseoptions.md) | HTTP response parameters for redirection response | | [RequestHandler](./kibana-plugin-core-server.requesthandler.md) | A function executed when route path matched requested resource path. Request handler is expected to return a result of one of [KibanaResponseFactory](./kibana-plugin-core-server.kibanaresponsefactory.md) functions. | | [RequestHandlerContextContainer](./kibana-plugin-core-server.requesthandlercontextcontainer.md) | An object that handles registration of http request context providers. | diff --git a/docs/development/core/server/kibana-plugin-core-server.recursivereadonly.md b/docs/development/core/server/kibana-plugin-core-server.recursivereadonly.md deleted file mode 100644 index bc9cd4680b17d..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.recursivereadonly.md +++ /dev/null @@ -1,14 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [RecursiveReadonly](./kibana-plugin-core-server.recursivereadonly.md) - -## RecursiveReadonly type - - -Signature: - -```typescript -export declare type RecursiveReadonly = T extends (...args: any[]) => any ? T : T extends any[] ? RecursiveReadonlyArray : T extends object ? Readonly<{ - [K in keyof T]: RecursiveReadonly; -}> : T; -``` diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md index 7b887d6d421e4..b09fb121b8a63 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md @@ -14,7 +14,7 @@ core: { }; elasticsearch: { legacy: { - client: IScopedClusterClient; + client: ILegacyScopedClusterClient; }; }; uiSettings: { diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md index 99be0676bcda3..55d6e931ac158 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md @@ -6,7 +6,7 @@ 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.legacy.client](./kibana-plugin-core-server.scopedclusterclient.md) - 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 +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.legacy.client](./kibana-plugin-core-server.legacyscopedclusterclient.md) - 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 Signature: @@ -18,5 +18,5 @@ export interface RequestHandlerContext | Property | Type | Description | | --- | --- | --- | -| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
};
elasticsearch: {
legacy: {
client: IScopedClusterClient;
};
};
uiSettings: {
client: IUiSettingsClient;
};
} | | +| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
};
elasticsearch: {
legacy: {
client: ILegacyScopedClusterClient;
};
};
uiSettings: {
client: IUiSettingsClient;
};
} | | diff --git a/docs/development/core/server/kibana-plugin-core-server.scopedclusterclient._constructor_.md b/docs/development/core/server/kibana-plugin-core-server.scopedclusterclient._constructor_.md deleted file mode 100644 index d4d1d442a57f9..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.scopedclusterclient._constructor_.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ScopedClusterClient](./kibana-plugin-core-server.scopedclusterclient.md) > [(constructor)](./kibana-plugin-core-server.scopedclusterclient._constructor_.md) - -## ScopedClusterClient.(constructor) - -Constructs a new instance of the `ScopedClusterClient` class - -Signature: - -```typescript -constructor(internalAPICaller: APICaller, scopedAPICaller: APICaller, headers?: Headers | undefined); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| internalAPICaller | APICaller | | -| scopedAPICaller | APICaller | | -| headers | Headers | undefined | | - diff --git a/docs/development/core/server/kibana-plugin-core-server.scopedclusterclient.md b/docs/development/core/server/kibana-plugin-core-server.scopedclusterclient.md deleted file mode 100644 index 058d65b587dbe..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.scopedclusterclient.md +++ /dev/null @@ -1,29 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [ScopedClusterClient](./kibana-plugin-core-server.scopedclusterclient.md) - -## ScopedClusterClient class - -Serves the same purpose as "normal" `ClusterClient` but exposes additional `callAsCurrentUser` method that doesn't use credentials of the Kibana internal user (as `callAsInternalUser` does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API. - -See [ScopedClusterClient](./kibana-plugin-core-server.scopedclusterclient.md). - -Signature: - -```typescript -export declare class ScopedClusterClient implements IScopedClusterClient -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(internalAPICaller, scopedAPICaller, headers)](./kibana-plugin-core-server.scopedclusterclient._constructor_.md) | | Constructs a new instance of the ScopedClusterClient class | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [callAsCurrentUser(endpoint, clientParams, options)](./kibana-plugin-core-server.scopedclusterclient.callascurrentuser.md) | | Calls specified endpoint with provided clientParams on behalf of the user initiated request to the Kibana server (via HTTP request headers). See [APICaller](./kibana-plugin-core-server.apicaller.md). | -| [callAsInternalUser(endpoint, clientParams, options)](./kibana-plugin-core-server.scopedclusterclient.callasinternaluser.md) | | Calls specified endpoint with provided clientParams on behalf of the Kibana internal user. See [APICaller](./kibana-plugin-core-server.apicaller.md). | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md index 6574e7ee37926..0268846772f2c 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern._constructor_.md @@ -9,7 +9,7 @@ Constructs a new instance of the `IndexPattern` class Signature: ```typescript -constructor(id: string | undefined, { getConfig, savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, }: IndexPatternDeps); +constructor(id: string | undefined, { getConfig, savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, uiSettingsValues, }: IndexPatternDeps); ``` ## Parameters @@ -17,5 +17,5 @@ constructor(id: string | undefined, { getConfig, savedObjectsClient, apiClient, | Parameter | Type | Description | | --- | --- | --- | | id | string | undefined | | -| { getConfig, savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, } | IndexPatternDeps | | +| { getConfig, savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, uiSettingsValues, } | IndexPatternDeps | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md index d39b384c538f1..bc999a3bb48e3 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md @@ -14,7 +14,7 @@ export declare class IndexPattern implements IIndexPattern | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(id, { getConfig, savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, })](./kibana-plugin-plugins-data-public.indexpattern._constructor_.md) | | Constructs a new instance of the IndexPattern class | +| [(constructor)(id, { getConfig, savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, uiSettingsValues, })](./kibana-plugin-plugins-data-public.indexpattern._constructor_.md) | | Constructs a new instance of the IndexPattern class | ## Properties diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.fieldformatmap.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.fieldformatmap.md new file mode 100644 index 0000000000000..9a454feab1e0e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.fieldformatmap.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternAttributes](./kibana-plugin-plugins-data-public.indexpatternattributes.md) > [fieldFormatMap](./kibana-plugin-plugins-data-public.indexpatternattributes.fieldformatmap.md) + +## IndexPatternAttributes.fieldFormatMap property + +Signature: + +```typescript +fieldFormatMap?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.intervalname.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.intervalname.md new file mode 100644 index 0000000000000..5902496fcd0e7 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.intervalname.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternAttributes](./kibana-plugin-plugins-data-public.indexpatternattributes.md) > [intervalName](./kibana-plugin-plugins-data-public.indexpatternattributes.intervalname.md) + +## IndexPatternAttributes.intervalName property + +Signature: + +```typescript +intervalName?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md index 39ae328c14501..eff2349f053ff 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md @@ -20,7 +20,10 @@ export interface IndexPatternAttributes | Property | Type | Description | | --- | --- | --- | +| [fieldFormatMap](./kibana-plugin-plugins-data-public.indexpatternattributes.fieldformatmap.md) | string | | | [fields](./kibana-plugin-plugins-data-public.indexpatternattributes.fields.md) | string | | +| [intervalName](./kibana-plugin-plugins-data-public.indexpatternattributes.intervalname.md) | string | | +| [sourceFilters](./kibana-plugin-plugins-data-public.indexpatternattributes.sourcefilters.md) | string | | | [timeFieldName](./kibana-plugin-plugins-data-public.indexpatternattributes.timefieldname.md) | string | | | [title](./kibana-plugin-plugins-data-public.indexpatternattributes.title.md) | string | | | [type](./kibana-plugin-plugins-data-public.indexpatternattributes.type.md) | string | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.sourcefilters.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.sourcefilters.md new file mode 100644 index 0000000000000..43966112b97c3 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.sourcefilters.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternAttributes](./kibana-plugin-plugins-data-public.indexpatternattributes.md) > [sourceFilters](./kibana-plugin-plugins-data-public.indexpatternattributes.sourcefilters.md) + +## IndexPatternAttributes.sourceFilters property + +Signature: + +```typescript +sourceFilters?: string; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md index 85eb4825bc2e3..a25f4a0c373b2 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.querystringinput.md @@ -7,5 +7,5 @@ Signature: ```typescript -QueryStringInput: React.FC> +QueryStringInput: React.FC> ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md index fc141b8c89c18..498691c06285d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md @@ -7,7 +7,7 @@ Signature: ```typescript -SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "timeHistory" | "onFiltersUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "indicateNoData" | "timeHistory" | "onFiltersUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; } ``` 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 new file mode 100644 index 0000000000000..54e7cf92f500c --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.gettime.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [getTime](./kibana-plugin-plugins-data-server.gettime.md) + +## getTime() function + +Signature: + +```typescript +export declare function getTime(indexPattern: IIndexPattern | undefined, timeRange: TimeRange, options?: { + forceNow?: Date; + fieldName?: string; +}): import("../..").RangeFilter | undefined; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| indexPattern | IIndexPattern | undefined | | +| timeRange | TimeRange | | +| options | {
forceNow?: Date;
fieldName?: string;
} | | + +Returns: + +`import("../..").RangeFilter | undefined` + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.fieldformatmap.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.fieldformatmap.md new file mode 100644 index 0000000000000..84cc8c705ff59 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.fieldformatmap.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) > [fieldFormatMap](./kibana-plugin-plugins-data-server.indexpatternattributes.fieldformatmap.md) + +## IndexPatternAttributes.fieldFormatMap property + +Signature: + +```typescript +fieldFormatMap?: string; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.intervalname.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.intervalname.md new file mode 100644 index 0000000000000..77a0872546679 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.intervalname.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) > [intervalName](./kibana-plugin-plugins-data-server.indexpatternattributes.intervalname.md) + +## IndexPatternAttributes.intervalName property + +Signature: + +```typescript +intervalName?: string; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md index 1fcc49796f59e..4a5b61f5c179b 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md @@ -20,7 +20,10 @@ export interface IndexPatternAttributes | Property | Type | Description | | --- | --- | --- | +| [fieldFormatMap](./kibana-plugin-plugins-data-server.indexpatternattributes.fieldformatmap.md) | string | | | [fields](./kibana-plugin-plugins-data-server.indexpatternattributes.fields.md) | string | | +| [intervalName](./kibana-plugin-plugins-data-server.indexpatternattributes.intervalname.md) | string | | +| [sourceFilters](./kibana-plugin-plugins-data-server.indexpatternattributes.sourcefilters.md) | string | | | [timeFieldName](./kibana-plugin-plugins-data-server.indexpatternattributes.timefieldname.md) | string | | | [title](./kibana-plugin-plugins-data-server.indexpatternattributes.title.md) | string | | | [type](./kibana-plugin-plugins-data-server.indexpatternattributes.type.md) | string | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.sourcefilters.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.sourcefilters.md new file mode 100644 index 0000000000000..10223a6353f10 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.sourcefilters.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) > [sourceFilters](./kibana-plugin-plugins-data-server.indexpatternattributes.sourcefilters.md) + +## IndexPatternAttributes.sourceFilters property + +Signature: + +```typescript +sourceFilters?: string; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher._constructor_.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher._constructor_.md index f9bbcc5a356e1..d36ebd0745e8d 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher._constructor_.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher._constructor_.md @@ -9,12 +9,12 @@ Constructs a new instance of the `IndexPatternsFetcher` class Signature: ```typescript -constructor(callDataCluster: APICaller); +constructor(callDataCluster: LegacyAPICaller); ``` ## Parameters | Parameter | Type | Description | | --- | --- | --- | -| callDataCluster | APICaller | | +| callDataCluster | LegacyAPICaller | | 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 f492ba2843a69..c80112fb17dde 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -23,6 +23,7 @@ | Function | Description | | --- | --- | | [getDefaultSearchParams(config)](./kibana-plugin-plugins-data-server.getdefaultsearchparams.md) | | +| [getTime(indexPattern, timeRange, options)](./kibana-plugin-plugins-data-server.gettime.md) | | | [parseInterval(interval)](./kibana-plugin-plugins-data-server.parseinterval.md) | | | [plugin(initializerContext)](./kibana-plugin-plugins-data-server.plugin.md) | Static code to be shared externally | | [shouldReadFieldFromDocValues(aggregatable, esType)](./kibana-plugin-plugins-data-server.shouldreadfieldfromdocvalues.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index 2c7a833ab641b..74bffc516725f 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -12,6 +12,9 @@ start(core: CoreStart): { fieldFormats: { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; + indexPatterns: { + indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest) => Promise; + }; }; ``` @@ -28,5 +31,8 @@ start(core: CoreStart): { fieldFormats: { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; + indexPatterns: { + indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest) => Promise; + }; }` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.pluginstart.indexpatterns.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.pluginstart.indexpatterns.md new file mode 100644 index 0000000000000..02ed24e05bc10 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.pluginstart.indexpatterns.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [PluginStart](./kibana-plugin-plugins-data-server.pluginstart.md) > [indexPatterns](./kibana-plugin-plugins-data-server.pluginstart.indexpatterns.md) + +## PluginStart.indexPatterns property + +Signature: + +```typescript +indexPatterns: IndexPatternsServiceStart; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.pluginstart.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.pluginstart.md index 1377d82123d41..b878a179657ed 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.pluginstart.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.pluginstart.md @@ -15,5 +15,6 @@ export interface DataPluginStart | Property | Type | Description | | --- | --- | --- | | [fieldFormats](./kibana-plugin-plugins-data-server.pluginstart.fieldformats.md) | FieldFormatsStart | | +| [indexPatterns](./kibana-plugin-plugins-data-server.pluginstart.indexpatterns.md) | IndexPatternsServiceStart | | | [search](./kibana-plugin-plugins-data-server.pluginstart.search.md) | ISearchStart | | diff --git a/docs/glossary.asciidoc b/docs/glossary.asciidoc new file mode 100644 index 0000000000000..07c0bfcf35cb7 --- /dev/null +++ b/docs/glossary.asciidoc @@ -0,0 +1,413 @@ +[glossary] +[[glossary]] += Glossary + +<> | <> | <> | <> | <> | <> | <> | H | I | J | <> | <> | <> | N | O | <> | <> | R | <> | <> | <> | V | <> | X | Y | Z + +[float] +[[a_glos]] +== A + +[glossary] +[[glossary-action]] action :: ++ +-- +// tag::action-def[] +The alert-specific response that occurs when an alert fires. +An alert can have multiple actions. +See +{kibana-ref}/action-types.html[Action and connector types]. +// end::action-def[] +-- + +[[glossary-advanced-settings]] Advanced Settings :: +// tag::advanced-settings-def[] +Enables you to control the appearance and behavior of {kib} +by setting the date format, default index, and other attributes. +Part of {kib} Stack Management. +See {kibana-ref}/advanced-options.html[Advanced Settings]. +// end::advanced-settings-def[] + +[[glossary-alert]] alert :: +// tag::alert-def[] +A set of <>, schedules, and <> +that enable notifications. +See <>. +// end::alert-def[] + +[[glossary-alerts-and-actions]] Alerts and Actions :: +// tag::alerts-and-actions-def[] +A comprehensive view of all your alerts. Enables you to access and +manage alerts for all {kib} apps from one place. +See {kibana-ref}/alerting-getting-started.html[Alerts and Actions]. +// end::alerts-and-actions-def[] + +[[glossary-annotation]] annotation :: +// tag::annotation-def[] +A way to augment a data display with descriptive domain knowledge. +// end::annotation-def[] + + +[[glossary-app]] app :: +// tag::app-def[] +A top-level {kib} component that is accessed through the side navigation. +Apps include core {kib} components such as Discover and Dashboard, +solutions like Observability and Security, and special-purpose tools +like Maps and Stack Management. +// end::app-def[] + + +[float] +[[b_glos]] +== B + +[[glossary-basemap]] basemap :: +// tag::basemap-def[] +The background detail necessary to orient the location of a map. +// end::basemap-def[] + +[[glossary-bucket]] bucket :: +// tag::bucket-def[] +A set of documents in {kib} that have certain characteristics in common. +For example, matching documents might be bucketed by color, distance, or date range. +// end::bucket-def[] + +[[glossary-bucket-aggregation]] bucket aggregation:: +// tag::bucket-aggregation-def[] +An aggregation that creates buckets of documents. Each bucket is associated with a +criterion (depending on the aggregation type), which determines whether or not a document +in the current context falls into the bucket. +// end::bucket-aggregation-def[] + +[float] +[[c_glos]] +== C + +[[glossary-canvas]] Canvas :: +// tag::canvas-def[] +Enables you to create presentations and infographics that pull live data directly from {es}. +See {kibana-ref}/canvas.html[Canvas]. +// end::canvas-def[] + +[[glossary-canvas-language]] Canvas expression language:: +// tag::canvas-language-def[] +A pipeline-based expression language for manipulating and visualizing data. +Includes dozens of functions and other capabilities, such as table transforms, +type casting, and sub-expressions. Supports TinyMath functions for complex math calculations. +See {kibana-ref}/canvas-function-reference.html[Canvas function reference]. +// end::canvas-language-def[] + + +[[glossary-certainty]] certainty :: +// tag::certainty-def[] +Specifies how many documents must contain a pair of terms before it is considered +a useful connection in a graph. +// end::certainty-def[] + +[[glossary-condition]] condition :: +// tag::condition-def[] +Specifies the circumstances that must be met to trigger an alert. +// end::condition-def[] + +[[glossary-connector]] connector :: +// tag::connector-def[] +A configuration that enables integration with an external system (the destination for an action). +See {kibana-ref}/action-types.html[Action and connector types]. +// end::connector-def[] + +[[glossary-console]] Console :: +// tag::console-def[] +A tool for interacting with the {es} REST API. +You can send requests to {es}, view responses, +view API documentation, and get your request history. +See {kibana-ref}/console-kibana.html[Console]. +// end::console-def[] + +[float] +[[d_glos]] +== D + +[[glossary-dashboard]] dashboard :: +// tag::dashboard-def[] +A collection of +<>, <>, and +<> that +provide insights into your data from multiple perspectives. +// end::dashboard-def[] + +[[glossary-data-source]] data source :: +// tag::data-source-def[] +A file, database, or service that provides the underlying data for a map, Canvas element, or visualization. +// end::data-source-def[] + +[[glossary-discover]] Discover :: +// tag::discover-def[] +Enables you to search and filter your data to zoom in on the information +that you are interested in. +// end::discover-def[] + +[[glossary-drilldown]] drilldown :: +// tag::drilldown-def[] +A navigation path that retains context (time range and filters) +from the source to the destination, so you can view the data from a new perspective. +A dashboard that shows the overall status of multiple data centers +might have a drilldown to a dashboard for a single data center. See {kibana-ref}/drilldowns.html[Drilldowns]. +// end::drilldown-def[] + + + +[float] +[[e_glos]] +== E + +[[glossary-edge]] edge :: +// tag::edge-def[] +A connection between nodes in a graph that shows that they are related. +The line weight indicates the strength of the relationship. See +{kibana-ref}/xpack-graph.html[Graph]. +// end::edge-def[] + + +[[glossary-ems]] Elastic Maps Service (EMS) :: +// tag::ems-def[] +A service that provides basemap tiles, shape files, and other key features +that are essential for visualizing geospatial data. +// end::ems-def[] + +[[glossary-element]] element :: +// tag::element-def[] +A <> workpad object that displays an image, text, or visualization. +// end::element-def[] + + +[float] +[[f_glos]] +== F + +[[glossary-feature-controls]] Feature Controls :: +// tag::feature-controls-def[] +Enables administrators to customize which features are +available in each <>. See +{kibana-ref}//xpack-spaces.html#spaces-control-feature-visibility[Feature Controls]. +// end::feature-controls-def[] + +[float] +[[g_glos]] +== G + +[[glossary-graph]] graph :: +// tag::graph-def[] +A data structure and visualization that shows interconnections between +a set of entities. Each entity is represented by a node. Connections between +nodes are represented by <>. See {kibana-ref}/xpack-graph.html[Graph]. +// end::graph-def[] + +[[glossary-grok-debugger]] Grok Debugger :: +// tag::grok-debugger-def[] +A tool for building and debugging grok patterns. Grok is good for parsing +syslog, Apache, and other webserver logs. See +{kibana-ref}/xpack-grokdebugger.html[Debugging grok expressions]. +// end::grok-debugger-def[] + + +[float] +[[k_glos]] +== K + +[[glossary-kql]] {kib} Query Language (KQL) :: +// tag::kql-def[] +The default language for querying in {kib}. KQL provides +support for scripted fields. See +{kibana-ref}/kuery-query.html[Kibana Query Language]. +// end::kql-def[] + + +[float] +[[l_glos]] +== L + +[[glossary-lens]] Lens :: +// tag::lens-def[] +Enables you to build visualizations by dragging and dropping data fields. +Lens makes makes smart visualization suggestions for your data, +allowing you to switch between visualization types. +See {kibana-ref}/lens.html[Lens]. +// end::lens-def[] + + +[[glossary-lucene]] Lucene query syntax :: +// tag::lucene-def[] +The query syntax for {kib}’s legacy query language. The Lucene query +syntax is available under the options menu in the query bar and from +<>. +// end::lucene-def[] + +[float] +[[m_glos]] +== M + +[[glossary-map]] map :: +// tag::map-def[] +A representation of geographic data using symbols and labels. +See {kibana-ref}/maps.html[Maps]. +// end::map-def[] + +[[glossary-metric-aggregation]] metric aggregation :: +// tag::metric-aggregation-def[] +An aggregation that calculates and tracks metrics for a set of documents. +// end::metric-aggregation-def[] + + +[float] +[[p_glos]] +== P + +[[glossary-painless-lab]] Painless Lab :: +// tag::painless-lab-def[] +An interactive code editor that lets you test and debug Painless scripts in real-time. +See {kibana-ref}/painlesslab.html[Painless Lab]. +// end::painless-lab-def[] + + +[[glossary-panel]] panel :: +// tag::panel-def[] +A <> component that contains a +query element or visualization, such as a chart, table, or list. +// end::panel-def[] + + +[float] +[[q_glos]] +== Q + +[[glossary-query-profiler]] Query Profiler :: +// tag::query-profiler-def[] +A tool that enables you to inspect and analyze search queries to diagnose and debug poorly performing queries. +See {kibana-ref}/xpack-profiler.html[Query Profiler]. +// end::query-profiler-def[] + +[float] +[[s_glos]] +== S + +[[glossary-saved-object]] saved object :: +// tag::saved-object-def[] +A representation of a dashboard, visualization, map, index pattern, or Canvas workpad +that can be stored and reloaded. +// end::saved-object-def[] + +[[glossary-saved-search]] saved search :: +// tag::saved-search-def[] +The query text, filters, and time filter that make up a search, +saved for later retrieval and reuse. +// end::saved-search-def[] + +[[glossary-scripted-field]] scripted field :: +// tag::scripted-field-def[] +A field that computes data on the fly from the data in {es} indices. +Scripted field data is shown in Discover and used in visualizations. +// end::scripted-field-def[] + +[[glossary-shareable]] shareable :: +// tag::shareable-def[] +A Canvas workpad that can be embedded on any webpage. +Shareables enable you to display Canvas visualizations on internal wiki pages or public websites. +// end::shareable-def[] + +[[glossary-space]] space :: +// tag::space-def[] +A place for organizing <>, +<>, and other <> by category. +For example, you might have different spaces for each team, use case, or individual. +See +{kibana-ref}/xpack-spaces.html[Spaces]. +// end::space-def[] + + +[float] +[[t_glos]] +== T + +[[glossary-term-join]] term join :: +// tag::term-join-def[] +A shared key that combines vector features with the results of an +{es} terms aggregation. Term joins augment vector features with +properties for data-driven styling and rich tooltip content in maps. +// end::term-join-def[] + +[[glossary-time-filter]] time filter :: +// tag::time-filter-def[] +A {kib} control that constrains the search results to a particular time period. +// end::time-filter-def[] + +[[glossary-timelion]] Timelion :: +// tag::timelion-def[] +A tool for building a time series visualization that analyzes data in time order. +See {kibana-ref}/timelion.html[Timelion]. +// end::timelion-def[] + + +[[glossary-time-series-data]] time series data :: +// tag::time-series-data-def[] +Timestamped data such as logs, metrics, and events that is indexed on an ongoing basis. +// end::time-series-data-def[] + + +[[glossary-TSVB-data]] TSVB :: +// tag::tsvb-def[] +A time series data visualizer that allows you to combine an +infinite number of aggregations to display complex data. +See {kibana-ref}/TSVB.html[TSVB]. +// end::tsvb-def[] + + +[float] +[[u_glos]] +== U + +[[glossary-upgrade-assistant]] Upgrade Assistant :: +// tag::upgrade-assistant-def[] +A tool that helps you prepare for an upgrade to the next major version of +{es}. The assistant identifies the deprecated settings in your cluster and +indices and guides you through resolving issues, including reindexing. See +{kibana-ref}/upgrade-assistant.html[Upgrade Assistant]. +// end::upgrade-assistant-def[] + + +[float] +[[v_glos]] +== V + +[[glossary-vega]] Vega :: +// tag::vega-def[] +A declarative language used to create interactive visualizations. +See {kibana-ref}/vega-graph.html[Vega]. +// end::vega-def[] + +[[glossary-vector]] vector data:: +// tag::vector-def[] +Points, lines, and polygons used to represent a map. +// end::vector-def[] + +[[glossary-visualization]] visualization :: +// tag::visualization-def[] +A graphical representation of query results in {kib} (e.g., a histogram, line graph, pie chart, or heat map). +// end::visualization-def[] + +[float] +[[w_glos]] +== W + +[[glossary-watcher]] Watcher :: +// tag::watcher-def[] +The original suite of alerting features. +See +{kibana-ref}/watcher-ui.html[Watcher]. +// end::watcher-def[] + +[[glossary-workpad]] workpad :: +// tag::workpad-def[] +A workspace where you build presentations of your live data in <>. +See +{kibana-ref}/create-canvas-workpad.html[Create a workpad]. +// end::workpad-def[] diff --git a/package.json b/package.json index 10eaef8ed5dc7..b520be4df6969 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,6 @@ "resolutions": { "**/@types/node": ">=10.17.17 <10.20.0", "**/@types/react": "^16.9.36", - "**/@types/react-router": "^5.1.3", "**/@types/hapi": "^17.0.18", "**/@types/angular": "^1.6.56", "**/@types/hoist-non-react-statics": "^3.3.1", @@ -139,6 +138,7 @@ "@kbn/babel-preset": "1.0.0", "@kbn/config-schema": "1.0.0", "@kbn/i18n": "1.0.0", + "@kbn/telemetry-tools": "1.0.0", "@kbn/interpreter": "1.0.0", "@kbn/pm": "1.0.0", "@kbn/test-subj-selector": "0.2.1", @@ -201,6 +201,7 @@ "inline-style": "^2.0.0", "joi": "^13.5.2", "jquery": "^3.5.0", + "js-levenshtein": "^1.1.6", "js-yaml": "3.13.1", "json-stable-stringify": "^1.0.1", "json-stringify-pretty-compact": "1.2.0", @@ -244,8 +245,8 @@ "react-monaco-editor": "~0.27.0", "react-redux": "^7.2.0", "react-resize-detector": "^4.2.0", - "react-router": "^5.1.2", - "react-router-dom": "^5.1.2", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", "react-sizeme": "^2.3.6", "react-use": "^13.27.0", "reactcss": "1.2.3", @@ -360,6 +361,7 @@ "@types/markdown-it": "^0.0.7", "@types/minimatch": "^2.0.29", "@types/mocha": "^7.0.2", + "@types/mock-fs": "^4.10.0", "@types/moment-timezone": "^0.5.12", "@types/mustache": "^0.8.31", "@types/node": ">=10.17.17 <10.20.0", @@ -376,8 +378,8 @@ "@types/react-grid-layout": "^0.16.7", "@types/react-redux": "^7.1.9", "@types/react-resize-detector": "^4.0.1", - "@types/react-router": "^5.1.3", - "@types/react-router-dom": "^5.1.3", + "@types/react-router": "^5.1.7", + "@types/react-router-dom": "^5.1.5", "@types/react-virtualized": "^9.18.7", "@types/recompose": "^0.30.6", "@types/redux-actions": "^2.6.1", @@ -472,6 +474,7 @@ "listr": "^0.14.1", "load-grunt-config": "^3.0.1", "mocha": "^7.1.1", + "mock-fs": "^4.12.0", "mock-http-server": "1.3.0", "ms-chromium-edge-driver": "^0.2.3", "multistream": "^2.1.1", diff --git a/packages/eslint-config-kibana/jest.js b/packages/eslint-config-kibana/jest.js index d682277ff905a..c374de7ae123c 100644 --- a/packages/eslint-config-kibana/jest.js +++ b/packages/eslint-config-kibana/jest.js @@ -2,8 +2,8 @@ module.exports = { overrides: [ { files: [ - '**/*.{test,test.mocks,mock}.{js,ts,tsx}', - '**/__mocks__/**/*.{js,ts,tsx}', + '**/*.{test,test.mocks,mock}.{js,mjs,ts,tsx}', + '**/__mocks__/**/*.{js,mjs,ts,tsx}', ], plugins: [ 'jest', diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/baz/kibana.json b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/kibana.json similarity index 100% rename from packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/baz/kibana.json rename to packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/kibana.json diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/baz/server/index.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/server/index.ts similarity index 100% rename from packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/baz/server/index.ts rename to packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/server/index.ts diff --git a/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/baz/server/lib.ts b/packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/server/lib.ts similarity index 100% rename from packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/baz/server/lib.ts rename to packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz/server/lib.ts diff --git a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index 2265bad9f6afa..b6b0973f0d539 100644 --- a/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/kbn-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -43,18 +43,18 @@ OptimizerConfig { "id": "bar", "isUiPlugin": true, }, - Object { - "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/baz, - "extraPublicDirs": Array [], - "id": "baz", - "isUiPlugin": false, - }, Object { "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/foo, "extraPublicDirs": Array [], "id": "foo", "isUiPlugin": true, }, + Object { + "directory": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo/plugins/nested/baz, + "extraPublicDirs": Array [], + "id": "baz", + "isUiPlugin": false, + }, ], "profileWebpack": false, "repoRoot": /packages/kbn-optimizer/src/__fixtures__/__tmp__/mock_repo, diff --git a/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.test.ts b/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.test.ts index 0961881df461c..f7b457ca42c6d 100644 --- a/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.test.ts +++ b/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.test.ts @@ -41,18 +41,18 @@ it('parses kibana.json files of plugins found in pluginDirs', () => { "id": "bar", "isUiPlugin": true, }, - Object { - "directory": /packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/baz, - "extraPublicDirs": Array [], - "id": "baz", - "isUiPlugin": false, - }, Object { "directory": /packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/foo, "extraPublicDirs": Array [], "id": "foo", "isUiPlugin": true, }, + Object { + "directory": /packages/kbn-optimizer/src/__fixtures__/mock_repo/plugins/nested/baz, + "extraPublicDirs": Array [], + "id": "baz", + "isUiPlugin": false, + }, Object { "directory": /packages/kbn-optimizer/src/__fixtures__/mock_repo/test_plugins/test_baz, "extraPublicDirs": Array [], diff --git a/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts b/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts index bfc60a29efa27..83637691004f4 100644 --- a/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts +++ b/packages/kbn-optimizer/src/optimizer/kibana_platform_plugins.ts @@ -37,7 +37,7 @@ export function findKibanaPlatformPlugins(scanDirs: string[], paths: string[]) { .sync( Array.from( new Set([ - ...scanDirs.map((dir) => `${dir}/*/kibana.json`), + ...scanDirs.map(nestedScanDirPaths).reduce((dirs, current) => [...dirs, ...current], []), ...paths.map((path) => `${path}/kibana.json`), ]) ), @@ -51,6 +51,17 @@ export function findKibanaPlatformPlugins(scanDirs: string[], paths: string[]) { ); } +function nestedScanDirPaths(dir: string): string[] { + // down to 5 level max + return [ + `${dir}/*/kibana.json`, + `${dir}/*/*/kibana.json`, + `${dir}/*/*/*/kibana.json`, + `${dir}/*/*/*/*/kibana.json`, + `${dir}/*/*/*/*/*/kibana.json`, + ]; +} + function readKibanaPlatformPlugin(manifestPath: string): KibanaPlatformPlugin { if (!Path.isAbsolute(manifestPath)) { throw new TypeError('expected new platform manifest path to be absolute'); diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 69611ed3f5c5e..b8794124ad197 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -8868,7 +8868,7 @@ const BootstrapCommand = { } if (cachedProjectCount > 0) { - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].success(`${cachedProjectCount} bootsrap builds are cached`); + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].success(`${cachedProjectCount} bootstrap builds are cached`); } await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async project => { diff --git a/packages/kbn-pm/src/commands/bootstrap.ts b/packages/kbn-pm/src/commands/bootstrap.ts index f8e50a8247856..a559f9a20432a 100644 --- a/packages/kbn-pm/src/commands/bootstrap.ts +++ b/packages/kbn-pm/src/commands/bootstrap.ts @@ -82,7 +82,7 @@ export const BootstrapCommand: ICommand = { } if (cachedProjectCount > 0) { - log.success(`${cachedProjectCount} bootsrap builds are cached`); + log.success(`${cachedProjectCount} bootstrap builds are cached`); } await parallelizeBatches(batchedProjects, async (project) => { diff --git a/packages/kbn-telemetry-tools/README.md b/packages/kbn-telemetry-tools/README.md new file mode 100644 index 0000000000000..ccd092c76a17c --- /dev/null +++ b/packages/kbn-telemetry-tools/README.md @@ -0,0 +1,89 @@ +# Telemetry Tools + +## Schema extraction tool + +### Description + +The tool is used to extract telemetry collectors schema from all `*.{ts}` files in provided plugins directories to JSON files. The tool looks for `.telemetryrc.json` files in the root of the project and in the `x-pack` dir for its runtime configurations. + +It uses typescript parser to build an AST for each file. The tool is able to validate, extract and match collector schemas. + +### Examples and restrictions + +**Global restrictions**: + +The `id` can be only a string literal, it cannot be a template literals w/o expressions or string-only concatenation expressions or anything else. + +``` +export const myCollector = makeUsageCollector({ + type: 'string_literal_only', + ... +}); +``` + +### Usage + +```bash +node scripts/telemetry_extract.js +``` + +This command has no additional flags or arguments. The `.telemetryrc.json` files specify the path to the directory where searching should start, output json files, and files to exclude. + + +### Output + + +The generated JSON files contain an ES mapping for each schema. This mapping is used to verify changes in the collectors and as the basis to map those fields into the external telemetry cluster. + +**Example**: + +```json +{ + "properties": { + "cloud": { + "properties": { + "isCloudEnabled": { + "type": "boolean" + } + } + } + } +} +``` + +## Schema validation tool + +### Description + +The tool performs a number of checks on all telemetry collectors and verifies the following: + +1. Verifies the collector structure, fields, and returned values are using the appropriate types. +2. Verifies that the collector `fetch` function Type matches the specified `schema` in the collector. +3. Verifies that the collector `schema` matches the stored json schema . + +### Notes + +We don't catch every possible misuse of the collectors, but only the most common and critical ones. + +What will not be caught by the validator: + +* Mistyped SavedObject/CallCluster return value. Since the hits returned from ES can be typed to anything without any checks. It is advised to add functional tests that grabs the schema json file and checks that the returned usage matches the types exactly. + +* Fields in the schema that are never collected. If you are trying to report a field from ES but that value is never stored in ES, the check will not be able to detect if that field is ever collected in the first palce. It is advised to add unit/functional tests to check that all the fields are being reported as expected. + +The tool looks for `.telemetryrc.json` files in the root of the project and in the `x-pack` dir for its runtime configurations. + +Currently auto-fixer (`--fix`) can automatically fix the json files with the following errors: + +* incompatible schema - this error means that the collector schema was changed but the stored json schema file was not updated. + +* unused schemas - this error means that a collector was removed or its `type` renamed, the json schema file contains a schema that does not have a corrisponding collector. + +### Usage + +```bash +node scripts/telemetry_check --fix +``` + +* `--path` specifies a collector path instead of checking all collectors specified in the `.telemetryrc.json` files. Accepts a `.ts` file. The file must be discoverable by at least one rc file. +* `--fix` tells the tool to try to fix as many violations as possible. All errors that tool won't be able to fix will be reported. diff --git a/packages/kbn-telemetry-tools/babel.config.js b/packages/kbn-telemetry-tools/babel.config.js new file mode 100644 index 0000000000000..3b09c7d74ccb5 --- /dev/null +++ b/packages/kbn-telemetry-tools/babel.config.js @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module.exports = { + presets: ['@kbn/babel-preset/node_preset'], + ignore: ['**/*.test.ts', '**/__fixture__/**'], +}; diff --git a/packages/kbn-telemetry-tools/package.json b/packages/kbn-telemetry-tools/package.json new file mode 100644 index 0000000000000..5593a72ecd965 --- /dev/null +++ b/packages/kbn-telemetry-tools/package.json @@ -0,0 +1,22 @@ +{ + "name": "@kbn/telemetry-tools", + "version": "1.0.0", + "license": "Apache-2.0", + "main": "./target/index.js", + "private": true, + "scripts": { + "build": "babel src --out-dir target --delete-dir-on-start --extensions .ts --source-maps=inline", + "kbn:bootstrap": "yarn build", + "kbn:watch": "yarn build --watch" + }, + "devDependencies": { + "lodash": "npm:@elastic/lodash@3.10.1-kibana4", + "@kbn/dev-utils": "1.0.0", + "@kbn/utility-types": "1.0.0", + "@types/normalize-path": "^3.0.0", + "normalize-path": "^3.0.0", + "@types/lodash": "^3.10.1", + "moment": "^2.24.0", + "typescript": "3.9.5" + } +} diff --git a/packages/kbn-telemetry-tools/src/cli/run_telemetry_check.ts b/packages/kbn-telemetry-tools/src/cli/run_telemetry_check.ts new file mode 100644 index 0000000000000..116c484a5c36a --- /dev/null +++ b/packages/kbn-telemetry-tools/src/cli/run_telemetry_check.ts @@ -0,0 +1,109 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Listr from 'listr'; +import chalk from 'chalk'; +import { createFailError, run } from '@kbn/dev-utils'; + +import { + createTaskContext, + ErrorReporter, + parseConfigsTask, + extractCollectorsTask, + checkMatchingSchemasTask, + generateSchemasTask, + checkCompatibleTypesTask, + writeToFileTask, + TaskContext, +} from '../tools/tasks'; + +export function runTelemetryCheck() { + run( + async ({ flags: { fix = false, path }, log }) => { + if (typeof fix !== 'boolean') { + throw createFailError(`${chalk.white.bgRed(' TELEMETRY ERROR ')} --fix can't have a value`); + } + + if (typeof path === 'boolean') { + throw createFailError(`${chalk.white.bgRed(' TELEMETRY ERROR ')} --path require a value`); + } + + if (fix && typeof path !== 'undefined') { + throw createFailError( + `${chalk.white.bgRed(' TELEMETRY ERROR ')} --fix is incompatible with --path flag.` + ); + } + + const list = new Listr([ + { + title: 'Checking .telemetryrc.json files', + task: () => new Listr(parseConfigsTask(), { exitOnError: true }), + }, + { + title: 'Extracting Collectors', + task: (context) => new Listr(extractCollectorsTask(context, path), { exitOnError: true }), + }, + { + title: 'Checking Compatible collector.schema with collector.fetch type', + task: (context) => new Listr(checkCompatibleTypesTask(context), { exitOnError: true }), + }, + { + title: 'Checking Matching collector.schema against stored json files', + task: (context) => new Listr(checkMatchingSchemasTask(context), { exitOnError: true }), + }, + { + enabled: (_) => fix, + skip: ({ roots }: TaskContext) => { + return roots.every(({ esMappingDiffs }) => !esMappingDiffs || !esMappingDiffs.length); + }, + title: 'Generating new telemetry mappings', + task: (context) => new Listr(generateSchemasTask(context), { exitOnError: true }), + }, + { + enabled: (_) => fix, + skip: ({ roots }: TaskContext) => { + return roots.every(({ esMappingDiffs }) => !esMappingDiffs || !esMappingDiffs.length); + }, + title: 'Updating telemetry mapping files', + task: (context) => new Listr(writeToFileTask(context), { exitOnError: true }), + }, + ]); + + try { + const context = createTaskContext(); + await list.run(context); + } catch (error) { + process.exitCode = 1; + if (error instanceof ErrorReporter) { + error.errors.forEach((e: string | Error) => log.error(e)); + } else { + log.error('Unhandled exception!'); + log.error(error); + } + } + process.exit(); + }, + { + flags: { + allowUnexpected: true, + guessTypesForUnexpectedFlags: true, + }, + } + ); +} diff --git a/packages/kbn-telemetry-tools/src/cli/run_telemetry_extract.ts b/packages/kbn-telemetry-tools/src/cli/run_telemetry_extract.ts new file mode 100644 index 0000000000000..27a406a4e216d --- /dev/null +++ b/packages/kbn-telemetry-tools/src/cli/run_telemetry_extract.ts @@ -0,0 +1,75 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Listr from 'listr'; +import { run } from '@kbn/dev-utils'; + +import { + createTaskContext, + ErrorReporter, + parseConfigsTask, + extractCollectorsTask, + generateSchemasTask, + writeToFileTask, +} from '../tools/tasks'; + +export function runTelemetryExtract() { + run( + async ({ flags: {}, log }) => { + const list = new Listr([ + { + title: 'Parsing .telemetryrc.json files', + task: () => new Listr(parseConfigsTask(), { exitOnError: true }), + }, + { + title: 'Extracting Telemetry Collectors', + task: (context) => new Listr(extractCollectorsTask(context), { exitOnError: true }), + }, + { + title: 'Generating Schema files', + task: (context) => new Listr(generateSchemasTask(context), { exitOnError: true }), + }, + { + title: 'Writing to file', + task: (context) => new Listr(writeToFileTask(context), { exitOnError: true }), + }, + ]); + + try { + const context = createTaskContext(); + await list.run(context); + } catch (error) { + process.exitCode = 1; + if (error instanceof ErrorReporter) { + error.errors.forEach((e: string | Error) => log.error(e)); + } else { + log.error('Unhandled exception'); + log.error(error); + } + } + process.exit(); + }, + { + flags: { + allowUnexpected: true, + guessTypesForUnexpectedFlags: true, + }, + } + ); +} diff --git a/src/plugins/visualize/public/application/editor/lib/index.ts b/packages/kbn-telemetry-tools/src/index.ts similarity index 80% rename from src/plugins/visualize/public/application/editor/lib/index.ts rename to packages/kbn-telemetry-tools/src/index.ts index 78589383925fb..3a018a9b3002c 100644 --- a/src/plugins/visualize/public/application/editor/lib/index.ts +++ b/packages/kbn-telemetry-tools/src/index.ts @@ -17,6 +17,5 @@ * under the License. */ -export { useVisualizeAppState } from './visualize_app_state'; -export { makeStateful } from './make_stateful'; -export { addEmbeddableToDashboardUrl } from '../../../../../dashboard/public/'; +export { runTelemetryCheck } from './cli/run_telemetry_check'; +export { runTelemetryExtract } from './cli/run_telemetry_extract'; diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/mock_schema.json b/packages/kbn-telemetry-tools/src/tools/__fixture__/mock_schema.json new file mode 100644 index 0000000000000..885fe0e38dacf --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/mock_schema.json @@ -0,0 +1,24 @@ +{ + "properties": { + "my_working_collector": { + "properties": { + "flat": { + "type": "keyword" + }, + "my_str": { + "type": "text" + }, + "my_objects": { + "properties": { + "total": { + "type": "number" + }, + "type": { + "type": "boolean" + } + } + } + } + } + } +} diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_externally_defined_collector.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_externally_defined_collector.ts new file mode 100644 index 0000000000000..fe45f6b7f3042 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_externally_defined_collector.ts @@ -0,0 +1,68 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SyntaxKind } from 'typescript'; +import { ParsedUsageCollection } from '../ts_parser'; + +export const parsedExternallyDefinedCollector: ParsedUsageCollection[] = [ + [ + 'src/fixtures/telemetry_collectors/externally_defined_collector.ts', + { + collectorName: 'from_variable_collector', + schema: { + value: { + locale: { + type: 'keyword', + }, + }, + }, + fetch: { + typeName: 'Usage', + typeDescriptor: { + locale: { + kind: SyntaxKind.StringKeyword, + type: 'StringKeyword', + }, + }, + }, + }, + ], + [ + 'src/fixtures/telemetry_collectors/externally_defined_collector.ts', + { + collectorName: 'from_fn_collector', + schema: { + value: { + locale: { + type: 'keyword', + }, + }, + }, + fetch: { + typeName: 'Usage', + typeDescriptor: { + locale: { + kind: SyntaxKind.StringKeyword, + type: 'StringKeyword', + }, + }, + }, + }, + ], +]; diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_imported_schema.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_imported_schema.ts new file mode 100644 index 0000000000000..4870252082950 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_imported_schema.ts @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SyntaxKind } from 'typescript'; +import { ParsedUsageCollection } from '../ts_parser'; + +export const parsedImportedSchemaCollector: ParsedUsageCollection[] = [ + [ + 'src/fixtures/telemetry_collectors/imported_schema.ts', + { + collectorName: 'with_imported_schema', + schema: { + value: { + locale: { + type: 'keyword', + }, + }, + }, + fetch: { + typeName: 'Usage', + typeDescriptor: { + locale: { + kind: SyntaxKind.StringKeyword, + type: 'StringKeyword', + }, + }, + }, + }, + ], +]; diff --git a/src/core/utils/integration_tests/deep_freeze.test.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_imported_usage_interface.ts similarity index 55% rename from src/core/utils/integration_tests/deep_freeze.test.ts rename to packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_imported_usage_interface.ts index f58e298fecfbf..42ed2140b5208 100644 --- a/src/core/utils/integration_tests/deep_freeze.test.ts +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_imported_usage_interface.ts @@ -17,24 +17,30 @@ * under the License. */ -import { resolve } from 'path'; +import { SyntaxKind } from 'typescript'; +import { ParsedUsageCollection } from '../ts_parser'; -import execa from 'execa'; - -const MINUTE = 60 * 1000; - -it( - 'types return values to prevent mutations in typescript', - async () => { - await expect( - execa('tsc', ['--noEmit'], { - cwd: resolve(__dirname, '__fixtures__/frozen_object_mutation'), - preferLocal: true, - }).catch((err) => err.stdout) - ).resolves.toMatchInlineSnapshot(` - "index.ts(28,12): error TS2540: Cannot assign to 'baz' because it is a read-only property. - index.ts(36,11): error TS2540: Cannot assign to 'bar' because it is a read-only property." - `); - }, - MINUTE -); +export const parsedImportedUsageInterface: ParsedUsageCollection[] = [ + [ + 'src/fixtures/telemetry_collectors/imported_usage_interface.ts', + { + collectorName: 'imported_usage_interface_collector', + schema: { + value: { + locale: { + type: 'keyword', + }, + }, + }, + fetch: { + typeName: 'Usage', + typeDescriptor: { + locale: { + kind: SyntaxKind.StringKeyword, + type: 'StringKeyword', + }, + }, + }, + }, + ], +]; diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_nested_collector.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_nested_collector.ts new file mode 100644 index 0000000000000..ed727c15b7c86 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_nested_collector.ts @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SyntaxKind } from 'typescript'; +import { ParsedUsageCollection } from '../ts_parser'; + +export const parsedNestedCollector: ParsedUsageCollection = [ + 'src/fixtures/telemetry_collectors/nested_collector.ts', + { + collectorName: 'my_nested_collector', + schema: { + value: { + locale: { + type: 'keyword', + }, + }, + }, + fetch: { + typeName: 'Usage', + typeDescriptor: { + locale: { + kind: SyntaxKind.StringKeyword, + type: 'StringKeyword', + }, + }, + }, + }, +]; diff --git a/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts new file mode 100644 index 0000000000000..25e49ea221c94 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_working_collector.ts @@ -0,0 +1,69 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SyntaxKind } from 'typescript'; +import { ParsedUsageCollection } from '../ts_parser'; + +export const parsedWorkingCollector: ParsedUsageCollection = [ + 'src/fixtures/telemetry_collectors/working_collector.ts', + { + collectorName: 'my_working_collector', + schema: { + value: { + flat: { + type: 'keyword', + }, + my_str: { + type: 'text', + }, + my_objects: { + total: { + type: 'number', + }, + type: { + type: 'boolean', + }, + }, + }, + }, + fetch: { + typeName: 'Usage', + typeDescriptor: { + flat: { + kind: SyntaxKind.StringKeyword, + type: 'StringKeyword', + }, + my_str: { + kind: SyntaxKind.StringKeyword, + type: 'StringKeyword', + }, + my_objects: { + total: { + kind: SyntaxKind.NumberKeyword, + type: 'NumberKeyword', + }, + type: { + kind: SyntaxKind.BooleanKeyword, + type: 'BooleanKeyword', + }, + }, + }, + }, + }, +]; diff --git a/packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap b/packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap new file mode 100644 index 0000000000000..44a12dfa9030c --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/__snapshots__/extract_collectors.test.ts.snap @@ -0,0 +1,163 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`extractCollectors extracts collectors given rc file 1`] = ` +Array [ + Array [ + "src/fixtures/telemetry_collectors/externally_defined_collector.ts", + Object { + "collectorName": "from_variable_collector", + "fetch": Object { + "typeDescriptor": Object { + "locale": Object { + "kind": 143, + "type": "StringKeyword", + }, + }, + "typeName": "Usage", + }, + "schema": Object { + "value": Object { + "locale": Object { + "type": "keyword", + }, + }, + }, + }, + ], + Array [ + "src/fixtures/telemetry_collectors/externally_defined_collector.ts", + Object { + "collectorName": "from_fn_collector", + "fetch": Object { + "typeDescriptor": Object { + "locale": Object { + "kind": 143, + "type": "StringKeyword", + }, + }, + "typeName": "Usage", + }, + "schema": Object { + "value": Object { + "locale": Object { + "type": "keyword", + }, + }, + }, + }, + ], + Array [ + "src/fixtures/telemetry_collectors/imported_schema.ts", + Object { + "collectorName": "with_imported_schema", + "fetch": Object { + "typeDescriptor": Object { + "locale": Object { + "kind": 143, + "type": "StringKeyword", + }, + }, + "typeName": "Usage", + }, + "schema": Object { + "value": Object { + "locale": Object { + "type": "keyword", + }, + }, + }, + }, + ], + Array [ + "src/fixtures/telemetry_collectors/imported_usage_interface.ts", + Object { + "collectorName": "imported_usage_interface_collector", + "fetch": Object { + "typeDescriptor": Object { + "locale": Object { + "kind": 143, + "type": "StringKeyword", + }, + }, + "typeName": "Usage", + }, + "schema": Object { + "value": Object { + "locale": Object { + "type": "keyword", + }, + }, + }, + }, + ], + Array [ + "src/fixtures/telemetry_collectors/nested_collector.ts", + Object { + "collectorName": "my_nested_collector", + "fetch": Object { + "typeDescriptor": Object { + "locale": Object { + "kind": 143, + "type": "StringKeyword", + }, + }, + "typeName": "Usage", + }, + "schema": Object { + "value": Object { + "locale": Object { + "type": "keyword", + }, + }, + }, + }, + ], + Array [ + "src/fixtures/telemetry_collectors/working_collector.ts", + Object { + "collectorName": "my_working_collector", + "fetch": Object { + "typeDescriptor": Object { + "flat": Object { + "kind": 143, + "type": "StringKeyword", + }, + "my_objects": Object { + "total": Object { + "kind": 140, + "type": "NumberKeyword", + }, + "type": Object { + "kind": 128, + "type": "BooleanKeyword", + }, + }, + "my_str": Object { + "kind": 143, + "type": "StringKeyword", + }, + }, + "typeName": "Usage", + }, + "schema": Object { + "value": Object { + "flat": Object { + "type": "keyword", + }, + "my_objects": Object { + "total": Object { + "type": "number", + }, + "type": Object { + "type": "boolean", + }, + }, + "my_str": Object { + "type": "text", + }, + }, + }, + }, + ], +] +`; diff --git a/packages/kbn-telemetry-tools/src/tools/__snapshots__/ts_parser.test.ts.snap b/packages/kbn-telemetry-tools/src/tools/__snapshots__/ts_parser.test.ts.snap new file mode 100644 index 0000000000000..5b1b3d9d35299 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/__snapshots__/ts_parser.test.ts.snap @@ -0,0 +1,6 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`parseUsageCollection throws when mapping fields is not defined 1`] = ` +"Error extracting collector in src/fixtures/telemetry_collectors/unmapped_collector.ts +Error: usageCollector.schema must be defined." +`; diff --git a/packages/kbn-telemetry-tools/src/tools/check_collector__integrity.test.ts b/packages/kbn-telemetry-tools/src/tools/check_collector__integrity.test.ts new file mode 100644 index 0000000000000..6083593431d9b --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/check_collector__integrity.test.ts @@ -0,0 +1,125 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as _ from 'lodash'; +import * as ts from 'typescript'; +import { parsedWorkingCollector } from './__fixture__/parsed_working_collector'; +import { checkCompatibleTypeDescriptor, checkMatchingMapping } from './check_collector_integrity'; +import * as path from 'path'; +import { readFile } from 'fs'; +import { promisify } from 'util'; +const read = promisify(readFile); + +async function parseJsonFile(relativePath: string) { + const schemaPath = path.resolve(__dirname, '__fixture__', relativePath); + const fileContent = await read(schemaPath, 'utf8'); + return JSON.parse(fileContent); +} + +describe('checkMatchingMapping', () => { + it('returns no diff on matching parsedCollections and stored mapping', async () => { + const mockSchema = await parseJsonFile('mock_schema.json'); + const diffs = checkMatchingMapping([parsedWorkingCollector], mockSchema); + expect(diffs).toEqual({}); + }); + + describe('Collector change', () => { + it('returns diff on mismatching parsedCollections and stored mapping', async () => { + const mockSchema = await parseJsonFile('mock_schema.json'); + const malformedParsedCollector = _.cloneDeep(parsedWorkingCollector); + const fieldMapping = { type: 'number' }; + malformedParsedCollector[1].schema.value.flat = fieldMapping; + + const diffs = checkMatchingMapping([malformedParsedCollector], mockSchema); + expect(diffs).toEqual({ + properties: { + my_working_collector: { + properties: { flat: fieldMapping }, + }, + }, + }); + }); + + it('returns diff on unknown parsedCollections', async () => { + const mockSchema = await parseJsonFile('mock_schema.json'); + const malformedParsedCollector = _.cloneDeep(parsedWorkingCollector); + const collectorName = 'New Collector in town!'; + const collectorMapping = { some_usage: { type: 'number' } }; + malformedParsedCollector[1].collectorName = collectorName; + malformedParsedCollector[1].schema.value = { some_usage: { type: 'number' } }; + + const diffs = checkMatchingMapping([malformedParsedCollector], mockSchema); + expect(diffs).toEqual({ + properties: { + [collectorName]: { + properties: collectorMapping, + }, + }, + }); + }); + }); +}); + +describe('checkCompatibleTypeDescriptor', () => { + it('returns no diff on compatible type descriptor with mapping', () => { + const incompatibles = checkCompatibleTypeDescriptor([parsedWorkingCollector]); + expect(incompatibles).toHaveLength(0); + }); + + describe('Interface Change', () => { + it('returns diff on incompatible type descriptor with mapping', () => { + const malformedParsedCollector = _.cloneDeep(parsedWorkingCollector); + malformedParsedCollector[1].fetch.typeDescriptor.flat.kind = ts.SyntaxKind.BooleanKeyword; + const incompatibles = checkCompatibleTypeDescriptor([malformedParsedCollector]); + expect(incompatibles).toHaveLength(1); + const { diff, message } = incompatibles[0]; + expect(diff).toEqual({ 'flat.kind': 'boolean' }); + expect(message).toHaveLength(1); + expect(message).toEqual([ + 'incompatible Type key (Usage.flat): expected ("string") got ("boolean").', + ]); + }); + + it.todo('returns diff when missing type descriptor'); + }); + + describe('Mapping change', () => { + it('returns no diff when mapping change between text and keyword', () => { + const malformedParsedCollector = _.cloneDeep(parsedWorkingCollector); + malformedParsedCollector[1].schema.value.flat.type = 'text'; + const incompatibles = checkCompatibleTypeDescriptor([malformedParsedCollector]); + expect(incompatibles).toHaveLength(0); + }); + + it('returns diff on incompatible type descriptor with mapping', () => { + const malformedParsedCollector = _.cloneDeep(parsedWorkingCollector); + malformedParsedCollector[1].schema.value.flat.type = 'boolean'; + const incompatibles = checkCompatibleTypeDescriptor([malformedParsedCollector]); + expect(incompatibles).toHaveLength(1); + const { diff, message } = incompatibles[0]; + expect(diff).toEqual({ 'flat.kind': 'string' }); + expect(message).toHaveLength(1); + expect(message).toEqual([ + 'incompatible Type key (Usage.flat): expected ("boolean") got ("string").', + ]); + }); + + it.todo('returns diff when missing mapping'); + }); +}); diff --git a/packages/kbn-telemetry-tools/src/tools/check_collector_integrity.ts b/packages/kbn-telemetry-tools/src/tools/check_collector_integrity.ts new file mode 100644 index 0000000000000..824132b05732c --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/check_collector_integrity.ts @@ -0,0 +1,103 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as _ from 'lodash'; +import { difference, flattenKeys, pickDeep } from './utils'; +import { ParsedUsageCollection } from './ts_parser'; +import { generateMapping, compatibleSchemaTypes } from './manage_schema'; +import { kindToDescriptorName } from './serializer'; + +export function checkMatchingMapping( + UsageCollections: ParsedUsageCollection[], + esMapping: any +): any { + const generatedMapping = generateMapping(UsageCollections); + return difference(generatedMapping, esMapping); +} + +interface IncompatibleDescriptor { + diff: Record; + collectorPath: string; + message: string[]; +} +export function checkCompatibleTypeDescriptor( + usageCollections: ParsedUsageCollection[] +): IncompatibleDescriptor[] { + const results: Array = usageCollections.map( + ([collectorPath, collectorDetails]) => { + const typeDescriptorTypes = flattenKeys( + pickDeep(collectorDetails.fetch.typeDescriptor, 'kind') + ); + const typeDescriptorKinds = _.reduce( + typeDescriptorTypes, + (acc: any, type: number, key: string) => { + try { + acc[key] = kindToDescriptorName(type); + } catch (err) { + throw Error(`Unrecognized type (${key}: ${type}) in ${collectorPath}`); + } + return acc; + }, + {} as any + ); + + const schemaTypes = flattenKeys(pickDeep(collectorDetails.schema.value, 'type')); + const transformedMappingKinds = _.reduce( + schemaTypes, + (acc: any, type: string, key: string) => { + try { + acc[key.replace(/.type$/, '.kind')] = compatibleSchemaTypes(type as any); + } catch (err) { + throw Error(`Unrecognized type (${key}: ${type}) in ${collectorPath}`); + } + return acc; + }, + {} as any + ); + + const diff: any = difference(typeDescriptorKinds, transformedMappingKinds); + const diffEntries = Object.entries(diff); + + if (!diffEntries.length) { + return false; + } + + return { + diff, + collectorPath, + message: diffEntries.map(([key]) => { + const interfaceKey = key.replace('.kind', ''); + try { + const expectedDescriptorType = JSON.stringify(transformedMappingKinds[key], null, 2); + const actualDescriptorType = JSON.stringify(typeDescriptorKinds[key], null, 2); + return `incompatible Type key (${collectorDetails.fetch.typeName}.${interfaceKey}): expected (${expectedDescriptorType}) got (${actualDescriptorType}).`; + } catch (err) { + throw Error(`Error converting ${key} in ${collectorPath}.\n${err}`); + } + }), + }; + } + ); + + return results.filter((entry): entry is IncompatibleDescriptor => entry !== false); +} + +export function checkCollectorIntegrity(UsageCollections: ParsedUsageCollection[], esMapping: any) { + return UsageCollections; +} diff --git a/src/dev/code_coverage/ingest_coverage/__tests__/ingest.test.js b/packages/kbn-telemetry-tools/src/tools/config.test.ts similarity index 54% rename from src/dev/code_coverage/ingest_coverage/__tests__/ingest.test.js rename to packages/kbn-telemetry-tools/src/tools/config.test.ts index ad5b4da0873b9..51ca0493cbb5a 100644 --- a/src/dev/code_coverage/ingest_coverage/__tests__/ingest.test.js +++ b/packages/kbn-telemetry-tools/src/tools/config.test.ts @@ -17,21 +17,24 @@ * under the License. */ -import expect from '@kbn/expect'; -import { maybeTeamAssign } from '../ingest'; -import { COVERAGE_INDEX, TOTALS_INDEX } from '../constants'; +import * as path from 'path'; +import { parseTelemetryRC } from './config'; -describe(`Ingest fns`, () => { - describe(`maybeTeamAssign fn`, () => { - describe(`against the coverage index`, () => { - it(`should have the pipeline prop`, () => { - expect(maybeTeamAssign(COVERAGE_INDEX, {})).to.have.property('pipeline'); - }); - }); - describe(`against the totals index`, () => { - it(`should not have the pipeline prop`, () => { - expect(maybeTeamAssign(TOTALS_INDEX, {})).not.to.have.property('pipeline'); - }); - }); +describe('parseTelemetryRC', () => { + it('throw if config path is not absolute', async () => { + const fixtureDir = './__fixture__/'; + await expect(parseTelemetryRC(fixtureDir)).rejects.toThrowError(); + }); + + it('returns parsed rc file', async () => { + const configRoot = path.join(process.cwd(), 'src', 'fixtures', 'telemetry_collectors'); + const config = await parseTelemetryRC(configRoot); + expect(config).toStrictEqual([ + { + root: configRoot, + output: configRoot, + exclude: [path.resolve(configRoot, './unmapped_collector.ts')], + }, + ]); }); }); diff --git a/packages/kbn-telemetry-tools/src/tools/config.ts b/packages/kbn-telemetry-tools/src/tools/config.ts new file mode 100644 index 0000000000000..5724b869e8f5e --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/config.ts @@ -0,0 +1,60 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as path from 'path'; +import { readFileAsync } from './utils'; +import { TELEMETRY_RC } from './constants'; + +export interface TelemetryRC { + root: string; + output: string; + exclude: string[]; +} + +export async function readRcFile(rcRoot: string) { + if (!path.isAbsolute(rcRoot)) { + throw Error(`config root (${rcRoot}) must be an absolute path.`); + } + + const rcFile = path.resolve(rcRoot, TELEMETRY_RC); + const configString = await readFileAsync(rcFile, 'utf8'); + return JSON.parse(configString); +} + +export async function parseTelemetryRC(rcRoot: string): Promise { + const parsedRc = await readRcFile(rcRoot); + const configs = Array.isArray(parsedRc) ? parsedRc : [parsedRc]; + return configs.map(({ root, output, exclude = [] }) => { + if (typeof root !== 'string') { + throw Error('config.root must be a string.'); + } + if (typeof output !== 'string') { + throw Error('config.output must be a string.'); + } + if (!Array.isArray(exclude)) { + throw Error('config.exclude must be an array of strings.'); + } + + return { + root: path.join(rcRoot, root), + output: path.join(rcRoot, output), + exclude: exclude.map((excludedPath) => path.resolve(rcRoot, excludedPath)), + }; + }); +} diff --git a/packages/kbn-telemetry-tools/src/tools/constants.ts b/packages/kbn-telemetry-tools/src/tools/constants.ts new file mode 100644 index 0000000000000..8635b1a2e2528 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/constants.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const TELEMETRY_RC = '.telemetryrc.json'; diff --git a/packages/kbn-telemetry-tools/src/tools/extract_collectors.test.ts b/packages/kbn-telemetry-tools/src/tools/extract_collectors.test.ts new file mode 100644 index 0000000000000..1b4ed21a1635c --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/extract_collectors.test.ts @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as ts from 'typescript'; +import * as path from 'path'; +import { extractCollectors, getProgramPaths } from './extract_collectors'; +import { parseTelemetryRC } from './config'; + +describe('extractCollectors', () => { + it('extracts collectors given rc file', async () => { + const configRoot = path.join(process.cwd(), 'src', 'fixtures', 'telemetry_collectors'); + const tsConfig = ts.findConfigFile('./', ts.sys.fileExists, 'tsconfig.json'); + if (!tsConfig) { + throw new Error('Could not find a valid tsconfig.json.'); + } + const configs = await parseTelemetryRC(configRoot); + expect(configs).toHaveLength(1); + const programPaths = await getProgramPaths(configs[0]); + + const results = [...extractCollectors(programPaths, tsConfig)]; + expect(results).toHaveLength(6); + expect(results).toMatchSnapshot(); + }); +}); diff --git a/packages/kbn-telemetry-tools/src/tools/extract_collectors.ts b/packages/kbn-telemetry-tools/src/tools/extract_collectors.ts new file mode 100644 index 0000000000000..a638fde021458 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/extract_collectors.ts @@ -0,0 +1,75 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as ts from 'typescript'; +import * as path from 'path'; +import { parseUsageCollection } from './ts_parser'; +import { globAsync } from './utils'; +import { TelemetryRC } from './config'; + +export async function getProgramPaths({ + root, + exclude, +}: Pick): Promise { + const filePaths = await globAsync('**/*.ts', { + cwd: root, + ignore: [ + '**/node_modules/**', + '**/*.test.*', + '**/*.mock.*', + '**/mocks.*', + '**/__fixture__/**', + '**/__tests__/**', + '**/public/**', + '**/dist/**', + '**/target/**', + '**/*.d.ts', + ], + }); + + if (filePaths.length === 0) { + throw Error(`No files found in ${root}`); + } + + const fullPaths = filePaths + .map((filePath) => path.join(root, filePath)) + .filter((fullPath) => !exclude.some((excludedPath) => fullPath.startsWith(excludedPath))); + + if (fullPaths.length === 0) { + throw Error(`No paths covered from ${root} by the .telemetryrc.json`); + } + + return fullPaths; +} + +export function* extractCollectors(fullPaths: string[], tsConfig: any) { + const program = ts.createProgram(fullPaths, tsConfig); + program.getTypeChecker(); + const sourceFiles = fullPaths.map((fullPath) => { + const sourceFile = program.getSourceFile(fullPath); + if (!sourceFile) { + throw Error(`Unable to get sourceFile ${fullPath}.`); + } + return sourceFile; + }); + + for (const sourceFile of sourceFiles) { + yield* parseUsageCollection(sourceFile, program); + } +} diff --git a/packages/kbn-telemetry-tools/src/tools/manage_schema.test.ts b/packages/kbn-telemetry-tools/src/tools/manage_schema.test.ts new file mode 100644 index 0000000000000..8f4bfc66b32ae --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/manage_schema.test.ts @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { generateMapping } from './manage_schema'; +import { parsedWorkingCollector } from './__fixture__/parsed_working_collector'; +import * as path from 'path'; +import { readFile } from 'fs'; +import { promisify } from 'util'; +const read = promisify(readFile); + +async function parseJsonFile(relativePath: string) { + const schemaPath = path.resolve(__dirname, '__fixture__', relativePath); + const fileContent = await read(schemaPath, 'utf8'); + return JSON.parse(fileContent); +} + +describe('generateMapping', () => { + it('generates a mapping file', async () => { + const mockSchema = await parseJsonFile('mock_schema.json'); + const result = generateMapping([parsedWorkingCollector]); + expect(result).toEqual(mockSchema); + }); +}); diff --git a/packages/kbn-telemetry-tools/src/tools/manage_schema.ts b/packages/kbn-telemetry-tools/src/tools/manage_schema.ts new file mode 100644 index 0000000000000..d422837140d80 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/manage_schema.ts @@ -0,0 +1,86 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ParsedUsageCollection } from './ts_parser'; + +export type AllowedSchemaTypes = + | 'keyword' + | 'text' + | 'number' + | 'boolean' + | 'long' + | 'date' + | 'float'; + +export function compatibleSchemaTypes(type: AllowedSchemaTypes) { + switch (type) { + case 'keyword': + case 'text': + case 'date': + return 'string'; + case 'boolean': + return 'boolean'; + case 'number': + case 'float': + case 'long': + return 'number'; + default: + throw new Error(`Unknown schema type ${type}`); + } +} + +export function isObjectMapping(entity: any) { + if (typeof entity === 'object') { + // 'type' is explicitly specified to be an object. + if (typeof entity.type === 'string' && entity.type === 'object') { + return true; + } + + // 'type' is not set; ES defaults to object mapping for when type is unspecified. + if (typeof entity.type === 'undefined') { + return true; + } + + // 'type' is a field in the mapping and is not the type of the mapping. + if (typeof entity.type === 'object') { + return true; + } + } + + return false; +} + +function transformToEsMapping(usageMappingValue: any) { + const fieldMapping: any = { properties: {} }; + for (const [key, value] of Object.entries(usageMappingValue)) { + fieldMapping.properties[key] = isObjectMapping(value) ? transformToEsMapping(value) : value; + } + return fieldMapping; +} + +export function generateMapping(usageCollections: ParsedUsageCollection[]) { + const esMapping: any = { properties: {} }; + for (const [, collecionDetails] of usageCollections) { + esMapping.properties[collecionDetails.collectorName] = transformToEsMapping( + collecionDetails.schema.value + ); + } + + return esMapping; +} diff --git a/packages/kbn-telemetry-tools/src/tools/serializer.test.ts b/packages/kbn-telemetry-tools/src/tools/serializer.test.ts new file mode 100644 index 0000000000000..9475574a44219 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/serializer.test.ts @@ -0,0 +1,105 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as ts from 'typescript'; +import * as path from 'path'; +import { getDescriptor, TelemetryKinds } from './serializer'; +import { traverseNodes } from './ts_parser'; + +export function loadFixtureProgram(fixtureName: string) { + const fixturePath = path.resolve( + process.cwd(), + 'src', + 'fixtures', + 'telemetry_collectors', + `${fixtureName}.ts` + ); + const tsConfig = ts.findConfigFile('./', ts.sys.fileExists, 'tsconfig.json'); + if (!tsConfig) { + throw new Error('Could not find a valid tsconfig.json.'); + } + const program = ts.createProgram([fixturePath], tsConfig as any); + const checker = program.getTypeChecker(); + const sourceFile = program.getSourceFile(fixturePath); + if (!sourceFile) { + throw Error('sourceFile is undefined!'); + } + return { program, checker, sourceFile }; +} + +describe('getDescriptor', () => { + const usageInterfaces = new Map(); + let tsProgram: ts.Program; + beforeAll(() => { + const { program, sourceFile } = loadFixtureProgram('constants'); + tsProgram = program; + for (const node of traverseNodes(sourceFile)) { + if (ts.isInterfaceDeclaration(node)) { + const interfaceName = node.name.getText(); + usageInterfaces.set(interfaceName, node); + } + } + }); + + it('serializes flat types', () => { + const usageInterface = usageInterfaces.get('Usage'); + const descriptor = getDescriptor(usageInterface!, tsProgram); + expect(descriptor).toEqual({ + locale: { kind: ts.SyntaxKind.StringKeyword, type: 'StringKeyword' }, + }); + }); + + it('serializes union types', () => { + const usageInterface = usageInterfaces.get('WithUnion'); + const descriptor = getDescriptor(usageInterface!, tsProgram); + + expect(descriptor).toEqual({ + prop1: { kind: ts.SyntaxKind.StringKeyword, type: 'StringKeyword' }, + prop2: { kind: ts.SyntaxKind.StringKeyword, type: 'StringKeyword' }, + prop3: { kind: ts.SyntaxKind.StringKeyword, type: 'StringKeyword' }, + prop4: { kind: ts.SyntaxKind.StringLiteral, type: 'StringLiteral' }, + prop5: { kind: ts.SyntaxKind.FirstLiteralToken, type: 'FirstLiteralToken' }, + }); + }); + + it('serializes Moment Dates', () => { + const usageInterface = usageInterfaces.get('WithMoment'); + const descriptor = getDescriptor(usageInterface!, tsProgram); + expect(descriptor).toEqual({ + prop1: { kind: TelemetryKinds.MomentDate, type: 'MomentDate' }, + prop2: { kind: TelemetryKinds.MomentDate, type: 'MomentDate' }, + prop3: { kind: TelemetryKinds.MomentDate, type: 'MomentDate' }, + prop4: { kind: TelemetryKinds.Date, type: 'Date' }, + }); + }); + + it('throws error on conflicting union types', () => { + const usageInterface = usageInterfaces.get('WithConflictingUnion'); + expect(() => getDescriptor(usageInterface!, tsProgram)).toThrowError( + 'Mapping does not support conflicting union types.' + ); + }); + + it('throws error on unsupported union types', () => { + const usageInterface = usageInterfaces.get('WithUnsupportedUnion'); + expect(() => getDescriptor(usageInterface!, tsProgram)).toThrowError( + 'Mapping does not support conflicting union types.' + ); + }); +}); diff --git a/packages/kbn-telemetry-tools/src/tools/serializer.ts b/packages/kbn-telemetry-tools/src/tools/serializer.ts new file mode 100644 index 0000000000000..bce5dd7f58643 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/serializer.ts @@ -0,0 +1,169 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as ts from 'typescript'; +import { uniq } from 'lodash'; +import { + getResolvedModuleSourceFile, + getIdentifierDeclarationFromSource, + getModuleSpecifier, +} from './utils'; + +export enum TelemetryKinds { + MomentDate = 1000, + Date = 10001, +} + +interface DescriptorValue { + kind: ts.SyntaxKind | TelemetryKinds; + type: keyof typeof ts.SyntaxKind | keyof typeof TelemetryKinds; +} + +export interface Descriptor { + [name: string]: Descriptor | DescriptorValue; +} + +export function isObjectDescriptor(value: any) { + if (typeof value === 'object') { + if (typeof value.type === 'string' && value.type === 'object') { + return true; + } + + if (typeof value.type === 'undefined') { + return true; + } + } + + return false; +} + +export function kindToDescriptorName(kind: number) { + switch (kind) { + case ts.SyntaxKind.StringKeyword: + case ts.SyntaxKind.StringLiteral: + case ts.SyntaxKind.SetKeyword: + case TelemetryKinds.Date: + case TelemetryKinds.MomentDate: + return 'string'; + case ts.SyntaxKind.BooleanKeyword: + return 'boolean'; + case ts.SyntaxKind.NumberKeyword: + case ts.SyntaxKind.NumericLiteral: + return 'number'; + default: + throw new Error(`Unknown kind ${kind}`); + } +} + +export function getDescriptor(node: ts.Node, program: ts.Program): Descriptor | DescriptorValue { + if (ts.isMethodSignature(node) || ts.isPropertySignature(node)) { + if (node.type) { + return getDescriptor(node.type, program); + } + } + if (ts.isTypeLiteralNode(node) || ts.isInterfaceDeclaration(node)) { + return node.members.reduce((acc, m) => { + acc[m.name?.getText() || ''] = getDescriptor(m, program); + return acc; + }, {} as any); + } + + if (ts.SyntaxKind.FirstNode === node.kind) { + return getDescriptor((node as any).right, program); + } + + if (ts.isIdentifier(node)) { + const identifierName = node.getText(); + if (identifierName === 'Date') { + return { kind: TelemetryKinds.Date, type: 'Date' }; + } + if (identifierName === 'Moment') { + return { kind: TelemetryKinds.MomentDate, type: 'MomentDate' }; + } + throw new Error(`Unsupported Identifier ${identifierName}.`); + } + + if (ts.isTypeReferenceNode(node)) { + const typeChecker = program.getTypeChecker(); + const symbol = typeChecker.getSymbolAtLocation(node.typeName); + const symbolName = symbol?.getName(); + if (symbolName === 'Moment') { + return { kind: TelemetryKinds.MomentDate, type: 'MomentDate' }; + } + if (symbolName === 'Date') { + return { kind: TelemetryKinds.Date, type: 'Date' }; + } + const declaration = (symbol?.getDeclarations() || [])[0]; + if (declaration) { + return getDescriptor(declaration, program); + } + return getDescriptor(node.typeName, program); + } + + if (ts.isImportSpecifier(node)) { + const source = node.getSourceFile(); + const importedModuleName = getModuleSpecifier(node); + + const declarationSource = getResolvedModuleSourceFile(source, program, importedModuleName); + const declarationNode = getIdentifierDeclarationFromSource(node.name, declarationSource); + return getDescriptor(declarationNode, program); + } + + if (ts.isArrayTypeNode(node)) { + return getDescriptor(node.elementType, program); + } + + if (ts.isLiteralTypeNode(node)) { + return { + kind: node.literal.kind, + type: ts.SyntaxKind[node.literal.kind] as keyof typeof ts.SyntaxKind, + }; + } + + if (ts.isUnionTypeNode(node)) { + const types = node.types.filter((typeNode) => { + return ( + typeNode.kind !== ts.SyntaxKind.NullKeyword && + typeNode.kind !== ts.SyntaxKind.UndefinedKeyword + ); + }); + + const kinds = types.map((typeNode) => getDescriptor(typeNode, program)); + + const uniqueKinds = uniq(kinds, 'kind'); + + if (uniqueKinds.length !== 1) { + throw Error('Mapping does not support conflicting union types.'); + } + + return uniqueKinds[0]; + } + + switch (node.kind) { + case ts.SyntaxKind.NumberKeyword: + case ts.SyntaxKind.BooleanKeyword: + case ts.SyntaxKind.StringKeyword: + case ts.SyntaxKind.SetKeyword: + return { kind: node.kind, type: ts.SyntaxKind[node.kind] as keyof typeof ts.SyntaxKind }; + case ts.SyntaxKind.UnionType: + case ts.SyntaxKind.AnyKeyword: + default: + throw new Error(`Unknown type ${ts.SyntaxKind[node.kind]}; ${node.getText()}`); + } +} diff --git a/packages/kbn-telemetry-tools/src/tools/tasks/check_compatible_types_task.ts b/packages/kbn-telemetry-tools/src/tools/tasks/check_compatible_types_task.ts new file mode 100644 index 0000000000000..dae4d0f1ad168 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/tasks/check_compatible_types_task.ts @@ -0,0 +1,43 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { TaskContext } from './task_context'; +import { checkCompatibleTypeDescriptor } from '../check_collector_integrity'; + +export function checkCompatibleTypesTask({ reporter, roots }: TaskContext) { + return roots.map((root) => ({ + task: async () => { + if (root.parsedCollections) { + const differences = checkCompatibleTypeDescriptor(root.parsedCollections); + const reporterWithContext = reporter.withContext({ name: root.config.root }); + if (differences.length) { + reporterWithContext.report( + `${JSON.stringify( + differences, + null, + 2 + )}. \nPlease fix the collectors and run the check again.` + ); + throw reporter; + } + } + }, + title: `Checking in ${root.config.root}`, + })); +} diff --git a/packages/kbn-telemetry-tools/src/tools/tasks/check_matching_schemas_task.ts b/packages/kbn-telemetry-tools/src/tools/tasks/check_matching_schemas_task.ts new file mode 100644 index 0000000000000..a1f23bcd44c76 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/tasks/check_matching_schemas_task.ts @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as path from 'path'; +import { TaskContext } from './task_context'; +import { checkMatchingMapping } from '../check_collector_integrity'; +import { readFileAsync } from '../utils'; + +export function checkMatchingSchemasTask({ roots }: TaskContext) { + return roots.map((root) => ({ + task: async () => { + const fullPath = path.resolve(process.cwd(), root.config.output); + const esMappingString = await readFileAsync(fullPath, 'utf-8'); + const esMapping = JSON.parse(esMappingString); + + if (root.parsedCollections) { + const differences = checkMatchingMapping(root.parsedCollections, esMapping); + + root.esMappingDiffs = Object.keys(differences); + } + }, + title: `Checking in ${root.config.root}`, + })); +} diff --git a/src/plugins/kibana_utils/common/default_feedback_message.ts b/packages/kbn-telemetry-tools/src/tools/tasks/error_reporter.ts similarity index 66% rename from src/plugins/kibana_utils/common/default_feedback_message.ts rename to packages/kbn-telemetry-tools/src/tools/tasks/error_reporter.ts index f61f36bc8810c..246d659667281 100644 --- a/src/plugins/kibana_utils/common/default_feedback_message.ts +++ b/packages/kbn-telemetry-tools/src/tools/tasks/error_reporter.ts @@ -17,12 +17,18 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; +import chalk from 'chalk'; +import { normalizePath } from '../utils'; -export const defaultFeedbackMessage = i18n.translate('kibana_utils.defaultFeedbackMessage', { - defaultMessage: 'Have feedback? Please create an issue in {link}.', - values: { - link: - 'GitHub', - }, -}); +export class ErrorReporter { + errors: string[] = []; + + withContext(context: any) { + return { report: (error: any) => this.report(error, context) }; + } + report(error: any, context: any) { + this.errors.push( + `${chalk.white.bgRed(' TELEMETRY ERROR ')} Error in ${normalizePath(context.name)}\n${error}` + ); + } +} diff --git a/packages/kbn-telemetry-tools/src/tools/tasks/extract_collectors_task.ts b/packages/kbn-telemetry-tools/src/tools/tasks/extract_collectors_task.ts new file mode 100644 index 0000000000000..834ec71e22032 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/tasks/extract_collectors_task.ts @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as ts from 'typescript'; +import * as path from 'path'; +import { TaskContext } from './task_context'; +import { extractCollectors, getProgramPaths } from '../extract_collectors'; + +export function extractCollectorsTask( + { roots }: TaskContext, + restrictProgramToPath?: string | string[] +) { + return roots.map((root) => ({ + task: async () => { + const tsConfig = ts.findConfigFile('./', ts.sys.fileExists, 'tsconfig.json'); + if (!tsConfig) { + throw new Error('Could not find a valid tsconfig.json.'); + } + const programPaths = await getProgramPaths(root.config); + + if (typeof restrictProgramToPath !== 'undefined') { + const restrictProgramToPaths = Array.isArray(restrictProgramToPath) + ? restrictProgramToPath + : [restrictProgramToPath]; + + const fullRestrictedPaths = restrictProgramToPaths.map((collectorPath) => + path.resolve(process.cwd(), collectorPath) + ); + const restrictedProgramPaths = programPaths.filter((programPath) => + fullRestrictedPaths.includes(programPath) + ); + if (restrictedProgramPaths.length) { + root.parsedCollections = [...extractCollectors(restrictedProgramPaths, tsConfig)]; + } + return; + } + + root.parsedCollections = [...extractCollectors(programPaths, tsConfig)]; + }, + title: `Extracting collectors in ${root.config.root}`, + })); +} diff --git a/packages/kbn-telemetry-tools/src/tools/tasks/generate_schemas_task.ts b/packages/kbn-telemetry-tools/src/tools/tasks/generate_schemas_task.ts new file mode 100644 index 0000000000000..f6d15c7127d4e --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/tasks/generate_schemas_task.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as _ from 'lodash'; +import { TaskContext } from './task_context'; +import { generateMapping } from '../manage_schema'; + +export function generateSchemasTask({ roots }: TaskContext) { + return roots.map((root) => ({ + task: () => { + if (!root.parsedCollections || !root.parsedCollections.length) { + return; + } + const mapping = generateMapping(root.parsedCollections); + root.mapping = mapping; + }, + title: `Generating mapping for ${root.config.root}`, + })); +} diff --git a/packages/kbn-telemetry-tools/src/tools/tasks/index.ts b/packages/kbn-telemetry-tools/src/tools/tasks/index.ts new file mode 100644 index 0000000000000..cbe74aeb483e4 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/tasks/index.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { ErrorReporter } from './error_reporter'; +export { TaskContext, createTaskContext } from './task_context'; + +export { parseConfigsTask } from './parse_configs_task'; +export { extractCollectorsTask } from './extract_collectors_task'; +export { generateSchemasTask } from './generate_schemas_task'; +export { writeToFileTask } from './write_to_file_task'; +export { checkMatchingSchemasTask } from './check_matching_schemas_task'; +export { checkCompatibleTypesTask } from './check_compatible_types_task'; diff --git a/packages/kbn-telemetry-tools/src/tools/tasks/parse_configs_task.ts b/packages/kbn-telemetry-tools/src/tools/tasks/parse_configs_task.ts new file mode 100644 index 0000000000000..00b319006e2ee --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/tasks/parse_configs_task.ts @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as path from 'path'; +import { parseTelemetryRC } from '../config'; +import { TaskContext } from './task_context'; + +export function parseConfigsTask() { + const kibanaRoot = process.cwd(); + const xpackRoot = path.join(kibanaRoot, 'x-pack'); + + const configRoots = [kibanaRoot, xpackRoot]; + + return configRoots.map((configRoot) => ({ + task: async (context: TaskContext) => { + try { + const configs = await parseTelemetryRC(configRoot); + configs.forEach((config) => { + context.roots.push({ config }); + }); + } catch (err) { + const { reporter } = context; + const reporterWithContext = reporter.withContext({ name: configRoot }); + reporterWithContext.report(err); + throw reporter; + } + }, + title: `Parsing configs in ${configRoot}`, + })); +} diff --git a/packages/kbn-telemetry-tools/src/tools/tasks/task_context.ts b/packages/kbn-telemetry-tools/src/tools/tasks/task_context.ts new file mode 100644 index 0000000000000..78d0b7fbd6c2d --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/tasks/task_context.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { TelemetryRC } from '../config'; +import { ErrorReporter } from './error_reporter'; +import { ParsedUsageCollection } from '../ts_parser'; +export interface TelemetryRoot { + config: TelemetryRC; + parsedCollections?: ParsedUsageCollection[]; + mapping?: any; + esMappingDiffs?: string[]; +} + +export interface TaskContext { + reporter: ErrorReporter; + roots: TelemetryRoot[]; +} + +export function createTaskContext(): TaskContext { + const reporter = new ErrorReporter(); + return { + roots: [], + reporter, + }; +} diff --git a/packages/kbn-telemetry-tools/src/tools/tasks/write_to_file_task.ts b/packages/kbn-telemetry-tools/src/tools/tasks/write_to_file_task.ts new file mode 100644 index 0000000000000..fcfc09db65426 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/tasks/write_to_file_task.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as path from 'path'; +import { writeFileAsync } from '../utils'; +import { TaskContext } from './task_context'; + +export function writeToFileTask({ roots }: TaskContext) { + return roots.map((root) => ({ + task: async () => { + const fullPath = path.resolve(process.cwd(), root.config.output); + if (root.mapping && Object.keys(root.mapping.properties).length > 0) { + const serializedMapping = JSON.stringify(root.mapping, null, 2).concat('\n'); + await writeFileAsync(fullPath, serializedMapping); + } + }, + title: `Writing mapping for ${root.config.root}`, + })); +} diff --git a/packages/kbn-telemetry-tools/src/tools/ts_parser.test.ts b/packages/kbn-telemetry-tools/src/tools/ts_parser.test.ts new file mode 100644 index 0000000000000..b7ca33a7bcd74 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/ts_parser.test.ts @@ -0,0 +1,94 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { parseUsageCollection } from './ts_parser'; +import * as ts from 'typescript'; +import * as path from 'path'; +import { parsedWorkingCollector } from './__fixture__/parsed_working_collector'; +import { parsedNestedCollector } from './__fixture__/parsed_nested_collector'; +import { parsedExternallyDefinedCollector } from './__fixture__/parsed_externally_defined_collector'; +import { parsedImportedUsageInterface } from './__fixture__/parsed_imported_usage_interface'; +import { parsedImportedSchemaCollector } from './__fixture__/parsed_imported_schema'; + +export function loadFixtureProgram(fixtureName: string) { + const fixturePath = path.resolve( + process.cwd(), + 'src', + 'fixtures', + 'telemetry_collectors', + `${fixtureName}.ts` + ); + const tsConfig = ts.findConfigFile('./', ts.sys.fileExists, 'tsconfig.json'); + if (!tsConfig) { + throw new Error('Could not find a valid tsconfig.json.'); + } + const program = ts.createProgram([fixturePath], tsConfig as any); + const checker = program.getTypeChecker(); + const sourceFile = program.getSourceFile(fixturePath); + if (!sourceFile) { + throw Error('sourceFile is undefined!'); + } + return { program, checker, sourceFile }; +} + +describe('parseUsageCollection', () => { + it.todo('throws when a function is returned from fetch'); + it.todo('throws when an object is not returned from fetch'); + + it('throws when mapping fields is not defined', () => { + const { program, sourceFile } = loadFixtureProgram('unmapped_collector'); + expect(() => [...parseUsageCollection(sourceFile, program)]).toThrowErrorMatchingSnapshot(); + }); + + it('parses root level defined collector', () => { + const { program, sourceFile } = loadFixtureProgram('working_collector'); + const result = [...parseUsageCollection(sourceFile, program)]; + expect(result).toEqual([parsedWorkingCollector]); + }); + + it('parses nested collectors', () => { + const { program, sourceFile } = loadFixtureProgram('nested_collector'); + const result = [...parseUsageCollection(sourceFile, program)]; + expect(result).toEqual([parsedNestedCollector]); + }); + + it('parses imported schema property', () => { + const { program, sourceFile } = loadFixtureProgram('imported_schema'); + const result = [...parseUsageCollection(sourceFile, program)]; + expect(result).toEqual(parsedImportedSchemaCollector); + }); + + it('parses externally defined collectors', () => { + const { program, sourceFile } = loadFixtureProgram('externally_defined_collector'); + const result = [...parseUsageCollection(sourceFile, program)]; + expect(result).toEqual(parsedExternallyDefinedCollector); + }); + + it('parses imported Usage interface', () => { + const { program, sourceFile } = loadFixtureProgram('imported_usage_interface'); + const result = [...parseUsageCollection(sourceFile, program)]; + expect(result).toEqual(parsedImportedUsageInterface); + }); + + it('skips files that do not define a collector', () => { + const { program, sourceFile } = loadFixtureProgram('file_with_no_collector'); + const result = [...parseUsageCollection(sourceFile, program)]; + expect(result).toEqual([]); + }); +}); diff --git a/packages/kbn-telemetry-tools/src/tools/ts_parser.ts b/packages/kbn-telemetry-tools/src/tools/ts_parser.ts new file mode 100644 index 0000000000000..6af8450f5a2e8 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/ts_parser.ts @@ -0,0 +1,210 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as ts from 'typescript'; +import { createFailError } from '@kbn/dev-utils'; +import * as path from 'path'; +import { getProperty, getPropertyValue } from './utils'; +import { getDescriptor, Descriptor } from './serializer'; + +export function* traverseNodes(maybeNodes: ts.Node | ts.Node[]): Generator { + const nodes: ts.Node[] = Array.isArray(maybeNodes) ? maybeNodes : [maybeNodes]; + + for (const node of nodes) { + const children: ts.Node[] = []; + yield node; + ts.forEachChild(node, (child) => { + children.push(child); + }); + for (const child of children) { + yield* traverseNodes(child); + } + } +} + +export function isMakeUsageCollectorFunction( + node: ts.Node, + sourceFile: ts.SourceFile +): node is ts.CallExpression { + if (ts.isCallExpression(node)) { + const isMakeUsageCollector = /makeUsageCollector$/.test(node.expression.getText(sourceFile)); + if (isMakeUsageCollector) { + return true; + } + } + + return false; +} + +export interface CollectorDetails { + collectorName: string; + fetch: { typeName: string; typeDescriptor: Descriptor }; + schema: { value: any }; +} + +function getCollectionConfigNode( + collectorNode: ts.CallExpression, + sourceFile: ts.SourceFile +): ts.Expression { + if (collectorNode.arguments.length > 1) { + throw Error(`makeUsageCollector does not accept more than one argument.`); + } + const collectorConfig = collectorNode.arguments[0]; + + if (ts.isObjectLiteralExpression(collectorConfig)) { + return collectorConfig; + } + + const variableDefintionName = collectorConfig.getText(); + for (const node of traverseNodes(sourceFile)) { + if (ts.isVariableDeclaration(node)) { + const declarationName = node.name.getText(); + if (declarationName === variableDefintionName) { + if (!node.initializer) { + throw Error(`Unable to parse collector configs.`); + } + if (ts.isObjectLiteralExpression(node.initializer)) { + return node.initializer; + } + if (ts.isCallExpression(node.initializer)) { + const functionName = node.initializer.expression.getText(sourceFile); + for (const sfNode of traverseNodes(sourceFile)) { + if (ts.isFunctionDeclaration(sfNode)) { + const fnDeclarationName = sfNode.name?.getText(); + if (fnDeclarationName === functionName) { + const returnStatements: ts.ReturnStatement[] = []; + for (const fnNode of traverseNodes(sfNode)) { + if (ts.isReturnStatement(fnNode) && fnNode.parent === sfNode.body) { + returnStatements.push(fnNode); + } + } + + if (returnStatements.length > 1) { + throw Error(`Collector function cannot have multiple return statements.`); + } + if (returnStatements.length === 0) { + throw Error(`Collector function must have a return statement.`); + } + if (!returnStatements[0].expression) { + throw Error(`Collector function return statement must be an expression.`); + } + + return returnStatements[0].expression; + } + } + } + } + } + } + } + + throw Error(`makeUsageCollector argument must be an object.`); +} + +function extractCollectorDetails( + collectorNode: ts.CallExpression, + program: ts.Program, + sourceFile: ts.SourceFile +): CollectorDetails { + if (collectorNode.arguments.length > 1) { + throw Error(`makeUsageCollector does not accept more than one argument.`); + } + + const collectorConfig = getCollectionConfigNode(collectorNode, sourceFile); + + const typeProperty = getProperty(collectorConfig, 'type'); + if (!typeProperty) { + throw Error(`usageCollector.type must be defined.`); + } + const typePropertyValue = getPropertyValue(typeProperty, program); + if (!typePropertyValue || typeof typePropertyValue !== 'string') { + throw Error(`usageCollector.type must be be a non-empty string literal.`); + } + + const fetchProperty = getProperty(collectorConfig, 'fetch'); + if (!fetchProperty) { + throw Error(`usageCollector.fetch must be defined.`); + } + const schemaProperty = getProperty(collectorConfig, 'schema'); + if (!schemaProperty) { + throw Error(`usageCollector.schema must be defined.`); + } + + const schemaPropertyValue = getPropertyValue(schemaProperty, program, { chaseImport: true }); + if (!schemaPropertyValue || typeof schemaPropertyValue !== 'object') { + throw Error(`usageCollector.schema must be be an object.`); + } + + const collectorNodeType = collectorNode.typeArguments; + if (!collectorNodeType || collectorNodeType?.length === 0) { + throw Error(`makeUsageCollector requires a Usage type makeUsageCollector({ ... }).`); + } + + const usageTypeNode = collectorNodeType[0]; + const usageTypeName = usageTypeNode.getText(); + const usageType = getDescriptor(usageTypeNode, program) as Descriptor; + + return { + collectorName: typePropertyValue, + schema: { + value: schemaPropertyValue, + }, + fetch: { + typeName: usageTypeName, + typeDescriptor: usageType, + }, + }; +} + +export function sourceHasUsageCollector(sourceFile: ts.SourceFile) { + if (sourceFile.isDeclarationFile === true || (sourceFile as any).identifierCount === 0) { + return false; + } + + const identifiers = (sourceFile as any).identifiers; + if ( + (!identifiers.get('makeUsageCollector') && !identifiers.get('type')) || + !identifiers.get('fetch') + ) { + return false; + } + + return true; +} + +export type ParsedUsageCollection = [string, CollectorDetails]; + +export function* parseUsageCollection( + sourceFile: ts.SourceFile, + program: ts.Program +): Generator { + const relativePath = path.relative(process.cwd(), sourceFile.fileName); + if (sourceHasUsageCollector(sourceFile)) { + for (const node of traverseNodes(sourceFile)) { + if (isMakeUsageCollectorFunction(node, sourceFile)) { + try { + const collectorDetails = extractCollectorDetails(node, program, sourceFile); + yield [relativePath, collectorDetails]; + } catch (err) { + throw createFailError(`Error extracting collector in ${relativePath}\n${err}`); + } + } + } + } +} diff --git a/packages/kbn-telemetry-tools/src/tools/utils.ts b/packages/kbn-telemetry-tools/src/tools/utils.ts new file mode 100644 index 0000000000000..f5cf74ae35e45 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/utils.ts @@ -0,0 +1,238 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as ts from 'typescript'; +import * as _ from 'lodash'; +import * as path from 'path'; +import glob from 'glob'; +import { readFile, writeFile } from 'fs'; +import { promisify } from 'util'; +import normalize from 'normalize-path'; +import { Optional } from '@kbn/utility-types'; + +export const readFileAsync = promisify(readFile); +export const writeFileAsync = promisify(writeFile); +export const globAsync = promisify(glob); + +export function isPropertyWithKey(property: ts.Node, identifierName: string) { + if (ts.isPropertyAssignment(property) || ts.isMethodDeclaration(property)) { + if (ts.isIdentifier(property.name)) { + return property.name.text === identifierName; + } + } + + return false; +} + +export function getProperty(objectNode: any, propertyName: string): ts.Node | null { + let foundProperty = null; + ts.visitNodes(objectNode?.properties || [], (node) => { + if (isPropertyWithKey(node, propertyName)) { + foundProperty = node; + return node; + } + }); + + return foundProperty; +} + +export function getModuleSpecifier(node: ts.Node): string { + if ((node as any).moduleSpecifier) { + return (node as any).moduleSpecifier.text; + } + return getModuleSpecifier(node.parent); +} + +export function getIdentifierDeclarationFromSource(node: ts.Node, source: ts.SourceFile) { + if (!ts.isIdentifier(node)) { + throw new Error(`node is not an identifier ${node.getText()}`); + } + + const identifierName = node.getText(); + const identifierDefinition: ts.Node = (source as any).locals.get(identifierName); + if (!identifierDefinition) { + throw new Error(`Unable to fine identifier in source ${identifierName}`); + } + const declarations = (identifierDefinition as any).declarations as ts.Node[]; + + const latestDeclaration: ts.Node | false | undefined = + Array.isArray(declarations) && declarations[declarations.length - 1]; + if (!latestDeclaration) { + throw new Error(`Unable to fine declaration for identifier ${identifierName}`); + } + + return latestDeclaration; +} + +export function getIdentifierDeclaration(node: ts.Node) { + const source = node.getSourceFile(); + if (!source) { + throw new Error('Unable to get source from node; check program configs.'); + } + + return getIdentifierDeclarationFromSource(node, source); +} + +export function getVariableValue(node: ts.Node): string | Record { + if (ts.isStringLiteral(node) || ts.isNumericLiteral(node)) { + return node.text; + } + + if (ts.isObjectLiteralExpression(node)) { + return serializeObject(node); + } + + throw Error(`Unsuppored Node: cannot get value of node (${node.getText()}) of kind ${node.kind}`); +} + +export function serializeObject(node: ts.Node) { + if (!ts.isObjectLiteralExpression(node)) { + throw new Error(`Expecting Object literal Expression got ${node.getText()}`); + } + + const value: Record = {}; + for (const property of node.properties) { + const propertyName = property.name?.getText(); + if (typeof propertyName === 'undefined') { + throw new Error(`Unable to get property name ${property.getText()}`); + } + if (ts.isPropertyAssignment(property)) { + value[propertyName] = getVariableValue(property.initializer); + } else { + value[propertyName] = getVariableValue(property); + } + } + + return value; +} + +export function getResolvedModuleSourceFile( + originalSource: ts.SourceFile, + program: ts.Program, + importedModuleName: string +) { + const resolvedModule = (originalSource as any).resolvedModules.get(importedModuleName); + const resolvedModuleSourceFile = program.getSourceFile(resolvedModule.resolvedFileName); + if (!resolvedModuleSourceFile) { + throw new Error(`Unable to find resolved module ${importedModuleName}`); + } + return resolvedModuleSourceFile; +} + +export function getPropertyValue( + node: ts.Node, + program: ts.Program, + config: Optional<{ chaseImport: boolean }> = {} +) { + const { chaseImport = false } = config; + + if (ts.isPropertyAssignment(node)) { + const { initializer } = node; + + if (ts.isIdentifier(initializer)) { + const identifierName = initializer.getText(); + const declaration = getIdentifierDeclaration(initializer); + if (ts.isImportSpecifier(declaration)) { + if (!chaseImport) { + throw new Error( + `Value of node ${identifierName} is imported from another file. Chasing imports is not allowed.` + ); + } + + const importedModuleName = getModuleSpecifier(declaration); + + const source = node.getSourceFile(); + const declarationSource = getResolvedModuleSourceFile(source, program, importedModuleName); + const declarationNode = getIdentifierDeclarationFromSource(initializer, declarationSource); + if (!ts.isVariableDeclaration(declarationNode)) { + throw new Error(`Expected ${identifierName} to be variable declaration.`); + } + if (!declarationNode.initializer) { + throw new Error(`Expected ${identifierName} to be initialized.`); + } + const serializedObject = serializeObject(declarationNode.initializer); + return serializedObject; + } + + return getVariableValue(declaration); + } + + return getVariableValue(initializer); + } +} + +export function pickDeep(collection: any, identity: any, thisArg?: any) { + const picked: any = _.pick(collection, identity, thisArg); + const collections = _.pick(collection, _.isObject, thisArg); + + _.each(collections, function (item, key) { + let object; + if (_.isArray(item)) { + object = _.reduce( + item, + function (result, value) { + const pickedDeep = pickDeep(value, identity, thisArg); + if (!_.isEmpty(pickedDeep)) { + result.push(pickedDeep); + } + return result; + }, + [] as any[] + ); + } else { + object = pickDeep(item, identity, thisArg); + } + + if (!_.isEmpty(object)) { + picked[key || ''] = object; + } + }); + + return picked; +} + +export const flattenKeys = (obj: any, keyPath: any[] = []): any => { + if (_.isObject(obj)) { + return _.reduce( + obj, + (cum, next, key) => { + const keys = [...keyPath, key]; + return _.merge(cum, flattenKeys(next, keys)); + }, + {} + ); + } + return { [keyPath.join('.')]: obj }; +}; + +export function difference(actual: any, expected: any) { + function changes(obj: any, base: any) { + return _.transform(obj, function (result, value, key) { + if (key && !_.isEqual(value, base[key])) { + result[key] = + _.isObject(value) && _.isObject(base[key]) ? changes(value, base[key]) : value; + } + }); + } + return changes(actual, expected); +} + +export function normalizePath(inputPath: string) { + return normalize(path.relative('.', inputPath)); +} diff --git a/packages/kbn-telemetry-tools/tsconfig.json b/packages/kbn-telemetry-tools/tsconfig.json new file mode 100644 index 0000000000000..13ce8ef2bad60 --- /dev/null +++ b/packages/kbn-telemetry-tools/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "src/**/*", + ] +} diff --git a/packages/kbn-ui-shared-deps/entry.js b/packages/kbn-ui-shared-deps/entry.js index 88e84fc87ae53..02b64157686c1 100644 --- a/packages/kbn-ui-shared-deps/entry.js +++ b/packages/kbn-ui-shared-deps/entry.js @@ -38,6 +38,7 @@ export const ReactDomServer = require('react-dom/server'); export const ReactIntl = require('react-intl'); export const ReactRouter = require('react-router'); // eslint-disable-line export const ReactRouterDom = require('react-router-dom'); +export const StyledComponents = require('styled-components'); Moment.tz.load(require('moment-timezone/data/packed/latest.json')); @@ -60,5 +61,8 @@ if (window.__kbnThemeVersion__ === 'v7') { ElasticEuiDarkTheme = require('@elastic/eui/dist/eui_theme_amsterdam_dark.json'); } +import * as Theme from './theme.ts'; +export { Theme }; + // massive deps that we should really get rid of or reduce in size substantially export const ElasticsearchBrowser = require('elasticsearch-browser/elasticsearch.js'); diff --git a/packages/kbn-ui-shared-deps/index.js b/packages/kbn-ui-shared-deps/index.js index 301d176555847..40e89f199b6a1 100644 --- a/packages/kbn-ui-shared-deps/index.js +++ b/packages/kbn-ui-shared-deps/index.js @@ -42,7 +42,9 @@ exports.externals = { 'react-intl': '__kbnSharedDeps__.ReactIntl', 'react-router': '__kbnSharedDeps__.ReactRouter', 'react-router-dom': '__kbnSharedDeps__.ReactRouterDom', + 'styled-components': '__kbnSharedDeps__.StyledComponents', '@kbn/monaco': '__kbnSharedDeps__.KbnMonaco', + '@kbn/ui-shared-deps/theme': '__kbnSharedDeps__.Theme', // this is how plugins/consumers from npm load monaco 'monaco-editor/esm/vs/editor/editor.api': '__kbnSharedDeps__.MonacoBarePluginApi', @@ -58,8 +60,8 @@ exports.externals = { '@elastic/eui/lib/services': '__kbnSharedDeps__.ElasticEuiLibServices', '@elastic/eui/lib/services/format': '__kbnSharedDeps__.ElasticEuiLibServicesFormat', '@elastic/eui/dist/eui_charts_theme': '__kbnSharedDeps__.ElasticEuiChartsTheme', - '@elastic/eui/dist/eui_theme_light.json': '__kbnSharedDeps__.ElasticEuiLightTheme', - '@elastic/eui/dist/eui_theme_dark.json': '__kbnSharedDeps__.ElasticEuiDarkTheme', + '@elastic/eui/dist/eui_theme_light.json': '__kbnSharedDeps__.Theme.euiLightVars', + '@elastic/eui/dist/eui_theme_dark.json': '__kbnSharedDeps__.Theme.euiDarkVars', /** * massive deps that we should really get rid of or reduce in size substantially diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 1cc04afad5669..0e3bb235c3d9f 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -26,20 +26,22 @@ "react": "^16.12.0", "react-dom": "^16.12.0", "react-intl": "^2.8.0", - "react-router": "^5.1.2", - "react-router-dom": "^5.1.2", + "react-is": "^16.8.0", + "react-router": "^5.2.0", + "react-router-dom": "^5.2.0", "regenerator-runtime": "^0.13.3", "rxjs": "^6.5.5", + "styled-components": "^5.1.0", "symbol-observable": "^1.2.0", "whatwg-fetch": "^3.0.0" }, "devDependencies": { "@kbn/babel-preset": "1.0.0", "@kbn/dev-utils": "1.0.0", - "loader-utils": "^1.2.3", - "val-loader": "^1.1.1", "css-loader": "^3.4.2", "del": "^5.1.0", + "loader-utils": "^1.2.3", + "val-loader": "^1.1.1", "webpack": "^4.41.5" } } diff --git a/packages/kbn-ui-shared-deps/theme.ts b/packages/kbn-ui-shared-deps/theme.ts new file mode 100644 index 0000000000000..ca4714779d39e --- /dev/null +++ b/packages/kbn-ui-shared-deps/theme.ts @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import LightTheme from '@elastic/eui/dist/eui_theme_light.json'; + +const globals: any = typeof window === 'undefined' ? {} : window; + +export type Theme = typeof LightTheme; + +export let euiLightVars: Theme; +export let euiDarkVars: Theme; +if (globals.__kbnThemeVersion__ === 'v7') { + euiLightVars = require('@elastic/eui/dist/eui_theme_light.json'); + euiDarkVars = require('@elastic/eui/dist/eui_theme_dark.json'); +} else { + euiLightVars = require('@elastic/eui/dist/eui_theme_amsterdam_light.json'); + euiDarkVars = require('@elastic/eui/dist/eui_theme_amsterdam_dark.json'); +} + +/** + * EUI Theme vars that automatically adjust to light/dark theme + */ +export let euiThemeVars: Theme; +if (globals.__kbnDarkTheme__) { + euiThemeVars = euiDarkVars; +} else { + euiThemeVars = euiLightVars; +} diff --git a/packages/kbn-ui-shared-deps/tsconfig.json b/packages/kbn-ui-shared-deps/tsconfig.json index 5aa0f45e4100d..cef9a442d17bc 100644 --- a/packages/kbn-ui-shared-deps/tsconfig.json +++ b/packages/kbn-ui-shared-deps/tsconfig.json @@ -1,4 +1,7 @@ { "extends": "../../tsconfig.json", - "include": ["index.d.ts", "./monaco"] + "include": [ + "index.d.ts", + "theme.ts" + ] } diff --git a/packages/kbn-ui-shared-deps/webpack.config.js b/packages/kbn-ui-shared-deps/webpack.config.js index 831e1e55573b3..c81da4689052a 100644 --- a/packages/kbn-ui-shared-deps/webpack.config.js +++ b/packages/kbn-ui-shared-deps/webpack.config.js @@ -78,6 +78,17 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'], }, + { + include: [require.resolve('./theme.ts')], + use: [ + { + loader: 'babel-loader', + options: { + presets: [require.resolve('@kbn/babel-preset/webpack_preset')], + }, + }, + ], + }, ], }, diff --git a/packages/kbn-utility-types/index.ts b/packages/kbn-utility-types/index.ts index 9a8a81460f410..6ccfeb8ab052c 100644 --- a/packages/kbn-utility-types/index.ts +++ b/packages/kbn-utility-types/index.ts @@ -61,7 +61,8 @@ export type Ensure = T extends X ? T : never; // If we define this inside RecursiveReadonly TypeScript complains. // eslint-disable-next-line @typescript-eslint/no-empty-interface -interface RecursiveReadonlyArray extends Array> {} +export interface RecursiveReadonlyArray extends ReadonlyArray> {} + export type RecursiveReadonly = T extends (...args: any) => any ? T : T extends any[] diff --git a/renovate.json5 b/renovate.json5 index 1af155fcc645e..49a255d60f29e 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -636,6 +636,14 @@ '(\\b|_)mocha(\\b|_)', ], }, + { + groupSlug: 'mock-fs', + groupName: 'mock-fs related packages', + packageNames: [ + 'mock-fs', + '@types/mock-fs', + ], + }, { groupSlug: 'moment', groupName: 'moment related packages', diff --git a/scripts/functional_tests.js b/scripts/functional_tests.js index fc88f2657018f..3fdab481dc750 100644 --- a/scripts/functional_tests.js +++ b/scripts/functional_tests.js @@ -22,6 +22,7 @@ const alwaysImportedTests = [ require.resolve('../test/functional/config.js'), require.resolve('../test/plugin_functional/config.js'), require.resolve('../test/ui_capabilities/newsfeed_err/config.ts'), + require.resolve('../test/new_visualize_flow/config.js'), ]; // eslint-disable-next-line no-restricted-syntax const onlyNotInCoverageTests = [ diff --git a/scripts/telemetry_check.js b/scripts/telemetry_check.js new file mode 100644 index 0000000000000..06b3ed46bdba6 --- /dev/null +++ b/scripts/telemetry_check.js @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +require('../src/setup_node_env/prebuilt_dev_only_entry'); +require('@kbn/telemetry-tools').runTelemetryCheck(); diff --git a/scripts/telemetry_extract.js b/scripts/telemetry_extract.js new file mode 100644 index 0000000000000..051bee26537b9 --- /dev/null +++ b/scripts/telemetry_extract.js @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +require('../src/setup_node_env/prebuilt_dev_only_entry'); +require('@kbn/telemetry-tools').runTelemetryExtract(); diff --git a/src/cli/cluster/cluster_manager.ts b/src/cli/cluster/cluster_manager.ts index 09f9bb2333dbe..6db6199b391e1 100644 --- a/src/cli/cluster/cluster_manager.ts +++ b/src/cli/cluster/cluster_manager.ts @@ -266,6 +266,11 @@ export class ClusterManager { fromRoot('x-pack/plugins/apm/e2e'), fromRoot('x-pack/plugins/apm/scripts'), fromRoot('x-pack/plugins/canvas/canvas_plugin_src'), // prevents server from restarting twice for Canvas plugin changes, + fromRoot('x-pack/plugins/case/server/scripts'), + fromRoot('x-pack/plugins/lists/scripts'), + fromRoot('x-pack/plugins/lists/server/scripts'), + fromRoot('x-pack/plugins/security_solution/scripts'), + fromRoot('x-pack/plugins/security_solution/server/lib/detection_engine/scripts'), 'plugins/java_languageserver', ]; diff --git a/src/core/public/application/capabilities/capabilities_service.test.ts b/src/core/public/application/capabilities/capabilities_service.test.ts index dfbb449b4d58e..286a93fdc2398 100644 --- a/src/core/public/application/capabilities/capabilities_service.test.ts +++ b/src/core/public/application/capabilities/capabilities_service.test.ts @@ -48,7 +48,7 @@ describe('#start', () => { appIds: ['app1', 'app2', 'legacyApp1', 'legacyApp2'], }); - // @ts-ignore TypeScript knows this shouldn't be possible + // @ts-expect-error TypeScript knows this shouldn't be possible expect(() => (capabilities.foo = 'foo')).toThrowError(); }); @@ -59,7 +59,7 @@ describe('#start', () => { appIds: ['app1', 'app2', 'legacyApp1', 'legacyApp2'], }); - // @ts-ignore TypeScript knows this shouldn't be possible + // @ts-expect-error TypeScript knows this shouldn't be possible expect(() => (capabilities.foo = 'foo')).toThrowError(); }); }); diff --git a/src/core/public/application/capabilities/capabilities_service.tsx b/src/core/public/application/capabilities/capabilities_service.tsx index d602422c14634..7304a8e5a66bc 100644 --- a/src/core/public/application/capabilities/capabilities_service.tsx +++ b/src/core/public/application/capabilities/capabilities_service.tsx @@ -16,9 +16,10 @@ * specific language governing permissions and limitations * under the License. */ +import { RecursiveReadonly } from '@kbn/utility-types'; import { Capabilities } from '../../../types/capabilities'; -import { deepFreeze, RecursiveReadonly } from '../../../utils'; +import { deepFreeze } from '../../../utils'; import { HttpStart } from '../../http'; interface StartDeps { diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 6926b6acf2411..0fe97431b1569 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -19,6 +19,7 @@ import { Observable } from 'rxjs'; import { History } from 'history'; +import { RecursiveReadonly } from '@kbn/utility-types'; import { Capabilities } from './capabilities'; import { ChromeStart } from '../chrome'; @@ -30,7 +31,6 @@ import { NotificationsStart } from '../notifications'; import { OverlayStart } from '../overlays'; import { PluginOpaqueId } from '../plugins'; import { IUiSettingsClient } from '../ui_settings'; -import { RecursiveReadonly } from '../../utils'; import { SavedObjectsStart } from '../saved_objects'; import { AppCategory } from '../../types'; import { ScopedHistory } from './scoped_history'; @@ -269,6 +269,10 @@ export interface LegacyApp extends AppBase { */ export type PublicAppInfo = Omit & { legacy: false; + // remove optional on fields populated with default values + status: AppStatus; + navLinkStatus: AppNavLinkStatus; + appRoute: string; }; /** @@ -278,6 +282,9 @@ export type PublicAppInfo = Omit & { */ export type PublicLegacyAppInfo = Omit & { legacy: true; + // remove optional on fields populated with default values + status: AppStatus; + navLinkStatus: AppNavLinkStatus; }; /** diff --git a/src/core/public/application/utils.ts b/src/core/public/application/utils.ts index 1dc9ec7059001..92d25fa468c4a 100644 --- a/src/core/public/application/utils.ts +++ b/src/core/public/application/utils.ts @@ -120,12 +120,17 @@ export function getAppInfo(app: App | LegacyApp): PublicAppInfo | Publi const { updater$, ...infos } = app; return { ...infos, + status: app.status!, + navLinkStatus: app.navLinkStatus!, legacy: true, }; } else { const { updater$, mount, ...infos } = app; return { ...infos, + status: app.status!, + navLinkStatus: app.navLinkStatus!, + appRoute: app.appRoute!, legacy: false, }; } diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index 0fe3c1f083cf0..1b894bc400f08 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -185,27 +185,27 @@ export class ChromeService { /> ), }); + } - if (isIE()) { - notifications.toasts.addWarning({ - title: mountReactNode( - - - - ), - }} - /> - ), - }); - } + if (isIE()) { + notifications.toasts.addWarning({ + title: mountReactNode( + + + + ), + }} + /> + ), + }); } return { diff --git a/src/core/public/chrome/recently_accessed/persisted_log.test.ts b/src/core/public/chrome/recently_accessed/persisted_log.test.ts index 9b307a2d25faf..4229efdf7ca9d 100644 --- a/src/core/public/chrome/recently_accessed/persisted_log.test.ts +++ b/src/core/public/chrome/recently_accessed/persisted_log.test.ts @@ -59,7 +59,7 @@ describe('PersistedLog', () => { describe('internal functionality', () => { test('reads from storage', () => { - // @ts-ignore + // @ts-expect-error const log = new PersistedLog(historyName, { maxLength: 10 }, storage); expect(storage.getItem).toHaveBeenCalledTimes(1); diff --git a/src/core/public/chrome/recently_accessed/recently_accessed_service.test.ts b/src/core/public/chrome/recently_accessed/recently_accessed_service.test.ts index 3c9713a93144a..14c3c581f9f17 100644 --- a/src/core/public/chrome/recently_accessed/recently_accessed_service.test.ts +++ b/src/core/public/chrome/recently_accessed/recently_accessed_service.test.ts @@ -55,11 +55,11 @@ describe('RecentlyAccessed#start()', () => { let originalLocalStorage: Storage; beforeAll(() => { originalLocalStorage = window.localStorage; - // @ts-ignore + // @ts-expect-error window.localStorage = new LocalStorageMock(); }); beforeEach(() => localStorage.clear()); - // @ts-ignore + // @ts-expect-error afterAll(() => (window.localStorage = originalLocalStorage)); const getStart = async () => { diff --git a/src/core/public/chrome/ui/header/header_help_menu.tsx b/src/core/public/chrome/ui/header/header_help_menu.tsx index 1023a561a0fe3..6d2938e3345a6 100644 --- a/src/core/public/chrome/ui/header/header_help_menu.tsx +++ b/src/core/public/chrome/ui/header/header_help_menu.tsx @@ -312,7 +312,6 @@ class HeaderHelpMenuUI extends Component { ); return ( - // @ts-ignore repositionOnScroll doesn't exist in EuiPopover { fetchMock.get('*', {}); await expect( fetchInstance.fetch( - // @ts-ignore + // @ts-expect-error { path: '/', headers: { hello: 'world' } }, { headers: { hello: 'mars' } } ) diff --git a/src/core/public/http/http_service.test.ts b/src/core/public/http/http_service.test.ts index 78220af9cc83b..0afea5aaa506a 100644 --- a/src/core/public/http/http_service.test.ts +++ b/src/core/public/http/http_service.test.ts @@ -17,7 +17,7 @@ * under the License. */ -// @ts-ignore +// @ts-expect-error import fetchMock from 'fetch-mock/es5/client'; import { loadingServiceMock } from './http_service.test.mocks'; diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 41af0f1b8395f..3e4e70fb99508 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -81,7 +81,6 @@ import { export { CoreContext, CoreSystem } from './core_system'; export { - RecursiveReadonly, DEFAULT_APP_CATEGORIES, getFlattenedObject, URLMeaningfulParts, diff --git a/src/core/public/injected_metadata/injected_metadata_service.test.ts b/src/core/public/injected_metadata/injected_metadata_service.test.ts index cf4b72114d5ac..1a8b4d14ee249 100644 --- a/src/core/public/injected_metadata/injected_metadata_service.test.ts +++ b/src/core/public/injected_metadata/injected_metadata_service.test.ts @@ -58,7 +58,6 @@ describe('setup.getCspConfig()', () => { const csp = injectedMetadata.setup().getCspConfig(); expect(() => { - // @ts-ignore TS knows this shouldn't be possible csp.warnLegacyBrowsers = false; }).toThrowError(); }); @@ -100,11 +99,11 @@ describe('setup.getPlugins()', () => { plugins.push({ id: 'new-plugin', plugin: {} as DiscoveredPlugin }); }).toThrowError(); expect(() => { - // @ts-ignore TS knows this shouldn't be possible + // @ts-expect-error TS knows this shouldn't be possible plugins[0].name = 'changed'; }).toThrowError(); expect(() => { - // @ts-ignore TS knows this shouldn't be possible + // @ts-expect-error TS knows this shouldn't be possible plugins[0].newProp = 'changed'; }).toThrowError(); }); @@ -136,7 +135,7 @@ describe('setup.getLegacyMetadata()', () => { foo: true, }); expect(() => { - // @ts-ignore TS knows this shouldn't be possible + // @ts-expect-error TS knows this shouldn't be possible legacyMetadata.foo = false; }).toThrowError(); }); diff --git a/src/core/public/integrations/styles/styles_service.ts b/src/core/public/integrations/styles/styles_service.ts index 41fc861d6cb39..d1d7f2170fde3 100644 --- a/src/core/public/integrations/styles/styles_service.ts +++ b/src/core/public/integrations/styles/styles_service.ts @@ -21,7 +21,7 @@ import { Subscription } from 'rxjs'; import { IUiSettingsClient } from '../../ui_settings'; import { CoreService } from '../../../types'; -// @ts-ignore +// @ts-expect-error import disableAnimationsCss from '!!raw-loader!./disable_animations.css'; interface StartDeps { diff --git a/src/core/public/kbn_bootstrap.ts b/src/core/public/kbn_bootstrap.ts index 0f86061816701..a108b5aaa47ec 100644 --- a/src/core/public/kbn_bootstrap.ts +++ b/src/core/public/kbn_bootstrap.ts @@ -42,7 +42,6 @@ export function __kbnBootstrap__() { const APM_ENABLED = process.env.IS_KIBANA_DISTRIBUTABLE !== 'true' && apmConfig != null; if (APM_ENABLED) { - // @ts-ignore // eslint-disable-next-line @typescript-eslint/no-var-requires const { init, apm } = require('@elastic/apm-rum'); if (apmConfig.globalLabels) { diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index d10e351f4d13e..86e281a49b744 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -116,6 +116,7 @@ import { PublicUiSettingsParams as PublicUiSettingsParams_2 } from 'src/core/ser import { PutScriptParams } from 'elasticsearch'; import { PutTemplateParams } from 'elasticsearch'; import React from 'react'; +import { RecursiveReadonly } from '@kbn/utility-types'; import { ReindexParams } from 'elasticsearch'; import { ReindexRethrottleParams } from 'elasticsearch'; import { RenderSearchTemplateParams } from 'elasticsearch'; @@ -1143,23 +1144,21 @@ export type PluginOpaqueId = symbol; // @public export type PublicAppInfo = Omit & { legacy: false; + status: AppStatus; + navLinkStatus: AppNavLinkStatus; + appRoute: string; }; // @public export type PublicLegacyAppInfo = Omit & { legacy: true; + status: AppStatus; + navLinkStatus: AppNavLinkStatus; }; // @public export type PublicUiSettingsParams = Omit; -// Warning: (ae-forgotten-export) The symbol "RecursiveReadonlyArray" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export type RecursiveReadonly = T extends (...args: any[]) => any ? T : T extends any[] ? RecursiveReadonlyArray : T extends object ? Readonly<{ - [K in keyof T]: RecursiveReadonly; -}> : T; - // Warning: (ae-missing-release-tag) "SavedObject" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/src/core/public/saved_objects/saved_objects_client.test.ts b/src/core/public/saved_objects/saved_objects_client.test.ts index 0c34a16c68e99..20824af38af0f 100644 --- a/src/core/public/saved_objects/saved_objects_client.test.ts +++ b/src/core/public/saved_objects/saved_objects_client.test.ts @@ -432,7 +432,7 @@ describe('SavedObjectsClient', () => { sortOrder: 'sort', // Not currently supported by API }; - // @ts-ignore + // @ts-expect-error savedObjectsClient.find(options); expect(http.fetch.mock.calls).toMatchInlineSnapshot(` Array [ diff --git a/src/core/public/ui_settings/ui_settings_api.test.ts b/src/core/public/ui_settings/ui_settings_api.test.ts index bab7081509d53..14791407d2550 100644 --- a/src/core/public/ui_settings/ui_settings_api.test.ts +++ b/src/core/public/ui_settings/ui_settings_api.test.ts @@ -17,7 +17,7 @@ * under the License. */ -// @ts-ignore +// @ts-expect-error import fetchMock from 'fetch-mock/es5/client'; import * as Rx from 'rxjs'; import { takeUntil, toArray } from 'rxjs/operators'; diff --git a/src/core/server/elasticsearch/__snapshots__/elasticsearch_config.test.ts.snap b/src/core/server/elasticsearch/__snapshots__/elasticsearch_config.test.ts.snap deleted file mode 100644 index 75627f311d9a5..0000000000000 --- a/src/core/server/elasticsearch/__snapshots__/elasticsearch_config.test.ts.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`#username throws if equal to "elastic", only while running from source 1`] = `"[username]: value of \\"elastic\\" is forbidden. This is a superuser account that can obfuscate privilege-related issues. You should use the \\"kibana_system\\" user instead."`; diff --git a/src/core/server/elasticsearch/elasticsearch_config.test.ts b/src/core/server/elasticsearch/elasticsearch_config.test.ts index ccd5fd0c7a571..648eaaf8a97f2 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.test.ts @@ -367,6 +367,8 @@ test('#username throws if equal to "elastic", only while running from source', ( const obj = { username: 'elastic', }; - expect(() => config.schema.validate(obj, { dist: false })).toThrowErrorMatchingSnapshot(); + expect(() => config.schema.validate(obj, { dist: false })).toThrowErrorMatchingInlineSnapshot( + `"[username]: value of \\"elastic\\" is forbidden. This is a superuser account that can obfuscate privilege-related issues. You should use the \\"kibana_system\\" user instead."` + ); expect(() => config.schema.validate(obj, { dist: true })).not.toThrow(); }); diff --git a/src/core/server/elasticsearch/elasticsearch_service.mock.ts b/src/core/server/elasticsearch/elasticsearch_service.mock.ts index 55e60f5987604..fdfc48fa9f754 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.mock.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.mock.ts @@ -19,26 +19,29 @@ import { BehaviorSubject } from 'rxjs'; import { Client } from 'elasticsearch'; -import { IClusterClient, ICustomClusterClient } from './cluster_client'; -import { IScopedClusterClient } from './scoped_cluster_client'; +import { + ILegacyClusterClient, + ILegacyCustomClusterClient, + ILegacyScopedClusterClient, +} from './legacy'; import { ElasticsearchConfig } from './elasticsearch_config'; import { ElasticsearchService } from './elasticsearch_service'; import { InternalElasticsearchServiceSetup, ElasticsearchStatusMeta } from './types'; import { NodesVersionCompatibility } from './version_check/ensure_es_version'; import { ServiceStatus, ServiceStatusLevels } from '../status'; -const createScopedClusterClientMock = (): jest.Mocked => ({ +const createScopedClusterClientMock = (): jest.Mocked => ({ callAsInternalUser: jest.fn(), callAsCurrentUser: jest.fn(), }); -const createCustomClusterClientMock = (): jest.Mocked => ({ +const createCustomClusterClientMock = (): jest.Mocked => ({ ...createClusterClientMock(), close: jest.fn(), }); function createClusterClientMock() { - const client: jest.Mocked = { + const client: jest.Mocked = { callAsInternalUser: jest.fn(), asScoped: jest.fn(), }; @@ -48,8 +51,8 @@ function createClusterClientMock() { interface MockedElasticSearchServiceSetup { legacy: { - createClient: jest.Mock; - client: jest.Mocked; + createClient: jest.Mock; + client: jest.Mocked; }; } @@ -81,7 +84,7 @@ const createStartContractMock = () => { type MockedInternalElasticSearchServiceSetup = jest.Mocked< InternalElasticsearchServiceSetup & { - legacy: { client: jest.Mocked }; + legacy: { client: jest.Mocked }; } >; const createInternalSetupContractMock = () => { diff --git a/src/core/server/elasticsearch/elasticsearch_service.test.mocks.ts b/src/core/server/elasticsearch/elasticsearch_service.test.mocks.ts index e87913a3c5e4f..c30230a7847a0 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.test.mocks.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.test.mocks.ts @@ -18,4 +18,4 @@ */ export const MockClusterClient = jest.fn(); -jest.mock('./cluster_client', () => ({ ClusterClient: MockClusterClient })); +jest.mock('./legacy/cluster_client', () => ({ LegacyClusterClient: MockClusterClient })); diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index 26001bf83924f..f47b33dd410f6 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.ts @@ -34,23 +34,26 @@ import { merge } from '../../utils'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; import { - ClusterClient, - ScopeableRequest, - IClusterClient, - ICustomClusterClient, -} from './cluster_client'; -import { ElasticsearchClientConfig } from './elasticsearch_client_config'; + LegacyClusterClient, + ILegacyClusterClient, + ILegacyCustomClusterClient, + LegacyElasticsearchClientConfig, + LegacyCallAPIOptions, +} from './legacy'; import { ElasticsearchConfig, ElasticsearchConfigType } from './elasticsearch_config'; import { InternalHttpServiceSetup, GetAuthHeaders } from '../http/'; -import { InternalElasticsearchServiceSetup, ElasticsearchServiceStart } from './types'; -import { CallAPIOptions } from './api_types'; +import { + InternalElasticsearchServiceSetup, + ElasticsearchServiceStart, + ScopeableRequest, +} from './types'; import { pollEsNodesVersion } from './version_check/ensure_es_version'; import { calculateStatus$ } from './status'; /** @internal */ interface CoreClusterClients { config: ElasticsearchConfig; - client: ClusterClient; + client: LegacyClusterClient; } interface SetupDeps { @@ -67,9 +70,9 @@ export class ElasticsearchService private kibanaVersion: string; private createClient?: ( type: string, - clientConfig?: Partial - ) => ICustomClusterClient; - private client?: IClusterClient; + clientConfig?: Partial + ) => ILegacyCustomClusterClient; + private client?: ILegacyClusterClient; constructor(private readonly coreContext: CoreContext) { this.kibanaVersion = coreContext.env.packageInfo.version; @@ -123,7 +126,7 @@ export class ElasticsearchService async callAsInternalUser( endpoint: string, clientParams: Record = {}, - options?: CallAPIOptions + options?: LegacyCallAPIOptions ) { const _client = await client$.pipe(take(1)).toPromise(); return await _client.callAsInternalUser(endpoint, clientParams, options); @@ -134,7 +137,7 @@ export class ElasticsearchService async callAsCurrentUser( endpoint: string, clientParams: Record = {}, - options?: CallAPIOptions + options?: LegacyCallAPIOptions ) { const _client = await client$.pipe(take(1)).toPromise(); return await _client @@ -155,7 +158,10 @@ export class ElasticsearchService kibanaVersion: this.kibanaVersion, }).pipe(takeUntil(this.stop$), shareReplay({ refCount: true, bufferSize: 1 })); - this.createClient = (type: string, clientConfig: Partial = {}) => { + this.createClient = ( + type: string, + clientConfig: Partial = {} + ) => { const finalConfig = merge({}, config, clientConfig); return this.createClusterClient(type, finalConfig, deps.http.getAuthHeaders); }; @@ -193,10 +199,10 @@ export class ElasticsearchService private createClusterClient( type: string, - config: ElasticsearchClientConfig, + config: LegacyElasticsearchClientConfig, getAuthHeaders?: GetAuthHeaders ) { - return new ClusterClient( + return new LegacyClusterClient( config, this.coreContext.logger.get('elasticsearch', type), getAuthHeaders diff --git a/src/core/server/elasticsearch/index.ts b/src/core/server/elasticsearch/index.ts index 2e45f710c4dcf..f5f5f5cc7b6f8 100644 --- a/src/core/server/elasticsearch/index.ts +++ b/src/core/server/elasticsearch/index.ts @@ -18,17 +18,14 @@ */ export { ElasticsearchService } from './elasticsearch_service'; +export { config, configSchema, ElasticsearchConfig } from './elasticsearch_config'; +export { NodesVersionCompatibility } from './version_check/ensure_es_version'; export { - ClusterClient, + ElasticsearchServiceSetup, + ElasticsearchServiceStart, + ElasticsearchStatusMeta, + InternalElasticsearchServiceSetup, FakeRequest, - IClusterClient, - ICustomClusterClient, ScopeableRequest, -} from './cluster_client'; -export { IScopedClusterClient, ScopedClusterClient, Headers } from './scoped_cluster_client'; -export { ElasticsearchClientConfig } from './elasticsearch_client_config'; -export { config, configSchema, ElasticsearchConfig } from './elasticsearch_config'; -export { ElasticsearchError, ElasticsearchErrorHelpers } from './errors'; -export * from './api_types'; -export * from './types'; -export { NodesVersionCompatibility } from './version_check/ensure_es_version'; +} from './types'; +export * from './legacy'; diff --git a/src/core/server/elasticsearch/api_types.ts b/src/core/server/elasticsearch/legacy/api_types.ts similarity index 54% rename from src/core/server/elasticsearch/api_types.ts rename to src/core/server/elasticsearch/legacy/api_types.ts index 5f2b61eb4935f..b9699ab290e3f 100644 --- a/src/core/server/elasticsearch/api_types.ts +++ b/src/core/server/elasticsearch/legacy/api_types.ts @@ -151,7 +151,7 @@ import { * * @public */ -export interface CallAPIOptions { +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` @@ -166,154 +166,154 @@ export interface CallAPIOptions { } /** @public */ -export interface APICaller { +export interface LegacyAPICaller { /* eslint-disable */ - (endpoint: 'bulk', params: BulkIndexDocumentsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'clearScroll', params: ClearScrollParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'count', params: CountParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'create', params: CreateDocumentParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'delete', params: DeleteDocumentParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'deleteByQuery', params: DeleteDocumentByQueryParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'deleteScript', params: DeleteScriptParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'deleteTemplate', params: DeleteTemplateParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'exists', params: ExistsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'explain', params: ExplainParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'fieldStats', params: FieldStatsParams, options?: CallAPIOptions): ReturnType; + (endpoint: 'bulk', params: BulkIndexDocumentsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'clearScroll', params: ClearScrollParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'count', params: CountParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'create', params: CreateDocumentParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'delete', params: DeleteDocumentParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'deleteByQuery', params: DeleteDocumentByQueryParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'deleteScript', params: DeleteScriptParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'deleteTemplate', params: DeleteTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'exists', params: ExistsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'explain', params: ExplainParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'fieldStats', params: FieldStatsParams, options?: LegacyCallAPIOptions): ReturnType; // Generic types cannot be properly looked up with ReturnType. Hard code these explicitly. - (endpoint: 'get', params: GetParams, options?: CallAPIOptions): Promise>; - (endpoint: 'getScript', params: GetScriptParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'getSource', params: GetSourceParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'getTemplate', params: GetTemplateParams, options?: CallAPIOptions): ReturnType; + (endpoint: 'get', params: GetParams, options?: LegacyCallAPIOptions): Promise>; + (endpoint: 'getScript', params: GetScriptParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'getSource', params: GetSourceParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'getTemplate', params: GetTemplateParams, options?: LegacyCallAPIOptions): ReturnType; // Generic types cannot be properly looked up with ReturnType. Hard code these explicitly. - (endpoint: 'index', params: IndexDocumentParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'info', params: InfoParams, options?: CallAPIOptions): ReturnType; + (endpoint: 'index', params: IndexDocumentParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'info', params: InfoParams, options?: LegacyCallAPIOptions): ReturnType; // Generic types cannot be properly looked up with ReturnType. Hard code these explicitly. - (endpoint: 'mget', params: MGetParams, options?: CallAPIOptions): Promise>; - (endpoint: 'msearch', params: MSearchParams, options?: CallAPIOptions): Promise>; - (endpoint: 'msearchTemplate', params: MSearchTemplateParams, options?: CallAPIOptions): Promise>; - (endpoint: 'mtermvectors', params: MTermVectorsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'ping', params: PingParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'putScript', params: PutScriptParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'putTemplate', params: PutTemplateParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'reindex', params: ReindexParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'reindexRethrottle', params: ReindexRethrottleParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'renderSearchTemplate', params: RenderSearchTemplateParams, options?: CallAPIOptions): ReturnType; + (endpoint: 'mget', params: MGetParams, options?: LegacyCallAPIOptions): Promise>; + (endpoint: 'msearch', params: MSearchParams, options?: LegacyCallAPIOptions): Promise>; + (endpoint: 'msearchTemplate', params: MSearchTemplateParams, options?: LegacyCallAPIOptions): Promise>; + (endpoint: 'mtermvectors', params: MTermVectorsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'ping', params: PingParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'putScript', params: PutScriptParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'putTemplate', params: PutTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'reindex', params: ReindexParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'reindexRethrottle', params: ReindexRethrottleParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'renderSearchTemplate', params: RenderSearchTemplateParams, options?: LegacyCallAPIOptions): ReturnType; // Generic types cannot be properly looked up with ReturnType. Hard code these explicitly. - (endpoint: 'scroll', params: ScrollParams, options?: CallAPIOptions): Promise>; - (endpoint: 'search', params: SearchParams, options?: CallAPIOptions): Promise>; - (endpoint: 'searchShards', params: SearchShardsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'searchTemplate', params: SearchTemplateParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'suggest', params: SuggestParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'termvectors', params: TermvectorsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'update', params: UpdateDocumentParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'updateByQuery', params: UpdateDocumentByQueryParams, options?: CallAPIOptions): ReturnType; + (endpoint: 'scroll', params: ScrollParams, options?: LegacyCallAPIOptions): Promise>; + (endpoint: 'search', params: SearchParams, options?: LegacyCallAPIOptions): Promise>; + (endpoint: 'searchShards', params: SearchShardsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'searchTemplate', params: SearchTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'suggest', params: SuggestParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'termvectors', params: TermvectorsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'update', params: UpdateDocumentParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'updateByQuery', params: UpdateDocumentByQueryParams, options?: LegacyCallAPIOptions): ReturnType; // cat namespace - (endpoint: 'cat.aliases', params: CatAliasesParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.allocation', params: CatAllocationParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.count', params: CatAllocationParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.fielddata', params: CatFielddataParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.health', params: CatHealthParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.help', params: CatHelpParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.indices', params: CatIndicesParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.master', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.nodeattrs', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.nodes', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.pendingTasks', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.plugins', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.recovery', params: CatRecoveryParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.repositories', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.segments', params: CatSegmentsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.shards', params: CatShardsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.snapshots', params: CatSnapshotsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.tasks', params: CatTasksParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cat.threadPool', params: CatThreadPoolParams, options?: CallAPIOptions): ReturnType; + (endpoint: 'cat.aliases', params: CatAliasesParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.allocation', params: CatAllocationParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.count', params: CatAllocationParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.fielddata', params: CatFielddataParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.health', params: CatHealthParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.help', params: CatHelpParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.indices', params: CatIndicesParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.master', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.nodeattrs', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.nodes', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.pendingTasks', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.plugins', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.recovery', params: CatRecoveryParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.repositories', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.segments', params: CatSegmentsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.shards', params: CatShardsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.snapshots', params: CatSnapshotsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.tasks', params: CatTasksParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cat.threadPool', params: CatThreadPoolParams, options?: LegacyCallAPIOptions): ReturnType; // cluster namespace - (endpoint: 'cluster.allocationExplain', params: ClusterAllocationExplainParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cluster.getSettings', params: ClusterGetSettingsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cluster.health', params: ClusterHealthParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cluster.pendingTasks', params: ClusterPendingTasksParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cluster.putSettings', params: ClusterPutSettingsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cluster.reroute', params: ClusterRerouteParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cluster.state', params: ClusterStateParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'cluster.stats', params: ClusterStatsParams, options?: CallAPIOptions): ReturnType; + (endpoint: 'cluster.allocationExplain', params: ClusterAllocationExplainParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cluster.getSettings', params: ClusterGetSettingsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cluster.health', params: ClusterHealthParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cluster.pendingTasks', params: ClusterPendingTasksParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cluster.putSettings', params: ClusterPutSettingsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cluster.reroute', params: ClusterRerouteParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cluster.state', params: ClusterStateParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'cluster.stats', params: ClusterStatsParams, options?: LegacyCallAPIOptions): ReturnType; // indices namespace - (endpoint: 'indices.analyze', params: IndicesAnalyzeParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.clearCache', params: IndicesClearCacheParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.close', params: IndicesCloseParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.create', params: IndicesCreateParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.delete', params: IndicesDeleteParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.deleteAlias', params: IndicesDeleteAliasParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.deleteTemplate', params: IndicesDeleteTemplateParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.exists', params: IndicesExistsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.existsAlias', params: IndicesExistsAliasParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.existsTemplate', params: IndicesExistsTemplateParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.existsType', params: IndicesExistsTypeParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.flush', params: IndicesFlushParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.flushSynced', params: IndicesFlushSyncedParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.forcemerge', params: IndicesForcemergeParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.get', params: IndicesGetParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.getAlias', params: IndicesGetAliasParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.getFieldMapping', params: IndicesGetFieldMappingParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.getMapping', params: IndicesGetMappingParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.getSettings', params: IndicesGetSettingsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.getTemplate', params: IndicesGetTemplateParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.getUpgrade', params: IndicesGetUpgradeParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.open', params: IndicesOpenParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.putAlias', params: IndicesPutAliasParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.putMapping', params: IndicesPutMappingParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.putSettings', params: IndicesPutSettingsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.putTemplate', params: IndicesPutTemplateParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.recovery', params: IndicesRecoveryParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.refresh', params: IndicesRefreshParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.rollover', params: IndicesRolloverParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.segments', params: IndicesSegmentsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.shardStores', params: IndicesShardStoresParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.shrink', params: IndicesShrinkParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.stats', params: IndicesStatsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.updateAliases', params: IndicesUpdateAliasesParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.upgrade', params: IndicesUpgradeParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'indices.validateQuery', params: IndicesValidateQueryParams, options?: CallAPIOptions): ReturnType; + (endpoint: 'indices.analyze', params: IndicesAnalyzeParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.clearCache', params: IndicesClearCacheParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.close', params: IndicesCloseParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.create', params: IndicesCreateParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.delete', params: IndicesDeleteParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.deleteAlias', params: IndicesDeleteAliasParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.deleteTemplate', params: IndicesDeleteTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.exists', params: IndicesExistsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.existsAlias', params: IndicesExistsAliasParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.existsTemplate', params: IndicesExistsTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.existsType', params: IndicesExistsTypeParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.flush', params: IndicesFlushParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.flushSynced', params: IndicesFlushSyncedParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.forcemerge', params: IndicesForcemergeParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.get', params: IndicesGetParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.getAlias', params: IndicesGetAliasParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.getFieldMapping', params: IndicesGetFieldMappingParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.getMapping', params: IndicesGetMappingParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.getSettings', params: IndicesGetSettingsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.getTemplate', params: IndicesGetTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.getUpgrade', params: IndicesGetUpgradeParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.open', params: IndicesOpenParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.putAlias', params: IndicesPutAliasParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.putMapping', params: IndicesPutMappingParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.putSettings', params: IndicesPutSettingsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.putTemplate', params: IndicesPutTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.recovery', params: IndicesRecoveryParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.refresh', params: IndicesRefreshParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.rollover', params: IndicesRolloverParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.segments', params: IndicesSegmentsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.shardStores', params: IndicesShardStoresParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.shrink', params: IndicesShrinkParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.stats', params: IndicesStatsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.updateAliases', params: IndicesUpdateAliasesParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.upgrade', params: IndicesUpgradeParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'indices.validateQuery', params: IndicesValidateQueryParams, options?: LegacyCallAPIOptions): ReturnType; // ingest namepsace - (endpoint: 'ingest.deletePipeline', params: IngestDeletePipelineParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'ingest.getPipeline', params: IngestGetPipelineParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'ingest.putPipeline', params: IngestPutPipelineParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'ingest.simulate', params: IngestSimulateParams, options?: CallAPIOptions): ReturnType; + (endpoint: 'ingest.deletePipeline', params: IngestDeletePipelineParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'ingest.getPipeline', params: IngestGetPipelineParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'ingest.putPipeline', params: IngestPutPipelineParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'ingest.simulate', params: IngestSimulateParams, options?: LegacyCallAPIOptions): ReturnType; // nodes namespace - (endpoint: 'nodes.hotThreads', params: NodesHotThreadsParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'nodes.info', params: NodesInfoParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'nodes.stats', params: NodesStatsParams, options?: CallAPIOptions): ReturnType; + (endpoint: 'nodes.hotThreads', params: NodesHotThreadsParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'nodes.info', params: NodesInfoParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'nodes.stats', params: NodesStatsParams, options?: LegacyCallAPIOptions): ReturnType; // snapshot namespace - (endpoint: 'snapshot.create', params: SnapshotCreateParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'snapshot.createRepository', params: SnapshotCreateRepositoryParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'snapshot.delete', params: SnapshotDeleteParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'snapshot.deleteRepository', params: SnapshotDeleteRepositoryParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'snapshot.get', params: SnapshotGetParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'snapshot.getRepository', params: SnapshotGetRepositoryParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'snapshot.restore', params: SnapshotRestoreParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'snapshot.status', params: SnapshotStatusParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'snapshot.verifyRepository', params: SnapshotVerifyRepositoryParams, options?: CallAPIOptions): ReturnType; + (endpoint: 'snapshot.create', params: SnapshotCreateParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'snapshot.createRepository', params: SnapshotCreateRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'snapshot.delete', params: SnapshotDeleteParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'snapshot.deleteRepository', params: SnapshotDeleteRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'snapshot.get', params: SnapshotGetParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'snapshot.getRepository', params: SnapshotGetRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'snapshot.restore', params: SnapshotRestoreParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'snapshot.status', params: SnapshotStatusParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'snapshot.verifyRepository', params: SnapshotVerifyRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; // tasks namespace - (endpoint: 'tasks.cancel', params: TasksCancelParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'tasks.get', params: TasksGetParams, options?: CallAPIOptions): ReturnType; - (endpoint: 'tasks.list', params: TasksListParams, options?: CallAPIOptions): ReturnType; + (endpoint: 'tasks.cancel', params: TasksCancelParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'tasks.get', params: TasksGetParams, options?: LegacyCallAPIOptions): ReturnType; + (endpoint: 'tasks.list', params: TasksListParams, options?: LegacyCallAPIOptions): ReturnType; // other APIs accessed via transport.request - (endpoint: 'transport.request', clientParams: AssistantAPIClientParams, options?: CallAPIOptions): Promise< + (endpoint: 'transport.request', clientParams: AssistantAPIClientParams, options?: LegacyCallAPIOptions): Promise< AssistanceAPIResponse >; - (endpoint: 'transport.request', clientParams: DeprecationAPIClientParams, options?: CallAPIOptions): Promise< + (endpoint: 'transport.request', clientParams: DeprecationAPIClientParams, options?: LegacyCallAPIOptions): Promise< DeprecationAPIResponse >; // Catch-all definition - (endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; + (endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; /* eslint-enable */ } diff --git a/src/core/server/elasticsearch/cluster_client.test.mocks.ts b/src/core/server/elasticsearch/legacy/cluster_client.test.mocks.ts similarity index 95% rename from src/core/server/elasticsearch/cluster_client.test.mocks.ts rename to src/core/server/elasticsearch/legacy/cluster_client.test.mocks.ts index 6873117aecc35..08585c2305e9b 100644 --- a/src/core/server/elasticsearch/cluster_client.test.mocks.ts +++ b/src/core/server/elasticsearch/legacy/cluster_client.test.mocks.ts @@ -29,7 +29,7 @@ jest.mock('elasticsearch', () => { export const MockScopedClusterClient = jest.fn(); jest.mock('./scoped_cluster_client', () => ({ - ScopedClusterClient: MockScopedClusterClient, + LegacyScopedClusterClient: MockScopedClusterClient, })); export const mockParseElasticsearchClientConfig = jest.fn(); diff --git a/src/core/server/elasticsearch/cluster_client.test.ts b/src/core/server/elasticsearch/legacy/cluster_client.test.ts similarity index 91% rename from src/core/server/elasticsearch/cluster_client.test.ts rename to src/core/server/elasticsearch/legacy/cluster_client.test.ts index 820272bdf14b8..9ab4862a1ab4c 100644 --- a/src/core/server/elasticsearch/cluster_client.test.ts +++ b/src/core/server/elasticsearch/legacy/cluster_client.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { ElasticsearchConfig } from './elasticsearch_config'; +import { ElasticsearchConfig } from '../elasticsearch_config'; import { MockClient, @@ -27,10 +27,10 @@ import { import { errors } from 'elasticsearch'; import { get } from 'lodash'; -import { Logger } from '../logging'; -import { loggingSystemMock } from '../logging/logging_system.mock'; -import { httpServerMock } from '../http/http_server.mocks'; -import { ClusterClient } from './cluster_client'; +import { Logger } from '../../logging'; +import { loggingSystemMock } from '../../logging/logging_system.mock'; +import { httpServerMock } from '../../http/http_server.mocks'; +import { LegacyClusterClient } from './cluster_client'; const logger = loggingSystemMock.create(); afterEach(() => jest.clearAllMocks()); @@ -42,7 +42,7 @@ test('#constructor creates client with parsed config', () => { const mockEsConfig = { apiVersion: 'es-version' } as any; const mockLogger = logger.get(); - const clusterClient = new ClusterClient(mockEsConfig, mockLogger); + const clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger); expect(clusterClient).toBeDefined(); expect(mockParseElasticsearchClientConfig).toHaveBeenCalledTimes(1); @@ -58,7 +58,7 @@ describe('#callAsInternalUser', () => { ping: jest.Mock; security: { authenticate: jest.Mock }; }; - let clusterClient: ClusterClient; + let clusterClient: LegacyClusterClient; beforeEach(() => { mockEsClientInstance = { @@ -68,7 +68,7 @@ describe('#callAsInternalUser', () => { }; MockClient.mockImplementation(() => mockEsClientInstance); - clusterClient = new ClusterClient({ apiVersion: 'es-version' } as any, logger.get()); + clusterClient = new LegacyClusterClient({ apiVersion: 'es-version' } as any, logger.get()); }); test('fails if cluster client is closed', async () => { @@ -220,7 +220,7 @@ describe('#asScoped', () => { let mockEsClientInstance: { ping: jest.Mock; close: jest.Mock }; let mockScopedEsClientInstance: { ping: jest.Mock; close: jest.Mock }; - let clusterClient: ClusterClient; + let clusterClient: LegacyClusterClient; let mockLogger: Logger; let mockEsConfig: ElasticsearchConfig; @@ -237,7 +237,7 @@ describe('#asScoped', () => { requestHeadersWhitelist: ['one', 'two'], } as any; - clusterClient = new ClusterClient(mockEsConfig, mockLogger); + clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger); jest.clearAllMocks(); }); @@ -272,7 +272,7 @@ describe('#asScoped', () => { test('properly configures `ignoreCertAndKey` for various configurations', () => { // Config without SSL. - clusterClient = new ClusterClient(mockEsConfig, mockLogger); + clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger); mockParseElasticsearchClientConfig.mockClear(); clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { one: '1' } })); @@ -285,7 +285,7 @@ describe('#asScoped', () => { // Config ssl.alwaysPresentCertificate === false mockEsConfig = { ...mockEsConfig, ssl: { alwaysPresentCertificate: false } } as any; - clusterClient = new ClusterClient(mockEsConfig, mockLogger); + clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger); mockParseElasticsearchClientConfig.mockClear(); clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { one: '1' } })); @@ -298,7 +298,7 @@ describe('#asScoped', () => { // Config ssl.alwaysPresentCertificate === true mockEsConfig = { ...mockEsConfig, ssl: { alwaysPresentCertificate: true } } as any; - clusterClient = new ClusterClient(mockEsConfig, mockLogger); + clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger); mockParseElasticsearchClientConfig.mockClear(); clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { one: '1' } })); @@ -341,7 +341,7 @@ describe('#asScoped', () => { }); test('does not fail when scope to not defined request', async () => { - clusterClient = new ClusterClient(mockEsConfig, mockLogger); + clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger); clusterClient.asScoped(); expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); expect(MockScopedClusterClient).toHaveBeenCalledWith( @@ -352,7 +352,7 @@ describe('#asScoped', () => { }); test('does not fail when scope to a request without headers', async () => { - clusterClient = new ClusterClient(mockEsConfig, mockLogger); + clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger); clusterClient.asScoped({} as any); expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); expect(MockScopedClusterClient).toHaveBeenCalledWith( @@ -363,7 +363,10 @@ describe('#asScoped', () => { }); test('calls getAuthHeaders and filters results for a real request', async () => { - clusterClient = new ClusterClient(mockEsConfig, mockLogger, () => ({ one: '1', three: '3' })); + clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, () => ({ + one: '1', + three: '3', + })); clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { two: '2' } })); expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); expect(MockScopedClusterClient).toHaveBeenCalledWith( @@ -374,7 +377,7 @@ describe('#asScoped', () => { }); test('getAuthHeaders results rewrite extends a request headers', async () => { - clusterClient = new ClusterClient(mockEsConfig, mockLogger, () => ({ one: 'foo' })); + clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, () => ({ one: 'foo' })); clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { one: '1', two: '2' } })); expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); expect(MockScopedClusterClient).toHaveBeenCalledWith( @@ -386,14 +389,14 @@ describe('#asScoped', () => { test("doesn't call getAuthHeaders for a fake request", async () => { const getAuthHeaders = jest.fn(); - clusterClient = new ClusterClient(mockEsConfig, mockLogger, getAuthHeaders); + clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, getAuthHeaders); clusterClient.asScoped({ headers: { one: '1', two: '2', three: '3' } }); expect(getAuthHeaders).not.toHaveBeenCalled(); }); test('filters a fake request headers', async () => { - clusterClient = new ClusterClient(mockEsConfig, mockLogger); + clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger); clusterClient.asScoped({ headers: { one: '1', two: '2', three: '3' } }); expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); @@ -409,7 +412,7 @@ describe('#close', () => { let mockEsClientInstance: { close: jest.Mock }; let mockScopedEsClientInstance: { close: jest.Mock }; - let clusterClient: ClusterClient; + let clusterClient: LegacyClusterClient; beforeEach(() => { mockEsClientInstance = { close: jest.fn() }; @@ -418,7 +421,7 @@ describe('#close', () => { () => mockScopedEsClientInstance ); - clusterClient = new ClusterClient( + clusterClient = new LegacyClusterClient( { apiVersion: 'es-version', requestHeadersWhitelist: [] } as any, logger.get() ); diff --git a/src/core/server/elasticsearch/cluster_client.ts b/src/core/server/elasticsearch/legacy/cluster_client.ts similarity index 77% rename from src/core/server/elasticsearch/cluster_client.ts rename to src/core/server/elasticsearch/legacy/cluster_client.ts index 2352677b8d3e0..8e2d20a8972fc 100644 --- a/src/core/server/elasticsearch/cluster_client.ts +++ b/src/core/server/elasticsearch/legacy/cluster_client.ts @@ -19,16 +19,17 @@ import { Client } from 'elasticsearch'; import { get } from 'lodash'; -import { ElasticsearchErrorHelpers } from './errors'; -import { GetAuthHeaders, isRealRequest, LegacyRequest } from '../http'; -import { filterHeaders, Headers, KibanaRequest, ensureRawRequest } from '../http/router'; -import { Logger } from '../logging'; +import { LegacyElasticsearchErrorHelpers } from './errors'; +import { GetAuthHeaders, isRealRequest } from '../../http'; +import { filterHeaders, ensureRawRequest } from '../../http/router'; +import { Logger } from '../../logging'; +import { ScopeableRequest } from '../types'; import { - ElasticsearchClientConfig, + LegacyElasticsearchClientConfig, parseElasticsearchClientConfig, } from './elasticsearch_client_config'; -import { ScopedClusterClient, IScopedClusterClient } from './scoped_cluster_client'; -import { CallAPIOptions, APICaller } from './api_types'; +import { LegacyScopedClusterClient, ILegacyScopedClusterClient } from './scoped_cluster_client'; +import { LegacyCallAPIOptions, LegacyAPICaller } from './api_types'; /** * Support Legacy platform request for the period of migration. @@ -50,7 +51,7 @@ const callAPI = async ( client: Client, endpoint: string, clientParams: Record = {}, - options: CallAPIOptions = { wrap401Errors: true } + options: LegacyCallAPIOptions = { wrap401Errors: true } ) => { const clientPath = endpoint.split('.'); const api: any = get(client, clientPath); @@ -75,55 +76,40 @@ const callAPI = async ( throw err; } - throw ElasticsearchErrorHelpers.decorateNotAuthorizedError(err); + throw LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(err); } }; -/** - * Fake request object created manually by Kibana plugins. - * @public - */ -export interface FakeRequest { - /** Headers used for authentication against Elasticsearch */ - headers: Headers; -} - /** * Represents an Elasticsearch cluster API client created by the platform. * It allows to call API on behalf of the internal Kibana user and * the actual user that is derived from the request headers (via `asScoped(...)`). * - * See {@link ClusterClient}. + * See {@link LegacyClusterClient}. * * @public */ -export type IClusterClient = Pick; +export type ILegacyClusterClient = Pick; /** * Represents an Elasticsearch cluster API client created by a plugin. * It allows to call API on behalf of the internal Kibana user and * the actual user that is derived from the request headers (via `asScoped(...)`). * - * See {@link ClusterClient}. - * - * @public - */ -export type ICustomClusterClient = Pick; - -/** - A user credentials container. - * It accommodates the necessary auth credentials to impersonate the current user. + * See {@link LegacyClusterClient}. * * @public - * See {@link KibanaRequest}. */ -export type ScopeableRequest = KibanaRequest | LegacyRequest | FakeRequest; +export type ILegacyCustomClusterClient = Pick< + LegacyClusterClient, + 'callAsInternalUser' | 'close' | 'asScoped' +>; /** * {@inheritDoc IClusterClient} * @public */ -export class ClusterClient implements IClusterClient { +export class LegacyClusterClient implements ILegacyClusterClient { /** * Raw Elasticsearch JS client that acts on behalf of the Kibana internal user. */ @@ -141,7 +127,7 @@ export class ClusterClient implements IClusterClient { private isClosed = false; constructor( - private readonly config: ElasticsearchClientConfig, + private readonly config: LegacyElasticsearchClientConfig, private readonly log: Logger, private readonly getAuthHeaders: GetAuthHeaders = noop ) { @@ -151,20 +137,24 @@ export class ClusterClient implements IClusterClient { /** * Calls specified endpoint with provided clientParams on behalf of the * Kibana internal user. - * See {@link APICaller}. + * See {@link LegacyAPICaller}. * * @param endpoint - String descriptor of the endpoint e.g. `cluster.getSettings` or `ping`. * @param clientParams - A dictionary of parameters that will be passed directly to the Elasticsearch JS client. * @param options - Options that affect the way we call the API and process the result. */ - public callAsInternalUser: APICaller = async ( + public callAsInternalUser: LegacyAPICaller = async ( endpoint: string, clientParams: Record = {}, - options?: CallAPIOptions + options?: LegacyCallAPIOptions ) => { this.assertIsNotClosed(); - return await (callAPI.bind(null, this.client) as APICaller)(endpoint, clientParams, options); + return await (callAPI.bind(null, this.client) as LegacyAPICaller)( + endpoint, + clientParams, + options + ); }; /** @@ -185,7 +175,7 @@ export class ClusterClient implements IClusterClient { } /** - * Creates an instance of {@link IScopedClusterClient} based on the configuration the + * Creates an instance of {@link ILegacyScopedClusterClient} based on the configuration the * current cluster client that exposes additional `callAsCurrentUser` method * scoped to the provided req. Consumers shouldn't worry about closing * scoped client instances, these will be automatically closed as soon as the @@ -194,7 +184,7 @@ export class ClusterClient implements IClusterClient { * @param request - Request the `IScopedClusterClient` instance will be scoped to. * Supports request optionality, Legacy.Request & FakeRequest for BWC with LegacyPlatform */ - public asScoped(request?: ScopeableRequest): IScopedClusterClient { + public asScoped(request?: ScopeableRequest): ILegacyScopedClusterClient { // It'd have been quite expensive to create and configure client for every incoming // request since it involves parsing of the config, reading of the SSL certificate and // key files etc. Moreover scoped client needs two Elasticsearch JS clients at the same @@ -210,7 +200,7 @@ export class ClusterClient implements IClusterClient { ); } - return new ScopedClusterClient( + return new LegacyScopedClusterClient( this.callAsInternalUser, this.callAsCurrentUser, filterHeaders(this.getHeaders(request), this.config.requestHeadersWhitelist) @@ -220,20 +210,20 @@ export class ClusterClient implements IClusterClient { /** * Calls specified endpoint with provided clientParams on behalf of the * user initiated request to the Kibana server (via HTTP request headers). - * See {@link APICaller}. + * See {@link LegacyAPICaller}. * * @param endpoint - String descriptor of the endpoint e.g. `cluster.getSettings` or `ping`. * @param clientParams - A dictionary of parameters that will be passed directly to the Elasticsearch JS client. * @param options - Options that affect the way we call the API and process the result. */ - private callAsCurrentUser: APICaller = async ( + private callAsCurrentUser: LegacyAPICaller = async ( endpoint: string, clientParams: Record = {}, - options?: CallAPIOptions + options?: LegacyCallAPIOptions ) => { this.assertIsNotClosed(); - return await (callAPI.bind(null, this.scopedClient!) as APICaller)( + return await (callAPI.bind(null, this.scopedClient!) as LegacyAPICaller)( endpoint, clientParams, options @@ -246,9 +236,7 @@ export class ClusterClient implements IClusterClient { } } - private getHeaders( - request?: KibanaRequest | LegacyRequest | FakeRequest - ): Record { + private getHeaders(request?: ScopeableRequest): Record { if (!isRealRequest(request)) { return request && request.headers ? request.headers : {}; } diff --git a/src/core/server/elasticsearch/elasticsearch_client_config.test.ts b/src/core/server/elasticsearch/legacy/elasticsearch_client_config.test.ts similarity index 98% rename from src/core/server/elasticsearch/elasticsearch_client_config.test.ts rename to src/core/server/elasticsearch/legacy/elasticsearch_client_config.test.ts index 77d1e41c9ad83..d6b65da7726d2 100644 --- a/src/core/server/elasticsearch/elasticsearch_client_config.test.ts +++ b/src/core/server/elasticsearch/legacy/elasticsearch_client_config.test.ts @@ -18,9 +18,9 @@ */ import { duration } from 'moment'; -import { loggingSystemMock } from '../logging/logging_system.mock'; +import { loggingSystemMock } from '../../logging/logging_system.mock'; import { - ElasticsearchClientConfig, + LegacyElasticsearchClientConfig, parseElasticsearchClientConfig, } from './elasticsearch_client_config'; const logger = loggingSystemMock.create(); @@ -64,7 +64,7 @@ Object { }); test('parses fully specified config', () => { - const elasticsearchConfig: ElasticsearchClientConfig = { + const elasticsearchConfig: LegacyElasticsearchClientConfig = { apiVersion: 'v7.0.0', customHeaders: { xsrf: 'something' }, logQueries: true, diff --git a/src/core/server/elasticsearch/elasticsearch_client_config.ts b/src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts similarity index 93% rename from src/core/server/elasticsearch/elasticsearch_client_config.ts rename to src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts index 287d835c40351..260a60ac74f11 100644 --- a/src/core/server/elasticsearch/elasticsearch_client_config.ts +++ b/src/core/server/elasticsearch/legacy/elasticsearch_client_config.ts @@ -22,9 +22,9 @@ import { cloneDeep } from 'lodash'; import { Duration } from 'moment'; import { checkServerIdentity } from 'tls'; import url from 'url'; -import { pick } from '../../utils'; -import { Logger } from '../logging'; -import { ElasticsearchConfig } from './elasticsearch_config'; +import { pick } from '../../../utils'; +import { Logger } from '../../logging'; +import { ElasticsearchConfig } from '../elasticsearch_config'; /** * @privateRemarks Config that consumers can pass to the Elasticsearch JS client is complex and includes @@ -33,7 +33,7 @@ import { ElasticsearchConfig } from './elasticsearch_config'; * * @public */ -export type ElasticsearchClientConfig = Pick & +export type LegacyElasticsearchClientConfig = Pick & Pick< ElasticsearchConfig, | 'apiVersion' @@ -53,7 +53,7 @@ export type ElasticsearchClientConfig = Pick { describe('NotAuthorized error', () => { describe('decorateNotAuthorizedError', () => { it('returns original object', () => { const error = new Error(); - expect(ElasticsearchErrorHelpers.decorateNotAuthorizedError(error)).toBe(error); + expect(LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(error)).toBe(error); }); it('makes the error identifiable as a NotAuthorized error', () => { const error = new Error(); - expect(ElasticsearchErrorHelpers.isNotAuthorizedError(error)).toBe(false); - ElasticsearchErrorHelpers.decorateNotAuthorizedError(error); - expect(ElasticsearchErrorHelpers.isNotAuthorizedError(error)).toBe(true); + expect(LegacyElasticsearchErrorHelpers.isNotAuthorizedError(error)).toBe(false); + LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(error); + expect(LegacyElasticsearchErrorHelpers.isNotAuthorizedError(error)).toBe(true); }); it('adds boom properties', () => { - const error = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()); + const error = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()); expect(typeof error.output).toBe('object'); expect(error.output.statusCode).toBe(401); }); it('preserves boom properties of input', () => { const error = Boom.notFound(); - ElasticsearchErrorHelpers.decorateNotAuthorizedError(error); + LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError(error); expect(error.output.statusCode).toBe(404); }); describe('error.output', () => { it('defaults to message of error', () => { - const error = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error('foobar')); + const error = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError( + new Error('foobar') + ); expect(error.output.payload).toHaveProperty('message', 'foobar'); }); it('prefixes message with passed reason', () => { - const error = ElasticsearchErrorHelpers.decorateNotAuthorizedError( + const error = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError( new Error('foobar'), 'biz' ); expect(error.output.payload).toHaveProperty('message', 'biz: foobar'); }); it('sets statusCode to 401', () => { - const error = ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error('foo')); + const error = LegacyElasticsearchErrorHelpers.decorateNotAuthorizedError( + new Error('foo') + ); expect(error.output).toHaveProperty('statusCode', 401); }); }); diff --git a/src/core/server/elasticsearch/errors.ts b/src/core/server/elasticsearch/legacy/errors.ts similarity index 88% rename from src/core/server/elasticsearch/errors.ts rename to src/core/server/elasticsearch/legacy/errors.ts index 6852b8632ebf1..f81903d76547a 100644 --- a/src/core/server/elasticsearch/errors.ts +++ b/src/core/server/elasticsearch/legacy/errors.ts @@ -27,11 +27,11 @@ enum ErrorCode { } /** @public */ -export interface ElasticsearchError extends Boom { +export interface LegacyElasticsearchError extends Boom { [code]?: string; } -function isElasticsearchError(error: any): error is ElasticsearchError { +function isElasticsearchError(error: any): error is LegacyElasticsearchError { return Boolean(error && error[code]); } @@ -40,7 +40,7 @@ function decorate( errorCode: ErrorCode, statusCode: number, message?: string -): ElasticsearchError { +): LegacyElasticsearchError { if (isElasticsearchError(error)) { return error; } @@ -50,7 +50,7 @@ function decorate( message, // keep status and messages if Boom error object already has them override: false, - }) as ElasticsearchError; + }) as LegacyElasticsearchError; boom[code] = errorCode; @@ -74,8 +74,8 @@ function decorate( * } * ``` */ -export class ElasticsearchErrorHelpers { - public static isNotAuthorizedError(error: any): error is ElasticsearchError { +export class LegacyElasticsearchErrorHelpers { + public static isNotAuthorizedError(error: any): error is LegacyElasticsearchError { return isElasticsearchError(error) && error[code] === ErrorCode.NOT_AUTHORIZED; } diff --git a/src/core/server/elasticsearch/legacy/index.ts b/src/core/server/elasticsearch/legacy/index.ts new file mode 100644 index 0000000000000..165980b9f4522 --- /dev/null +++ b/src/core/server/elasticsearch/legacy/index.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +export { + LegacyClusterClient, + ILegacyClusterClient, + ILegacyCustomClusterClient, +} from './cluster_client'; +export { ILegacyScopedClusterClient, LegacyScopedClusterClient } from './scoped_cluster_client'; +export { LegacyElasticsearchClientConfig } from './elasticsearch_client_config'; +export { retryCallCluster, migrationsRetryCallCluster } from './retry_call_cluster'; +export { LegacyElasticsearchError, LegacyElasticsearchErrorHelpers } from './errors'; +export * from './api_types'; diff --git a/src/core/server/elasticsearch/retry_call_cluster.test.ts b/src/core/server/elasticsearch/legacy/retry_call_cluster.test.ts similarity index 98% rename from src/core/server/elasticsearch/retry_call_cluster.test.ts rename to src/core/server/elasticsearch/legacy/retry_call_cluster.test.ts index 18ffa95048c4d..62789a4fe952d 100644 --- a/src/core/server/elasticsearch/retry_call_cluster.test.ts +++ b/src/core/server/elasticsearch/legacy/retry_call_cluster.test.ts @@ -19,7 +19,7 @@ import * as legacyElasticsearch from 'elasticsearch'; import { retryCallCluster, migrationsRetryCallCluster } from './retry_call_cluster'; -import { loggingSystemMock } from '../logging/logging_system.mock'; +import { loggingSystemMock } from '../../logging/logging_system.mock'; describe('retryCallCluster', () => { it('retries ES API calls that rejects with NoConnections', () => { diff --git a/src/core/server/elasticsearch/retry_call_cluster.ts b/src/core/server/elasticsearch/legacy/retry_call_cluster.ts similarity index 87% rename from src/core/server/elasticsearch/retry_call_cluster.ts rename to src/core/server/elasticsearch/legacy/retry_call_cluster.ts index aa3e39d948593..475a76d406017 100644 --- a/src/core/server/elasticsearch/retry_call_cluster.ts +++ b/src/core/server/elasticsearch/legacy/retry_call_cluster.ts @@ -21,9 +21,9 @@ import { retryWhen, concatMap } from 'rxjs/operators'; import { defer, throwError, iif, timer } from 'rxjs'; import * as legacyElasticsearch from 'elasticsearch'; -import { CallAPIOptions } from '.'; -import { APICaller } from './api_types'; -import { Logger } from '../logging'; +import { LegacyCallAPIOptions } from '.'; +import { LegacyAPICaller } from './api_types'; +import { Logger } from '../../logging'; const esErrors = legacyElasticsearch.errors; @@ -39,12 +39,16 @@ const esErrors = legacyElasticsearch.errors; * @param delay */ export function migrationsRetryCallCluster( - apiCaller: APICaller, + apiCaller: LegacyAPICaller, log: Logger, delay: number = 2500 ) { const previousErrors: string[] = []; - return (endpoint: string, clientParams: Record = {}, options?: CallAPIOptions) => { + return ( + endpoint: string, + clientParams: Record = {}, + options?: LegacyCallAPIOptions + ) => { return defer(() => apiCaller(endpoint, clientParams, options)) .pipe( retryWhen((error$) => @@ -63,7 +67,7 @@ export function migrationsRetryCallCluster( error instanceof esErrors.RequestTimeout || error instanceof esErrors.AuthenticationException || error instanceof esErrors.AuthorizationException || - // @ts-ignore + // @ts-expect-error error instanceof esErrors.Gone || error?.body?.error?.type === 'snapshot_in_progress_exception' ); @@ -86,8 +90,12 @@ export function migrationsRetryCallCluster( * * @param apiCaller */ -export function retryCallCluster(apiCaller: APICaller) { - return (endpoint: string, clientParams: Record = {}, options?: CallAPIOptions) => { +export function retryCallCluster(apiCaller: LegacyAPICaller) { + return ( + endpoint: string, + clientParams: Record = {}, + options?: LegacyCallAPIOptions + ) => { return defer(() => apiCaller(endpoint, clientParams, options)) .pipe( retryWhen((errors) => diff --git a/src/core/server/elasticsearch/scoped_cluster_client.test.ts b/src/core/server/elasticsearch/legacy/scoped_cluster_client.test.ts similarity index 95% rename from src/core/server/elasticsearch/scoped_cluster_client.test.ts rename to src/core/server/elasticsearch/legacy/scoped_cluster_client.test.ts index 04c13f85a1ef7..2eb8cefb564ae 100644 --- a/src/core/server/elasticsearch/scoped_cluster_client.test.ts +++ b/src/core/server/elasticsearch/legacy/scoped_cluster_client.test.ts @@ -17,15 +17,15 @@ * under the License. */ -import { ScopedClusterClient } from './scoped_cluster_client'; +import { LegacyScopedClusterClient } from './scoped_cluster_client'; let internalAPICaller: jest.Mock; let scopedAPICaller: jest.Mock; -let clusterClient: ScopedClusterClient; +let clusterClient: LegacyScopedClusterClient; beforeEach(() => { internalAPICaller = jest.fn(); scopedAPICaller = jest.fn(); - clusterClient = new ScopedClusterClient(internalAPICaller, scopedAPICaller, { one: '1' }); + clusterClient = new LegacyScopedClusterClient(internalAPICaller, scopedAPICaller, { one: '1' }); }); afterEach(() => jest.clearAllMocks()); @@ -184,7 +184,10 @@ describe('#callAsCurrentUser', () => { const mockResponse = { data: 'response' }; scopedAPICaller.mockResolvedValue(mockResponse); - const clusterClientWithoutHeaders = new ScopedClusterClient(internalAPICaller, scopedAPICaller); + const clusterClientWithoutHeaders = new LegacyScopedClusterClient( + internalAPICaller, + scopedAPICaller + ); await expect(clusterClientWithoutHeaders.callAsCurrentUser('ping')).resolves.toBe(mockResponse); expect(scopedAPICaller).toHaveBeenCalledTimes(1); diff --git a/src/core/server/elasticsearch/scoped_cluster_client.ts b/src/core/server/elasticsearch/legacy/scoped_cluster_client.ts similarity index 85% rename from src/core/server/elasticsearch/scoped_cluster_client.ts rename to src/core/server/elasticsearch/legacy/scoped_cluster_client.ts index 4b64bfba15190..e62409ff00c7e 100644 --- a/src/core/server/elasticsearch/scoped_cluster_client.ts +++ b/src/core/server/elasticsearch/legacy/scoped_cluster_client.ts @@ -18,11 +18,8 @@ */ import { intersection, isObject } from 'lodash'; -import { Headers } from '../http/router'; -import { APICaller, CallAPIOptions } from './api_types'; - -/** @public */ -export { Headers }; +import { Headers } from '../../http/router'; +import { LegacyAPICaller, LegacyCallAPIOptions } from './api_types'; /** * Serves the same purpose as "normal" `ClusterClient` but exposes additional @@ -30,12 +27,12 @@ export { Headers }; * user (as `callAsInternalUser` does) to request Elasticsearch API, but rather * passes HTTP headers extracted from the current user request to the API. * - * See {@link ScopedClusterClient}. + * See {@link LegacyScopedClusterClient}. * * @public */ -export type IScopedClusterClient = Pick< - ScopedClusterClient, +export type ILegacyScopedClusterClient = Pick< + LegacyScopedClusterClient, 'callAsCurrentUser' | 'callAsInternalUser' >; @@ -43,10 +40,10 @@ export type IScopedClusterClient = Pick< * {@inheritDoc IScopedClusterClient} * @public */ -export class ScopedClusterClient implements IScopedClusterClient { +export class LegacyScopedClusterClient implements ILegacyScopedClusterClient { constructor( - private readonly internalAPICaller: APICaller, - private readonly scopedAPICaller: APICaller, + private readonly internalAPICaller: LegacyAPICaller, + private readonly scopedAPICaller: LegacyAPICaller, private readonly headers?: Headers ) { this.callAsCurrentUser = this.callAsCurrentUser.bind(this); @@ -56,7 +53,7 @@ export class ScopedClusterClient implements IScopedClusterClient { /** * Calls specified `endpoint` with provided `clientParams` on behalf of the * Kibana internal user. - * See {@link APICaller}. + * See {@link LegacyAPICaller}. * * @param endpoint - String descriptor of the endpoint e.g. `cluster.getSettings` or `ping`. * @param clientParams - A dictionary of parameters that will be passed directly to the Elasticsearch JS client. @@ -65,7 +62,7 @@ export class ScopedClusterClient implements IScopedClusterClient { public callAsInternalUser( endpoint: string, clientParams: Record = {}, - options?: CallAPIOptions + options?: LegacyCallAPIOptions ) { return this.internalAPICaller(endpoint, clientParams, options); } @@ -73,7 +70,7 @@ export class ScopedClusterClient implements IScopedClusterClient { /** * Calls specified `endpoint` with provided `clientParams` on behalf of the * user initiated request to the Kibana server (via HTTP request headers). - * See {@link APICaller}. + * See {@link LegacyAPICaller}. * * @param endpoint - String descriptor of the endpoint e.g. `cluster.getSettings` or `ping`. * @param clientParams - A dictionary of parameters that will be passed directly to the Elasticsearch JS client. @@ -82,7 +79,7 @@ export class ScopedClusterClient implements IScopedClusterClient { public callAsCurrentUser( endpoint: string, clientParams: Record = {}, - options?: CallAPIOptions + options?: LegacyCallAPIOptions ) { const defaultHeaders = this.headers; if (defaultHeaders !== undefined) { diff --git a/src/core/server/elasticsearch/types.ts b/src/core/server/elasticsearch/types.ts index 6fef08fc298ff..2b4ba4b0a0a55 100644 --- a/src/core/server/elasticsearch/types.ts +++ b/src/core/server/elasticsearch/types.ts @@ -18,9 +18,14 @@ */ import { Observable } from 'rxjs'; +import { Headers } from '../http/router'; +import { LegacyRequest, KibanaRequest } from '../http'; import { ElasticsearchConfig } from './elasticsearch_config'; -import { ElasticsearchClientConfig } from './elasticsearch_client_config'; -import { IClusterClient, ICustomClusterClient } from './cluster_client'; +import { + LegacyElasticsearchClientConfig, + ILegacyClusterClient, + ILegacyCustomClusterClient, +} from './legacy'; import { NodesVersionCompatibility } from './version_check/ensure_es_version'; import { ServiceStatus } from '../status'; @@ -38,7 +43,7 @@ export interface ElasticsearchServiceSetup { * @deprecated * Use {@link ElasticsearchServiceStart.legacy | ElasticsearchServiceStart.legacy.createClient} instead. * - * Create application specific Elasticsearch cluster API client with customized config. See {@link IClusterClient}. + * Create application specific Elasticsearch cluster API client with customized config. See {@link ILegacyClusterClient}. * * @param type Unique identifier of the client * @param clientConfig A config consists of Elasticsearch JS client options and @@ -56,22 +61,22 @@ export interface ElasticsearchServiceSetup { */ readonly createClient: ( type: string, - clientConfig?: Partial - ) => ICustomClusterClient; + clientConfig?: Partial + ) => ILegacyCustomClusterClient; /** * @deprecated * Use {@link ElasticsearchServiceStart.legacy | ElasticsearchServiceStart.legacy.client} instead. * * All Elasticsearch config value changes are processed under the hood. - * See {@link IClusterClient}. + * See {@link ILegacyClusterClient}. * * @example * ```js * const client = core.elasticsearch.legacy.client; * ``` */ - readonly client: IClusterClient; + readonly client: ILegacyClusterClient; }; } @@ -86,7 +91,7 @@ export interface ElasticsearchServiceStart { * */ legacy: { /** - * Create application specific Elasticsearch cluster API client with customized config. See {@link IClusterClient}. + * Create application specific Elasticsearch cluster API client with customized config. See {@link ILegacyClusterClient}. * * @param type Unique identifier of the client * @param clientConfig A config consists of Elasticsearch JS client options and @@ -104,19 +109,19 @@ export interface ElasticsearchServiceStart { */ readonly createClient: ( type: string, - clientConfig?: Partial - ) => ICustomClusterClient; + clientConfig?: Partial + ) => ILegacyCustomClusterClient; /** * A pre-configured Elasticsearch client. All Elasticsearch config value changes are processed under the hood. - * See {@link IClusterClient}. + * See {@link ILegacyClusterClient}. * * @example * ```js * const client = core.elasticsearch.client; * ``` */ - readonly client: IClusterClient; + readonly client: ILegacyClusterClient; }; } @@ -135,3 +140,21 @@ export interface ElasticsearchStatusMeta { warningNodes: NodesVersionCompatibility['warningNodes']; incompatibleNodes: NodesVersionCompatibility['incompatibleNodes']; } + +/** + * Fake request object created manually by Kibana plugins. + * @public + */ +export interface FakeRequest { + /** Headers used for authentication against Elasticsearch */ + headers: Headers; +} + +/** + A user credentials container. + * It accommodates the necessary auth credentials to impersonate the current user. + * + * @public + * See {@link KibanaRequest}. + */ +export type ScopeableRequest = KibanaRequest | LegacyRequest | FakeRequest; diff --git a/src/core/server/elasticsearch/version_check/ensure_es_version.ts b/src/core/server/elasticsearch/version_check/ensure_es_version.ts index 776298e5869a0..3f562dac22a02 100644 --- a/src/core/server/elasticsearch/version_check/ensure_es_version.ts +++ b/src/core/server/elasticsearch/version_check/ensure_es_version.ts @@ -29,10 +29,10 @@ import { esVersionEqualsKibana, } from './es_kibana_version_compatability'; import { Logger } from '../../logging'; -import { APICaller } from '..'; +import { LegacyAPICaller } from '..'; export interface PollEsNodesVersionOptions { - callWithInternalUser: APICaller; + callWithInternalUser: LegacyAPICaller; log: Logger; kibanaVersion: string; ignoreVersionMismatch: boolean; diff --git a/src/core/server/http/cookie_session_storage.ts b/src/core/server/http/cookie_session_storage.ts index 13f498233f695..5ca70045f81db 100644 --- a/src/core/server/http/cookie_session_storage.ts +++ b/src/core/server/http/cookie_session_storage.ts @@ -19,7 +19,7 @@ import { Request, Server } from 'hapi'; import hapiAuthCookie from 'hapi-auth-cookie'; -// @ts-ignore no TS definitions +// @ts-expect-error no TS definitions import Statehood from 'statehood'; import { KibanaRequest, ensureRawRequest } from './router'; diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts index ca9dfde2e71dc..65d633260a791 100644 --- a/src/core/server/http/index.ts +++ b/src/core/server/http/index.ts @@ -25,6 +25,7 @@ export { CustomHttpResponseOptions, IKibanaSocket, isRealRequest, + Headers, HttpResponseOptions, HttpResponsePayload, ErrorHttpResponseOptions, diff --git a/src/core/server/http/integration_tests/core_service.test.mocks.ts b/src/core/server/http/integration_tests/core_service.test.mocks.ts index 8e782970e2a8e..6f9b4b96eae9d 100644 --- a/src/core/server/http/integration_tests/core_service.test.mocks.ts +++ b/src/core/server/http/integration_tests/core_service.test.mocks.ts @@ -19,8 +19,8 @@ import { elasticsearchServiceMock } from '../../elasticsearch/elasticsearch_service.mock'; export const clusterClientMock = jest.fn(); -jest.doMock('../../elasticsearch/scoped_cluster_client', () => ({ - ScopedClusterClient: clusterClientMock.mockImplementation(function () { +jest.doMock('../../elasticsearch/legacy/scoped_cluster_client', () => ({ + LegacyScopedClusterClient: clusterClientMock.mockImplementation(function () { return elasticsearchServiceMock.createScopedClusterClient(); }), })); diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts index f266677c1a172..fefd75ad9710e 100644 --- a/src/core/server/http/router/request.ts +++ b/src/core/server/http/router/request.ts @@ -21,8 +21,9 @@ import { Url } from 'url'; import { Request, ApplicationState } from 'hapi'; import { Observable, fromEvent, merge } from 'rxjs'; import { shareReplay, first, takeUntil } from 'rxjs/operators'; +import { RecursiveReadonly } from '@kbn/utility-types'; -import { deepFreeze, RecursiveReadonly } from '../../../utils'; +import { deepFreeze } from '../../../utils'; import { Headers } from './headers'; import { RouteMethod, RouteConfigOptions, validBodyOutput, isSafeMethod } from './route'; import { KibanaSocket, IKibanaSocket } from './socket'; @@ -156,7 +157,7 @@ export class KibanaRequest< public readonly params: Params, public readonly query: Query, public readonly body: Body, - // @ts-ignore we will use this flag as soon as http request proxy is supported in the core + // @ts-expect-error we will use this flag as soon as http request proxy is supported in the core // until that time we have to expose all the headers private readonly withoutSecretHeaders: boolean ) { diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 7520111bf33ac..35aabab4a0b26 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -41,7 +41,7 @@ import { ElasticsearchServiceSetup, - IScopedClusterClient, + ILegacyScopedClusterClient, configSchema as elasticsearchConfigSchema, ElasticsearchServiceStart, } from './elasticsearch'; @@ -91,25 +91,24 @@ export { export { CoreId } from './core_context'; export { CspConfig, ICspConfig } from './csp'; export { - ClusterClient, - IClusterClient, - ICustomClusterClient, - Headers, - ScopedClusterClient, - IScopedClusterClient, + LegacyClusterClient, + ILegacyClusterClient, + ILegacyCustomClusterClient, + LegacyScopedClusterClient, + ILegacyScopedClusterClient, ElasticsearchConfig, - ElasticsearchClientConfig, - ElasticsearchError, - ElasticsearchErrorHelpers, + LegacyElasticsearchClientConfig, + LegacyElasticsearchError, + LegacyElasticsearchErrorHelpers, ElasticsearchServiceSetup, ElasticsearchServiceStart, ElasticsearchStatusMeta, NodesVersionCompatibility, - APICaller, + LegacyAPICaller, FakeRequest, ScopeableRequest, } from './elasticsearch'; -export * from './elasticsearch/api_types'; +export * from './elasticsearch/legacy/api_types'; export { AuthenticationHandler, AuthHeaders, @@ -127,6 +126,7 @@ export { CustomHttpResponseOptions, GetAuthHeaders, GetAuthState, + Headers, HttpAuth, HttpResponseOptions, HttpResponsePayload, @@ -307,7 +307,6 @@ export { } from './metrics'; export { - RecursiveReadonly, DEFAULT_APP_CATEGORIES, getFlattenedObject, URLMeaningfulParts, @@ -356,7 +355,7 @@ export { * which uses the credentials of the incoming request * - {@link ISavedObjectTypeRegistry | savedObjects.typeRegistry} - Type registry containing * all the registered types. - * - {@link ScopedClusterClient | elasticsearch.legacy.client} - Elasticsearch + * - {@link LegacyScopedClusterClient | elasticsearch.legacy.client} - Elasticsearch * data client which uses the credentials of the incoming request * - {@link IUiSettingsClient | uiSettings.client} - uiSettings client * which uses the credentials of the incoming request @@ -371,7 +370,7 @@ export interface RequestHandlerContext { }; elasticsearch: { legacy: { - client: IScopedClusterClient; + client: ILegacyScopedClusterClient; }; }; uiSettings: { diff --git a/src/core/server/legacy/config/get_unused_config_keys.ts b/src/core/server/legacy/config/get_unused_config_keys.ts index 6cd193d896109..8e53178142180 100644 --- a/src/core/server/legacy/config/get_unused_config_keys.ts +++ b/src/core/server/legacy/config/get_unused_config_keys.ts @@ -18,7 +18,7 @@ */ import { difference, get, set } from 'lodash'; -// @ts-ignore +// @ts-expect-error import { getTransform } from '../../../../legacy/deprecation/index'; import { unset } from '../../../../legacy/utils'; import { getFlattenedObject } from '../../../utils'; diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index ccadae757fe54..ffe3b2375bc90 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -29,7 +29,6 @@ import { import { BehaviorSubject, throwError } from 'rxjs'; -// @ts-ignore: implicit any for JS file import { ClusterManager as MockClusterManager } from '../../../cli/cluster/cluster_manager'; import KbnServer from '../../../legacy/server/kbn_server'; import { Config, Env, ObjectToConfigAdapter } from '../config'; diff --git a/src/core/server/legacy/logging/legacy_logging_server.ts b/src/core/server/legacy/logging/legacy_logging_server.ts index 85a8686b4eded..4a7fea87cf69f 100644 --- a/src/core/server/legacy/logging/legacy_logging_server.ts +++ b/src/core/server/legacy/logging/legacy_logging_server.ts @@ -19,9 +19,9 @@ import { ServerExtType } from 'hapi'; import Podium from 'podium'; -// @ts-ignore: implicit any for JS file +// @ts-expect-error: implicit any for JS file import { Config } from '../../../../legacy/server/config'; -// @ts-ignore: implicit any for JS file +// @ts-expect-error: implicit any for JS file import { setupLogging } from '../../../../legacy/server/logging'; import { LogLevel } from '../../logging/log_level'; import { LogRecord } from '../../logging/log_record'; diff --git a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts index 5039b3a55cc58..f3ec2ed8335c5 100644 --- a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts +++ b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts @@ -23,7 +23,7 @@ import { toArray, tap, distinct, map } from 'rxjs/operators'; import { findPluginSpecs, defaultConfig, - // @ts-ignore + // @ts-expect-error } from '../../../../legacy/plugin_discovery/find_plugin_specs.js'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { collectUiExports as collectLegacyUiExports } from '../../../../legacy/ui/ui_exports/collect_ui_exports'; diff --git a/src/core/server/logging/integration_tests/logging.test.ts b/src/core/server/logging/integration_tests/logging.test.ts index b88f5ba2c2b60..a80939a25ae65 100644 --- a/src/core/server/logging/integration_tests/logging.test.ts +++ b/src/core/server/logging/integration_tests/logging.test.ts @@ -18,6 +18,9 @@ */ import * as kbnTestServer from '../../../../test_utils/kbn_server'; +import { InternalCoreSetup } from '../../internal_types'; +import { LoggerContextConfigInput } from '../logging_config'; +import { Subject } from 'rxjs'; function createRoot() { return kbnTestServer.createRoot({ @@ -111,4 +114,162 @@ describe('logging service', () => { expect(mockConsoleLog).toHaveBeenCalledTimes(0); }); }); + + describe('custom context configuration', () => { + const CUSTOM_LOGGING_CONFIG: LoggerContextConfigInput = { + appenders: { + customJsonConsole: { + kind: 'console', + layout: { + kind: 'json', + }, + }, + customPatternConsole: { + kind: 'console', + layout: { + kind: 'pattern', + pattern: 'CUSTOM - PATTERN [%logger][%level] %message', + }, + }, + }, + + loggers: [ + { context: 'debug_json', appenders: ['customJsonConsole'], level: 'debug' }, + { context: 'debug_pattern', appenders: ['customPatternConsole'], level: 'debug' }, + { context: 'info_json', appenders: ['customJsonConsole'], level: 'info' }, + { context: 'info_pattern', appenders: ['customPatternConsole'], level: 'info' }, + { + context: 'all', + appenders: ['customJsonConsole', 'customPatternConsole'], + level: 'debug', + }, + ], + }; + + let root: ReturnType; + let setup: InternalCoreSetup; + let mockConsoleLog: jest.SpyInstance; + const loggingConfig$ = new Subject(); + const setContextConfig = (enable: boolean) => + enable ? loggingConfig$.next(CUSTOM_LOGGING_CONFIG) : loggingConfig$.next({}); + beforeAll(async () => { + mockConsoleLog = jest.spyOn(global.console, 'log'); + root = kbnTestServer.createRoot(); + + setup = await root.setup(); + setup.logging.configure(['plugins', 'myplugin'], loggingConfig$); + }, 30000); + + beforeEach(() => { + mockConsoleLog.mockClear(); + }); + + afterAll(async () => { + mockConsoleLog.mockRestore(); + await root.shutdown(); + }); + + it('does not write to custom appenders when not configured', async () => { + const logger = root.logger.get('plugins.myplugin.debug_pattern'); + setContextConfig(false); + logger.info('log1'); + setContextConfig(true); + logger.debug('log2'); + logger.info('log3'); + setContextConfig(false); + logger.info('log4'); + expect(mockConsoleLog).toHaveBeenCalledTimes(2); + expect(mockConsoleLog).toHaveBeenCalledWith( + 'CUSTOM - PATTERN [plugins.myplugin.debug_pattern][DEBUG] log2' + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + 'CUSTOM - PATTERN [plugins.myplugin.debug_pattern][INFO ] log3' + ); + }); + + it('writes debug_json context to custom JSON appender', async () => { + setContextConfig(true); + const logger = root.logger.get('plugins.myplugin.debug_json'); + logger.debug('log1'); + logger.info('log2'); + expect(mockConsoleLog).toHaveBeenCalledTimes(2); + + const [firstCall, secondCall] = mockConsoleLog.mock.calls.map(([jsonString]) => + JSON.parse(jsonString) + ); + expect(firstCall).toMatchObject({ + level: 'DEBUG', + context: 'plugins.myplugin.debug_json', + message: 'log1', + }); + expect(secondCall).toMatchObject({ + level: 'INFO', + context: 'plugins.myplugin.debug_json', + message: 'log2', + }); + }); + + it('writes info_json context to custom JSON appender', async () => { + setContextConfig(true); + const logger = root.logger.get('plugins.myplugin.info_json'); + logger.debug('i should not be logged!'); + logger.info('log2'); + + expect(mockConsoleLog).toHaveBeenCalledTimes(1); + expect(JSON.parse(mockConsoleLog.mock.calls[0][0])).toMatchObject({ + level: 'INFO', + context: 'plugins.myplugin.info_json', + message: 'log2', + }); + }); + + it('writes debug_pattern context to custom pattern appender', async () => { + setContextConfig(true); + const logger = root.logger.get('plugins.myplugin.debug_pattern'); + logger.debug('log1'); + logger.info('log2'); + + expect(mockConsoleLog).toHaveBeenCalledTimes(2); + expect(mockConsoleLog).toHaveBeenCalledWith( + 'CUSTOM - PATTERN [plugins.myplugin.debug_pattern][DEBUG] log1' + ); + expect(mockConsoleLog).toHaveBeenCalledWith( + 'CUSTOM - PATTERN [plugins.myplugin.debug_pattern][INFO ] log2' + ); + }); + + it('writes info_pattern context to custom pattern appender', async () => { + setContextConfig(true); + const logger = root.logger.get('plugins.myplugin.info_pattern'); + logger.debug('i should not be logged!'); + logger.info('log2'); + expect(mockConsoleLog).toHaveBeenCalledTimes(1); + expect(mockConsoleLog).toHaveBeenCalledWith( + 'CUSTOM - PATTERN [plugins.myplugin.info_pattern][INFO ] log2' + ); + }); + + it('writes all context to both appenders', async () => { + setContextConfig(true); + const logger = root.logger.get('plugins.myplugin.all'); + logger.debug('log1'); + logger.info('log2'); + + expect(mockConsoleLog).toHaveBeenCalledTimes(4); + const logs = mockConsoleLog.mock.calls.map(([jsonString]) => jsonString); + + expect(JSON.parse(logs[0])).toMatchObject({ + level: 'DEBUG', + context: 'plugins.myplugin.all', + message: 'log1', + }); + expect(logs[1]).toEqual('CUSTOM - PATTERN [plugins.myplugin.all][DEBUG] log1'); + expect(JSON.parse(logs[2])).toMatchObject({ + level: 'INFO', + context: 'plugins.myplugin.all', + message: 'log2', + }); + expect(logs[3]).toEqual('CUSTOM - PATTERN [plugins.myplugin.all][INFO ] log2'); + }); + }); }); diff --git a/src/core/server/logging/logging_system.test.ts b/src/core/server/logging/logging_system.test.ts index f73e40fe320dc..ac52973081106 100644 --- a/src/core/server/logging/logging_system.test.ts +++ b/src/core/server/logging/logging_system.test.ts @@ -230,6 +230,49 @@ test('setContextConfig() updates config with relative contexts', () => { ); }); +test('setContextConfig() updates config for a root context', () => { + const testsLogger = system.get('tests'); + const testsChildLogger = system.get('tests', 'child'); + const testsGrandchildLogger = system.get('tests', 'child', 'grandchild'); + + system.upgrade( + config.schema.validate({ + appenders: { default: { kind: 'console', layout: { kind: 'json' } } }, + root: { level: 'info' }, + }) + ); + + system.setContextConfig(['tests', 'child'], { + appenders: new Map([ + [ + 'custom', + { kind: 'console', layout: { kind: 'pattern', pattern: '[%level][%logger] %message' } }, + ], + ]), + loggers: [{ context: '', appenders: ['custom'], level: 'debug' }], + }); + + testsLogger.warn('tests log to default!'); + testsChildLogger.error('tests.child log to custom!'); + testsGrandchildLogger.debug('tests.child.grandchild log to custom!'); + + expect(mockConsoleLog).toHaveBeenCalledTimes(3); + // Parent context is unaffected + expect(JSON.parse(mockConsoleLog.mock.calls[0][0])).toMatchObject({ + context: 'tests', + message: 'tests log to default!', + level: 'WARN', + }); + // Customized contexts + expect(mockConsoleLog.mock.calls[1][0]).toMatchInlineSnapshot( + `"[ERROR][tests.child] tests.child log to custom!"` + ); + + expect(mockConsoleLog.mock.calls[2][0]).toMatchInlineSnapshot( + `"[DEBUG][tests.child.grandchild] tests.child.grandchild log to custom!"` + ); +}); + test('custom context configs are applied on subsequent calls to update()', () => { system.setContextConfig(['tests', 'child'], { appenders: new Map([ diff --git a/src/core/server/logging/logging_system.ts b/src/core/server/logging/logging_system.ts index 0bab9534d2d05..8aadab83bf716 100644 --- a/src/core/server/logging/logging_system.ts +++ b/src/core/server/logging/logging_system.ts @@ -100,7 +100,9 @@ export class LoggingSystem implements LoggerFactory { // Automatically prepend the base context to the logger sub-contexts loggers: contextConfig.loggers.map((l) => ({ ...l, - context: LoggingConfig.getLoggerContext([context, l.context]), + context: LoggingConfig.getLoggerContext( + l.context.length > 0 ? [context, l.context] : [context] + ), })), }); diff --git a/src/core/server/plugins/discovery/plugins_discovery.test.mocks.ts b/src/core/server/plugins/discovery/plugins_discovery.test.mocks.ts index d92465e4dd497..83accc06cb995 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.test.mocks.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.test.mocks.ts @@ -17,14 +17,5 @@ * under the License. */ -export const mockReaddir = jest.fn(); -export const mockReadFile = jest.fn(); -export const mockStat = jest.fn(); -jest.mock('fs', () => ({ - readdir: mockReaddir, - readFile: mockReadFile, - stat: mockStat, -})); - export const mockPackage = new Proxy({ raw: {} as any }, { get: (obj, prop) => obj.raw[prop] }); jest.mock('../../../../../package.json', () => mockPackage); diff --git a/src/core/server/plugins/discovery/plugins_discovery.test.ts b/src/core/server/plugins/discovery/plugins_discovery.test.ts index 1c42f5dcfc7a7..70413757de9da 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.test.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.test.ts @@ -17,251 +17,384 @@ * under the License. */ -import { mockPackage, mockReaddir, mockReadFile, mockStat } from './plugins_discovery.test.mocks'; -import { rawConfigServiceMock } from '../../config/raw_config_service.mock'; +import { mockPackage } from './plugins_discovery.test.mocks'; +import mockFs from 'mock-fs'; import { loggingSystemMock } from '../../logging/logging_system.mock'; -import { resolve } from 'path'; import { first, map, toArray } from 'rxjs/operators'; - +import { resolve } from 'path'; import { ConfigService, Env } from '../../config'; import { getEnvOptions } from '../../config/__mocks__/env'; -import { PluginWrapper } from '../plugin'; import { PluginsConfig, PluginsConfigType, config } from '../plugins_config'; import { discover } from './plugins_discovery'; +import { rawConfigServiceMock } from '../../config/raw_config_service.mock'; +import { CoreContext } from '../../core_context'; -const TEST_PLUGIN_SEARCH_PATHS = { - nonEmptySrcPlugins: resolve(process.cwd(), 'src', 'plugins'), - emptyPlugins: resolve(process.cwd(), 'plugins'), - nonExistentKibanaExtra: resolve(process.cwd(), '..', 'kibana-extra'), +const KIBANA_ROOT = process.cwd(); + +const Plugins = { + invalid: () => ({ + 'kibana.json': 'not-json', + }), + incomplete: () => ({ + 'kibana.json': JSON.stringify({ version: '1' }), + }), + incompatible: () => ({ + 'kibana.json': JSON.stringify({ id: 'plugin', version: '1' }), + }), + missingManifest: () => ({}), + inaccessibleManifest: () => ({ + 'kibana.json': mockFs.file({ + mode: 0, // 0000, + content: JSON.stringify({ id: 'plugin', version: '1' }), + }), + }), + valid: (id: string) => ({ + 'kibana.json': JSON.stringify({ + id, + configPath: ['plugins', id], + version: '1', + kibanaVersion: '1.2.3', + requiredPlugins: [], + optionalPlugins: [], + server: true, + }), + }), +}; + +const packageMock = { + branch: 'master', + version: '1.2.3', + build: { + distributable: true, + number: 1, + sha: '', + }, }; -const TEST_EXTRA_PLUGIN_PATH = resolve(process.cwd(), 'my-extra-plugin'); - -const logger = loggingSystemMock.create(); - -beforeEach(() => { - mockReaddir.mockImplementation((path, cb) => { - if (path === TEST_PLUGIN_SEARCH_PATHS.nonEmptySrcPlugins) { - cb(null, [ - '1', - '2-no-manifest', - '3', - '4-incomplete-manifest', - '5-invalid-manifest', - '6', - '7-non-dir', - '8-incompatible-manifest', - '9-inaccessible-dir', - ]); - } else if (path === TEST_PLUGIN_SEARCH_PATHS.nonExistentKibanaExtra) { - cb(new Error('ENOENT')); - } else { - cb(null, []); - } + +const manifestPath = (...pluginPath: string[]) => + resolve(KIBANA_ROOT, 'src', 'plugins', ...pluginPath, 'kibana.json'); + +describe('plugins discovery system', () => { + let logger: ReturnType; + let env: Env; + let configService: ConfigService; + let pluginConfig: PluginsConfigType; + let coreContext: CoreContext; + + beforeEach(async () => { + logger = loggingSystemMock.create(); + + mockPackage.raw = packageMock; + + env = Env.createDefault( + getEnvOptions({ + cliArgs: { envName: 'development' }, + }) + ); + + configService = new ConfigService( + rawConfigServiceMock.create({ rawConfig: { plugins: { paths: [] } } }), + env, + logger + ); + await configService.setSchema(config.path, config.schema); + + coreContext = { + coreId: Symbol(), + configService, + env, + logger, + }; + + pluginConfig = await configService + .atPath('plugins') + .pipe(first()) + .toPromise(); + + // jest relies on the filesystem to get sourcemaps when using console.log + // which breaks with the mocked FS, see https://github.com/tschaub/mock-fs/issues/234 + // hijacking logging to process.stdout as a workaround for this suite. + jest.spyOn(console, 'log').mockImplementation((...args) => { + process.stdout.write(args + '\n'); + }); }); - mockStat.mockImplementation((path, cb) => { - if (path.includes('9-inaccessible-dir')) { - cb(new Error(`ENOENT (disappeared between "readdir" and "stat").`)); - } else { - cb(null, { isDirectory: () => !path.includes('non-dir') }); - } + afterEach(() => { + mockFs.restore(); + // restore the console.log behavior + jest.restoreAllMocks(); }); - mockReadFile.mockImplementation((path, cb) => { - if (path.includes('no-manifest')) { - cb(new Error('ENOENT')); - } else if (path.includes('invalid-manifest')) { - cb(null, Buffer.from('not-json')); - } else if (path.includes('incomplete-manifest')) { - cb(null, Buffer.from(JSON.stringify({ version: '1' }))); - } else if (path.includes('incompatible-manifest')) { - cb(null, Buffer.from(JSON.stringify({ id: 'plugin', version: '1' }))); - } else { - cb( - null, - Buffer.from( - JSON.stringify({ - id: 'plugin', - configPath: ['core', 'config'], - version: '1', - kibanaVersion: '1.2.3', - requiredPlugins: ['a', 'b'], - optionalPlugins: ['c', 'd'], - server: true, - }) - ) - ); - } + it('discovers plugins in the search locations', async () => { + const { plugin$ } = discover(new PluginsConfig(pluginConfig, env), coreContext); + + mockFs( + { + [`${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'), + }, + { createCwd: false } + ); + + 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'])); }); -}); -afterEach(() => { - jest.clearAllMocks(); -}); + it('return errors when the manifest is invalid or incompatible', async () => { + const { plugin$, error$ } = discover(new PluginsConfig(pluginConfig, env), coreContext); + + mockFs( + { + [`${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_ad`]: Plugins.missingManifest(), + }, + { createCwd: false } + ); + + const plugins = await plugin$.pipe(toArray()).toPromise(); + expect(plugins).toHaveLength(0); + + const errors = await error$ + .pipe( + map((error) => error.toString()), + toArray() + ) + .toPromise(); -test('properly iterates through plugin search locations', async () => { - mockPackage.raw = { - branch: 'master', - version: '1.2.3', - build: { - distributable: true, - number: 1, - sha: '', - }, - }; - - const env = Env.createDefault( - getEnvOptions({ - cliArgs: { envName: 'development' }, - }) - ); - const configService = new ConfigService( - rawConfigServiceMock.create({ rawConfig: { plugins: { paths: [TEST_EXTRA_PLUGIN_PATH] } } }), - env, - logger - ); - await configService.setSchema(config.path, config.schema); - - const rawConfig = await configService - .atPath('plugins') - .pipe(first()) - .toPromise(); - const { plugin$, error$ } = discover(new PluginsConfig(rawConfig, env), { - coreId: Symbol(), - configService, - env, - logger, + expect(errors).toEqual( + expect.arrayContaining([ + `Error: Unexpected token o in JSON at position 1 (invalid-manifest, ${manifestPath( + 'plugin_a' + )})`, + `Error: Plugin manifest must contain an "id" property. (invalid-manifest, ${manifestPath( + 'plugin_b' + )})`, + `Error: Plugin "plugin" is only compatible with Kibana version "1", but used Kibana version is "1.2.3". (incompatible-version, ${manifestPath( + 'plugin_c' + )})`, + ]) + ); }); - const plugins = await plugin$.pipe(toArray()).toPromise(); - expect(plugins).toHaveLength(4); - - for (const path of [ - resolve(TEST_PLUGIN_SEARCH_PATHS.nonEmptySrcPlugins, '1'), - resolve(TEST_PLUGIN_SEARCH_PATHS.nonEmptySrcPlugins, '3'), - resolve(TEST_PLUGIN_SEARCH_PATHS.nonEmptySrcPlugins, '6'), - TEST_EXTRA_PLUGIN_PATH, - ]) { - const discoveredPlugin = plugins.find((plugin) => plugin.path === path)!; - expect(discoveredPlugin).toBeInstanceOf(PluginWrapper); - expect(discoveredPlugin.configPath).toEqual(['core', 'config']); - expect(discoveredPlugin.requiredPlugins).toEqual(['a', 'b']); - expect(discoveredPlugin.optionalPlugins).toEqual(['c', 'd']); - } - - await expect( - error$ + it('return errors when the plugin search path is not accessible', async () => { + const { plugin$, error$ } = discover(new PluginsConfig(pluginConfig, env), coreContext); + + mockFs( + { + [`${KIBANA_ROOT}/src/plugins`]: mockFs.directory({ + mode: 0, // 0000 + items: { + plugin_a: Plugins.valid('pluginA'), + }, + }), + }, + { createCwd: false } + ); + + const plugins = await plugin$.pipe(toArray()).toPromise(); + expect(plugins).toHaveLength(0); + + const errors = await error$ .pipe( map((error) => error.toString()), toArray() ) - .toPromise() - ).resolves.toEqual([ - `Error: ENOENT (disappeared between "readdir" and "stat"). (invalid-plugin-path, ${resolve( - TEST_PLUGIN_SEARCH_PATHS.nonEmptySrcPlugins, - '9-inaccessible-dir' - )})`, - `Error: ENOENT (invalid-search-path, ${TEST_PLUGIN_SEARCH_PATHS.nonExistentKibanaExtra})`, - `Error: ENOENT (missing-manifest, ${resolve( - TEST_PLUGIN_SEARCH_PATHS.nonEmptySrcPlugins, - '2-no-manifest', - 'kibana.json' - )})`, - `Error: Plugin manifest must contain an "id" property. (invalid-manifest, ${resolve( - TEST_PLUGIN_SEARCH_PATHS.nonEmptySrcPlugins, - '4-incomplete-manifest', - 'kibana.json' - )})`, - `Error: Unexpected token o in JSON at position 1 (invalid-manifest, ${resolve( - TEST_PLUGIN_SEARCH_PATHS.nonEmptySrcPlugins, - '5-invalid-manifest', - 'kibana.json' - )})`, - `Error: Plugin "plugin" is only compatible with Kibana version "1", but used Kibana version is "1.2.3". (incompatible-version, ${resolve( - TEST_PLUGIN_SEARCH_PATHS.nonEmptySrcPlugins, - '8-incompatible-manifest', - 'kibana.json' - )})`, - ]); -}); + .toPromise(); -test('logs a warning about --plugin-path when used in development', async () => { - mockPackage.raw = { - branch: 'master', - version: '1.2.3', - build: { - distributable: true, - number: 1, - sha: '', - }, - }; - - const env = Env.createDefault( - getEnvOptions({ - cliArgs: { dev: false, envName: 'development' }, - }) - ); - const configService = new ConfigService( - rawConfigServiceMock.create({ rawConfig: { plugins: { paths: [TEST_EXTRA_PLUGIN_PATH] } } }), - env, - logger - ); - await configService.setSchema(config.path, config.schema); - - const rawConfig = await configService - .atPath('plugins') - .pipe(first()) - .toPromise(); - - discover(new PluginsConfig(rawConfig, env), { - coreId: Symbol(), - configService, - env, - logger, + const srcPluginsPath = resolve(KIBANA_ROOT, 'src', 'plugins'); + const xpackPluginsPath = resolve(KIBANA_ROOT, 'x-pack', 'plugins'); + expect(errors).toEqual( + expect.arrayContaining([ + `Error: EACCES, permission denied '${srcPluginsPath}' (invalid-search-path, ${srcPluginsPath})`, + `Error: ENOENT, no such file or directory '${xpackPluginsPath}' (invalid-search-path, ${xpackPluginsPath})`, + ]) + ); }); - expect(loggingSystemMock.collect(logger).warn).toEqual([ - [ - `Explicit plugin paths [${TEST_EXTRA_PLUGIN_PATH}] should only be used in development. Relative imports may not work properly in production.`, - ], - ]); -}); + it('return an error when the manifest file is not accessible', async () => { + const { plugin$, error$ } = discover(new PluginsConfig(pluginConfig, env), coreContext); + + mockFs( + { + [`${KIBANA_ROOT}/src/plugins/plugin_a`]: { + ...Plugins.inaccessibleManifest(), + nested_plugin: Plugins.valid('nestedPlugin'), + }, + }, + { createCwd: false } + ); -test('does not log a warning about --plugin-path when used in production', async () => { - mockPackage.raw = { - branch: 'master', - version: '1.2.3', - build: { - distributable: true, - number: 1, - sha: '', - }, - }; - - const env = Env.createDefault( - getEnvOptions({ - cliArgs: { dev: false, envName: 'production' }, - }) - ); - const configService = new ConfigService( - rawConfigServiceMock.create({ rawConfig: { plugins: { paths: [TEST_EXTRA_PLUGIN_PATH] } } }), - env, - logger - ); - await configService.setSchema(config.path, config.schema); - - const rawConfig = await configService - .atPath('plugins') - .pipe(first()) - .toPromise(); - - discover(new PluginsConfig(rawConfig, env), { - coreId: Symbol(), - configService, - env, - logger, + const plugins = await plugin$.pipe(toArray()).toPromise(); + expect(plugins).toHaveLength(0); + + const errors = await error$ + .pipe( + map((error) => error.toString()), + toArray() + ) + .toPromise(); + + const errorPath = manifestPath('plugin_a'); + expect(errors).toEqual( + expect.arrayContaining([ + `Error: EACCES, permission denied '${errorPath}' (missing-manifest, ${errorPath})`, + ]) + ); + }); + + it('discovers plugins in nested directories', async () => { + const { plugin$, error$ } = discover(new PluginsConfig(pluginConfig, env), coreContext); + + mockFs( + { + [`${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(), + }, + { createCwd: false } + ); + + 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'])); + + const errors = await error$ + .pipe( + map((error) => error.toString()), + toArray() + ) + .toPromise(); + + expect(errors).toEqual( + expect.arrayContaining([ + `Error: Plugin manifest must contain an "id" property. (invalid-manifest, ${manifestPath( + 'sub1', + 'sub2', + 'plugin_d' + )})`, + ]) + ); + }); + + it('does not discover plugins nested inside another plugin', async () => { + const { plugin$ } = discover(new PluginsConfig(pluginConfig, env), coreContext); + + mockFs( + { + [`${KIBANA_ROOT}/src/plugins/plugin_a`]: { + ...Plugins.valid('pluginA'), + nested_plugin: Plugins.valid('nestedPlugin'), + }, + }, + { createCwd: false } + ); + + const plugins = await plugin$.pipe(toArray()).toPromise(); + const pluginNames = plugins.map((plugin) => plugin.name); + + expect(pluginNames).toEqual(['pluginA']); + }); + + it('stops scanning when reaching `maxDepth`', async () => { + const { plugin$ } = discover(new PluginsConfig(pluginConfig, env), coreContext); + + mockFs( + { + [`${KIBANA_ROOT}/src/plugins/sub1/plugin`]: Plugins.valid('plugin1'), + [`${KIBANA_ROOT}/src/plugins/sub1/sub2/plugin`]: Plugins.valid('plugin2'), + [`${KIBANA_ROOT}/src/plugins/sub1/sub2/sub3/plugin`]: Plugins.valid('plugin3'), + [`${KIBANA_ROOT}/src/plugins/sub1/sub2/sub3/sub4/plugin`]: Plugins.valid('plugin4'), + [`${KIBANA_ROOT}/src/plugins/sub1/sub2/sub3/sub4/sub5/plugin`]: Plugins.valid('plugin5'), + [`${KIBANA_ROOT}/src/plugins/sub1/sub2/sub3/sub4/sub5/sub6/plugin`]: Plugins.valid( + 'plugin6' + ), + }, + { createCwd: false } + ); + + const plugins = await plugin$.pipe(toArray()).toPromise(); + const pluginNames = plugins.map((plugin) => plugin.name); + + expect(pluginNames).toHaveLength(5); + expect(pluginNames).toEqual( + expect.arrayContaining(['plugin1', 'plugin2', 'plugin3', 'plugin4', 'plugin5']) + ); + }); + + it('works with symlinks', async () => { + const { plugin$ } = discover(new PluginsConfig(pluginConfig, env), coreContext); + + const pluginFolder = resolve(KIBANA_ROOT, '..', 'ext-plugins'); + + mockFs( + { + [`${KIBANA_ROOT}/plugins`]: mockFs.symlink({ + path: '../ext-plugins', + }), + [pluginFolder]: { + plugin_a: Plugins.valid('pluginA'), + plugin_b: Plugins.valid('pluginB'), + }, + }, + { createCwd: false } + ); + + 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(loggingSystemMock.collect(logger).warn).toEqual([]); + it('logs a warning about --plugin-path when used in development', async () => { + const extraPluginTestPath = resolve(process.cwd(), 'my-extra-plugin'); + + env = Env.createDefault( + getEnvOptions({ + cliArgs: { dev: false, envName: 'development' }, + }) + ); + + discover(new PluginsConfig({ ...pluginConfig, paths: [extraPluginTestPath] }, env), { + coreId: Symbol(), + configService, + env, + logger, + }); + + expect(loggingSystemMock.collect(logger).warn).toEqual([ + [ + `Explicit plugin paths [${extraPluginTestPath}] should only be used in development. Relative imports may not work properly in production.`, + ], + ]); + }); + + test('does not log a warning about --plugin-path when used in production', async () => { + const extraPluginTestPath = resolve(process.cwd(), 'my-extra-plugin'); + + env = Env.createDefault( + getEnvOptions({ + cliArgs: { dev: false, envName: 'production' }, + }) + ); + + discover(new PluginsConfig({ ...pluginConfig, paths: [extraPluginTestPath] }, env), { + coreId: Symbol(), + configService, + env, + logger, + }); + + expect(loggingSystemMock.collect(logger).warn).toEqual([]); + }); }); diff --git a/src/core/server/plugins/discovery/plugins_discovery.ts b/src/core/server/plugins/discovery/plugins_discovery.ts index 1910483211e34..5e765a9632e55 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.ts @@ -19,7 +19,7 @@ import { readdir, stat } from 'fs'; import { resolve } from 'path'; -import { bindNodeCallback, from, merge } from 'rxjs'; +import { bindNodeCallback, from, merge, Observable } from 'rxjs'; import { catchError, filter, map, mergeMap, shareReplay } from 'rxjs/operators'; import { CoreContext } from '../../core_context'; import { Logger } from '../../logging'; @@ -32,6 +32,13 @@ import { parseManifest } from './plugin_manifest_parser'; const fsReadDir$ = bindNodeCallback(readdir); const fsStat$ = bindNodeCallback(stat); +const maxScanDepth = 5; + +interface PluginSearchPathEntry { + dir: string; + depth: number; +} + /** * Tries to discover all possible plugins based on the provided plugin config. * Discovery result consists of two separate streams, the one (`plugin$`) is @@ -75,34 +82,96 @@ export function discover(config: PluginsConfig, coreContext: CoreContext) { } /** - * Iterates over every plugin search path and returns a merged stream of all - * sub-directories. If directory cannot be read or it's impossible to get stat + * Recursively iterates over every plugin search path and returns a merged stream of all + * sub-directories containing a manifest file. If directory cannot be read or it's impossible to get stat * for any of the nested entries then error is added into the stream instead. + * * @param pluginDirs List of the top-level directories to process. * @param log Plugin discovery logger instance. */ -function processPluginSearchPaths$(pluginDirs: readonly string[], log: Logger) { - return from(pluginDirs).pipe( - mergeMap((dir) => { - log.debug(`Scanning "${dir}" for plugin sub-directories...`); +function processPluginSearchPaths$( + pluginDirs: readonly string[], + log: Logger +): Observable { + function recursiveScanFolder( + ent: PluginSearchPathEntry + ): Observable { + return from([ent]).pipe( + mergeMap((entry) => { + return findManifestInFolder(entry.dir, () => { + if (entry.depth > maxScanDepth) { + return []; + } + return mapSubdirectories(entry.dir, (subDir) => + recursiveScanFolder({ dir: subDir, depth: entry.depth + 1 }) + ); + }); + }) + ); + } - return fsReadDir$(dir).pipe( - mergeMap((subDirs: string[]) => subDirs.map((subDir) => resolve(dir, subDir))), - mergeMap((path) => - fsStat$(path).pipe( - // Filter out non-directory entries from target directories, it's expected that - // these directories may contain files (e.g. `README.md` or `package.json`). - // We shouldn't silently ignore the entries we couldn't get stat for though. - mergeMap((pathStat) => (pathStat.isDirectory() ? [path] : [])), - catchError((err) => [PluginDiscoveryError.invalidPluginPath(path, err)]) - ) - ), - catchError((err) => [PluginDiscoveryError.invalidSearchPath(dir, err)]) + return from(pluginDirs.map((dir) => ({ dir, depth: 0 }))).pipe( + mergeMap((entry) => { + log.debug(`Scanning "${entry.dir}" for plugin sub-directories...`); + return fsReadDir$(entry.dir).pipe( + mergeMap(() => recursiveScanFolder(entry)), + catchError((err) => [PluginDiscoveryError.invalidSearchPath(entry.dir, err)]) ); }) ); } +/** + * Attempts to read manifest file in specified directory or calls `notFound` and returns results if not found. For any + * manifest files that cannot be read, a PluginDiscoveryError is added. + * @param dir + * @param notFound + */ +function findManifestInFolder( + dir: string, + notFound: () => never[] | Observable +): string[] | Observable { + return fsStat$(resolve(dir, 'kibana.json')).pipe( + mergeMap((stats) => { + // `kibana.json` exists in given directory, we got a plugin + if (stats.isFile()) { + return [dir]; + } + return []; + }), + catchError((manifestStatError) => { + // did not find manifest. recursively process sub directories until we reach max depth. + if (manifestStatError.code !== 'ENOENT') { + return [PluginDiscoveryError.invalidPluginPath(dir, manifestStatError)]; + } + return notFound(); + }) + ); +} + +/** + * Finds all subdirectories in `dir` and executed `mapFunc` for each one. For any directories that cannot be read, + * a PluginDiscoveryError is added. + * @param dir + * @param mapFunc + */ +function mapSubdirectories( + dir: string, + mapFunc: (subDir: string) => Observable +): Observable { + return fsReadDir$(dir).pipe( + mergeMap((subDirs: string[]) => subDirs.map((subDir) => resolve(dir, subDir))), + mergeMap((subDir) => + fsStat$(subDir).pipe( + mergeMap((pathStat) => (pathStat.isDirectory() ? mapFunc(subDir) : [])), + catchError((subDirStatError) => [ + PluginDiscoveryError.invalidPluginPath(subDir, subDirStatError), + ]) + ) + ) + ); +} + /** * Tries to load and parse the plugin manifest file located at the provided plugin * directory path and produces an error result if it fails to do so or plugin manifest diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts index c277dc85e5e04..46fd2b00c2304 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/src/core/server/plugins/plugins_service.test.ts @@ -51,7 +51,7 @@ const logger = loggingSystemMock.create(); expect.addSnapshotSerializer(createAbsolutePathSerializer()); -['path-1', 'path-2', 'path-3', 'path-4', 'path-5'].forEach((path) => { +['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, }); @@ -227,6 +227,26 @@ describe('PluginsService', () => { path: 'path-4', configPath: 'path-4-disabled', }), + createPlugin('plugin-with-disabled-optional-dep', { + path: 'path-5', + configPath: 'path-5', + optionalPlugins: ['explicitly-disabled-plugin'], + }), + createPlugin('plugin-with-missing-optional-dep', { + path: 'path-6', + configPath: 'path-6', + optionalPlugins: ['missing-plugin'], + }), + createPlugin('plugin-with-disabled-nested-transitive-dep', { + path: 'path-7', + configPath: 'path-7', + requiredPlugins: ['plugin-with-disabled-transitive-dep'], + }), + createPlugin('plugin-with-missing-nested-dep', { + path: 'path-8', + configPath: 'path-8', + requiredPlugins: ['plugin-with-missing-required-deps'], + }), ]), }); @@ -234,7 +254,7 @@ describe('PluginsService', () => { const setup = await pluginsService.setup(setupDeps); expect(setup.contracts).toBeInstanceOf(Map); - expect(mockPluginSystem.addPlugin).not.toHaveBeenCalled(); + expect(mockPluginSystem.addPlugin).toHaveBeenCalledTimes(2); expect(mockPluginSystem.setupPlugins).toHaveBeenCalledTimes(1); expect(mockPluginSystem.setupPlugins).toHaveBeenCalledWith(setupDeps); @@ -244,14 +264,20 @@ describe('PluginsService', () => { "Plugin \\"explicitly-disabled-plugin\\" is disabled.", ], Array [ - "Plugin \\"plugin-with-missing-required-deps\\" has been disabled since some of its direct or transitive dependencies are missing or disabled.", + "Plugin \\"plugin-with-missing-required-deps\\" has been disabled since the following direct or transitive dependencies are missing or disabled: [missing-plugin]", ], Array [ - "Plugin \\"plugin-with-disabled-transitive-dep\\" has been disabled since some of its direct or transitive dependencies are missing or disabled.", + "Plugin \\"plugin-with-disabled-transitive-dep\\" has been disabled since the following direct or transitive dependencies are missing or disabled: [another-explicitly-disabled-plugin]", ], Array [ "Plugin \\"another-explicitly-disabled-plugin\\" is disabled.", ], + 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]", + ], + 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]", + ], ] `); }); diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index 7441e753efa6a..5d1261e697bc0 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -239,11 +239,15 @@ export class PluginsService implements CoreService, parents: PluginName[] = [] - ): boolean { + ): { enabled: true } | { enabled: false; missingDependencies: string[] } { const pluginInfo = pluginEnableStatuses.get(pluginName); - return ( - pluginInfo !== undefined && - pluginInfo.isEnabled && - pluginInfo.plugin.requiredPlugins - .filter((dep) => !parents.includes(dep)) - .every((dependencyName) => - this.shouldEnablePlugin(dependencyName, pluginEnableStatuses, [...parents, pluginName]) - ) - ); + + if (pluginInfo === undefined || !pluginInfo.isEnabled) { + return { + enabled: false, + missingDependencies: [], + }; + } + + const missingDependencies = pluginInfo.plugin.requiredPlugins + .filter((dep) => !parents.includes(dep)) + .filter( + (dependencyName) => + !this.shouldEnablePlugin(dependencyName, pluginEnableStatuses, [...parents, pluginName]) + .enabled + ); + + if (missingDependencies.length === 0) { + return { + enabled: true, + }; + } + + return { + enabled: false, + missingDependencies, + }; } private registerPluginStaticDirs(deps: PluginsServiceSetupDeps) { diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index 2ca5c9f6ed3c5..9e86ee22c607b 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -19,8 +19,8 @@ import { Observable } from 'rxjs'; import { Type } from '@kbn/config-schema'; +import { RecursiveReadonly } from '@kbn/utility-types'; -import { RecursiveReadonly } from 'kibana/public'; import { ConfigPath, EnvironmentMode, PackageInfo, ConfigDeprecationProvider } from '../config'; import { LoggerFactory } from '../logging'; import { KibanaConfigType } from '../kibana_config'; diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index 48b1e12fc187e..c2d4f49d7ee2a 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -30,13 +30,13 @@ import { KibanaMigrator, IKibanaMigrator } from './migrations'; import { CoreContext } from '../core_context'; import { LegacyServiceDiscoverPlugins } from '../legacy'; import { - APICaller, + LegacyAPICaller, ElasticsearchServiceStart, - IClusterClient, + ILegacyClusterClient, InternalElasticsearchServiceSetup, } from '../elasticsearch'; import { KibanaConfigType } from '../kibana_config'; -import { migrationsRetryCallCluster } from '../elasticsearch/retry_call_cluster'; +import { migrationsRetryCallCluster } from '../elasticsearch/legacy'; import { SavedObjectsConfigType, SavedObjectsMigrationConfigType, @@ -434,7 +434,7 @@ export class SavedObjectsService await migrator.runMigrations(); } - const createRepository = (callCluster: APICaller, includedHiddenTypes: string[] = []) => { + const createRepository = (callCluster: LegacyAPICaller, includedHiddenTypes: string[] = []) => { return SavedObjectsRepository.createRepository( migrator, this.typeRegistry, @@ -484,7 +484,7 @@ export class SavedObjectsService private createMigrator( kibanaConfig: KibanaConfigType, savedObjectsConfig: SavedObjectsMigrationConfigType, - esClient: IClusterClient, + esClient: ILegacyClusterClient, migrationsRetryDelay?: number ): KibanaMigrator { return new KibanaMigrator({ diff --git a/src/core/server/saved_objects/service/lib/decorate_es_error.ts b/src/core/server/saved_objects/service/lib/decorate_es_error.ts index e57f08aa7a527..7d1575798c357 100644 --- a/src/core/server/saved_objects/service/lib/decorate_es_error.ts +++ b/src/core/server/saved_objects/service/lib/decorate_es_error.ts @@ -26,11 +26,11 @@ const { NoConnections, RequestTimeout, Conflict, - // @ts-ignore + // @ts-expect-error 401: NotAuthorized, - // @ts-ignore + // @ts-expect-error 403: Forbidden, - // @ts-ignore + // @ts-expect-error 413: RequestEntityTooLarge, NotFound, BadRequest, diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 40c5282a77e49..f24195c0f295e 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -19,8 +19,8 @@ import { omit } from 'lodash'; import uuid from 'uuid'; -import { retryCallCluster } from '../../../elasticsearch/retry_call_cluster'; -import { APICaller } from '../../../elasticsearch/'; +import { retryCallCluster } from '../../../elasticsearch/legacy'; +import { LegacyAPICaller } from '../../../elasticsearch/'; import { getRootPropertiesObjects, IndexMapping } from '../../mappings'; import { getSearchDsl } from './search_dsl'; @@ -74,7 +74,7 @@ const isRight = (either: Either): either is Right => either.tag === 'Right'; export interface SavedObjectsRepositoryOptions { index: string; mappings: IndexMapping; - callCluster: APICaller; + callCluster: LegacyAPICaller; typeRegistry: SavedObjectTypeRegistry; serializer: SavedObjectsSerializer; migrator: KibanaMigrator; @@ -117,7 +117,7 @@ export class SavedObjectsRepository { private _mappings: IndexMapping; private _registry: SavedObjectTypeRegistry; private _allowedTypes: string[]; - private _unwrappedCallCluster: APICaller; + private _unwrappedCallCluster: LegacyAPICaller; private _serializer: SavedObjectsSerializer; /** @@ -132,7 +132,7 @@ export class SavedObjectsRepository { migrator: KibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, - callCluster: APICaller, + callCluster: LegacyAPICaller, includedHiddenTypes: string[] = [], injectedConstructor: any = SavedObjectsRepository ): ISavedObjectsRepository { @@ -188,7 +188,7 @@ export class SavedObjectsRepository { } this._allowedTypes = allowedTypes; - this._unwrappedCallCluster = async (...args: Parameters) => { + this._unwrappedCallCluster = async (...args: Parameters) => { await migrator.runMigrations(); return callCluster(...args); }; @@ -1300,7 +1300,7 @@ export class SavedObjectsRepository { }; } - private async _writeToCluster(...args: Parameters) { + private async _writeToCluster(...args: Parameters) { try { return await this._callCluster(...args); } catch (err) { @@ -1308,7 +1308,7 @@ export class SavedObjectsRepository { } } - private async _callCluster(...args: Parameters) { + private async _callCluster(...args: Parameters) { try { return await this._unwrappedCallCluster(...args); } catch (err) { diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 108826ad61aa2..9cc5a8a386b0b 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -108,7 +108,7 @@ import { PingParams } from 'elasticsearch'; import { PutScriptParams } from 'elasticsearch'; import { PutTemplateParams } from 'elasticsearch'; import { Readable } from 'stream'; -import { RecursiveReadonly as RecursiveReadonly_2 } from 'kibana/public'; +import { RecursiveReadonly } from '@kbn/utility-types'; import { ReindexParams } from 'elasticsearch'; import { ReindexRethrottleParams } from 'elasticsearch'; import { RenderSearchTemplateParams } from 'elasticsearch'; @@ -144,250 +144,6 @@ import { UpdateDocumentByQueryParams } from 'elasticsearch'; import { UpdateDocumentParams } from 'elasticsearch'; import { Url } from 'url'; -// @public (undocumented) -export interface APICaller { - // (undocumented) - (endpoint: 'bulk', params: BulkIndexDocumentsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'clearScroll', params: ClearScrollParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'count', params: CountParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'create', params: CreateDocumentParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'delete', params: DeleteDocumentParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'deleteByQuery', params: DeleteDocumentByQueryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'deleteScript', params: DeleteScriptParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'deleteTemplate', params: DeleteTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'exists', params: ExistsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'explain', params: ExplainParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'fieldStats', params: FieldStatsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'get', params: GetParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'getScript', params: GetScriptParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'getSource', params: GetSourceParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'getTemplate', params: GetTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'index', params: IndexDocumentParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'info', params: InfoParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'mget', params: MGetParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'msearch', params: MSearchParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'msearchTemplate', params: MSearchTemplateParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'mtermvectors', params: MTermVectorsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ping', params: PingParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'putScript', params: PutScriptParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'putTemplate', params: PutTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'reindex', params: ReindexParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'reindexRethrottle', params: ReindexRethrottleParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'renderSearchTemplate', params: RenderSearchTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'scroll', params: ScrollParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'search', params: SearchParams, options?: CallAPIOptions): Promise>; - // (undocumented) - (endpoint: 'searchShards', params: SearchShardsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'searchTemplate', params: SearchTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'suggest', params: SuggestParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'termvectors', params: TermvectorsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'update', params: UpdateDocumentParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'updateByQuery', params: UpdateDocumentByQueryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.aliases', params: CatAliasesParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.allocation', params: CatAllocationParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.count', params: CatAllocationParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.fielddata', params: CatFielddataParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.health', params: CatHealthParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.help', params: CatHelpParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.indices', params: CatIndicesParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.master', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.nodeattrs', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.nodes', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.pendingTasks', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.plugins', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.recovery', params: CatRecoveryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.repositories', params: CatCommonParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.segments', params: CatSegmentsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.shards', params: CatShardsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.snapshots', params: CatSnapshotsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.tasks', params: CatTasksParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cat.threadPool', params: CatThreadPoolParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.allocationExplain', params: ClusterAllocationExplainParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.getSettings', params: ClusterGetSettingsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.health', params: ClusterHealthParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.pendingTasks', params: ClusterPendingTasksParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.putSettings', params: ClusterPutSettingsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.reroute', params: ClusterRerouteParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.state', params: ClusterStateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'cluster.stats', params: ClusterStatsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.analyze', params: IndicesAnalyzeParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.clearCache', params: IndicesClearCacheParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.close', params: IndicesCloseParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.create', params: IndicesCreateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.delete', params: IndicesDeleteParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.deleteAlias', params: IndicesDeleteAliasParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.deleteTemplate', params: IndicesDeleteTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.exists', params: IndicesExistsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.existsAlias', params: IndicesExistsAliasParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.existsTemplate', params: IndicesExistsTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.existsType', params: IndicesExistsTypeParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.flush', params: IndicesFlushParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.flushSynced', params: IndicesFlushSyncedParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.forcemerge', params: IndicesForcemergeParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.get', params: IndicesGetParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getAlias', params: IndicesGetAliasParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getFieldMapping', params: IndicesGetFieldMappingParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getMapping', params: IndicesGetMappingParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getSettings', params: IndicesGetSettingsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getTemplate', params: IndicesGetTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.getUpgrade', params: IndicesGetUpgradeParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.open', params: IndicesOpenParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putAlias', params: IndicesPutAliasParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putMapping', params: IndicesPutMappingParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putSettings', params: IndicesPutSettingsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.putTemplate', params: IndicesPutTemplateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.recovery', params: IndicesRecoveryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.refresh', params: IndicesRefreshParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.rollover', params: IndicesRolloverParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.segments', params: IndicesSegmentsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.shardStores', params: IndicesShardStoresParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.shrink', params: IndicesShrinkParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.stats', params: IndicesStatsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.updateAliases', params: IndicesUpdateAliasesParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.upgrade', params: IndicesUpgradeParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'indices.validateQuery', params: IndicesValidateQueryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.deletePipeline', params: IngestDeletePipelineParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.getPipeline', params: IngestGetPipelineParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.putPipeline', params: IngestPutPipelineParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'ingest.simulate', params: IngestSimulateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'nodes.hotThreads', params: NodesHotThreadsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'nodes.info', params: NodesInfoParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'nodes.stats', params: NodesStatsParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.create', params: SnapshotCreateParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.createRepository', params: SnapshotCreateRepositoryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.delete', params: SnapshotDeleteParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.deleteRepository', params: SnapshotDeleteRepositoryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.get', params: SnapshotGetParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.getRepository', params: SnapshotGetRepositoryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.restore', params: SnapshotRestoreParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.status', params: SnapshotStatusParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'snapshot.verifyRepository', params: SnapshotVerifyRepositoryParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'tasks.cancel', params: TasksCancelParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'tasks.get', params: TasksGetParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'tasks.list', params: TasksListParams, options?: CallAPIOptions): ReturnType; - // (undocumented) - (endpoint: 'transport.request', clientParams: AssistantAPIClientParams, options?: CallAPIOptions): Promise; - // (undocumented) - (endpoint: 'transport.request', clientParams: DeprecationAPIClientParams, options?: CallAPIOptions): Promise; - // (undocumented) - (endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; -} - // Warning: (ae-forgotten-export) The symbol "appendersSchema" needs to be exported by the entry point index.d.ts // // @public (undocumented) @@ -497,12 +253,6 @@ export class BasePath { // @internal (undocumented) export function bootstrap({ configs, cliArgs, applyConfigOverrides, features, }: BootstrapArgs): Promise; -// @public -export interface CallAPIOptions { - signal?: AbortSignal; - wrap401Errors?: boolean; -} - // @public export interface Capabilities { [key: string]: Record>; @@ -530,14 +280,6 @@ export interface CapabilitiesStart { // @public export type CapabilitiesSwitcher = (request: KibanaRequest, uiCapabilities: Capabilities) => Partial | Promise>; -// @public -export class ClusterClient implements IClusterClient { - constructor(config: ElasticsearchClientConfig, log: Logger, getAuthHeaders?: GetAuthHeaders); - asScoped(request?: ScopeableRequest): IScopedClusterClient; - callAsInternalUser: APICaller; - close(): void; - } - // @alpha export const config: { elasticsearch: { @@ -557,7 +299,7 @@ export const config: { startupTimeout: import("@kbn/config-schema").Type; logQueries: import("@kbn/config-schema").Type; ssl: import("@kbn/config-schema").ObjectType<{ - verificationMode: import("@kbn/config-schema").Type<"none" | "full" | "certificate">; + verificationMode: import("@kbn/config-schema").Type<"none" | "certificate" | "full">; certificateAuthorities: import("@kbn/config-schema").Type; certificate: import("@kbn/config-schema").Type; key: import("@kbn/config-schema").Type; @@ -851,14 +593,6 @@ export interface DiscoveredPlugin { readonly requiredPlugins: readonly PluginName[]; } -// @public (undocumented) -export type ElasticsearchClientConfig = Pick & Pick & { - pingTimeout?: ElasticsearchConfig['pingTimeout'] | ConfigOptions['pingTimeout']; - requestTimeout?: ElasticsearchConfig['requestTimeout'] | ConfigOptions['requestTimeout']; - sniffInterval?: ElasticsearchConfig['sniffInterval'] | ConfigOptions['sniffInterval']; - ssl?: Partial; -}; - // @public export class ElasticsearchConfig { constructor(rawConfig: ElasticsearchConfigType); @@ -884,26 +618,12 @@ export class ElasticsearchConfig { readonly username?: string; } -// @public (undocumented) -export interface ElasticsearchError extends Boom { - // (undocumented) - [code]?: string; -} - -// @public -export class ElasticsearchErrorHelpers { - // (undocumented) - static decorateNotAuthorizedError(error: Error, reason?: string): ElasticsearchError; - // (undocumented) - static isNotAuthorizedError(error: any): error is ElasticsearchError; -} - // @public (undocumented) export interface ElasticsearchServiceSetup { // @deprecated (undocumented) legacy: { - readonly createClient: (type: string, clientConfig?: Partial) => ICustomClusterClient; - readonly client: IClusterClient; + readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient; + readonly client: ILegacyClusterClient; }; } @@ -911,8 +631,8 @@ export interface ElasticsearchServiceSetup { export interface ElasticsearchServiceStart { // @deprecated (undocumented) legacy: { - readonly createClient: (type: string, clientConfig?: Partial) => ICustomClusterClient; - readonly client: IClusterClient; + readonly createClient: (type: string, clientConfig?: Partial) => ILegacyCustomClusterClient; + readonly client: ILegacyClusterClient; }; } @@ -1056,9 +776,6 @@ export interface HttpServiceStart { // @public export type IBasePath = Pick; -// @public -export type IClusterClient = Pick; - // @public export interface IContextContainer> { createHandler(pluginOpaqueId: PluginOpaqueId, handler: THandler): (...rest: HandlerParameters) => ShallowPromise>; @@ -1078,9 +795,6 @@ export interface ICspConfig { readonly warnLegacyBrowsers: boolean; } -// @public -export type ICustomClusterClient = Pick; - // @public export interface IKibanaResponse { // (undocumented) @@ -1102,6 +816,15 @@ export interface IKibanaSocket { getPeerCertificate(detailed?: boolean): PeerCertificate | DetailedPeerCertificate | null; } +// @public +export type ILegacyClusterClient = Pick; + +// @public +export type ILegacyCustomClusterClient = Pick; + +// @public +export type ILegacyScopedClusterClient = Pick; + // @public (undocumented) export interface ImageValidation { // (undocumented) @@ -1155,9 +878,6 @@ export type ISavedObjectsRepository = Pick; -// @public -export type IScopedClusterClient = Pick; - // @public export function isRelativeUrl(candidatePath: string): boolean; @@ -1247,6 +967,266 @@ export const kibanaResponseFactory: { // @public export type KnownHeaders = KnownKeys; +// @public (undocumented) +export interface LegacyAPICaller { + // (undocumented) + (endpoint: 'bulk', params: BulkIndexDocumentsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'clearScroll', params: ClearScrollParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'count', params: CountParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'create', params: CreateDocumentParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'delete', params: DeleteDocumentParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'deleteByQuery', params: DeleteDocumentByQueryParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'deleteScript', params: DeleteScriptParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'deleteTemplate', params: DeleteTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'exists', params: ExistsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'explain', params: ExplainParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'fieldStats', params: FieldStatsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'get', params: GetParams, options?: LegacyCallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'getScript', params: GetScriptParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'getSource', params: GetSourceParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'getTemplate', params: GetTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'index', params: IndexDocumentParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'info', params: InfoParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'mget', params: MGetParams, options?: LegacyCallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'msearch', params: MSearchParams, options?: LegacyCallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'msearchTemplate', params: MSearchTemplateParams, options?: LegacyCallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'mtermvectors', params: MTermVectorsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'ping', params: PingParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'putScript', params: PutScriptParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'putTemplate', params: PutTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'reindex', params: ReindexParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'reindexRethrottle', params: ReindexRethrottleParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'renderSearchTemplate', params: RenderSearchTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'scroll', params: ScrollParams, options?: LegacyCallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'search', params: SearchParams, options?: LegacyCallAPIOptions): Promise>; + // (undocumented) + (endpoint: 'searchShards', params: SearchShardsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'searchTemplate', params: SearchTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'suggest', params: SuggestParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'termvectors', params: TermvectorsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'update', params: UpdateDocumentParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'updateByQuery', params: UpdateDocumentByQueryParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.aliases', params: CatAliasesParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.allocation', params: CatAllocationParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.count', params: CatAllocationParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.fielddata', params: CatFielddataParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.health', params: CatHealthParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.help', params: CatHelpParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.indices', params: CatIndicesParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.master', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.nodeattrs', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.nodes', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.pendingTasks', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.plugins', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.recovery', params: CatRecoveryParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.repositories', params: CatCommonParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.segments', params: CatSegmentsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.shards', params: CatShardsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.snapshots', params: CatSnapshotsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.tasks', params: CatTasksParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cat.threadPool', params: CatThreadPoolParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.allocationExplain', params: ClusterAllocationExplainParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.getSettings', params: ClusterGetSettingsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.health', params: ClusterHealthParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.pendingTasks', params: ClusterPendingTasksParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.putSettings', params: ClusterPutSettingsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.reroute', params: ClusterRerouteParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.state', params: ClusterStateParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'cluster.stats', params: ClusterStatsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.analyze', params: IndicesAnalyzeParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.clearCache', params: IndicesClearCacheParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.close', params: IndicesCloseParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.create', params: IndicesCreateParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.delete', params: IndicesDeleteParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.deleteAlias', params: IndicesDeleteAliasParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.deleteTemplate', params: IndicesDeleteTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.exists', params: IndicesExistsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.existsAlias', params: IndicesExistsAliasParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.existsTemplate', params: IndicesExistsTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.existsType', params: IndicesExistsTypeParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.flush', params: IndicesFlushParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.flushSynced', params: IndicesFlushSyncedParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.forcemerge', params: IndicesForcemergeParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.get', params: IndicesGetParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getAlias', params: IndicesGetAliasParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getFieldMapping', params: IndicesGetFieldMappingParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getMapping', params: IndicesGetMappingParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getSettings', params: IndicesGetSettingsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getTemplate', params: IndicesGetTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.getUpgrade', params: IndicesGetUpgradeParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.open', params: IndicesOpenParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.putAlias', params: IndicesPutAliasParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.putMapping', params: IndicesPutMappingParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.putSettings', params: IndicesPutSettingsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.putTemplate', params: IndicesPutTemplateParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.recovery', params: IndicesRecoveryParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.refresh', params: IndicesRefreshParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.rollover', params: IndicesRolloverParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.segments', params: IndicesSegmentsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.shardStores', params: IndicesShardStoresParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.shrink', params: IndicesShrinkParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.stats', params: IndicesStatsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.updateAliases', params: IndicesUpdateAliasesParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.upgrade', params: IndicesUpgradeParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'indices.validateQuery', params: IndicesValidateQueryParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'ingest.deletePipeline', params: IngestDeletePipelineParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'ingest.getPipeline', params: IngestGetPipelineParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'ingest.putPipeline', params: IngestPutPipelineParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'ingest.simulate', params: IngestSimulateParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'nodes.hotThreads', params: NodesHotThreadsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'nodes.info', params: NodesInfoParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'nodes.stats', params: NodesStatsParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.create', params: SnapshotCreateParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.createRepository', params: SnapshotCreateRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.delete', params: SnapshotDeleteParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.deleteRepository', params: SnapshotDeleteRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.get', params: SnapshotGetParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.getRepository', params: SnapshotGetRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.restore', params: SnapshotRestoreParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.status', params: SnapshotStatusParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'snapshot.verifyRepository', params: SnapshotVerifyRepositoryParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'tasks.cancel', params: TasksCancelParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'tasks.get', params: TasksGetParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'tasks.list', params: TasksListParams, options?: LegacyCallAPIOptions): ReturnType; + // (undocumented) + (endpoint: 'transport.request', clientParams: AssistantAPIClientParams, options?: LegacyCallAPIOptions): Promise; + // (undocumented) + (endpoint: 'transport.request', clientParams: DeprecationAPIClientParams, options?: LegacyCallAPIOptions): Promise; + // (undocumented) + (endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; +} + +// @public +export interface LegacyCallAPIOptions { + signal?: AbortSignal; + wrap401Errors?: boolean; +} + +// Warning: (ae-unresolved-inheritdoc-reference) The @inheritDoc reference could not be resolved: The package "kibana" does not have an export "IClusterClient" +// +// @public (undocumented) +export class LegacyClusterClient implements ILegacyClusterClient { + constructor(config: LegacyElasticsearchClientConfig, log: Logger, getAuthHeaders?: GetAuthHeaders); + asScoped(request?: ScopeableRequest): ILegacyScopedClusterClient; + callAsInternalUser: LegacyAPICaller; + close(): void; + } + // @internal @deprecated export interface LegacyConfig { // (undocumented) @@ -1261,6 +1241,28 @@ export interface LegacyConfig { set(config: LegacyVars): void; } +// @public (undocumented) +export type LegacyElasticsearchClientConfig = Pick & Pick & { + pingTimeout?: ElasticsearchConfig['pingTimeout'] | ConfigOptions['pingTimeout']; + requestTimeout?: ElasticsearchConfig['requestTimeout'] | ConfigOptions['requestTimeout']; + sniffInterval?: ElasticsearchConfig['sniffInterval'] | ConfigOptions['sniffInterval']; + ssl?: Partial; +}; + +// @public (undocumented) +export interface LegacyElasticsearchError extends Boom { + // (undocumented) + [code]?: string; +} + +// @public +export class LegacyElasticsearchErrorHelpers { + // (undocumented) + static decorateNotAuthorizedError(error: Error, reason?: string): LegacyElasticsearchError; + // (undocumented) + static isNotAuthorizedError(error: any): error is LegacyElasticsearchError; +} + // Warning: (ae-forgotten-export) The symbol "ILegacyInternals" needs to be exported by the entry point index.d.ts // // @internal @deprecated (undocumented) @@ -1280,6 +1282,15 @@ export class LegacyInternals implements ILegacyInternals { export interface LegacyRequest extends Request { } +// Warning: (ae-unresolved-inheritdoc-reference) The @inheritDoc reference could not be resolved: The package "kibana" does not have an export "IScopedClusterClient" +// +// @public (undocumented) +export class LegacyScopedClusterClient implements ILegacyScopedClusterClient { + constructor(internalAPICaller: LegacyAPICaller, scopedAPICaller: LegacyAPICaller, headers?: Headers | undefined); + callAsCurrentUser(endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; + callAsInternalUser(endpoint: string, clientParams?: Record, options?: LegacyCallAPIOptions): Promise; + } + // Warning: (ae-forgotten-export) The symbol "LegacyPlugins" needs to be exported by the entry point index.d.ts // // @internal @deprecated (undocumented) @@ -1652,13 +1663,6 @@ export interface PluginsServiceStart { // @public export type PublicUiSettingsParams = Omit; -// Warning: (ae-forgotten-export) The symbol "RecursiveReadonlyArray" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export type RecursiveReadonly = T extends (...args: any[]) => any ? T : T extends any[] ? RecursiveReadonlyArray : T extends object ? Readonly<{ - [K in keyof T]: RecursiveReadonly; -}> : T; - // @public export type RedirectResponseOptions = HttpResponseOptions & { headers: { @@ -1679,7 +1683,7 @@ export interface RequestHandlerContext { }; elasticsearch: { legacy: { - client: IScopedClusterClient; + client: ILegacyScopedClusterClient; }; }; uiSettings: { @@ -2323,7 +2327,7 @@ export class SavedObjectsRepository { // Warning: (ae-forgotten-export) The symbol "KibanaMigrator" needs to be exported by the entry point index.d.ts // // @internal - static createRepository(migrator: KibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, callCluster: APICaller, includedHiddenTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository; + static createRepository(migrator: KibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, callCluster: LegacyAPICaller, includedHiddenTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository; delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise<{}>; @@ -2478,13 +2482,6 @@ export type SavedObjectUnsanitizedDoc = SavedObjectDoc & Partial // @public export type ScopeableRequest = KibanaRequest | LegacyRequest | FakeRequest; -// @public -export class ScopedClusterClient implements IScopedClusterClient { - constructor(internalAPICaller: APICaller, scopedAPICaller: APICaller, headers?: Headers | undefined); - callAsCurrentUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; - callAsInternalUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; - } - // @public export interface ServiceStatus | unknown = unknown> { detail?: string; @@ -2546,7 +2543,7 @@ export interface SessionStorageFactory { } // @public (undocumented) -export type SharedGlobalConfig = RecursiveReadonly_2<{ +export type SharedGlobalConfig = RecursiveReadonly<{ kibana: Pick; elasticsearch: Pick; path: Pick; diff --git a/src/core/server/ui_settings/integration_tests/lib/servers.ts b/src/core/server/ui_settings/integration_tests/lib/servers.ts index 486abd5a0c790..ea462291059a5 100644 --- a/src/core/server/ui_settings/integration_tests/lib/servers.ts +++ b/src/core/server/ui_settings/integration_tests/lib/servers.ts @@ -25,7 +25,7 @@ import { TestKibanaUtils, TestUtils, } from '../../../../../test_utils/kbn_server'; -import { APICaller } from '../../../elasticsearch/'; +import { LegacyAPICaller } from '../../../elasticsearch/'; import { httpServerMock } from '../../../http/http_server.mocks'; let servers: TestUtils; @@ -37,7 +37,7 @@ let kbnServer: TestKibanaUtils['kbnServer']; interface AllServices { kbnServer: TestKibanaUtils['kbnServer']; savedObjectsClient: SavedObjectsClientContract; - callCluster: APICaller; + callCluster: LegacyAPICaller; uiSettings: IUiSettingsClient; deleteKibanaIndex: typeof deleteKibanaIndex; } @@ -62,7 +62,7 @@ export async function startServers() { kbnServer = kbn.kbnServer; } -async function deleteKibanaIndex(callCluster: APICaller) { +async function deleteKibanaIndex(callCluster: LegacyAPICaller) { const kibanaIndices = await callCluster('cat.indices', { index: '.kibana*', format: 'json' }); const indexNames = kibanaIndices.map((x: any) => x.index); if (!indexNames.length) { diff --git a/src/core/utils/deep_freeze.test.ts b/src/core/utils/deep_freeze.test.ts index b4531d80d0252..58aa9c9b8c92b 100644 --- a/src/core/utils/deep_freeze.test.ts +++ b/src/core/utils/deep_freeze.test.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - import { deepFreeze } from './deep_freeze'; it('returns the first argument with all original references', () => { @@ -33,7 +32,7 @@ it('returns the first argument with all original references', () => { it('prevents adding properties to argument', () => { const frozen = deepFreeze({}); expect(() => { - // @ts-ignore ts knows this shouldn't be possible, but just making sure + // @ts-expect-error ts knows this shouldn't be possible, but just making sure frozen.foo = true; }).toThrowError(`object is not extensible`); }); @@ -41,7 +40,7 @@ it('prevents adding properties to argument', () => { it('prevents changing properties on argument', () => { const frozen = deepFreeze({ foo: false }); expect(() => { - // @ts-ignore ts knows this shouldn't be possible, but just making sure + // @ts-expect-error ts knows this shouldn't be possible, but just making sure frozen.foo = true; }).toThrowError(`read only property 'foo'`); }); @@ -49,7 +48,7 @@ it('prevents changing properties on argument', () => { it('prevents changing properties on nested children of argument', () => { const frozen = deepFreeze({ foo: { bar: { baz: { box: 1 } } } }); expect(() => { - // @ts-ignore ts knows this shouldn't be possible, but just making sure + // @ts-expect-error ts knows this shouldn't be possible, but just making sure frozen.foo.bar.baz.box = 2; }).toThrowError(`read only property 'box'`); }); @@ -57,7 +56,7 @@ it('prevents changing properties on nested children of argument', () => { it('prevents adding items to a frozen array', () => { const frozen = deepFreeze({ foo: [1] }); expect(() => { - // @ts-ignore ts knows this shouldn't be possible, but just making sure + // @ts-expect-error ts knows this shouldn't be possible, but just making sure frozen.foo.push(2); }).toThrowError(`object is not extensible`); }); @@ -65,7 +64,7 @@ it('prevents adding items to a frozen array', () => { it('prevents reassigning items in a frozen array', () => { const frozen = deepFreeze({ foo: [1] }); expect(() => { - // @ts-ignore ts knows this shouldn't be possible, but just making sure + // @ts-expect-error ts knows this shouldn't be possible, but just making sure frozen.foo[0] = 2; }).toThrowError(`read only property '0'`); }); diff --git a/src/core/utils/deep_freeze.ts b/src/core/utils/deep_freeze.ts index b0f283c60d0fc..fbc35acb45b0f 100644 --- a/src/core/utils/deep_freeze.ts +++ b/src/core/utils/deep_freeze.ts @@ -16,19 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - -// if we define this inside RecursiveReadonly TypeScript complains -// eslint-disable-next-line @typescript-eslint/no-empty-interface -interface RecursiveReadonlyArray extends Array> {} - -/** @public */ -export type RecursiveReadonly = T extends (...args: any[]) => any - ? T - : T extends any[] - ? RecursiveReadonlyArray - : T extends object - ? Readonly<{ [K in keyof T]: RecursiveReadonly }> - : T; +import { RecursiveReadonly } from '@kbn/utility-types'; /** @public */ export type Freezable = { [k: string]: any } | any[]; @@ -47,6 +35,5 @@ export function deepFreeze(object: T) { deepFreeze(value); } } - return Object.freeze(object) as RecursiveReadonly; } diff --git a/src/core/utils/integration_tests/__fixtures__/frozen_object_mutation/tsconfig.json b/src/core/utils/integration_tests/__fixtures__/frozen_object_mutation/tsconfig.json deleted file mode 100644 index 12307c46b95fa..0000000000000 --- a/src/core/utils/integration_tests/__fixtures__/frozen_object_mutation/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "strict": true, - "skipLibCheck": true, - "lib": [ - "es2018" - ] - }, - "files": [ - "index.ts" - ] -} diff --git a/src/dev/code_coverage/ingest_coverage/__tests__/ingest_helpers.test.js b/src/dev/code_coverage/ingest_coverage/__tests__/ingest_helpers.test.js new file mode 100644 index 0000000000000..7ca7279e0d64c --- /dev/null +++ b/src/dev/code_coverage/ingest_coverage/__tests__/ingest_helpers.test.js @@ -0,0 +1,75 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; +import { maybeTeamAssign, whichIndex } from '../ingest_helpers'; +import { + TOTALS_INDEX, + RESEARCH_TOTALS_INDEX, + RESEARCH_COVERAGE_INDEX, + // COVERAGE_INDEX, +} from '../constants'; + +describe(`Ingest Helper fns`, () => { + describe(`whichIndex`, () => { + describe(`against the research job`, () => { + const whichIndexAgainstResearchJob = whichIndex(true); + describe(`against the totals index`, () => { + const isTotal = true; + it(`should return the Research Totals Index`, () => { + const actual = whichIndexAgainstResearchJob(isTotal); + expect(actual).to.be(RESEARCH_TOTALS_INDEX); + }); + }); + describe(`against the coverage index`, () => { + it(`should return the Research Totals Index`, () => { + const isTotal = false; + const actual = whichIndexAgainstResearchJob(isTotal); + expect(actual).to.be(RESEARCH_COVERAGE_INDEX); + }); + }); + }); + describe(`against the "prod" job`, () => { + const whichIndexAgainstProdJob = whichIndex(false); + describe(`against the totals index`, () => { + const isTotal = true; + it(`should return the "Prod" Totals Index`, () => { + const actual = whichIndexAgainstProdJob(isTotal); + expect(actual).to.be(TOTALS_INDEX); + }); + }); + }); + }); + describe(`maybeTeamAssign`, () => { + describe(`against a coverage index`, () => { + it(`should have the pipeline prop`, () => { + const actual = maybeTeamAssign(true, { a: 'blah' }); + expect(actual).to.have.property('pipeline'); + }); + }); + describe(`against a totals index`, () => { + describe(`for "prod"`, () => { + it(`should not have the pipeline prop`, () => { + const actual = maybeTeamAssign(false, { b: 'blah' }); + expect(actual).not.to.have.property('pipeline'); + }); + }); + }); + }); +}); diff --git a/src/dev/code_coverage/ingest_coverage/__tests__/transforms.test.js b/src/dev/code_coverage/ingest_coverage/__tests__/transforms.test.js index 8c982b792ed3b..2fd1d5cbe8d48 100644 --- a/src/dev/code_coverage/ingest_coverage/__tests__/transforms.test.js +++ b/src/dev/code_coverage/ingest_coverage/__tests__/transforms.test.js @@ -32,17 +32,33 @@ describe(`Transform fn`, () => { }); }); describe(`coveredFilePath`, () => { - it(`should remove the jenkins workspace path`, () => { - const obj = { - staticSiteUrl: - '/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana/x-pack/plugins/reporting/server/browsers/extract/unzip.js', - COVERAGE_INGESTION_KIBANA_ROOT: - '/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana', - }; - expect(coveredFilePath(obj)).to.have.property( - 'coveredFilePath', - 'x-pack/plugins/reporting/server/browsers/extract/unzip.js' - ); + describe(`in the code-coverage job`, () => { + it(`should remove the jenkins workspace path`, () => { + const obj = { + staticSiteUrl: + '/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana/x-pack/plugins/reporting/server/browsers/extract/unzip.js', + COVERAGE_INGESTION_KIBANA_ROOT: + '/var/lib/jenkins/workspace/elastic+kibana+code-coverage/kibana', + }; + expect(coveredFilePath(obj)).to.have.property( + 'coveredFilePath', + 'x-pack/plugins/reporting/server/browsers/extract/unzip.js' + ); + }); + }); + describe(`in the qa research job`, () => { + it(`should remove the jenkins workspace path`, () => { + const obj = { + staticSiteUrl: + '/var/lib/jenkins/workspace/elastic+kibana+qa-research/kibana/x-pack/plugins/reporting/server/browsers/extract/unzip.js', + COVERAGE_INGESTION_KIBANA_ROOT: + '/var/lib/jenkins/workspace/elastic+kibana+qa-research/kibana', + }; + expect(coveredFilePath(obj)).to.have.property( + 'coveredFilePath', + 'x-pack/plugins/reporting/server/browsers/extract/unzip.js' + ); + }); }); }); describe(`itemizeVcs`, () => { diff --git a/src/dev/code_coverage/ingest_coverage/constants.js b/src/dev/code_coverage/ingest_coverage/constants.js index a7303f0778d1c..ddee7106f4490 100644 --- a/src/dev/code_coverage/ingest_coverage/constants.js +++ b/src/dev/code_coverage/ingest_coverage/constants.js @@ -18,4 +18,17 @@ */ export const COVERAGE_INDEX = process.env.COVERAGE_INDEX || 'kibana_code_coverage'; + export const TOTALS_INDEX = process.env.TOTALS_INDEX || `kibana_total_code_coverage`; + +export const RESEARCH_COVERAGE_INDEX = + process.env.RESEARCH_COVERAGE_INDEX || 'qa_research_code_coverage'; + +export const RESEARCH_TOTALS_INDEX = + process.env.RESEARCH_TOTALS_INDEX || `qa_research_total_code_coverage`; + +export const TEAM_ASSIGNMENT_PIPELINE_NAME = process.env.PIPELINE_NAME || 'team_assignment'; + +export const CODE_COVERAGE_CI_JOB_NAME = 'elastic+kibana+code-coverage'; +export const RESEARCH_CI_JOB_NAME = 'elastic+kibana+qa-research'; +export const CI_JOB_NAME = process.env.COVERAGE_JOB_NAME || RESEARCH_CI_JOB_NAME; diff --git a/src/dev/code_coverage/ingest_coverage/ingest.js b/src/dev/code_coverage/ingest_coverage/ingest.js index d6c55a9a655b8..43f0663ad0359 100644 --- a/src/dev/code_coverage/ingest_coverage/ingest.js +++ b/src/dev/code_coverage/ingest_coverage/ingest.js @@ -19,40 +19,77 @@ const { Client } = require('@elastic/elasticsearch'); import { createFailError } from '@kbn/dev-utils'; -import { COVERAGE_INDEX, TOTALS_INDEX } from './constants'; -import { errMsg, redact } from './ingest_helpers'; -import { noop } from './utils'; +import { RESEARCH_CI_JOB_NAME, TEAM_ASSIGNMENT_PIPELINE_NAME } from './constants'; +import { errMsg, redact, whichIndex } from './ingest_helpers'; +import { pretty, green } from './utils'; import { right, left } from './either'; const node = process.env.ES_HOST || 'http://localhost:9200'; + const client = new Client({ node }); -const pipeline = process.env.PIPELINE_NAME || 'team_assignment'; -const redacted = redact(node); +const redactedEsHostUrl = redact(node); +const parse = JSON.parse.bind(null); +const isResearchJob = process.env.COVERAGE_JOB_NAME === RESEARCH_CI_JOB_NAME ? true : false; export const ingest = (log) => async (body) => { - const index = body.isTotal ? TOTALS_INDEX : COVERAGE_INDEX; - const maybeWithPipeline = maybeTeamAssign(index, body); - const withIndex = { index, body: maybeWithPipeline }; - const dontSend = noop; - - log.verbose(withIndex); - - process.env.NODE_ENV === 'integration_test' - ? left(null) - : right(withIndex).fold(dontSend, async function doSend(finalPayload) { - await send(index, redacted, finalPayload); - }); + const isTotal = !!body.isTotal; + const index = whichIndex(isResearchJob)(isTotal); + const isACoverageIndex = isTotal ? false : true; + + const stringified = pretty(body); + const pipeline = TEAM_ASSIGNMENT_PIPELINE_NAME; + + const finalPayload = isACoverageIndex + ? { index, body: stringified, pipeline } + : { index, body: stringified }; + + const justLog = dontSendButLog(log); + const doSendToIndex = doSend(index); + const doSendRedacted = doSendToIndex(redactedEsHostUrl)(log)(client); + + eitherSendOrNot(finalPayload).fold(justLog, doSendRedacted); }; -async function send(idx, redacted, requestBody) { +function doSend(index) { + return (redactedEsHostUrl) => (log) => (client) => async (payload) => { + const logF = logSend(true)(redactedEsHostUrl)(log); + await send(logF, index, redactedEsHostUrl, client, payload); + }; +} + +function dontSendButLog(log) { + return (payload) => { + logSend(false)(null)(log)(payload); + }; +} + +async function send(logF, idx, redactedEsHostUrl, client, requestBody) { try { await client.index(requestBody); + logF(requestBody); } catch (e) { - throw createFailError(errMsg(idx, redacted, requestBody, e)); + const { body } = requestBody; + const parsed = parse(body); + throw createFailError(errMsg(idx, redactedEsHostUrl, parsed, e)); } } -export function maybeTeamAssign(index, body) { - const payload = index === TOTALS_INDEX ? body : { ...body, pipeline }; - return payload; +const sendMsg = (actuallySent, redactedEsHostUrl, payload) => { + const { index, body } = payload; + return `### ${actuallySent ? 'Sent' : 'Fake Sent'}: +${redactedEsHostUrl ? `\t### ES Host: ${redactedEsHostUrl}` : ''} +\t### Index: ${green(index)} +\t### payload.body: ${body} +${process.env.NODE_ENV === 'integration_test' ? `ingest-pipe=>${payload.pipeline}` : ''} +`; +}; + +function logSend(actuallySent) { + return (redactedEsHostUrl) => (log) => (payload) => { + log.verbose(sendMsg(actuallySent, redactedEsHostUrl, payload)); + }; +} + +function eitherSendOrNot(payload) { + return process.env.NODE_ENV === 'integration_test' ? left(payload) : right(payload); } diff --git a/src/dev/code_coverage/ingest_coverage/ingest_helpers.js b/src/dev/code_coverage/ingest_coverage/ingest_helpers.js index 11e5755bb0282..86bcf03977082 100644 --- a/src/dev/code_coverage/ingest_coverage/ingest_helpers.js +++ b/src/dev/code_coverage/ingest_coverage/ingest_helpers.js @@ -20,6 +20,13 @@ import { always, pretty } from './utils'; import chalk from 'chalk'; import { fromNullable } from './either'; +import { + COVERAGE_INDEX, + RESEARCH_COVERAGE_INDEX, + RESEARCH_TOTALS_INDEX, + TEAM_ASSIGNMENT_PIPELINE_NAME, + TOTALS_INDEX, +} from './constants'; export function errMsg(index, redacted, body, e) { const orig = fromNullable(e.body).fold( @@ -38,6 +45,9 @@ ${orig} ### Troubleshooting Hint: ${red('Perhaps the coverage data was not merged properly?\n')} + +### Error.meta (stringified): +${pretty(e.meta)} `; } @@ -59,3 +69,21 @@ function color(whichColor) { return chalk[whichColor].bgWhiteBright(x); }; } + +export function maybeTeamAssign(isACoverageIndex, body) { + const doAddTeam = isACoverageIndex ? true : false; + const payload = doAddTeam ? { ...body, pipeline: TEAM_ASSIGNMENT_PIPELINE_NAME } : body; + return payload; +} + +export function whichIndex(isResearchJob) { + return (isTotal) => + isTotal ? whichTotalsIndex(isResearchJob) : whichCoverageIndex(isResearchJob); +} +function whichTotalsIndex(isResearchJob) { + return isResearchJob ? RESEARCH_TOTALS_INDEX : TOTALS_INDEX; +} + +function whichCoverageIndex(isResearchJob) { + return isResearchJob ? RESEARCH_COVERAGE_INDEX : COVERAGE_INDEX; +} diff --git a/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js b/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js index 013adc8b6b0af..2a65839f85ac3 100644 --- a/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js +++ b/src/dev/code_coverage/ingest_coverage/integration_tests/ingest_coverage.test.js @@ -47,7 +47,7 @@ describe('Ingesting coverage', () => { describe(`staticSiteUrl`, () => { let actualUrl = ''; - const siteUrlRegex = /staticSiteUrl:\s*(.+,)/; + const siteUrlRegex = /"staticSiteUrl":\s*(.+,)/; beforeAll(async () => { const opts = [...verboseArgs, resolved]; @@ -70,8 +70,8 @@ describe('Ingesting coverage', () => { }); describe(`vcsInfo`, () => { - let vcsInfo; describe(`without a commit msg in the vcs info file`, () => { + let vcsInfo; const args = [ 'scripts/ingest_coverage.js', '--verbose', @@ -93,9 +93,6 @@ describe('Ingesting coverage', () => { }); }); describe(`team assignment`, () => { - let shouldNotHavePipelineOut = ''; - let shouldIndeedHavePipelineOut = ''; - const args = [ 'scripts/ingest_coverage.js', '--verbose', @@ -104,28 +101,26 @@ describe('Ingesting coverage', () => { '--path', ]; - const teamAssignRE = /pipeline:/; - - beforeAll(async () => { - const summaryPath = 'jest-combined/coverage-summary-just-total.json'; - const resolved = resolve(MOCKS_DIR, summaryPath); - const opts = [...args, resolved]; - const { stdout } = await execa(process.execPath, opts, { cwd: ROOT_DIR, env }); - shouldNotHavePipelineOut = stdout; - }); - beforeAll(async () => { - const summaryPath = 'jest-combined/coverage-summary-manual-mix.json'; - const resolved = resolve(MOCKS_DIR, summaryPath); - const opts = [...args, resolved]; - const { stdout } = await execa(process.execPath, opts, { cwd: ROOT_DIR, env }); - shouldIndeedHavePipelineOut = stdout; - }); - - it(`should not occur when going to the totals index`, () => { - expect(teamAssignRE.test(shouldNotHavePipelineOut)).to.not.be.ok(); + it(`should not occur when going to the totals index`, async () => { + const teamAssignRE = /"pipeline":/; + const shouldNotHavePipelineOut = await prokJustTotalOrNot(true, args); + const actual = teamAssignRE.test(shouldNotHavePipelineOut); + expect(actual).to.not.be.ok(); }); - it(`should indeed occur when going to the coverage index`, () => { - expect(teamAssignRE.test(shouldIndeedHavePipelineOut)).to.be.ok(); + it(`should indeed occur when going to the coverage index`, async () => { + const shouldIndeedHavePipelineOut = await prokJustTotalOrNot(false, args); + const onlyForTestingRe = /ingest-pipe=>team_assignment/; + const actual = onlyForTestingRe.test(shouldIndeedHavePipelineOut); + expect(actual).to.be.ok(); }); }); }); +async function prokJustTotalOrNot(isTotal, args) { + const justTotalPath = 'jest-combined/coverage-summary-just-total.json'; + const notJustTotalPath = 'jest-combined/coverage-summary-manual-mix.json'; + + const resolved = resolve(MOCKS_DIR, isTotal ? justTotalPath : notJustTotalPath); + const opts = [...args, resolved]; + const { stdout } = await execa(process.execPath, opts, { cwd: ROOT_DIR, env }); + return stdout; +} diff --git a/src/dev/code_coverage/ingest_coverage/integration_tests/mocks/jest-combined/coverage-summary-NO-total.json b/src/dev/code_coverage/ingest_coverage/integration_tests/mocks/jest-combined/coverage-summary-NO-total.json deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/src/dev/code_coverage/ingest_coverage/process.js b/src/dev/code_coverage/ingest_coverage/process.js index 6b9c8f09febfe..85a42cfffa6e2 100644 --- a/src/dev/code_coverage/ingest_coverage/process.js +++ b/src/dev/code_coverage/ingest_coverage/process.js @@ -36,13 +36,17 @@ import { import { resolve } from 'path'; import { createReadStream } from 'fs'; import readline from 'readline'; +import * as moment from 'moment'; const ROOT = '../../../..'; const COVERAGE_INGESTION_KIBANA_ROOT = process.env.COVERAGE_INGESTION_KIBANA_ROOT || resolve(__dirname, ROOT); const ms = process.env.DELAY || 0; const staticSiteUrlBase = process.env.STATIC_SITE_URL_BASE || 'https://kibana-coverage.elastic.dev'; -const addPrePopulatedTimeStamp = addTimeStamp(process.env.TIME_STAMP); +const format = 'YYYY-MM-DDTHH:mm:SS'; +// eslint-disable-next-line import/namespace +const formatted = `${moment.utc().format(format)}Z`; +const addPrePopulatedTimeStamp = addTimeStamp(process.env.TIME_STAMP || formatted); const preamble = pipe(statsAndstaticSiteUrl, rootDirAndOrigPath, buildId, addPrePopulatedTimeStamp); const addTestRunnerAndStaticSiteUrl = pipe(testRunner, staticSite(staticSiteUrlBase)); diff --git a/src/dev/code_coverage/shell_scripts/ingest_coverage.sh b/src/dev/code_coverage/shell_scripts/ingest_coverage.sh index b7064a1e42671..d3cf31fc0f427 100644 --- a/src/dev/code_coverage/shell_scripts/ingest_coverage.sh +++ b/src/dev/code_coverage/shell_scripts/ingest_coverage.sh @@ -3,11 +3,14 @@ echo "### Ingesting Code Coverage" echo "" +COVERAGE_JOB_NAME=$1 +export COVERAGE_JOB_NAME +echo "### debug COVERAGE_JOB_NAME: ${COVERAGE_JOB_NAME}" -BUILD_ID=$1 +BUILD_ID=$2 export BUILD_ID -CI_RUN_URL=$2 +CI_RUN_URL=$3 export CI_RUN_URL echo "### debug CI_RUN_URL: ${CI_RUN_URL}" @@ -17,7 +20,10 @@ export ES_HOST STATIC_SITE_URL_BASE='https://kibana-coverage.elastic.dev' export STATIC_SITE_URL_BASE -for x in jest functional mocha; do +DELAY=100 +export DELAY + +for x in jest functional; do echo "### Ingesting coverage for ${x}" COVERAGE_SUMMARY_FILE=target/kibana-coverage/${x}-combined/coverage-summary.json @@ -25,5 +31,11 @@ for x in jest functional mocha; do node scripts/ingest_coverage.js --verbose --path ${COVERAGE_SUMMARY_FILE} --vcsInfoPath ./VCS_INFO.txt done +# Need to override COVERAGE_INGESTION_KIBANA_ROOT since mocha json file has original intake worker path +COVERAGE_SUMMARY_FILE=target/kibana-coverage/mocha-combined/coverage-summary.json +export COVERAGE_INGESTION_KIBANA_ROOT=/dev/shm/workspace/kibana + +node scripts/ingest_coverage.js --verbose --path ${COVERAGE_SUMMARY_FILE} --vcsInfoPath ./VCS_INFO.txt + echo "### Ingesting Code Coverage - Complete" echo "" diff --git a/src/dev/i18n/integrate_locale_files.test.ts b/src/dev/i18n/integrate_locale_files.test.ts index 7ff1d87f1bc55..3bd3dc61c044f 100644 --- a/src/dev/i18n/integrate_locale_files.test.ts +++ b/src/dev/i18n/integrate_locale_files.test.ts @@ -21,7 +21,7 @@ import { mockMakeDirAsync, mockWriteFileAsync } from './integrate_locale_files.t import path from 'path'; import { integrateLocaleFiles, verifyMessages } from './integrate_locale_files'; -// @ts-ignore +// @ts-expect-error import { normalizePath } from './utils'; const localePath = path.resolve(__dirname, '__fixtures__', 'integrate_locale_files', 'fr.json'); @@ -36,6 +36,7 @@ const defaultIntegrateOptions = { sourceFileName: localePath, dryRun: false, ignoreIncompatible: false, + ignoreMalformed: false, ignoreMissing: false, ignoreUnused: false, config: { diff --git a/src/dev/i18n/integrate_locale_files.ts b/src/dev/i18n/integrate_locale_files.ts index d8ccccca15559..f9cd6dd1971c7 100644 --- a/src/dev/i18n/integrate_locale_files.ts +++ b/src/dev/i18n/integrate_locale_files.ts @@ -31,7 +31,8 @@ import { normalizePath, readFileAsync, writeFileAsync, - // @ts-ignore + verifyICUMessage, + // @ts-expect-error } from './utils'; import { I18nConfig } from './config'; @@ -41,6 +42,7 @@ export interface IntegrateOptions { sourceFileName: string; targetFileName?: string; dryRun: boolean; + ignoreMalformed: boolean; ignoreIncompatible: boolean; ignoreUnused: boolean; ignoreMissing: boolean; @@ -105,6 +107,23 @@ export function verifyMessages( } } + for (const messageId of localizedMessagesIds) { + const defaultMessage = defaultMessagesMap.get(messageId); + if (defaultMessage) { + try { + const message = localizedMessagesMap.get(messageId)!; + verifyICUMessage(message); + } catch (err) { + if (options.ignoreMalformed) { + localizedMessagesMap.delete(messageId); + options.log.warning(`Malformed translation ignored (${messageId}): ${err}`); + } else { + errorMessage += `\nMalformed translation (${messageId}): ${err}\n`; + } + } + } + } + if (errorMessage) { throw createFailError(errorMessage); } diff --git a/src/dev/i18n/tasks/check_compatibility.ts b/src/dev/i18n/tasks/check_compatibility.ts index 5900bf5aff252..afaf3cd875a8a 100644 --- a/src/dev/i18n/tasks/check_compatibility.ts +++ b/src/dev/i18n/tasks/check_compatibility.ts @@ -22,13 +22,14 @@ import { integrateLocaleFiles, I18nConfig } from '..'; export interface I18nFlags { fix: boolean; + ignoreMalformed: boolean; ignoreIncompatible: boolean; ignoreUnused: boolean; ignoreMissing: boolean; } export function checkCompatibility(config: I18nConfig, flags: I18nFlags, log: ToolingLog) { - const { fix, ignoreIncompatible, ignoreUnused, ignoreMissing } = flags; + const { fix, ignoreIncompatible, ignoreUnused, ignoreMalformed, ignoreMissing } = flags; return config.translations.map((translationsPath) => ({ task: async ({ messages }: { messages: Map }) => { // If `fix` is set we should try apply all possible fixes and override translations file. @@ -37,6 +38,7 @@ export function checkCompatibility(config: I18nConfig, flags: I18nFlags, log: To ignoreIncompatible: fix || ignoreIncompatible, ignoreUnused: fix || ignoreUnused, ignoreMissing: fix || ignoreMissing, + ignoreMalformed: fix || ignoreMalformed, sourceFileName: translationsPath, targetFileName: fix ? translationsPath : undefined, config, diff --git a/src/dev/i18n/utils.js b/src/dev/i18n/utils.js index 1d1c3118e0852..11a002fdbf4a8 100644 --- a/src/dev/i18n/utils.js +++ b/src/dev/i18n/utils.js @@ -208,6 +208,28 @@ export function checkValuesProperty(prefixedValuesKeys, defaultMessage, messageI } } +/** + * Verifies valid ICU message. + * @param message ICU message. + * @param messageId ICU message id + * @returns {undefined} + */ +export function verifyICUMessage(message) { + try { + parser.parse(message); + } catch (error) { + if (error.name === 'SyntaxError') { + const errorWithContext = createParserErrorMessage(message, { + loc: { + line: error.location.start.line, + column: error.location.start.column - 1, + }, + message: error.message, + }); + throw errorWithContext; + } + } +} /** * Extracts value references from the ICU message. * @param message ICU message. diff --git a/src/dev/jest/config.js b/src/dev/jest/config.js index 18fc7ebaa2af4..e11668ab57f55 100644 --- a/src/dev/jest/config.js +++ b/src/dev/jest/config.js @@ -43,14 +43,16 @@ export default { 'src/plugins/**/*.{ts,tsx}', '!src/plugins/**/{__test__,__snapshots__,__examples__,mocks,tests}/**/*', '!src/plugins/**/*.d.ts', + '!src/plugins/**/test_helpers/**', 'packages/kbn-ui-framework/src/components/**/*.js', '!packages/kbn-ui-framework/src/components/index.js', '!packages/kbn-ui-framework/src/components/**/*/index.js', 'packages/kbn-ui-framework/src/services/**/*.js', '!packages/kbn-ui-framework/src/services/index.js', '!packages/kbn-ui-framework/src/services/**/*/index.js', - 'src/legacy/core_plugins/**/*.{js,jsx,ts,tsx}', + 'src/legacy/core_plugins/**/*.{js,mjs,jsx,ts,tsx}', '!src/legacy/core_plugins/**/{__test__,__snapshots__}/**/*', + '!src/legacy/core_plugins/tests_bundle/**', ], moduleNameMapper: { '@elastic/eui$': '/node_modules/@elastic/eui/test-env', @@ -79,10 +81,10 @@ export default { ], coverageDirectory: '/target/kibana-coverage/jest', coverageReporters: !!process.env.CODE_COVERAGE ? ['json'] : ['html', 'text'], - moduleFileExtensions: ['js', 'json', 'ts', 'tsx', 'node'], + moduleFileExtensions: ['js', 'mjs', 'json', 'ts', 'tsx', 'node'], modulePathIgnorePatterns: ['__fixtures__/', 'target/'], testEnvironment: 'jest-environment-jsdom-thirteen', - testMatch: ['**/*.test.{js,ts,tsx}'], + testMatch: ['**/*.test.{js,mjs,ts,tsx}'], testPathIgnorePatterns: [ '/packages/kbn-ui-framework/(dist|doc_site|generator-kui)/', '/packages/kbn-pm/dist/', diff --git a/src/dev/run_eslint.js b/src/dev/run_eslint.js index 3bfbb9cc876e0..3214a2fb45471 100644 --- a/src/dev/run_eslint.js +++ b/src/dev/run_eslint.js @@ -31,7 +31,7 @@ if (!process.argv.includes('--no-cache')) { } if (!process.argv.includes('--ext')) { - process.argv.push('--ext', '.js,.ts,.tsx'); + process.argv.push('--ext', '.js,.mjs,.ts,.tsx'); } // common-js is required so that logic before this executes before loading eslint diff --git a/src/dev/run_i18n_check.ts b/src/dev/run_i18n_check.ts index 97ea988b1de3a..70eeedac2b8b6 100644 --- a/src/dev/run_i18n_check.ts +++ b/src/dev/run_i18n_check.ts @@ -36,6 +36,7 @@ run( async ({ flags: { 'ignore-incompatible': ignoreIncompatible, + 'ignore-malformed': ignoreMalformed, 'ignore-missing': ignoreMissing, 'ignore-unused': ignoreUnused, 'include-config': includeConfig, @@ -48,12 +49,13 @@ run( fix && (ignoreIncompatible !== undefined || ignoreUnused !== undefined || + ignoreMalformed !== undefined || ignoreMissing !== undefined) ) { throw createFailError( `${chalk.white.bgRed( ' I18N ERROR ' - )} none of the --ignore-incompatible, --ignore-unused or --ignore-missing is allowed when --fix is set.` + )} none of the --ignore-incompatible, --ignore-malformed, --ignore-unused or --ignore-missing is allowed when --fix is set.` ); } @@ -99,6 +101,7 @@ run( checkCompatibility( config, { + ignoreMalformed: !!ignoreMalformed, ignoreIncompatible: !!ignoreIncompatible, ignoreUnused: !!ignoreUnused, ignoreMissing: !!ignoreMissing, diff --git a/src/dev/run_i18n_integrate.ts b/src/dev/run_i18n_integrate.ts index 23d66fae9f26e..25c3ea32783aa 100644 --- a/src/dev/run_i18n_integrate.ts +++ b/src/dev/run_i18n_integrate.ts @@ -31,6 +31,7 @@ run( 'ignore-incompatible': ignoreIncompatible = false, 'ignore-missing': ignoreMissing = false, 'ignore-unused': ignoreUnused = false, + 'ignore-malformed': ignoreMalformed = false, 'include-config': includeConfig, path, source, @@ -66,12 +67,13 @@ run( typeof ignoreIncompatible !== 'boolean' || typeof ignoreUnused !== 'boolean' || typeof ignoreMissing !== 'boolean' || + typeof ignoreMalformed !== 'boolean' || typeof dryRun !== 'boolean' ) { throw createFailError( `${chalk.white.bgRed( ' I18N ERROR ' - )} --ignore-incompatible, --ignore-unused, --ignore-missing, and --dry-run can't have values` + )} --ignore-incompatible, --ignore-unused, --ignore-malformed, --ignore-missing, and --dry-run can't have values` ); } @@ -97,6 +99,7 @@ run( ignoreIncompatible, ignoreUnused, ignoreMissing, + ignoreMalformed, config, log, }); diff --git a/src/fixtures/stubbed_saved_object_index_pattern.js b/src/fixtures/stubbed_saved_object_index_pattern.ts similarity index 87% rename from src/fixtures/stubbed_saved_object_index_pattern.js rename to src/fixtures/stubbed_saved_object_index_pattern.ts index 8e0e230ef33dd..02e6cb85e341f 100644 --- a/src/fixtures/stubbed_saved_object_index_pattern.js +++ b/src/fixtures/stubbed_saved_object_index_pattern.ts @@ -17,13 +17,13 @@ * under the License. */ +// @ts-expect-error import stubbedLogstashFields from './logstash_fields'; -import { SimpleSavedObject } from '../core/public'; const mockLogstashFields = stubbedLogstashFields(); -export function stubbedSavedObjectIndexPattern(id) { - return new SimpleSavedObject(undefined, { +export function stubbedSavedObjectIndexPattern(id: string | null = null) { + return { id, type: 'index-pattern', attributes: { @@ -32,5 +32,5 @@ export function stubbedSavedObjectIndexPattern(id) { fields: mockLogstashFields, }, version: 2, - }); + }; } diff --git a/src/fixtures/telemetry_collectors/.telemetryrc.json b/src/fixtures/telemetry_collectors/.telemetryrc.json new file mode 100644 index 0000000000000..31203149c9b57 --- /dev/null +++ b/src/fixtures/telemetry_collectors/.telemetryrc.json @@ -0,0 +1,7 @@ +{ + "root": ".", + "output": ".", + "exclude": [ + "./unmapped_collector.ts" + ] +} diff --git a/src/fixtures/telemetry_collectors/constants.ts b/src/fixtures/telemetry_collectors/constants.ts new file mode 100644 index 0000000000000..4aac9e66cdbdb --- /dev/null +++ b/src/fixtures/telemetry_collectors/constants.ts @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import moment, { Moment } from 'moment'; +import { MakeSchemaFrom } from '../../plugins/usage_collection/server'; + +export interface Usage { + locale: string; +} + +export interface WithUnion { + prop1: string | null; + prop2: string | null | undefined; + prop3?: string | null; + prop4: 'opt1' | 'opt2'; + prop5: 123 | 431; +} + +export interface WithMoment { + prop1: Moment; + prop2: moment.Moment; + prop3: Moment[]; + prop4: Date[]; +} + +export interface WithConflictingUnion { + prop1: 123 | 'str'; +} + +export interface WithUnsupportedUnion { + prop1: 123 | Moment; +} + +export const externallyDefinedSchema: MakeSchemaFrom<{ locale: string }> = { + locale: { + type: 'keyword', + }, +}; diff --git a/src/fixtures/telemetry_collectors/externally_defined_collector.ts b/src/fixtures/telemetry_collectors/externally_defined_collector.ts new file mode 100644 index 0000000000000..00a8d643e27b3 --- /dev/null +++ b/src/fixtures/telemetry_collectors/externally_defined_collector.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { CollectorSet, CollectorOptions } from '../../plugins/usage_collection/server/collector'; +import { loggerMock } from '../../core/server/logging/logger.mock'; + +const collectorSet = new CollectorSet({ + logger: loggerMock.create(), + maximumWaitTimeForAllCollectorsInS: 0, +}); + +interface Usage { + locale: string; +} + +function createCollector(): CollectorOptions { + return { + type: 'from_fn_collector', + isReady: () => true, + fetch(): Usage { + return { + locale: 'en', + }; + }, + schema: { + locale: { + type: 'keyword', + }, + }, + }; +} + +export function defineCollectorFromVariable() { + const fromVarCollector: CollectorOptions = { + type: 'from_variable_collector', + isReady: () => true, + fetch(): Usage { + return { + locale: 'en', + }; + }, + schema: { + locale: { + type: 'keyword', + }, + }, + }; + + collectorSet.makeUsageCollector(fromVarCollector); +} + +export function defineCollectorFromFn() { + const fromFnCollector = createCollector(); + + collectorSet.makeUsageCollector(fromFnCollector); +} diff --git a/src/fixtures/telemetry_collectors/file_with_no_collector.ts b/src/fixtures/telemetry_collectors/file_with_no_collector.ts new file mode 100644 index 0000000000000..2e1870e486269 --- /dev/null +++ b/src/fixtures/telemetry_collectors/file_with_no_collector.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const SOME_CONST: number = 123; diff --git a/src/fixtures/telemetry_collectors/imported_schema.ts b/src/fixtures/telemetry_collectors/imported_schema.ts new file mode 100644 index 0000000000000..66d04700642d1 --- /dev/null +++ b/src/fixtures/telemetry_collectors/imported_schema.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { CollectorSet } from '../../plugins/usage_collection/server/collector'; +import { loggerMock } from '../../core/server/logging/logger.mock'; +import { externallyDefinedSchema } from './constants'; + +const { makeUsageCollector } = new CollectorSet({ + logger: loggerMock.create(), + maximumWaitTimeForAllCollectorsInS: 0, +}); + +interface Usage { + locale?: string; +} + +export const myCollector = makeUsageCollector({ + type: 'with_imported_schema', + isReady: () => true, + schema: externallyDefinedSchema, + fetch(): Usage { + return { + locale: 'en', + }; + }, +}); diff --git a/src/fixtures/telemetry_collectors/imported_usage_interface.ts b/src/fixtures/telemetry_collectors/imported_usage_interface.ts new file mode 100644 index 0000000000000..a4a0f4ae1b3c4 --- /dev/null +++ b/src/fixtures/telemetry_collectors/imported_usage_interface.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { CollectorSet } from '../../plugins/usage_collection/server/collector'; +import { loggerMock } from '../../core/server/logging/logger.mock'; +import { Usage } from './constants'; + +const { makeUsageCollector } = new CollectorSet({ + logger: loggerMock.create(), + maximumWaitTimeForAllCollectorsInS: 0, +}); + +export const myCollector = makeUsageCollector({ + type: 'imported_usage_interface_collector', + isReady: () => true, + fetch() { + return { + locale: 'en', + }; + }, + schema: { + locale: { + type: 'keyword', + }, + }, +}); diff --git a/src/fixtures/telemetry_collectors/nested_collector.ts b/src/fixtures/telemetry_collectors/nested_collector.ts new file mode 100644 index 0000000000000..bde89fe4a7060 --- /dev/null +++ b/src/fixtures/telemetry_collectors/nested_collector.ts @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { CollectorSet, UsageCollector } from '../../plugins/usage_collection/server/collector'; +import { loggerMock } from '../../core/server/logging/logger.mock'; + +const collectorSet = new CollectorSet({ + logger: loggerMock.create(), + maximumWaitTimeForAllCollectorsInS: 0, +}); + +interface Usage { + locale?: string; +} + +export class NestedInside { + collector?: UsageCollector; + createMyCollector() { + this.collector = collectorSet.makeUsageCollector({ + type: 'my_nested_collector', + isReady: () => true, + fetch: async () => { + return { + locale: 'en', + }; + }, + schema: { + locale: { + type: 'keyword', + }, + }, + }); + } +} diff --git a/src/fixtures/telemetry_collectors/unmapped_collector.ts b/src/fixtures/telemetry_collectors/unmapped_collector.ts new file mode 100644 index 0000000000000..1ea360fcd9e96 --- /dev/null +++ b/src/fixtures/telemetry_collectors/unmapped_collector.ts @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { CollectorSet } from '../../plugins/usage_collection/server/collector'; +import { loggerMock } from '../../core/server/logging/logger.mock'; + +const { makeUsageCollector } = new CollectorSet({ + logger: loggerMock.create(), + maximumWaitTimeForAllCollectorsInS: 0, +}); + +interface Usage { + locale: string; +} + +export const myCollector = makeUsageCollector({ + type: 'unmapped_collector', + isReady: () => true, + fetch(): Usage { + return { + locale: 'en', + }; + }, +}); diff --git a/src/fixtures/telemetry_collectors/working_collector.ts b/src/fixtures/telemetry_collectors/working_collector.ts new file mode 100644 index 0000000000000..d70a247c61e70 --- /dev/null +++ b/src/fixtures/telemetry_collectors/working_collector.ts @@ -0,0 +1,81 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { CollectorSet } from '../../plugins/usage_collection/server/collector'; +import { loggerMock } from '../../core/server/logging/logger.mock'; + +const { makeUsageCollector } = new CollectorSet({ + logger: loggerMock.create(), + maximumWaitTimeForAllCollectorsInS: 0, +}); + +interface MyObject { + total: number; + type: boolean; +} + +interface Usage { + flat?: string; + my_str?: string; + my_objects: MyObject; +} + +const SOME_NUMBER: number = 123; + +export const myCollector = makeUsageCollector({ + type: 'my_working_collector', + isReady: () => true, + fetch() { + const testString = '123'; + // query ES and get some data + + // summarize the data into a model + // return the modeled object that includes whatever you want to track + try { + return { + flat: 'hello', + my_str: testString, + my_objects: { + total: SOME_NUMBER, + type: true, + }, + }; + } catch (err) { + return { + my_objects: { + total: 0, + type: true, + }, + }; + } + }, + schema: { + flat: { + type: 'keyword', + }, + my_str: { + type: 'text', + }, + my_objects: { + total: { + type: 'number', + }, + type: { type: 'boolean' }, + }, + }, +}); diff --git a/src/legacy/core_plugins/elasticsearch/server/lib/cluster.ts b/src/legacy/core_plugins/elasticsearch/server/lib/cluster.ts index a595fffb3c235..0e7692f6be755 100644 --- a/src/legacy/core_plugins/elasticsearch/server/lib/cluster.ts +++ b/src/legacy/core_plugins/elasticsearch/server/lib/cluster.ts @@ -19,18 +19,18 @@ import { Request } from 'hapi'; import { errors } from 'elasticsearch'; -import { CallAPIOptions, ClusterClient, FakeRequest } from 'kibana/server'; +import { LegacyCallAPIOptions, LegacyClusterClient, FakeRequest } from 'kibana/server'; export class Cluster { public readonly errors = errors; - constructor(private readonly clusterClient: ClusterClient) {} + constructor(private readonly clusterClient: LegacyClusterClient) {} public callWithRequest = async ( req: Request | FakeRequest, endpoint: string, clientParams?: Record, - options?: CallAPIOptions + options?: LegacyCallAPIOptions ) => { return await this.clusterClient .asScoped(req) @@ -40,7 +40,7 @@ export class Cluster { public callWithInternalUser = async ( endpoint: string, clientParams?: Record, - options?: CallAPIOptions + options?: LegacyCallAPIOptions ) => { return await this.clusterClient.callAsInternalUser(endpoint, clientParams, options); }; diff --git a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js index 17610702a0bc7..30e7587707d2e 100644 --- a/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js +++ b/src/legacy/core_plugins/kibana/public/__tests__/vis_type_vega/vega_visualization.js @@ -66,10 +66,9 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../../../plugins/vis_type_vega/public/services'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { setInjectedVarFunc } from '../../../../../../plugins/maps_legacy/public/kibana_services'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ServiceSettings } from '../../../../../../plugins/maps_legacy/public/map/service_settings'; -import { getKibanaMapFactoryProvider } from '../../../../../../plugins/maps_legacy/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { KibanaMap } from '../../../../../../plugins/maps_legacy/public/map/kibana_map'; const THRESHOLD = 0.1; const PIXEL_DIFF = 30; @@ -82,18 +81,7 @@ describe('VegaVisualizations', () => { let vegaVisualizationDependencies; let vegaVisType; - const coreSetupMock = { - notifications: { - toasts: {}, - }, - uiSettings: { - get: () => {}, - }, - injectedMetadata: { - getInjectedVar: () => {}, - }, - }; - setKibanaMapFactory(getKibanaMapFactoryProvider(coreSetupMock)); + setKibanaMapFactory((...args) => new KibanaMap(...args)); setInjectedVars({ emsTileLayerId: {}, enableExternalUrls: true, @@ -139,30 +127,6 @@ describe('VegaVisualizations', () => { beforeEach(ngMock.module('kibana')); beforeEach( ngMock.inject(() => { - setInjectedVarFunc((injectedVar) => { - switch (injectedVar) { - case 'mapConfig': - return { - emsFileApiUrl: '', - emsTileApiUrl: '', - emsLandingPageUrl: '', - }; - case 'tilemapsConfig': - return { - deprecated: { - config: { - options: { - attribution: '123', - }, - }, - }, - }; - case 'version': - return '123'; - default: - return 'not found'; - } - }); const serviceSettings = new ServiceSettings(mockMapConfig, mockMapConfig.tilemap); vegaVisualizationDependencies = { serviceSettings, diff --git a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts index 395cb60587832..63c2cbec21b57 100644 --- a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts +++ b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.test.ts @@ -34,6 +34,7 @@ const createMockKbnServer = () => ({ describe('csp collector', () => { let kbnServer: ReturnType; + const mockCallCluster = null as any; function updateCsp(config: Partial) { kbnServer.newPlatform.setup.core.http.csp = new CspConfig(config); @@ -46,28 +47,28 @@ describe('csp collector', () => { test('fetches whether strict mode is enabled', async () => { const collector = createCspCollector(kbnServer as any); - expect((await collector.fetch()).strict).toEqual(true); + expect((await collector.fetch(mockCallCluster)).strict).toEqual(true); updateCsp({ strict: false }); - expect((await collector.fetch()).strict).toEqual(false); + expect((await collector.fetch(mockCallCluster)).strict).toEqual(false); }); test('fetches whether the legacy browser warning is enabled', async () => { const collector = createCspCollector(kbnServer as any); - expect((await collector.fetch()).warnLegacyBrowsers).toEqual(true); + expect((await collector.fetch(mockCallCluster)).warnLegacyBrowsers).toEqual(true); updateCsp({ warnLegacyBrowsers: false }); - expect((await collector.fetch()).warnLegacyBrowsers).toEqual(false); + expect((await collector.fetch(mockCallCluster)).warnLegacyBrowsers).toEqual(false); }); test('fetches whether the csp rules have been changed or not', async () => { const collector = createCspCollector(kbnServer as any); - expect((await collector.fetch()).rulesChangedFromDefault).toEqual(false); + expect((await collector.fetch(mockCallCluster)).rulesChangedFromDefault).toEqual(false); updateCsp({ rules: ['not', 'default'] }); - expect((await collector.fetch()).rulesChangedFromDefault).toEqual(true); + expect((await collector.fetch(mockCallCluster)).rulesChangedFromDefault).toEqual(true); }); test('does not include raw csp rules under any property names', async () => { @@ -79,7 +80,7 @@ describe('csp collector', () => { // // We use a snapshot here to ensure csp.rules isn't finding its way into the // payload under some new and unexpected variable name (e.g. cspRules). - expect(await collector.fetch()).toMatchInlineSnapshot(` + expect(await collector.fetch(mockCallCluster)).toMatchInlineSnapshot(` Object { "rulesChangedFromDefault": false, "strict": true, diff --git a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts index 6622ed4bef478..9c124a90e66eb 100644 --- a/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts +++ b/src/legacy/core_plugins/kibana/server/lib/csp_usage_collector/csp_collector.ts @@ -19,9 +19,18 @@ import { Server } from 'hapi'; import { CspConfig } from '../../../../../../core/server'; -import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; +import { + UsageCollectionSetup, + CollectorOptions, +} from '../../../../../../plugins/usage_collection/server'; -export function createCspCollector(server: Server) { +interface Usage { + strict: boolean; + warnLegacyBrowsers: boolean; + rulesChangedFromDefault: boolean; +} + +export function createCspCollector(server: Server): CollectorOptions { return { type: 'csp', isReady: () => true, @@ -37,10 +46,22 @@ export function createCspCollector(server: Server) { rulesChangedFromDefault: header !== CspConfig.DEFAULT.header, }; }, + schema: { + strict: { + type: 'boolean', + }, + warnLegacyBrowsers: { + type: 'boolean', + }, + rulesChangedFromDefault: { + type: 'boolean', + }, + }, }; } export function registerCspCollector(usageCollection: UsageCollectionSetup, server: Server): void { - const collector = usageCollection.makeUsageCollector(createCspCollector(server)); + const collectorConfig = createCspCollector(server); + const collector = usageCollection.makeUsageCollector(collectorConfig); usageCollection.registerCollector(collector); } diff --git a/src/plugins/advanced_settings/public/plugin.ts b/src/plugins/advanced_settings/public/plugin.ts index 2784b74ab726c..8b3347f8d88f0 100644 --- a/src/plugins/advanced_settings/public/plugin.ts +++ b/src/plugins/advanced_settings/public/plugin.ts @@ -17,8 +17,8 @@ * under the License. */ import { i18n } from '@kbn/i18n'; -import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import { ManagementApp, ManagementSectionId } from '../../management/public'; +import { CoreSetup, Plugin } from 'kibana/public'; +import { ManagementSectionId } from '../../management/public'; import { ComponentRegistry } from './component_registry'; import { AdvancedSettingsSetup, AdvancedSettingsStart, AdvancedSettingsPluginSetup } from './types'; @@ -30,11 +30,10 @@ const title = i18n.translate('advancedSettings.advancedSettingsLabel', { export class AdvancedSettingsPlugin implements Plugin { - private managementApp?: ManagementApp; public setup(core: CoreSetup, { management }: AdvancedSettingsPluginSetup) { const kibanaSection = management.sections.getSection(ManagementSectionId.Kibana); - this.managementApp = kibanaSection.registerApp({ + kibanaSection.registerApp({ id: 'settings', title, order: 3, @@ -51,11 +50,7 @@ export class AdvancedSettingsPlugin }; } - public start(core: CoreStart) { - if (!core.application.capabilities.management.kibana.settings) { - this.managementApp!.disable(); - } - + public start() { return { component: component.start, }; diff --git a/src/plugins/console/public/plugin.ts b/src/plugins/console/public/plugin.ts index 49a232ce35cd0..851dc7a063d7b 100644 --- a/src/plugins/console/public/plugin.ts +++ b/src/plugins/console/public/plugin.ts @@ -18,17 +18,13 @@ */ import { i18n } from '@kbn/i18n'; - -import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; +import { Plugin, CoreSetup } from 'src/core/public'; import { FeatureCatalogueCategory } from '../../home/public'; - import { AppSetupUIPluginDependencies } from './types'; export class ConsoleUIPlugin implements Plugin { - constructor() {} - - async setup( + public setup( { notifications, getStartServices }: CoreSetup, { devTools, home, usageCollection }: AppSetupUIPluginDependencies ) { @@ -53,16 +49,25 @@ export class ConsoleUIPlugin implements Plugin { + mount: async ({ element }) => { + const [core] = await getStartServices(); + + const { + injectedMetadata, + i18n: { Context: I18nContext }, + docLinks: { DOC_LINK_VERSION }, + } = core; + const { renderApp } = await import('./application'); - const [{ injectedMetadata }] = await getStartServices(); + const elasticsearchUrl = injectedMetadata.getInjectedVar( 'elasticsearchUrl', 'http://localhost:9200' ) as string; + return renderApp({ - docLinkVersion: docLinks.DOC_LINK_VERSION, - I18nContext: i18nDep.Context, + docLinkVersion: DOC_LINK_VERSION, + I18nContext, notifications, elasticsearchUrl, usageCollection, @@ -72,5 +77,5 @@ export class ConsoleUIPlugin implements Plugin(incomingState.type, { + container.addNewEmbeddable(incomingState.type, { savedObjectId: incomingState.id, }); } else if ('input' in incomingState) { - container.addNewEmbeddable( - incomingState.type, - incomingState.input - ); + const input = incomingState.input; + delete input.id; + const explicitInput = { + savedVis: input, + }; + container.addNewEmbeddable(incomingState.type, explicitInput); } } } diff --git a/src/plugins/data/common/field_formats/mocks.ts b/src/plugins/data/common/field_formats/mocks.ts index 394d4c383032f..9bbaefe2d146a 100644 --- a/src/plugins/data/common/field_formats/mocks.ts +++ b/src/plugins/data/common/field_formats/mocks.ts @@ -17,7 +17,8 @@ * under the License. */ -import { IFieldFormatsRegistry } from '.'; +import { identity } from 'lodash'; +import { FieldFormat, IFieldFormatsRegistry } from '.'; export const fieldFormatsMock: IFieldFormatsRegistry = { getByFieldType: jest.fn(), @@ -35,6 +36,9 @@ export const fieldFormatsMock: IFieldFormatsRegistry = { init: jest.fn(), register: jest.fn(), parseDefaultTypeMap: jest.fn(), - deserialize: jest.fn(), + deserialize: jest.fn().mockImplementation(() => { + const DefaultFieldFormat = FieldFormat.from(identity); + return new DefaultFieldFormat(); + }), getTypeWithoutMetaParams: jest.fn(), }; diff --git a/src/plugins/data/common/index.ts b/src/plugins/data/common/index.ts index b40e02b709d30..0fb45fcc739d4 100644 --- a/src/plugins/data/common/index.ts +++ b/src/plugins/data/common/index.ts @@ -26,6 +26,5 @@ export * from './kbn_field_types'; export * from './query'; export * from './search'; export * from './search/aggs'; -export * from './timefilter'; export * from './types'; export * from './utils'; diff --git a/src/plugins/data/common/index_patterns/index.ts b/src/plugins/data/common/index_patterns/index.ts index d26587efccc0f..51a642b775c29 100644 --- a/src/plugins/data/common/index_patterns/index.ts +++ b/src/plugins/data/common/index_patterns/index.ts @@ -19,3 +19,4 @@ export * from './fields'; export * from './types'; +export { IndexPatternsService } from './index_patterns'; diff --git a/src/plugins/data/common/index_patterns/index_patterns/_fields_fetcher.ts b/src/plugins/data/common/index_patterns/index_patterns/_fields_fetcher.ts index 727c4d445688d..baeb1587d57a9 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/_fields_fetcher.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/_fields_fetcher.ts @@ -17,7 +17,8 @@ * under the License. */ -import { GetFieldsOptions, IIndexPatternsApiClient, IndexPattern } from '.'; +import { IndexPattern } from '.'; +import { GetFieldsOptions, IIndexPatternsApiClient } from '../types'; /** @internal */ export const createFieldsFetcher = ( diff --git a/src/plugins/data/common/index_patterns/index_patterns/ensure_default_index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/ensure_default_index_pattern.ts index 2737627bf1977..26f1a185ada3a 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/ensure_default_index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/ensure_default_index_pattern.ts @@ -18,13 +18,13 @@ */ import { contains } from 'lodash'; -import { CoreStart } from 'kibana/public'; import { IndexPatternsContract } from './index_patterns'; +import { UiSettingsCommon } from '../types'; export type EnsureDefaultIndexPattern = () => Promise | undefined; export const createEnsureDefaultIndexPattern = ( - uiSettings: CoreStart['uiSettings'], + uiSettings: UiSettingsCommon, onRedirectNoIndexPattern: () => Promise | void ) => { /** @@ -33,12 +33,12 @@ export const createEnsureDefaultIndexPattern = ( */ return async function ensureDefaultIndexPattern(this: IndexPatternsContract) { const patterns = await this.getIds(); - let defaultId = uiSettings.get('defaultIndex'); + let defaultId = await uiSettings.get('defaultIndex'); let defined = !!defaultId; const exists = contains(patterns, defaultId); if (defined && !exists) { - uiSettings.remove('defaultIndex'); + await uiSettings.remove('defaultIndex'); defaultId = defined = false; } @@ -49,7 +49,7 @@ export const createEnsureDefaultIndexPattern = ( // If there is any index pattern created, set the first as default if (patterns.length >= 1) { defaultId = patterns[0]; - uiSettings.set('defaultIndex', defaultId); + await uiSettings.set('defaultIndex', defaultId); } else { return onRedirectNoIndexPattern(); } diff --git a/src/plugins/data/common/index_patterns/index_patterns/index.ts b/src/plugins/data/common/index_patterns/index_patterns/index.ts index 77527857ed0ca..31cd06b7dd0ea 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index.ts @@ -17,7 +17,6 @@ * under the License. */ -export * from './index_patterns_api_client'; export * from './_pattern_cache'; export * from './flatten_hit'; export * from './format_hit'; diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts index ba8e4f6fb3695..7fb1210fe1f32 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.test.ts @@ -66,7 +66,7 @@ const savedObjectsClient = { create: jest.fn(), get: jest.fn().mockImplementation(() => object), update: jest.fn().mockImplementation(async (type, id, body, { version }) => { - if (object._version !== version) { + if (object.version !== version) { throw new Object({ res: { status: 409, @@ -74,10 +74,10 @@ const savedObjectsClient = { }); } object.attributes.title = body.title; - object._version += 'a'; + object.version += 'a'; return { - id: object._id, - _version: object._version, + id: object.id, + version: object.version, }; }), }; @@ -109,6 +109,7 @@ function create(id: string, payload?: any): Promise { fieldFormats: fieldFormatsMock, onNotification: () => {}, onError: () => {}, + uiSettingsValues: { shortDotsEnable: false, metaFields: [] }, }); setDocsourcePayload(id, payload); @@ -382,8 +383,8 @@ describe('IndexPattern', () => { test('should handle version conflicts', async () => { setDocsourcePayload(null, { - _id: 'foo', - _version: 'foo', + id: 'foo', + version: 'foo', attributes: { title: 'something', }, @@ -397,6 +398,7 @@ describe('IndexPattern', () => { fieldFormats: fieldFormatsMock, onNotification: () => {}, onError: () => {}, + uiSettingsValues: { shortDotsEnable: false, metaFields: [] }, }); await pattern.init(); @@ -411,6 +413,7 @@ describe('IndexPattern', () => { fieldFormats: fieldFormatsMock, onNotification: () => {}, onError: () => {}, + uiSettingsValues: { shortDotsEnable: false, metaFields: [] }, }); await samePattern.init(); diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts index e9ac5a09b9db3..bde550c660a32 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -19,25 +19,23 @@ import _, { each, reject } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { SavedObjectsClientContract } from 'src/core/public'; -import { SavedObjectAttributes } from 'src/core/public'; +import { SavedObjectsClientCommon } from '../..'; import { DuplicateField, SavedObjectNotFound } from '../../../../kibana_utils/common'; -import { - ES_FIELD_TYPES, - KBN_FIELD_TYPES, - IIndexPattern, - IFieldType, - UI_SETTINGS, -} from '../../../common'; +import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, IFieldType } from '../../../common'; import { findByTitle } from '../utils'; import { IndexPatternMissingIndices } from '../lib'; import { Field, IIndexPatternFieldList, getIndexPatternFieldListCreator } from '../fields'; import { createFieldsFetcher } from './_fields_fetcher'; import { formatHitProvider } from './format_hit'; import { flattenHitWrapper } from './flatten_hit'; -import { IIndexPatternsApiClient } from '.'; -import { OnNotification, OnError } from '../types'; +import { + OnNotification, + OnError, + UiSettingsCommon, + IIndexPatternsApiClient, + IndexPatternAttributes, +} from '../types'; import { FieldFormatsStartCommon } from '../../field_formats'; import { PatternCache } from './_pattern_cache'; import { expandShorthand, FieldMappingSpec, MappingObject } from '../../field_mapping'; @@ -45,16 +43,22 @@ import { IndexPatternSpec, TypeMeta, FieldSpec, SourceFilter } from '../types'; import { SerializedFieldFormat } from '../../../../expressions/common'; const MAX_ATTEMPTS_TO_RESOLVE_CONFLICTS = 3; -const type = 'index-pattern'; +const savedObjectType = 'index-pattern'; +interface IUiSettingsValues { + [key: string]: any; + shortDotsEnable: any; + metaFields: any; +} interface IndexPatternDeps { - getConfig: any; - savedObjectsClient: SavedObjectsClientContract; + getConfig: UiSettingsCommon['get']; + savedObjectsClient: SavedObjectsClientCommon; apiClient: IIndexPatternsApiClient; patternCache: PatternCache; fieldFormats: FieldFormatsStartCommon; onNotification: OnNotification; onError: OnError; + uiSettingsValues: IUiSettingsValues; } export class IndexPattern implements IIndexPattern { @@ -72,9 +76,9 @@ export class IndexPattern implements IIndexPattern { public metaFields: string[]; private version: string | undefined; - private savedObjectsClient: SavedObjectsClientContract; + private savedObjectsClient: SavedObjectsClientCommon; private patternCache: PatternCache; - private getConfig: any; + private getConfig: UiSettingsCommon['get']; private sourceFilters?: SourceFilter[]; private originalBody: { [key: string]: any } = {}; public fieldsFetcher: any; // probably want to factor out any direct usage and change to private @@ -83,6 +87,7 @@ export class IndexPattern implements IIndexPattern { private onNotification: OnNotification; private onError: OnError; private apiClient: IIndexPatternsApiClient; + private uiSettingsValues: IUiSettingsValues; private mapping: MappingObject = expandShorthand({ title: ES_FIELD_TYPES.TEXT, @@ -116,6 +121,7 @@ export class IndexPattern implements IIndexPattern { fieldFormats, onNotification, onError, + uiSettingsValues, }: IndexPatternDeps ) { this.id = id; @@ -127,9 +133,10 @@ export class IndexPattern implements IIndexPattern { this.fieldFormats = fieldFormats; this.onNotification = onNotification; this.onError = onError; + this.uiSettingsValues = uiSettingsValues; - this.shortDotsEnable = this.getConfig(UI_SETTINGS.SHORT_DOTS_ENABLE); - this.metaFields = this.getConfig(UI_SETTINGS.META_FIELDS); + this.shortDotsEnable = uiSettingsValues.shortDotsEnable; + this.metaFields = uiSettingsValues.metaFields; this.createFieldList = getIndexPatternFieldListCreator({ fieldFormats, @@ -138,12 +145,8 @@ export class IndexPattern implements IIndexPattern { this.fields = this.createFieldList(this, [], this.shortDotsEnable); this.apiClient = apiClient; - this.fieldsFetcher = createFieldsFetcher( - this, - apiClient, - this.getConfig(UI_SETTINGS.META_FIELDS) - ); - this.flattenHit = flattenHitWrapper(this, this.getConfig(UI_SETTINGS.META_FIELDS)); + this.fieldsFetcher = createFieldsFetcher(this, apiClient, uiSettingsValues.metaFields); + this.flattenHit = flattenHitWrapper(this, uiSettingsValues.metaFields); this.formatHit = formatHitProvider( this, fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.STRING) @@ -160,7 +163,13 @@ export class IndexPattern implements IIndexPattern { private deserializeFieldFormatMap(mapping: any) { const FieldFormat = this.fieldFormats.getType(mapping.id); - return FieldFormat && new FieldFormat(mapping.params, this.getConfig); + return ( + FieldFormat && + new FieldFormat( + mapping.params, + (key: string) => this.uiSettingsValues[key]?.userValue || this.uiSettingsValues[key]?.value + ) + ); } private initFields(input?: any) { @@ -228,7 +237,7 @@ export class IndexPattern implements IIndexPattern { private updateFromElasticSearch(response: any, forceFieldRefresh: boolean = false) { if (!response.found) { - throw new SavedObjectNotFound(type, this.id, 'management/kibana/indexPatterns'); + throw new SavedObjectNotFound(savedObjectType, this.id, 'management/kibana/indexPatterns'); } _.forOwn(this.mapping, (fieldMapping: FieldMappingSpec, name: string | undefined) => { @@ -296,12 +305,22 @@ export class IndexPattern implements IIndexPattern { return this; // no id === no elasticsearch document } - const savedObject = await this.savedObjectsClient.get(type, this.id); + const savedObject = await this.savedObjectsClient.get( + savedObjectType, + this.id + ); const response = { - version: savedObject._version, - found: savedObject._version ? true : false, - ...(_.cloneDeep(savedObject.attributes) as SavedObjectAttributes), + version: savedObject.version, + found: savedObject.version ? true : false, + title: savedObject.attributes.title, + timeFieldName: savedObject.attributes.timeFieldName, + intervalName: savedObject.attributes.intervalName, + fields: savedObject.attributes.fields, + sourceFilters: savedObject.attributes.sourceFilters, + fieldFormatMap: savedObject.attributes.fieldFormatMap, + typeMeta: savedObject.attributes.typeMeta, + type: savedObject.attributes.type, }; // Do this before we attempt to update from ES since that call can potentially perform a save this.originalBody = this.prepBody(); @@ -388,10 +407,10 @@ export class IndexPattern implements IIndexPattern { field.count = count; try { - const res = await this.savedObjectsClient.update(type, this.id, this.prepBody(), { + const res = await this.savedObjectsClient.update(savedObjectType, this.id, this.prepBody(), { version: this.version, }); - this.version = res._version; + this.version = res.version; } catch (e) { // no need for an error message here } @@ -462,13 +481,17 @@ export class IndexPattern implements IIndexPattern { fieldFormats: this.fieldFormats, onNotification: this.onNotification, onError: this.onError, + uiSettingsValues: { + shortDotsEnable: this.shortDotsEnable, + metaFields: this.metaFields, + }, }); await duplicatePattern.destroy(); } const body = this.prepBody(); - const response = await this.savedObjectsClient.create(type, body, { id: this.id }); + const response = await this.savedObjectsClient.create(savedObjectType, body, { id: this.id }); this.id = response.id; return response.id; @@ -496,10 +519,10 @@ export class IndexPattern implements IIndexPattern { (key) => body[key] !== this.originalBody[key] ); return this.savedObjectsClient - .update(type, this.id, body, { version: this.version }) - .then((resp: any) => { + .update(savedObjectType, this.id, body, { version: this.version }) + .then((resp) => { this.id = resp.id; - this.version = resp._version; + this.version = resp.version; }) .catch((err) => { if ( @@ -514,7 +537,12 @@ export class IndexPattern implements IIndexPattern { fieldFormats: this.fieldFormats, onNotification: this.onNotification, onError: this.onError, + uiSettingsValues: { + shortDotsEnable: this.shortDotsEnable, + metaFields: this.metaFields, + }, }); + return samePattern.init().then(() => { // What keys changed from now and what the server returned const updatedBody = samePattern.prepBody(); @@ -610,7 +638,7 @@ export class IndexPattern implements IIndexPattern { destroy() { if (this.id) { this.patternCache.clear(this.id); - return this.savedObjectsClient.delete(type, this.id); + return this.savedObjectsClient.delete(savedObjectType, this.id); } } } diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts index b0ecfc89d376b..2eb9744fc16b3 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts @@ -19,12 +19,14 @@ // eslint-disable-next-line max-classes-per-file import { IndexPatternsService } from './index_patterns'; -import { SavedObjectsClientContract, SavedObjectsFindResponsePublic } from 'kibana/public'; -import { coreMock, httpServiceMock } from '../../../../../core/public/mocks'; import { fieldFormatsMock } from '../../field_formats/mocks'; +import { + UiSettingsCommon, + IIndexPatternsApiClient, + SavedObjectsClientCommon, + SavedObject, +} from '../types'; -const core = coreMock.createStart(); -const http = httpServiceMock.createStartContract(); const fieldFormats = fieldFormatsMock; jest.mock('./index_pattern', () => { @@ -39,33 +41,26 @@ jest.mock('./index_pattern', () => { }; }); -jest.mock('./index_patterns_api_client', () => { - class IndexPatternsApiClient { - getFieldsForWildcard = async () => ({}); - } - - return { - IndexPatternsApiClient, - }; -}); - describe('IndexPatterns', () => { let indexPatterns: IndexPatternsService; - let savedObjectsClient: SavedObjectsClientContract; + let savedObjectsClient: SavedObjectsClientCommon; beforeEach(() => { - savedObjectsClient = {} as SavedObjectsClientContract; + savedObjectsClient = {} as SavedObjectsClientCommon; savedObjectsClient.find = jest.fn( () => - Promise.resolve({ - savedObjects: [{ id: 'id', attributes: { title: 'title' } }], - }) as Promise> + Promise.resolve([{ id: 'id', attributes: { title: 'title' } }]) as Promise< + Array> + > ); indexPatterns = new IndexPatternsService({ - uiSettings: core.uiSettings, - savedObjectsClient, - http, + uiSettings: ({ + get: () => Promise.resolve(false), + getAll: () => {}, + } as any) as UiSettingsCommon, + savedObjectsClient: (savedObjectsClient as unknown) as SavedObjectsClientCommon, + apiClient: {} as IIndexPatternsApiClient, fieldFormats, onNotification: () => {}, onError: () => {}, diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts index 5e51897d13372..ef03ca8fe2d14 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts @@ -17,25 +17,26 @@ * under the License. */ -import { - SavedObjectsClientContract, - SimpleSavedObject, - IUiSettingsClient, - HttpStart, - CoreStart, -} from 'src/core/public'; +import { SavedObjectsClientCommon } from '../..'; import { createIndexPatternCache } from '.'; import { IndexPattern } from './index_pattern'; -import { IndexPatternsApiClient, GetFieldsOptions } from '.'; import { createEnsureDefaultIndexPattern, EnsureDefaultIndexPattern, } from './ensure_default_index_pattern'; import { getIndexPatternFieldListCreator, CreateIndexPatternFieldList, Field } from '../fields'; -import { IndexPatternSpec, FieldSpec } from '../types'; -import { OnNotification, OnError } from '../types'; +import { + OnNotification, + OnError, + UiSettingsCommon, + IIndexPatternsApiClient, + GetFieldsOptions, + FieldSpec, + IndexPatternSpec, +} from '../types'; import { FieldFormatsStartCommon } from '../../field_formats'; +import { UI_SETTINGS, SavedObject } from '../../../common'; const indexPatternCache = createIndexPatternCache(); @@ -46,20 +47,20 @@ export interface IndexPatternSavedObjectAttrs { } interface IndexPatternsServiceDeps { - uiSettings: CoreStart['uiSettings']; - savedObjectsClient: SavedObjectsClientContract; - http: HttpStart; + uiSettings: UiSettingsCommon; + savedObjectsClient: SavedObjectsClientCommon; + apiClient: IIndexPatternsApiClient; fieldFormats: FieldFormatsStartCommon; onNotification: OnNotification; onError: OnError; - onRedirectNoIndexPattern: () => void; + onRedirectNoIndexPattern?: () => void; } export class IndexPatternsService { - private config: IUiSettingsClient; - private savedObjectsClient: SavedObjectsClientContract; - private savedObjectsCache?: Array> | null; - private apiClient: IndexPatternsApiClient; + private config: UiSettingsCommon; + private savedObjectsClient: SavedObjectsClientCommon; + private savedObjectsCache?: Array> | null; + private apiClient: IIndexPatternsApiClient; private fieldFormats: FieldFormatsStartCommon; private onNotification: OnNotification; private onError: OnError; @@ -74,13 +75,13 @@ export class IndexPatternsService { constructor({ uiSettings, savedObjectsClient, - http, + apiClient, fieldFormats, onNotification, onError, - onRedirectNoIndexPattern, + onRedirectNoIndexPattern = () => {}, }: IndexPatternsServiceDeps) { - this.apiClient = new IndexPatternsApiClient(http); + this.apiClient = apiClient; this.config = uiSettings; this.savedObjectsClient = savedObjectsClient; this.fieldFormats = fieldFormats; @@ -103,13 +104,11 @@ export class IndexPatternsService { } private async refreshSavedObjectsCache() { - this.savedObjectsCache = ( - await this.savedObjectsClient.find({ - type: 'index-pattern', - fields: ['title'], - perPage: 10000, - }) - ).savedObjects; + this.savedObjectsCache = await this.savedObjectsClient.find({ + type: 'index-pattern', + fields: ['title'], + perPage: 10000, + }); } getIds = async (refresh: boolean = false) => { @@ -172,7 +171,7 @@ export class IndexPatternsService { }; getDefault = async () => { - const defaultIndexPatternId = this.config.get('defaultIndex'); + const defaultIndexPatternId = await this.config.get('defaultIndex'); if (defaultIndexPatternId) { return await this.get(defaultIndexPatternId); } @@ -191,7 +190,11 @@ export class IndexPatternsService { return indexPatternCache.set(id, indexPattern); }; - specToIndexPattern(spec: IndexPatternSpec) { + async specToIndexPattern(spec: IndexPatternSpec) { + const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE); + const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); + const uiSettingsValues = await this.config.getAll(); + const indexPattern = new IndexPattern(spec.id, { getConfig: (cfg: any) => this.config.get(cfg), savedObjectsClient: this.savedObjectsClient, @@ -200,13 +203,18 @@ export class IndexPatternsService { fieldFormats: this.fieldFormats, onNotification: this.onNotification, onError: this.onError, + uiSettingsValues: { ...uiSettingsValues, shortDotsEnable, metaFields }, }); indexPattern.initFromSpec(spec); return indexPattern; } - make = (id?: string): Promise => { + async make(id?: string): Promise { + const shortDotsEnable = await this.config.get(UI_SETTINGS.SHORT_DOTS_ENABLE); + const metaFields = await this.config.get(UI_SETTINGS.META_FIELDS); + const uiSettingsValues = await this.config.getAll(); + const indexPattern = new IndexPattern(id, { getConfig: (cfg: any) => this.config.get(cfg), savedObjectsClient: this.savedObjectsClient, @@ -215,10 +223,11 @@ export class IndexPatternsService { fieldFormats: this.fieldFormats, onNotification: this.onNotification, onError: this.onError, + uiSettingsValues: { ...uiSettingsValues, shortDotsEnable, metaFields }, }); return indexPattern.init(); - }; + } } export type IndexPatternsContract = PublicMethodsOf; diff --git a/src/plugins/data/common/index_patterns/lib/errors.ts b/src/plugins/data/common/index_patterns/lib/errors.ts index 12efab7a2ca40..59019000f1924 100644 --- a/src/plugins/data/common/index_patterns/lib/errors.ts +++ b/src/plugins/data/common/index_patterns/lib/errors.ts @@ -19,7 +19,7 @@ /* eslint-disable */ -import { KbnError } from '../../../../kibana_utils/public'; +import { KbnError } from '../../../../kibana_utils/common/'; /** * Tried to call a method that relies on SearchSource having an indexPattern assigned diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts index 94121a274d686..4241df5718243 100644 --- a/src/plugins/data/common/index_patterns/types.ts +++ b/src/plugins/data/common/index_patterns/types.ts @@ -18,6 +18,8 @@ */ import { ToastInputFields, ErrorToastOptions } from 'src/core/public/notifications'; +// eslint-disable-next-line +import type { SavedObject } from 'src/core/server'; import { IFieldType } from './fields'; import { SerializedFieldFormat } from '../../../expressions/common'; import { KBN_FIELD_TYPES } from '..'; @@ -49,11 +51,61 @@ export interface IndexPatternAttributes { title: string; typeMeta: string; timeFieldName?: string; + intervalName?: string; + sourceFilters?: string; + fieldFormatMap?: string; } export type OnNotification = (toastInputFields: ToastInputFields) => void; export type OnError = (error: Error, toastInputFields: ErrorToastOptions) => void; +export interface UiSettingsCommon { + get: (key: string) => Promise; + getAll: () => Promise>; + set: (key: string, value: any) => Promise; + remove: (key: string) => Promise; +} + +export interface SavedObjectsClientCommonFindArgs { + type: string | string[]; + fields?: string[]; + perPage?: number; + search?: string; + searchFields?: string[]; +} + +export interface SavedObjectsClientCommon { + find: (options: SavedObjectsClientCommonFindArgs) => Promise>>; + get: (type: string, id: string) => Promise>; + update: ( + type: string, + id: string, + attributes: Record, + options: Record + ) => Promise; + create: ( + type: string, + attributes: Record, + options: Record + ) => Promise; + delete: (type: string, id: string) => Promise<{}>; +} + +export interface GetFieldsOptions { + pattern?: string; + type?: string; + params?: any; + lookBack?: boolean; + metaFields?: string; +} + +export interface IIndexPatternsApiClient { + getFieldsForTimePattern: (options: GetFieldsOptions) => Promise; + getFieldsForWildcard: (options: GetFieldsOptions) => Promise; +} + +export type { SavedObject }; + export type AggregationRestrictions = Record< string, { diff --git a/src/plugins/data/common/index_patterns/utils.ts b/src/plugins/data/common/index_patterns/utils.ts index c3f9af62f8c0e..d9e1cfa0d952a 100644 --- a/src/plugins/data/common/index_patterns/utils.ts +++ b/src/plugins/data/common/index_patterns/utils.ts @@ -18,24 +18,24 @@ */ import { find } from 'lodash'; -import { SavedObjectsClientContract, SimpleSavedObject } from 'src/core/public'; +import { SavedObjectsClientCommon, SavedObject } from '..'; /** * Returns an object matching a given title * - * @param client {SavedObjectsClientContract} + * @param client {SavedObjectsClientCommon} * @param title {string} - * @returns {Promise} + * @returns {Promise} */ export async function findByTitle( - client: SavedObjectsClientContract, + client: SavedObjectsClientCommon, title: string -): Promise | void> { +): Promise | void> { if (!title) { return Promise.resolve(); } - const { savedObjects } = await client.find({ + const savedObjects = await client.find({ type: 'index-pattern', perPage: 10, search: `"${title}"`, @@ -45,6 +45,6 @@ export async function findByTitle( return find( savedObjects, - (obj: SimpleSavedObject) => obj.get('title').toLowerCase() === title.toLowerCase() + (obj: SavedObject) => obj.attributes.title.toLowerCase() === title.toLowerCase() ); } diff --git a/src/plugins/data/common/query/index.ts b/src/plugins/data/common/query/index.ts index 4e90f6f8bb83e..b0dfbbb82355e 100644 --- a/src/plugins/data/common/query/index.ts +++ b/src/plugins/data/common/query/index.ts @@ -18,5 +18,6 @@ */ export * from './filter_manager'; +export * from './timefilter'; export * from './types'; export * from './is_query'; diff --git a/src/plugins/data/public/query/timefilter/get_time.test.ts b/src/plugins/data/common/query/timefilter/get_time.test.ts similarity index 100% rename from src/plugins/data/public/query/timefilter/get_time.test.ts rename to src/plugins/data/common/query/timefilter/get_time.test.ts diff --git a/src/plugins/data/public/query/timefilter/get_time.ts b/src/plugins/data/common/query/timefilter/get_time.ts similarity index 90% rename from src/plugins/data/public/query/timefilter/get_time.ts rename to src/plugins/data/common/query/timefilter/get_time.ts index 3706972ce4a2e..6e4eda95accc7 100644 --- a/src/plugins/data/public/query/timefilter/get_time.ts +++ b/src/plugins/data/common/query/timefilter/get_time.ts @@ -18,14 +18,16 @@ */ import dateMath from '@elastic/datemath'; -import { IIndexPattern } from '../..'; -import { TimeRange, buildRangeFilter } from '../../../common'; +import { buildRangeFilter, IIndexPattern, TimeRange, TimeRangeBounds } from '../..'; interface CalculateBoundsOptions { forceNow?: Date; } -export function calculateBounds(timeRange: TimeRange, options: CalculateBoundsOptions = {}) { +export function calculateBounds( + timeRange: TimeRange, + options: CalculateBoundsOptions = {} +): TimeRangeBounds { return { min: dateMath.parse(timeRange.from, { forceNow: options.forceNow }), max: dateMath.parse(timeRange.to, { roundUp: true, forceNow: options.forceNow }), diff --git a/test/plugin_functional/plugins/core_logging/server/index.ts b/src/plugins/data/common/query/timefilter/index.ts similarity index 78% rename from test/plugin_functional/plugins/core_logging/server/index.ts rename to src/plugins/data/common/query/timefilter/index.ts index ca1d9da95b495..55739511a0ef5 100644 --- a/test/plugin_functional/plugins/core_logging/server/index.ts +++ b/src/plugins/data/common/query/timefilter/index.ts @@ -17,7 +17,5 @@ * under the License. */ -import type { PluginInitializerContext } from '../../../../../src/core/server'; -import { CoreLoggingPlugin } from './plugin'; - -export const plugin = (init: PluginInitializerContext) => new CoreLoggingPlugin(init); +export * from './get_time'; +export * from './is_time_range'; diff --git a/src/plugins/data/common/timefilter/is_time_range.ts b/src/plugins/data/common/query/timefilter/is_time_range.ts similarity index 100% rename from src/plugins/data/common/timefilter/is_time_range.ts rename to src/plugins/data/common/query/timefilter/is_time_range.ts diff --git a/src/plugins/data/common/timefilter/types.ts b/src/plugins/data/common/query/timefilter/types.ts similarity index 88% rename from src/plugins/data/common/timefilter/types.ts rename to src/plugins/data/common/query/timefilter/types.ts index b197b16e67dd1..60008ce6054e1 100644 --- a/src/plugins/data/common/timefilter/types.ts +++ b/src/plugins/data/common/query/timefilter/types.ts @@ -17,6 +17,8 @@ * under the License. */ +import { Moment } from 'moment'; + export interface RefreshInterval { pause: boolean; value: number; @@ -27,3 +29,8 @@ export interface TimeRange { to: string; mode?: 'absolute' | 'relative'; } + +export interface TimeRangeBounds { + min: Moment | undefined; + max: Moment | undefined; +} diff --git a/src/plugins/data/common/query/types.ts b/src/plugins/data/common/query/types.ts index 61b5d5b2b7b4a..6b34a1baf293b 100644 --- a/src/plugins/data/common/query/types.ts +++ b/src/plugins/data/common/query/types.ts @@ -17,6 +17,8 @@ * under the License. */ +export * from './timefilter/types'; + export interface Query { query: string | { [key: string]: any }; language: string; diff --git a/src/plugins/data/common/types.ts b/src/plugins/data/common/types.ts index 93629c3dbaf62..e2ec1a031b0ca 100644 --- a/src/plugins/data/common/types.ts +++ b/src/plugins/data/common/types.ts @@ -17,7 +17,6 @@ * under the License. */ -export * from './timefilter/types'; export * from './query/types'; export * from './kbn_field_types/types'; export * from './index_patterns/types'; diff --git a/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts index 409614ca9c380..a0eb49d773f3d 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts @@ -22,9 +22,9 @@ import moment from 'moment'; import { esFilters, IFieldType, RangeFilterParams } from '../../../public'; import { getIndexPatterns } from '../../../public/services'; import { deserializeAggConfig } from '../../search/expressions/utils'; -import { RangeSelectTriggerContext } from '../../../../embeddable/public'; +import { RangeSelectContext } from '../../../../embeddable/public'; -export async function createFiltersFromRangeSelectAction(event: RangeSelectTriggerContext['data']) { +export async function createFiltersFromRangeSelectAction(event: RangeSelectContext['data']) { const column: Record = event.table.columns[event.column]; if (!column || !column.meta) { diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts index a0e285c20d776..3e38477a908b8 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts @@ -27,7 +27,7 @@ import { dataPluginMock } from '../../../public/mocks'; import { setIndexPatterns } from '../../../public/services'; import { mockDataServices } from '../../../public/search/aggs/test_helpers'; import { createFiltersFromValueClickAction } from './create_filters_from_value_click'; -import { ValueClickTriggerContext } from '../../../../embeddable/public'; +import { ValueClickContext } from '../../../../embeddable/public'; const mockField = { name: 'bytes', @@ -39,7 +39,7 @@ const mockField = { }; describe('createFiltersFromValueClick', () => { - let dataPoints: ValueClickTriggerContext['data']['data']; + let dataPoints: ValueClickContext['data']['data']; beforeEach(() => { dataPoints = [ diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts index 2fdd746535519..1974b9f776748 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts @@ -21,7 +21,7 @@ import { KibanaDatatable } from '../../../../../plugins/expressions/public'; import { deserializeAggConfig } from '../../search/expressions'; import { esFilters, Filter } from '../../../public'; import { getIndexPatterns } from '../../../public/services'; -import { ValueClickTriggerContext } from '../../../../embeddable/public'; +import { ValueClickContext } from '../../../../embeddable/public'; /** * For terms aggregations on `__other__` buckets, this assembles a list of applicable filter @@ -114,7 +114,7 @@ const createFilter = async ( export const createFiltersFromValueClickAction = async ({ data, negate, -}: ValueClickTriggerContext['data']) => { +}: ValueClickContext['data']) => { const filters: Filter[] = []; await Promise.all( diff --git a/src/plugins/data/public/actions/select_range_action.ts b/src/plugins/data/public/actions/select_range_action.ts index 18853f7e292f6..49766143b5588 100644 --- a/src/plugins/data/public/actions/select_range_action.ts +++ b/src/plugins/data/public/actions/select_range_action.ts @@ -24,12 +24,12 @@ import { ActionByType, } from '../../../../plugins/ui_actions/public'; import { createFiltersFromRangeSelectAction } from './filters/create_filters_from_range_select'; -import { RangeSelectTriggerContext } from '../../../embeddable/public'; +import { RangeSelectContext } from '../../../embeddable/public'; import { FilterManager, TimefilterContract, esFilters } from '..'; export const ACTION_SELECT_RANGE = 'ACTION_SELECT_RANGE'; -export type SelectRangeActionContext = RangeSelectTriggerContext; +export type SelectRangeActionContext = RangeSelectContext; async function isCompatible(context: SelectRangeActionContext) { try { diff --git a/src/plugins/data/public/actions/value_click_action.ts b/src/plugins/data/public/actions/value_click_action.ts index 5d4f1f5f1d6db..dd74a7ee507f3 100644 --- a/src/plugins/data/public/actions/value_click_action.ts +++ b/src/plugins/data/public/actions/value_click_action.ts @@ -27,12 +27,12 @@ import { import { getOverlays, getIndexPatterns } from '../services'; import { applyFiltersPopover } from '../ui/apply_filters'; import { createFiltersFromValueClickAction } from './filters/create_filters_from_value_click'; -import { ValueClickTriggerContext } from '../../../embeddable/public'; +import { ValueClickContext } from '../../../embeddable/public'; import { Filter, FilterManager, TimefilterContract, esFilters } from '..'; export const ACTION_VALUE_CLICK = 'ACTION_VALUE_CLICK'; -export type ValueClickActionContext = ValueClickTriggerContext; +export type ValueClickActionContext = ValueClickContext; async function isCompatible(context: ValueClickActionContext) { try { diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index efce8d2c021c9..89b0d7e0303b9 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -421,7 +421,6 @@ export { connectToQueryState, syncQueryStateWithUrl, QueryState, - getTime, getQueryLog, getDefaultQuery, FilterManager, @@ -435,6 +434,7 @@ export { } from './query'; export { + getTime, // kbn field types castEsToKbnFieldTypeName, getKbnTypeNames, diff --git a/src/plugins/data/common/timefilter/index.ts b/src/plugins/data/public/index_patterns/expressions/index.ts similarity index 94% rename from src/plugins/data/common/timefilter/index.ts rename to src/plugins/data/public/index_patterns/expressions/index.ts index e0c509e119fda..fa37e3b216ac9 100644 --- a/src/plugins/data/common/timefilter/index.ts +++ b/src/plugins/data/public/index_patterns/expressions/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { isTimeRange } from './is_time_range'; +export * from './load_index_pattern'; diff --git a/src/plugins/data/public/index_patterns/expressions/load_index_pattern.test.ts b/src/plugins/data/public/index_patterns/expressions/load_index_pattern.test.ts new file mode 100644 index 0000000000000..378ceb376f5f1 --- /dev/null +++ b/src/plugins/data/public/index_patterns/expressions/load_index_pattern.test.ts @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { indexPatternLoad } from './load_index_pattern'; + +jest.mock('../../services', () => ({ + getIndexPatterns: () => ({ + get: (id: string) => ({ + toSpec: () => ({ + title: 'value', + }), + }), + }), +})); + +describe('indexPattern expression function', () => { + test('returns serialized index pattern', async () => { + const indexPatternDefinition = indexPatternLoad(); + const result = await indexPatternDefinition.fn(null, { id: '1' }, {} as any); + expect(result.type).toEqual('index_pattern'); + expect(result.value.title).toEqual('value'); + }); +}); diff --git a/src/plugins/data/public/index_patterns/expressions/load_index_pattern.ts b/src/plugins/data/public/index_patterns/expressions/load_index_pattern.ts new file mode 100644 index 0000000000000..901d6aac7fbff --- /dev/null +++ b/src/plugins/data/public/index_patterns/expressions/load_index_pattern.ts @@ -0,0 +1,62 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { ExpressionFunctionDefinition } from '../../../../../plugins/expressions/public'; +import { getIndexPatterns } from '../../services'; +import { IndexPatternSpec } from '../../../common/index_patterns'; + +const name = 'indexPatternLoad'; + +type Input = null; +type Output = Promise<{ type: 'index_pattern'; value: IndexPatternSpec }>; + +interface Arguments { + id: string; +} + +export const indexPatternLoad = (): ExpressionFunctionDefinition< + typeof name, + Input, + Arguments, + Output +> => ({ + name, + type: 'index_pattern', + inputTypes: ['null'], + help: i18n.translate('data.functions.indexPatternLoad.help', { + defaultMessage: 'Loads an index pattern', + }), + args: { + id: { + types: ['string'], + required: true, + help: i18n.translate('data.functions.indexPatternLoad.id.help', { + defaultMessage: 'index pattern id to load', + }), + }, + }, + async fn(input, args) { + const indexPatterns = getIndexPatterns(); + + const indexPattern = await indexPatterns.get(args.id); + + return { type: 'index_pattern', value: indexPattern.toSpec() }; + }, +}); diff --git a/src/plugins/data/public/index_patterns/index.ts b/src/plugins/data/public/index_patterns/index.ts index 2c540527f468d..a6ee71c624f5a 100644 --- a/src/plugins/data/public/index_patterns/index.ts +++ b/src/plugins/data/public/index_patterns/index.ts @@ -34,4 +34,11 @@ export { IIndexPatternFieldList, } from '../../common/index_patterns'; -export { IndexPatternsService, IndexPatternsContract, IndexPattern } from './index_patterns'; +export { + IndexPatternsService, + IndexPatternsContract, + IndexPattern, + IndexPatternsApiClient, +} from './index_patterns'; +export { UiSettingsPublicToCommon } from './ui_settings_wrapper'; +export { SavedObjectsClientPublicToCommon } from './saved_objects_client_wrapper'; diff --git a/src/plugins/data/public/index_patterns/index_patterns/index.ts b/src/plugins/data/public/index_patterns/index_patterns/index.ts index 0db1c8c68b4ac..f63b48f877771 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index.ts @@ -19,3 +19,4 @@ export * from '../../../common/index_patterns/index_patterns'; export * from './redirect_no_index_pattern'; +export * from './index_patterns_api_client'; diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts similarity index 100% rename from src/plugins/data/common/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.mock.ts diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns_api_client.test.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts similarity index 100% rename from src/plugins/data/common/index_patterns/index_patterns/index_patterns_api_client.test.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.test.ts diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns_api_client.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts similarity index 87% rename from src/plugins/data/common/index_patterns/index_patterns/index_patterns_api_client.ts rename to src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts index cd189ccf0135b..377a3f7f91a50 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns_api_client.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts @@ -18,21 +18,12 @@ */ import { HttpSetup } from 'src/core/public'; -import { IndexPatternMissingIndices } from '../lib'; +import { IndexPatternMissingIndices } from '../../../common/index_patterns/lib'; +import { GetFieldsOptions, IIndexPatternsApiClient } from '../../../common/index_patterns/types'; const API_BASE_URL: string = `/api/index_patterns/`; -export interface GetFieldsOptions { - pattern?: string; - type?: string; - params?: any; - lookBack?: boolean; - metaFields?: string; -} - -export type IIndexPatternsApiClient = PublicMethodsOf; - -export class IndexPatternsApiClient { +export class IndexPatternsApiClient implements IIndexPatternsApiClient { private http: HttpSetup; constructor(http: HttpSetup) { @@ -53,7 +44,7 @@ export class IndexPatternsApiClient { }); } - _getUrl(path: string[]) { + private _getUrl(path: string[]) { return API_BASE_URL + path.filter(Boolean).map(encodeURIComponent).join('/'); } diff --git a/src/plugins/data/public/index_patterns/index_patterns/redirect_no_index_pattern.tsx b/src/plugins/data/public/index_patterns/index_patterns/redirect_no_index_pattern.tsx index e32a8e023cf40..7ed6525db6350 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/redirect_no_index_pattern.tsx +++ b/src/plugins/data/public/index_patterns/index_patterns/redirect_no_index_pattern.tsx @@ -30,7 +30,7 @@ export const onRedirectNoIndexPattern = ( navigateToApp: CoreStart['application']['navigateToApp'], overlays: CoreStart['overlays'] ) => () => { - const canManageIndexPatterns = capabilities.management.kibana.index_patterns; + const canManageIndexPatterns = capabilities.management.kibana.indexPatterns; const redirectTarget = canManageIndexPatterns ? '/management/kibana/indexPatterns' : '/home'; let timeoutId: NodeJS.Timeout | undefined; diff --git a/src/plugins/data/public/index_patterns/saved_objects_client_wrapper.ts b/src/plugins/data/public/index_patterns/saved_objects_client_wrapper.ts new file mode 100644 index 0000000000000..8f1d02c5ffd54 --- /dev/null +++ b/src/plugins/data/public/index_patterns/saved_objects_client_wrapper.ts @@ -0,0 +1,67 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { omit } from 'lodash'; +import { SavedObjectsClient, SimpleSavedObject } from 'src/core/public'; +import { + SavedObjectsClientCommon, + SavedObjectsClientCommonFindArgs, + SavedObject, +} from '../../common/index_patterns'; + +type SOClient = Pick; + +const simpleSavedObjectToSavedObject = ( + simpleSavedObject: SimpleSavedObject +): SavedObject => ({ + version: simpleSavedObject._version, + ...omit(simpleSavedObject, '_version'), +}); + +export class SavedObjectsClientPublicToCommon implements SavedObjectsClientCommon { + private savedObjectClient: SOClient; + constructor(savedObjectClient: SOClient) { + this.savedObjectClient = savedObjectClient; + } + async find(options: SavedObjectsClientCommonFindArgs) { + const response = (await this.savedObjectClient.find(options)).savedObjects; + return response.map>(simpleSavedObjectToSavedObject); + } + + async get(type: string, id: string) { + const response = await this.savedObjectClient.get(type, id); + return simpleSavedObjectToSavedObject(response); + } + async update( + type: string, + id: string, + attributes: Record, + options: Record + ) { + const response = await this.savedObjectClient.update(type, id, attributes, options); + return simpleSavedObjectToSavedObject(response); + } + async create(type: string, attributes: Record, options: Record) { + const response = await this.savedObjectClient.create(type, attributes, options); + return simpleSavedObjectToSavedObject(response); + } + delete(type: string, id: string) { + return this.savedObjectClient.delete(type, id); + } +} diff --git a/src/plugins/data/public/index_patterns/ui_settings_wrapper.ts b/src/plugins/data/public/index_patterns/ui_settings_wrapper.ts new file mode 100644 index 0000000000000..17fc88ddd674d --- /dev/null +++ b/src/plugins/data/public/index_patterns/ui_settings_wrapper.ts @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IUiSettingsClient } from 'src/core/public'; +import { UiSettingsCommon } from '../../common/index_patterns'; + +export class UiSettingsPublicToCommon implements UiSettingsCommon { + private uiSettings: IUiSettingsClient; + constructor(uiSettings: IUiSettingsClient) { + this.uiSettings = uiSettings; + } + get(key: string) { + return Promise.resolve(this.uiSettings.get(key)); + } + + getAll() { + return Promise.resolve(this.uiSettings.getAll()); + } + + set(key: string, value: any) { + this.uiSettings.set(key, value); + return Promise.resolve(); + } + + remove(key: string) { + this.uiSettings.remove(key); + return Promise.resolve(); + } +} diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 493733daf261b..ec71794fde87d 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -40,7 +40,12 @@ import { SearchService } from './search/search_service'; import { FieldFormatsService } from './field_formats'; import { QueryService } from './query'; import { createIndexPatternSelect } from './ui/index_pattern_select'; -import { IndexPatternsService, onRedirectNoIndexPattern } from './index_patterns'; +import { + IndexPatternsService, + onRedirectNoIndexPattern, + IndexPatternsApiClient, + UiSettingsPublicToCommon, +} from './index_patterns'; import { setFieldFormats, setHttp, @@ -76,6 +81,8 @@ import { ACTION_VALUE_CLICK, ValueClickActionContext, } from './actions/value_click_action'; +import { SavedObjectsClientPublicToCommon } from './index_patterns'; +import { indexPatternLoad } from './index_patterns/expressions/load_index_pattern'; declare module '../../ui_actions/public' { export interface ActionContextMapping { @@ -120,6 +127,7 @@ export class DataPublicPlugin implements Plugin { notifications.toasts.add(toastInputFields); @@ -184,10 +191,7 @@ export class DataPublicPlugin implements Plugin({ timefilter: { timefil // Warning: (ae-missing-release-tag) "createSavedQueryService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const createSavedQueryService: (savedObjectsClient: SavedObjectsClientContract) => SavedQueryService; +export const createSavedQueryService: (savedObjectsClient: SavedObjectsClientContract_3) => SavedQueryService; // Warning: (ae-missing-release-tag) "CustomFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -981,7 +980,7 @@ export type IMetricAggType = MetricAggType; // @public (undocumented) export class IndexPattern implements IIndexPattern { // Warning: (ae-forgotten-export) The symbol "IndexPatternDeps" needs to be exported by the entry point index.d.ts - constructor(id: string | undefined, { getConfig, savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, }: IndexPatternDeps); + constructor(id: string | undefined, { getConfig, savedObjectsClient, apiClient, patternCache, fieldFormats, onNotification, onError, uiSettingsValues, }: IndexPatternDeps); // (undocumented) [key: string]: any; // (undocumented) @@ -1096,9 +1095,15 @@ export type IndexPatternAggRestrictions = Record>; +export const QueryStringInput: React.FC>; // @public (undocumented) export type QuerySuggestion = QuerySuggestionBasic | QuerySuggestionField; @@ -1743,8 +1748,8 @@ export const search: { // Warning: (ae-missing-release-tag) "SearchBar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "timeHistory" | "onFiltersUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "onRefresh" | "onRefreshChange" | "refreshInterval" | "indexPatterns" | "dataTestSubj" | "customSubmitButton" | "screenTitle" | "showQueryBar" | "showQueryInput" | "showFilterBar" | "showDatePicker" | "showAutoRefreshOnly" | "isRefreshPaused" | "dateRangeFrom" | "dateRangeTo" | "showSaveQuery" | "savedQuery" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated" | "onClearSavedQuery" | "indicateNoData" | "timeHistory" | "onFiltersUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; }; // Warning: (ae-forgotten-export) The symbol "SearchBarOwnProps" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/query/timefilter/index.ts b/src/plugins/data/public/query/timefilter/index.ts index a5885a59f60ed..f71061677ceb7 100644 --- a/src/plugins/data/public/query/timefilter/index.ts +++ b/src/plugins/data/public/query/timefilter/index.ts @@ -22,6 +22,5 @@ export { TimefilterService, TimefilterSetup } from './timefilter_service'; export * from './types'; export { Timefilter, TimefilterContract } from './timefilter'; export { TimeHistory, TimeHistoryContract } from './time_history'; -export { getTime, calculateBounds } from './get_time'; export { changeTimeFilter, convertRangeFilterToTimeRangeString } from './lib/change_time_filter'; export { extractTimeFilter } from './lib/extract_time_filter'; diff --git a/src/plugins/kibana_utils/common/default_feedback_message.test.ts b/src/plugins/data/public/query/timefilter/lib/get_force_now.ts similarity index 67% rename from src/plugins/kibana_utils/common/default_feedback_message.test.ts rename to src/plugins/data/public/query/timefilter/lib/get_force_now.ts index 5c1afa4634b71..fe68656f0c3aa 100644 --- a/src/plugins/kibana_utils/common/default_feedback_message.test.ts +++ b/src/plugins/data/public/query/timefilter/lib/get_force_now.ts @@ -17,10 +17,18 @@ * under the License. */ -import { defaultFeedbackMessage } from './default_feedback_message'; +import { parseQueryString } from './parse_querystring'; -test('default feedback message with link', () => { - expect(defaultFeedbackMessage).toMatchInlineSnapshot( - `"Have feedback? Please create an issue in GitHub."` - ); -}); +/** @internal */ +export function getForceNow() { + const forceNow = parseQueryString().forceNow as string; + if (!forceNow) { + return; + } + + const ticks = Date.parse(forceNow); + if (isNaN(ticks)) { + throw new Error(`forceNow query parameter, ${forceNow}, can't be parsed by Date.parse`); + } + return new Date(ticks); +} diff --git a/src/plugins/data/public/query/timefilter/lib/parse_querystring.ts b/src/plugins/data/public/query/timefilter/lib/parse_querystring.ts index 2220ad4eef1b7..5982bfd0bd276 100644 --- a/src/plugins/data/public/query/timefilter/lib/parse_querystring.ts +++ b/src/plugins/data/public/query/timefilter/lib/parse_querystring.ts @@ -18,6 +18,7 @@ */ import { parse } from 'query-string'; +/** @internal */ export function parseQueryString() { // window.location.search is an empty string // get search from href diff --git a/src/plugins/data/public/query/timefilter/timefilter.ts b/src/plugins/data/public/query/timefilter/timefilter.ts index 86ef69be572a9..5eb1546fa015f 100644 --- a/src/plugins/data/public/query/timefilter/timefilter.ts +++ b/src/plugins/data/public/query/timefilter/timefilter.ts @@ -21,10 +21,9 @@ import _ from 'lodash'; import { Subject, BehaviorSubject } from 'rxjs'; import moment from 'moment'; import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals'; -import { parseQueryString } from './lib/parse_querystring'; -import { calculateBounds, getTime } from './get_time'; +import { getForceNow } from './lib/get_force_now'; import { TimefilterConfig, InputTimeRange, TimeRangeBounds } from './types'; -import { RefreshInterval, TimeRange } from '../../../common'; +import { calculateBounds, getTime, RefreshInterval, TimeRange } from '../../../common'; import { TimeHistoryContract } from './time_history'; import { IndexPattern } from '../../index_patterns'; @@ -224,16 +223,7 @@ export class Timefilter { } private getForceNow = () => { - const forceNow = parseQueryString().forceNow as string; - if (!forceNow) { - return; - } - - const ticks = Date.parse(forceNow); - if (isNaN(ticks)) { - throw new Error(`forceNow query parameter, ${forceNow}, can't be parsed by Date.parse`); - } - return new Date(ticks); + return getForceNow(); }; } diff --git a/src/plugins/data/public/query/timefilter/types.ts b/src/plugins/data/public/query/timefilter/types.ts index 8b8deea43f808..d47a9cbb3bd60 100644 --- a/src/plugins/data/public/query/timefilter/types.ts +++ b/src/plugins/data/public/query/timefilter/types.ts @@ -16,7 +16,9 @@ * specific language governing permissions and limitations * under the License. */ + import { Moment } from 'moment'; + import { TimeRange, RefreshInterval } from '../../../common'; export interface TimefilterConfig { @@ -32,7 +34,4 @@ export type InputTimeRange = to: Moment; }; -export interface TimeRangeBounds { - min: Moment | undefined; - max: Moment | undefined; -} +export { TimeRangeBounds } from '../../../common'; diff --git a/src/plugins/data/public/search/aggs/agg_config.test.ts b/src/plugins/data/public/search/aggs/agg_config.test.ts index 95e0b2cd27186..18a86d767c2de 100644 --- a/src/plugins/data/public/search/aggs/agg_config.test.ts +++ b/src/plugins/data/public/search/aggs/agg_config.test.ts @@ -25,31 +25,23 @@ import { AggType } from './agg_type'; import { AggTypesRegistryStart } from './agg_types_registry'; import { mockDataServices, mockAggTypesRegistry } from './test_helpers'; import { MetricAggType } from './metrics/metric_agg_type'; -import { - Field as IndexPatternField, - IndexPattern, - IIndexPatternFieldList, -} from '../../index_patterns'; +import { IndexPattern, IIndexPatternFieldList } from '../../index_patterns'; import { stubIndexPatternWithFields } from '../../../public/stubs'; -import { FieldFormatsStart } from '../../field_formats'; -import { fieldFormatsServiceMock } from '../../field_formats/mocks'; describe('AggConfig', () => { let indexPattern: IndexPattern; let typesRegistry: AggTypesRegistryStart; - let fieldFormats: FieldFormatsStart; beforeEach(() => { jest.restoreAllMocks(); mockDataServices(); - fieldFormats = fieldFormatsServiceMock.createStartContract(); indexPattern = stubIndexPatternWithFields as IndexPattern; typesRegistry = mockAggTypesRegistry(); }); describe('#toDsl', () => { it('calls #write()', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); const configStates = { enabled: true, type: 'date_histogram', @@ -64,7 +56,7 @@ describe('AggConfig', () => { }); it('uses the type name as the agg name', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); const configStates = { enabled: true, type: 'date_histogram', @@ -79,7 +71,7 @@ describe('AggConfig', () => { }); it('uses the params from #write() output as the agg params', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); const configStates = { enabled: true, type: 'date_histogram', @@ -109,7 +101,7 @@ describe('AggConfig', () => { params: {}, }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); const histoConfig = ac.byName('date_histogram')[0]; const avgConfig = ac.byName('avg')[0]; @@ -219,8 +211,8 @@ describe('AggConfig', () => { testsIdentical.forEach((configState, index) => { it(`identical aggregations (${index})`, () => { - const ac1 = new AggConfigs(indexPattern, configState, { typesRegistry, fieldFormats }); - const ac2 = new AggConfigs(indexPattern, configState, { typesRegistry, fieldFormats }); + const ac1 = new AggConfigs(indexPattern, configState, { typesRegistry }); + const ac2 = new AggConfigs(indexPattern, configState, { typesRegistry }); expect(ac1.jsonDataEquals(ac2.aggs)).toBe(true); }); }); @@ -260,8 +252,8 @@ describe('AggConfig', () => { testsIdenticalDifferentOrder.forEach((test, index) => { it(`identical aggregations (${index}) - init json is in different order`, () => { - const ac1 = new AggConfigs(indexPattern, test.config1, { typesRegistry, fieldFormats }); - const ac2 = new AggConfigs(indexPattern, test.config2, { typesRegistry, fieldFormats }); + const ac1 = new AggConfigs(indexPattern, test.config1, { typesRegistry }); + const ac2 = new AggConfigs(indexPattern, test.config2, { typesRegistry }); expect(ac1.jsonDataEquals(ac2.aggs)).toBe(true); }); }); @@ -325,8 +317,8 @@ describe('AggConfig', () => { testsDifferent.forEach((test, index) => { it(`different aggregations (${index})`, () => { - const ac1 = new AggConfigs(indexPattern, test.config1, { typesRegistry, fieldFormats }); - const ac2 = new AggConfigs(indexPattern, test.config2, { typesRegistry, fieldFormats }); + const ac1 = new AggConfigs(indexPattern, test.config1, { typesRegistry }); + const ac2 = new AggConfigs(indexPattern, test.config2, { typesRegistry }); expect(ac1.jsonDataEquals(ac2.aggs)).toBe(false); }); }); @@ -334,7 +326,7 @@ describe('AggConfig', () => { describe('#serialize', () => { it('includes the aggs id, params, type and schema', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); const configStates = { enabled: true, type: 'date_histogram', @@ -365,8 +357,8 @@ describe('AggConfig', () => { params: {}, }, ]; - const ac1 = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); - const ac2 = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac1 = new AggConfigs(indexPattern, configStates, { typesRegistry }); + const ac2 = new AggConfigs(indexPattern, configStates, { typesRegistry }); // this relies on the assumption that js-engines consistently loop over properties in insertion order. // most likely the case, but strictly speaking not guaranteed by the JS and JSON specifications. @@ -394,7 +386,7 @@ describe('AggConfig', () => { params: { field: 'machine.os.keyword' }, }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); expect(ac.aggs.map((agg) => agg.toSerializedFieldFormat())).toMatchInlineSnapshot(` Array [ @@ -456,7 +448,7 @@ describe('AggConfig', () => { }, }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); expect(ac.aggs.map((agg) => agg.toSerializedFieldFormat())).toMatchInlineSnapshot(` Array [ @@ -478,20 +470,8 @@ describe('AggConfig', () => { }); describe('#toExpressionAst', () => { - beforeEach(() => { - fieldFormats.getDefaultInstance = (() => ({ - getConverterFor: (t?: string) => t || identity, - })) as any; - indexPattern.fields.getByName = (name) => - ({ - format: { - getConverterFor: (t?: string) => t || identity, - }, - } as IndexPatternField); - }); - it('works with primitive param types', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); const configStates = { enabled: true, type: 'terms', @@ -540,7 +520,7 @@ describe('AggConfig', () => { }); it('creates a subexpression for params of type "agg"', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); const configStates = { type: 'terms', params: { @@ -616,7 +596,7 @@ describe('AggConfig', () => { }, }); - const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); const configStates = { type: 'range', params: { @@ -647,7 +627,7 @@ describe('AggConfig', () => { }); it('stringifies any other params which are an object', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); const configStates = { type: 'terms', params: { @@ -662,7 +642,7 @@ describe('AggConfig', () => { }); it(`returns undefined if an expressionName doesn't exist on the agg type`, () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); const configStates = { type: 'unknown type', params: {}, @@ -676,7 +656,7 @@ describe('AggConfig', () => { let aggConfig: AggConfig; beforeEach(() => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, [], { typesRegistry }); aggConfig = ac.createAggConfig({ type: 'count' } as CreateAggConfigParams); }); @@ -702,85 +682,4 @@ describe('AggConfig', () => { expect(label).toBe(''); }); }); - - describe('#fieldFormatter - custom getFormat handler', () => { - it('returns formatter from getFormat handler', () => { - const ac = new AggConfigs(indexPattern, [], { typesRegistry, fieldFormats }); - const configStates = { - enabled: true, - type: 'count', - schema: 'metric', - params: { field: '@timestamp' }, - }; - const aggConfig = ac.createAggConfig(configStates); - - const fieldFormatter = aggConfig.fieldFormatter(); - expect(fieldFormatter).toBeDefined(); - expect(fieldFormatter('text')).toBe('text'); - }); - }); - - // TODO: Converting these field formatter tests from browser tests to unit - // tests makes them much less helpful due to the extensive use of mocking. - // We should revisit these and rewrite them into something more useful. - describe('#fieldFormatter - no custom getFormat handler', () => { - let aggConfig: AggConfig; - - beforeEach(() => { - fieldFormats.getDefaultInstance = (() => ({ - getConverterFor: (t?: string) => t || identity, - })) as any; - indexPattern.fields.getByName = (name) => - ({ - format: { - getConverterFor: (t?: string) => t || identity, - }, - } as IndexPatternField); - - const configStates = { - enabled: true, - type: 'histogram', - schema: 'bucket', - params: { - field: 'bytes', - }, - }; - const ac = new AggConfigs(indexPattern, [configStates], { typesRegistry, fieldFormats }); - aggConfig = ac.createAggConfig(configStates); - }); - - it("returns the field's formatter", () => { - aggConfig.params.field = { - format: { - getConverterFor: (t?: string) => t || identity, - }, - }; - expect(aggConfig.fieldFormatter().toString()).toBe( - aggConfig.getField().format.getConverterFor().toString() - ); - }); - - it('returns the string format if the field does not have a format', () => { - const agg = aggConfig; - agg.params.field = { type: 'number', format: null }; - const fieldFormatter = agg.fieldFormatter(); - expect(fieldFormatter).toBeDefined(); - expect(fieldFormatter('text')).toBe('text'); - }); - - it('returns the string format if there is no field', () => { - const agg = aggConfig; - delete agg.params.field; - const fieldFormatter = agg.fieldFormatter(); - expect(fieldFormatter).toBeDefined(); - expect(fieldFormatter('text')).toBe('text'); - }); - - it('returns the html converter if "html" is passed in', () => { - const field = indexPattern.fields.getByName('bytes'); - expect(aggConfig.fieldFormatter('html').toString()).toBe( - field!.format.getConverterFor('html').toString() - ); - }); - }); }); diff --git a/src/plugins/data/public/search/aggs/agg_config.ts b/src/plugins/data/public/search/aggs/agg_config.ts index a2b74eca58476..8650f5920e520 100644 --- a/src/plugins/data/public/search/aggs/agg_config.ts +++ b/src/plugins/data/public/search/aggs/agg_config.ts @@ -30,8 +30,6 @@ import { writeParams } from './agg_params'; import { IAggConfigs } from './agg_configs'; import { FetchOptions } from '../fetch'; import { ISearchSource } from '../search_source'; -import { FieldFormatsContentType, KBN_FIELD_TYPES } from '../../../common'; -import { FieldFormatsStart } from '../../field_formats'; type State = string | number | boolean | null | undefined | SerializableState; @@ -52,10 +50,6 @@ export type AggConfigSerialized = Ensure< SerializableState >; -export interface AggConfigDependencies { - fieldFormats: FieldFormatsStart; -} - export type AggConfigOptions = Assign; /** @@ -116,13 +110,8 @@ export class AggConfig { private __type: IAggType; private __typeDecorations: any; private subAggs: AggConfig[] = []; - private readonly fieldFormats: FieldFormatsStart; - constructor( - aggConfigs: IAggConfigs, - opts: AggConfigOptions, - { fieldFormats }: AggConfigDependencies - ) { + constructor(aggConfigs: IAggConfigs, opts: AggConfigOptions) { this.aggConfigs = aggConfigs; this.id = String(opts.id || AggConfig.nextId(aggConfigs.aggs as any)); this.enabled = typeof opts.enabled === 'boolean' ? opts.enabled : true; @@ -143,8 +132,6 @@ export class AggConfig { // @ts-ignore this.__type = this.__type; - - this.fieldFormats = fieldFormats; } /** @@ -433,24 +420,6 @@ export class AggConfig { return this.aggConfigs.timeRange; } - fieldFormatter(contentType?: FieldFormatsContentType, defaultFormat?: any) { - const format = this.type && this.type.getFormat(this); - - if (format) { - return format.getConverterFor(contentType); - } - - return this.fieldOwnFormatter(contentType, defaultFormat); - } - - fieldOwnFormatter(contentType?: FieldFormatsContentType, defaultFormat?: any) { - const field = this.getField(); - let format = field && field.format; - if (!format) format = defaultFormat; - if (!format) format = this.fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.STRING); - return format.getConverterFor(contentType); - } - fieldName() { const field = this.getField(); return field ? field.name : ''; diff --git a/src/plugins/data/public/search/aggs/agg_configs.test.ts b/src/plugins/data/public/search/aggs/agg_configs.test.ts index 6e6fb3350d901..121bb29f6f8ed 100644 --- a/src/plugins/data/public/search/aggs/agg_configs.test.ts +++ b/src/plugins/data/public/search/aggs/agg_configs.test.ts @@ -24,12 +24,10 @@ import { AggTypesRegistryStart } from './agg_types_registry'; import { mockDataServices, mockAggTypesRegistry } from './test_helpers'; import { Field as IndexPatternField, IndexPattern } from '../../index_patterns'; import { stubIndexPattern, stubIndexPatternWithFields } from '../../../public/stubs'; -import { fieldFormatsServiceMock } from '../../field_formats/mocks'; describe('AggConfigs', () => { let indexPattern: IndexPattern; let typesRegistry: AggTypesRegistryStart; - const fieldFormats = fieldFormatsServiceMock.createStartContract(); beforeEach(() => { mockDataServices(); @@ -47,7 +45,7 @@ describe('AggConfigs', () => { }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); expect(ac.aggs).toHaveLength(1); }); @@ -72,7 +70,7 @@ describe('AggConfigs', () => { ]; const spy = jest.spyOn(AggConfig, 'ensureIds'); - new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + new AggConfigs(indexPattern, configStates, { typesRegistry }); expect(spy).toHaveBeenCalledTimes(1); expect(spy.mock.calls[0]).toEqual([configStates]); spy.mockRestore(); @@ -94,20 +92,16 @@ describe('AggConfigs', () => { }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); expect(ac.aggs).toHaveLength(2); ac.createAggConfig( - new AggConfig( - ac, - { - enabled: true, - type: typesRegistry.get('terms'), - params: {}, - schema: 'split', - }, - { fieldFormats } - ) + new AggConfig(ac, { + enabled: true, + type: typesRegistry.get('terms'), + params: {}, + schema: 'split', + }) ); expect(ac.aggs).toHaveLength(3); }); @@ -121,7 +115,7 @@ describe('AggConfigs', () => { }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); expect(ac.aggs).toHaveLength(1); ac.createAggConfig({ @@ -142,7 +136,7 @@ describe('AggConfigs', () => { }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); expect(ac.aggs).toHaveLength(1); ac.createAggConfig( @@ -170,7 +164,7 @@ describe('AggConfigs', () => { { type: 'percentiles', enabled: true, params: {}, schema: 'metric' }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); const sorted = ac.getRequestAggs(); const aggs = indexBy(ac.aggs, (agg) => agg.type.name); @@ -193,7 +187,7 @@ describe('AggConfigs', () => { { type: 'count', enabled: true, params: {}, schema: 'metric' }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); const sorted = ac.getResponseAggs(); const aggs = indexBy(ac.aggs, (agg) => agg.type.name); @@ -210,7 +204,7 @@ describe('AggConfigs', () => { { type: 'percentiles', enabled: true, params: { percents: [1, 2, 3] }, schema: 'metric' }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); const sorted = ac.getResponseAggs(); const aggs = indexBy(ac.aggs, (agg) => agg.type.name); @@ -231,7 +225,7 @@ describe('AggConfigs', () => { it('uses the sorted aggs', () => { const configStates = [{ enabled: true, type: 'avg', params: { field: 'bytes' } }]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); const spy = jest.spyOn(AggConfigs.prototype, 'getRequestAggs'); ac.toDsl(); expect(spy).toHaveBeenCalledTimes(1); @@ -245,10 +239,7 @@ describe('AggConfigs', () => { { enabled: true, type: 'count', params: {} }, ]; - const ac = new AggConfigs(indexPattern, configStates, { - typesRegistry, - fieldFormats, - }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); const aggInfos = ac.aggs.map((aggConfig) => { const football = {}; @@ -291,7 +282,7 @@ describe('AggConfigs', () => { }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); const dsl = ac.toDsl(); const histo = ac.byName('date_histogram')[0]; const count = ac.byName('count')[0]; @@ -316,10 +307,7 @@ describe('AggConfigs', () => { { enabled: true, type: 'max', schema: 'metric', params: { field: 'bytes' } }, ]; - const ac = new AggConfigs(indexPattern, configStates, { - typesRegistry, - fieldFormats, - }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); const dsl = ac.toDsl(); const histo = ac.byName('date_histogram')[0]; const metrics = ac.bySchemaName('metrics'); @@ -344,7 +332,7 @@ describe('AggConfigs', () => { { enabled: true, type: 'max', schema: 'metric', params: { field: 'bytes' } }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); const topLevelDsl = ac.toDsl(true); const buckets = ac.bySchemaName('buckets'); const metrics = ac.bySchemaName('metrics'); @@ -414,7 +402,7 @@ describe('AggConfigs', () => { }, ]; - const ac = new AggConfigs(indexPattern, configStates, { typesRegistry, fieldFormats }); + const ac = new AggConfigs(indexPattern, configStates, { typesRegistry }); const topLevelDsl = ac.toDsl(true)['2']; expect(Object.keys(topLevelDsl.aggs)).toContain('1'); diff --git a/src/plugins/data/public/search/aggs/agg_configs.ts b/src/plugins/data/public/search/aggs/agg_configs.ts index 6cc03be292d7b..b272dfd3c7468 100644 --- a/src/plugins/data/public/search/aggs/agg_configs.ts +++ b/src/plugins/data/public/search/aggs/agg_configs.ts @@ -28,7 +28,6 @@ import { IndexPattern } from '../../index_patterns'; import { ISearchSource } from '../search_source'; import { FetchOptions } from '../fetch'; import { TimeRange } from '../../../common'; -import { FieldFormatsStart } from '../../field_formats'; function removeParentAggs(obj: any) { for (const prop in obj) { @@ -48,7 +47,6 @@ function parseParentAggs(dslLvlCursor: any, dsl: any) { export interface AggConfigsOptions { typesRegistry: AggTypesRegistryStart; - fieldFormats: FieldFormatsStart; } export type CreateAggConfigParams = Assign; @@ -70,7 +68,6 @@ export type IAggConfigs = AggConfigs; export class AggConfigs { public indexPattern: IndexPattern; public timeRange?: TimeRange; - private readonly fieldFormats: FieldFormatsStart; private readonly typesRegistry: AggTypesRegistryStart; aggs: IAggConfig[]; @@ -86,7 +83,6 @@ export class AggConfigs { this.aggs = []; this.indexPattern = indexPattern; - this.fieldFormats = opts.fieldFormats; configStates.forEach((params: any) => this.createAggConfig(params)); } @@ -117,7 +113,6 @@ export class AggConfigs { const aggConfigs = new AggConfigs(this.indexPattern, this.aggs.filter(filterAggs), { typesRegistry: this.typesRegistry, - fieldFormats: this.fieldFormats, }); return aggConfigs; @@ -134,14 +129,10 @@ export class AggConfigs { aggConfig = params; params.parent = this; } else { - aggConfig = new AggConfig( - this, - { - ...params, - type: typeof type === 'string' ? this.typesRegistry.get(type) : type, - }, - { fieldFormats: this.fieldFormats } - ); + aggConfig = new AggConfig(this, { + ...params, + type: typeof type === 'string' ? this.typesRegistry.get(type) : type, + }); } if (addToAggConfigs) { diff --git a/src/plugins/data/public/search/aggs/agg_type.test.ts b/src/plugins/data/public/search/aggs/agg_type.test.ts index cc45b935d45b5..99b1c4d861219 100644 --- a/src/plugins/data/public/search/aggs/agg_type.test.ts +++ b/src/plugins/data/public/search/aggs/agg_type.test.ts @@ -159,47 +159,6 @@ describe('AggType Class', () => { }); }); - describe('getFormat', function () { - let aggConfig: IAggConfig; - let field: any; - - beforeEach(() => { - aggConfig = ({ - getField: jest.fn(() => field), - } as unknown) as IAggConfig; - }); - - test('returns the formatter for the aggConfig', () => { - const aggType = new AggType( - { - name: 'name', - title: 'title', - }, - dependencies - ); - - field = { - format: 'format', - }; - - expect(aggType.getFormat(aggConfig)).toBe('format'); - }); - - test('returns default formatter', () => { - const aggType = new AggType( - { - name: 'name', - title: 'title', - }, - dependencies - ); - - field = undefined; - - expect(aggType.getFormat(aggConfig)).toBe('default'); - }); - }); - describe('getSerializedFormat', () => { test('returns the default serialized field format if it exists', () => { const aggConfig = ({ diff --git a/src/plugins/data/public/search/aggs/agg_type.ts b/src/plugins/data/public/search/aggs/agg_type.ts index e909cd8134e83..13655afdf469e 100644 --- a/src/plugins/data/public/search/aggs/agg_type.ts +++ b/src/plugins/data/public/search/aggs/agg_type.ts @@ -28,7 +28,6 @@ import { IAggConfigs } from './agg_configs'; import { Adapters } from '../../../../../plugins/inspector/public'; import { BaseParamType } from './param_types/base'; import { AggParamType } from './param_types/agg'; -import { KBN_FIELD_TYPES, IFieldFormat } from '../../../common'; import { ISearchSource } from '../search_source'; import { GetInternalStartServicesFn } from '../../types'; @@ -58,7 +57,6 @@ export interface AggTypeConfig< inspectorAdapters: Adapters, abortSignal?: AbortSignal ) => Promise; - getFormat?: (agg: TAggConfig) => IFieldFormat; getSerializedFormat?: (agg: TAggConfig) => SerializedFieldFormat; getValue?: (agg: TAggConfig, bucket: any) => any; getKey?: (bucket: any, key: any, agg: TAggConfig) => any; @@ -197,16 +195,6 @@ export class AggType< inspectorAdapters: Adapters, abortSignal?: AbortSignal ) => Promise; - /** - * Pick a format for the values produced by this agg type, - * overridden by several metrics that always output a simple - * number - * - * @param {agg} agg - the agg to pick a format for - * @return {FieldFormat} - */ - getFormat: (agg: TAggConfig) => IFieldFormat; - /** * Get the serialized format for the values produced by this agg type, * overridden by several metrics that always output a simple number. @@ -283,15 +271,6 @@ export class AggType< this.decorateAggConfig = config.decorateAggConfig || (() => ({})); this.postFlightRequest = config.postFlightRequest || identity; - this.getFormat = - config.getFormat || - ((agg: TAggConfig) => { - const field = agg.getField(); - const { fieldFormats } = getInternalStartServices(); - - return field ? field.format : fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.STRING); - }); - this.getSerializedFormat = config.getSerializedFormat || ((agg: TAggConfig) => { diff --git a/src/plugins/data/public/search/aggs/agg_types.ts b/src/plugins/data/public/search/aggs/agg_types.ts index 2af29d3600246..68542b66e6c35 100644 --- a/src/plugins/data/public/search/aggs/agg_types.ts +++ b/src/plugins/data/public/search/aggs/agg_types.ts @@ -18,7 +18,8 @@ */ import { IUiSettingsClient } from 'src/core/public'; -import { QuerySetup } from '../../query/query_service'; +import { TimeRange, TimeRangeBounds } from '../../../common'; +import { GetInternalStartServicesFn } from '../../types'; import { getCountMetricAgg } from './metrics/count'; import { getAvgMetricAgg } from './metrics/avg'; @@ -54,18 +55,16 @@ import { getBucketAvgMetricAgg } from './metrics/bucket_avg'; import { getBucketMinMetricAgg } from './metrics/bucket_min'; import { getBucketMaxMetricAgg } from './metrics/bucket_max'; -import { GetInternalStartServicesFn } from '../../types'; - export interface AggTypesDependencies { - uiSettings: IUiSettingsClient; - query: QuerySetup; + calculateBounds: (timeRange: TimeRange) => TimeRangeBounds; getInternalStartServices: GetInternalStartServicesFn; + uiSettings: IUiSettingsClient; } export const getAggTypes = ({ - uiSettings, - query, + calculateBounds, getInternalStartServices, + uiSettings, }: AggTypesDependencies) => ({ metrics: [ getCountMetricAgg({ getInternalStartServices }), @@ -91,7 +90,7 @@ export const getAggTypes = ({ getGeoCentroidMetricAgg({ getInternalStartServices }), ], buckets: [ - getDateHistogramBucketAgg({ uiSettings, query, getInternalStartServices }), + getDateHistogramBucketAgg({ calculateBounds, uiSettings, getInternalStartServices }), getHistogramBucketAgg({ uiSettings, getInternalStartServices }), getRangeBucketAgg({ getInternalStartServices }), getDateRangeBucketAgg({ uiSettings, getInternalStartServices }), diff --git a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts index 44d99375bbd30..8e862b5692ca3 100644 --- a/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.test.ts @@ -26,7 +26,6 @@ import { AggConfigs, CreateAggConfigParams } from '../agg_configs'; import { BUCKET_TYPES } from './bucket_agg_types'; import { IBucketAggConfig } from './bucket_agg_type'; import { mockAggTypesRegistry } from '../test_helpers'; -import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; const indexPattern = { id: '1234', @@ -220,10 +219,9 @@ const nestedOtherResponse = { describe('Terms Agg Other bucket helper', () => { const typesRegistry = mockAggTypesRegistry(); - const fieldFormats = fieldFormatsServiceMock.createStartContract(); const getAggConfigs = (aggs: CreateAggConfigParams[] = []) => { - return new AggConfigs(indexPattern, [...aggs], { typesRegistry, fieldFormats }); + return new AggConfigs(indexPattern, [...aggs], { typesRegistry }); }; describe('buildOtherBucketAgg', () => { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts index 2f03b8ec67112..518bdbfe0c135 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts @@ -30,7 +30,6 @@ import { import { BUCKET_TYPES } from '../bucket_agg_types'; import { RangeFilter } from '../../../../../common'; import { coreMock, notificationServiceMock } from '../../../../../../../core/public/mocks'; -import { queryServiceMock } from '../../../../query/mocks'; import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; import { InternalStartServices } from '../../../../types'; @@ -46,13 +45,13 @@ describe('AggConfig Filters', () => { const { uiSettings } = coreMock.createSetup(); aggTypesDependencies = { - uiSettings, - query: queryServiceMock.createSetupContract(), + calculateBounds: jest.fn(), getInternalStartServices: () => (({ fieldFormats: fieldFormatsServiceMock.createStartContract(), notifications: notificationServiceMock.createStartContract(), } as unknown) as InternalStartServices), + uiSettings, }; mockDataServices(); @@ -82,7 +81,6 @@ describe('AggConfig Filters', () => { ], { typesRegistry: mockAggTypesRegistry([getDateHistogramBucketAgg(aggTypesDependencies)]), - fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, } ); const bucketKey = 1422579600000; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts index 0d66d9cfcdca2..08f39e10b6006 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts @@ -76,7 +76,6 @@ describe('AggConfig Filters', () => { ], { typesRegistry: mockAggTypesRegistry([getDateRangeBucketAgg(aggTypesDependencies)]), - fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts index 0fdb07cc4198a..c5ad1a61e6676 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts @@ -73,7 +73,6 @@ describe('AggConfig Filters', () => { ], { typesRegistry: mockAggTypesRegistry([getFiltersBucketAgg(aggTypesDependencies)]), - fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts index 396d515f3b580..3f9f5dd5672f0 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts @@ -23,12 +23,21 @@ import { mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../bucket_agg_type'; import { BytesFormat, FieldFormatsGetConfigFn } from '../../../../../common'; +import { GetInternalStartServicesFn, InternalStartServices } from '../../../../types'; +import { FieldFormatsStart } from '../../../../field_formats'; import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; describe('AggConfig Filters', () => { + let getInternalStartServices: GetInternalStartServicesFn; + let fieldFormats: FieldFormatsStart; + + beforeEach(() => { + fieldFormats = fieldFormatsServiceMock.createStartContract(); + getInternalStartServices = () => (({ fieldFormats } as unknown) as InternalStartServices); + }); + describe('histogram', () => { const getConfig = (() => {}) as FieldFormatsGetConfigFn; - const fieldFormats = fieldFormatsServiceMock.createStartContract(); const getAggConfigs = () => { const field = { name: 'bytes', @@ -57,21 +66,25 @@ describe('AggConfig Filters', () => { }, }, ], - { typesRegistry: mockAggTypesRegistry(), fieldFormats } + { typesRegistry: mockAggTypesRegistry() } ); }; test('should return an range filter for histogram', () => { const aggConfigs = getAggConfigs(); - const filter = createFilterHistogram(aggConfigs.aggs[0] as IBucketAggConfig, '2048'); + const filter = createFilterHistogram(getInternalStartServices)( + aggConfigs.aggs[0] as IBucketAggConfig, + '2048' + ); + expect(fieldFormats.deserialize).toHaveBeenCalledTimes(1); expect(filter).toHaveProperty('meta'); expect(filter.meta).toHaveProperty('index', '1234'); expect(filter).toHaveProperty('range'); expect(filter.range).toHaveProperty('bytes'); expect(filter.range.bytes).toHaveProperty('gte', 2048); expect(filter.range.bytes).toHaveProperty('lt', 3072); - expect(filter.meta).toHaveProperty('formattedValue', '2,048'); + expect(filter.meta).toHaveProperty('formattedValue'); }); }); }); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts index f8e7747d49147..f3626bc9130ad 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/histogram.ts @@ -19,15 +19,20 @@ import { IBucketAggConfig } from '../bucket_agg_type'; import { buildRangeFilter, RangeFilterParams } from '../../../../../common'; +import { GetInternalStartServicesFn } from '../../../../types'; -export const createFilterHistogram = (aggConfig: IBucketAggConfig, key: string) => { - const value = parseInt(key, 10); - const params: RangeFilterParams = { gte: value, lt: value + aggConfig.params.interval }; +/** @internal */ +export const createFilterHistogram = (getInternalStartServices: GetInternalStartServicesFn) => { + return (aggConfig: IBucketAggConfig, key: string) => { + const { fieldFormats } = getInternalStartServices(); + const value = parseInt(key, 10); + const params: RangeFilterParams = { gte: value, lt: value + aggConfig.params.interval }; - return buildRangeFilter( - aggConfig.params.field, - params, - aggConfig.getIndexPattern(), - aggConfig.fieldFormatter()(key) - ); + return buildRangeFilter( + aggConfig.params.field, + params, + aggConfig.getIndexPattern(), + fieldFormats.deserialize(aggConfig.toSerializedFieldFormat()).convert(key) + ); + }; }; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts index 990adde5f8a0b..89a5fd6577891 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts @@ -55,7 +55,7 @@ describe('AggConfig Filters', () => { }, } as any; - return new AggConfigs(indexPattern, aggs, { typesRegistry, fieldFormats }); + return new AggConfigs(indexPattern, aggs, { typesRegistry }); }; test('should return a range filter for ip_range agg', () => { diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts index 564e7b4763c8d..238e5737d8f47 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/range.test.ts @@ -24,19 +24,29 @@ import { AggConfigs } from '../../agg_configs'; import { mockDataServices, mockAggTypesRegistry } from '../../test_helpers'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../bucket_agg_type'; +import { FieldFormatsStart } from '../../../../field_formats'; import { fieldFormatsServiceMock } from '../../../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../../core/public/mocks'; -import { InternalStartServices } from '../../../../types'; +import { GetInternalStartServicesFn, InternalStartServices } from '../../../../types'; describe('AggConfig Filters', () => { describe('range', () => { let aggTypesDependencies: RangeBucketAggDependencies; + let getInternalStartServices: GetInternalStartServicesFn; + let fieldFormats: FieldFormatsStart; beforeEach(() => { + fieldFormats = fieldFormatsServiceMock.createStartContract(); + + getInternalStartServices = () => + (({ + fieldFormats, + notifications: notificationServiceMock.createStartContract(), + } as unknown) as InternalStartServices); + aggTypesDependencies = { getInternalStartServices: () => (({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), notifications: notificationServiceMock.createStartContract(), } as unknown) as InternalStartServices), }; @@ -75,25 +85,28 @@ describe('AggConfig Filters', () => { ], { typesRegistry: mockAggTypesRegistry([getRangeBucketAgg(aggTypesDependencies)]), - fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, } ); }; test('should return a range filter for range agg', () => { const aggConfigs = getAggConfigs(); - const filter = createFilterRange(aggConfigs.aggs[0] as IBucketAggConfig, { - gte: 1024, - lt: 2048.0, - }); + const filter = createFilterRange(getInternalStartServices)( + aggConfigs.aggs[0] as IBucketAggConfig, + { + gte: 1024, + lt: 2048.0, + } + ); + expect(fieldFormats.deserialize).toHaveBeenCalledTimes(1); expect(filter).toHaveProperty('range'); expect(filter).toHaveProperty('meta'); expect(filter.meta).toHaveProperty('index', '1234'); expect(filter.range).toHaveProperty('bytes'); expect(filter.range.bytes).toHaveProperty('gte', 1024.0); expect(filter.range.bytes).toHaveProperty('lt', 2048.0); - expect(filter.meta).toHaveProperty('formattedValue', '≥ 1,024 and < 2,048'); + expect(filter.meta).toHaveProperty('formattedValue'); }); }); }); diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts index cbad8742bfab6..f9db2973af136 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/range.ts @@ -19,12 +19,17 @@ import { IBucketAggConfig } from '../bucket_agg_type'; import { buildRangeFilter } from '../../../../../common'; +import { GetInternalStartServicesFn } from '../../../../types'; -export const createFilterRange = (aggConfig: IBucketAggConfig, params: any) => { - return buildRangeFilter( - aggConfig.params.field, - params, - aggConfig.getIndexPattern(), - aggConfig.fieldFormatter()(params) - ); +/** @internal */ +export const createFilterRange = (getInternalStartServices: GetInternalStartServicesFn) => { + return (aggConfig: IBucketAggConfig, params: any) => { + const { fieldFormats } = getInternalStartServices(); + return buildRangeFilter( + aggConfig.params.field, + params, + aggConfig.getIndexPattern(), + fieldFormats.deserialize(aggConfig.toSerializedFieldFormat()).convert(params) + ); + }; }; diff --git a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts index 36e4bef025ef9..a45087916a395 100644 --- a/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts @@ -60,7 +60,6 @@ describe('AggConfig Filters', () => { return new AggConfigs(indexPattern, aggs, { typesRegistry: mockAggTypesRegistry([getTermsBucketAgg(aggTypesDependencies)]), - fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, }); }; diff --git a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts index fc42d43b2fea8..e4c4bc0cedc3c 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_histogram.ts @@ -27,28 +27,30 @@ import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { createFilterDateHistogram } from './create_filter/date_histogram'; import { intervalOptions } from './_interval_options'; -import { dateHistogramInterval, TimeRange } from '../../../../common'; import { writeParams } from '../agg_params'; import { isMetricAggType } from '../metrics/metric_agg_type'; -import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES, UI_SETTINGS } from '../../../../common'; -import { TimefilterContract } from '../../../query'; -import { QuerySetup } from '../../../query/query_service'; +import { + dateHistogramInterval, + KBN_FIELD_TYPES, + TimeRange, + TimeRangeBounds, + UI_SETTINGS, +} from '../../../../common'; import { GetInternalStartServicesFn } from '../../../types'; import { BaseAggParams } from '../types'; import { ExtendedBounds } from './lib/extended_bounds'; -const detectedTimezone = moment.tz.guess(); -const tzOffset = moment().format('Z'); +type CalculateBoundsFn = (timeRange: TimeRange) => TimeRangeBounds; const updateTimeBuckets = ( agg: IBucketDateHistogramAggConfig, - timefilter: TimefilterContract, + calculateBounds: CalculateBoundsFn, customBuckets?: IBucketDateHistogramAggConfig['buckets'] ) => { const bounds = agg.params.timeRange && (agg.fieldIsTimeField() || agg.params.interval === 'auto') - ? timefilter.calculateBounds(agg.params.timeRange) + ? calculateBounds(agg.params.timeRange) : undefined; const buckets = customBuckets || agg.buckets; buckets.setBounds(bounds); @@ -56,9 +58,9 @@ const updateTimeBuckets = ( }; export interface DateHistogramBucketAggDependencies { - uiSettings: IUiSettingsClient; - query: QuerySetup; + calculateBounds: CalculateBoundsFn; getInternalStartServices: GetInternalStartServicesFn; + uiSettings: IUiSettingsClient; } export interface IBucketDateHistogramAggConfig extends IBucketAggConfig { @@ -83,9 +85,9 @@ export interface AggParamsDateHistogram extends BaseAggParams { } export const getDateHistogramBucketAgg = ({ - uiSettings, - query, + calculateBounds, getInternalStartServices, + uiSettings, }: DateHistogramBucketAggDependencies) => new BucketAggType( { @@ -123,35 +125,19 @@ export const getDateHistogramBucketAgg = ({ get() { if (buckets) return buckets; - const { timefilter } = query.timefilter; buckets = new TimeBuckets({ 'histogram:maxBars': uiSettings.get(UI_SETTINGS.HISTOGRAM_MAX_BARS), 'histogram:barTarget': uiSettings.get(UI_SETTINGS.HISTOGRAM_BAR_TARGET), dateFormat: uiSettings.get('dateFormat'), 'dateFormat:scaled': uiSettings.get('dateFormat:scaled'), }); - updateTimeBuckets(this, timefilter, buckets); + updateTimeBuckets(this, calculateBounds, buckets); return buckets; }, } as any, }; }, - getFormat(agg) { - const { fieldFormats } = getInternalStartServices(); - const DateFieldFormat = fieldFormats.getType(FIELD_FORMAT_IDS.DATE); - - if (!DateFieldFormat) { - throw new Error('Unable to retrieve Date Field Format'); - } - - return new DateFieldFormat( - { - pattern: agg.buckets.getScaledDateFormat(), - }, - (key: string) => uiSettings.get(key) - ); - }, getSerializedFormat(agg) { return { id: 'date', @@ -210,8 +196,7 @@ export const getDateHistogramBucketAgg = ({ default: 'auto', options: intervalOptions, write(agg, output, aggs) { - const { timefilter } = query.timefilter; - updateTimeBuckets(agg, timefilter); + updateTimeBuckets(agg, calculateBounds); const { useNormalizedEsInterval, scaleMetricValues } = agg.params; const interval = agg.buckets.getInterval(useNormalizedEsInterval); @@ -272,6 +257,8 @@ export const getDateHistogramBucketAgg = ({ if (!tz) { // If the index pattern typeMeta data, didn't had a time zone assigned for the selected field use the configured tz const isDefaultTimezone = uiSettings.isDefault('dateFormat:tz'); + const detectedTimezone = moment.tz.guess(); + const tzOffset = moment().format('Z'); tz = isDefaultTimezone ? detectedTimezone || tzOffset : uiSettings.get('dateFormat:tz'); diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts index e1881c3bbc7f4..0829d6e9cdc9f 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.test.ts @@ -78,7 +78,6 @@ describe('date_range params', () => { ], { typesRegistry: mockAggTypesRegistry([getDateRangeBucketAgg(aggTypesDependencies)]), - fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/date_range.ts b/src/plugins/data/public/search/aggs/buckets/date_range.ts index 3e14ab422ccbe..dbcf5517fb08f 100644 --- a/src/plugins/data/public/search/aggs/buckets/date_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/date_range.ts @@ -25,9 +25,9 @@ import { IUiSettingsClient } from 'src/core/public'; import { BUCKET_TYPES } from './bucket_agg_types'; import { BucketAggType, IBucketAggConfig } from './bucket_agg_type'; import { createFilterDateRange } from './create_filter/date_range'; -import { convertDateRangeToString, DateRangeKey } from './lib/date_range'; +import { DateRangeKey } from './lib/date_range'; -import { KBN_FIELD_TYPES, FieldFormat, TEXT_CONTEXT_TYPE } from '../../../../common'; +import { KBN_FIELD_TYPES } from '../../../../common'; import { GetInternalStartServicesFn } from '../../../types'; import { BaseAggParams } from '../types'; @@ -58,18 +58,6 @@ export const getDateRangeBucketAgg = ({ getKey({ from, to }): DateRangeKey { return { from, to }; }, - getFormat(agg) { - const { fieldFormats } = getInternalStartServices(); - - const formatter = agg.fieldOwnFormatter( - TEXT_CONTEXT_TYPE, - fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.DATE) - ); - const DateRangeFormat = FieldFormat.from(function (range: DateRangeKey) { - return convertDateRangeToString(range, formatter); - }); - return new DateRangeFormat(); - }, getSerializedFormat(agg) { return { id: 'date_range', diff --git a/src/plugins/data/public/search/aggs/buckets/filter.ts b/src/plugins/data/public/search/aggs/buckets/filter.ts index 69157edad4f68..d048df9f0e43e 100644 --- a/src/plugins/data/public/search/aggs/buckets/filter.ts +++ b/src/plugins/data/public/search/aggs/buckets/filter.ts @@ -41,6 +41,7 @@ export const getFilterBucketAgg = ({ getInternalStartServices }: FilterBucketAgg { name: BUCKET_TYPES.FILTER, title: filterTitle, + makeLabel: () => filterTitle, params: [ { name: 'geo_bounding_box', diff --git a/src/plugins/data/public/search/aggs/buckets/filters.test.ts b/src/plugins/data/public/search/aggs/buckets/filters.test.ts index 295e740a2a780..7554e4081726c 100644 --- a/src/plugins/data/public/search/aggs/buckets/filters.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/filters.test.ts @@ -71,7 +71,6 @@ describe('Filters Agg', () => { ], { typesRegistry: mockAggTypesRegistry([getFiltersBucketAgg(aggTypesDependencies)]), - fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts index 877a817984dc6..d4c0a5b328844 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_hash.test.ts @@ -81,7 +81,6 @@ describe('Geohash Agg', () => { ], { typesRegistry: mockAggTypesRegistry(), - fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/geo_hash.ts b/src/plugins/data/public/search/aggs/buckets/geo_hash.ts index be339de5d7fae..fbe7c76681f81 100644 --- a/src/plugins/data/public/search/aggs/buckets/geo_hash.ts +++ b/src/plugins/data/public/search/aggs/buckets/geo_hash.ts @@ -54,6 +54,7 @@ export const getGeoHashBucketAgg = ({ getInternalStartServices }: GeoHashBucketA { name: BUCKET_TYPES.GEOHASH_GRID, title: geohashGridTitle, + makeLabel: () => geohashGridTitle, params: [ { name: 'field', diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts index 4756669f5b4b3..787603ee14361 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.test.ts @@ -74,7 +74,6 @@ describe('Histogram Agg', () => { ], { typesRegistry: mockAggTypesRegistry([getHistogramBucketAgg(aggTypesDependencies)]), - fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/histogram.ts b/src/plugins/data/public/search/aggs/buckets/histogram.ts index c1fad17f488db..ce275e2dc1639 100644 --- a/src/plugins/data/public/search/aggs/buckets/histogram.ts +++ b/src/plugins/data/public/search/aggs/buckets/histogram.ts @@ -67,7 +67,7 @@ export const getHistogramBucketAgg = ({ makeLabel(aggConfig) { return aggConfig.getFieldDisplayName(); }, - createFilter: createFilterHistogram, + createFilter: createFilterHistogram(getInternalStartServices), decorateAggConfig() { let autoBounds: AutoBounds; diff --git a/src/plugins/data/public/search/aggs/buckets/ip_range.ts b/src/plugins/data/public/search/aggs/buckets/ip_range.ts index b3e90bdf9b56a..018fcb365b585 100644 --- a/src/plugins/data/public/search/aggs/buckets/ip_range.ts +++ b/src/plugins/data/public/search/aggs/buckets/ip_range.ts @@ -23,13 +23,8 @@ import { BucketAggType } from './bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { createFilterIpRange } from './create_filter/ip_range'; -import { - convertIPRangeToString, - IpRangeKey, - RangeIpRangeAggKey, - CidrMaskIpRangeAggKey, -} from './lib/ip_range'; -import { KBN_FIELD_TYPES, FieldFormat, TEXT_CONTEXT_TYPE } from '../../../../common'; +import { IpRangeKey, RangeIpRangeAggKey, CidrMaskIpRangeAggKey } from './lib/ip_range'; +import { KBN_FIELD_TYPES } from '../../../../common'; import { GetInternalStartServicesFn } from '../../../types'; import { BaseAggParams } from '../types'; @@ -67,17 +62,6 @@ export const getIpRangeBucketAgg = ({ getInternalStartServices }: IpRangeBucketA } return { type: 'range', from: bucket.from, to: bucket.to }; }, - getFormat(agg) { - const { fieldFormats } = getInternalStartServices(); - const formatter = agg.fieldOwnFormatter( - TEXT_CONTEXT_TYPE, - fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.IP) - ); - const IpRangeFormat = FieldFormat.from(function (range: IpRangeKey) { - return convertIPRangeToString(range, formatter); - }); - return new IpRangeFormat(); - }, getSerializedFormat(agg) { return { id: 'ip_range', diff --git a/src/plugins/data/public/search/aggs/buckets/range.test.ts b/src/plugins/data/public/search/aggs/buckets/range.test.ts index 2b8a36f2fbdbc..fea5572b75795 100644 --- a/src/plugins/data/public/search/aggs/buckets/range.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/range.test.ts @@ -22,30 +22,9 @@ import { AggConfigs } from '../agg_configs'; import { mockDataServices, mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; import { FieldFormatsGetConfigFn, NumberFormat } from '../../../../common'; -import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; import { InternalStartServices } from '../../../types'; -const buckets = [ - { - to: 1024, - to_as_string: '1024.0', - doc_count: 20904, - }, - { - from: 1024, - from_as_string: '1024.0', - to: 2560, - to_as_string: '2560.0', - doc_count: 23358, - }, - { - from: 2560, - from_as_string: '2560.0', - doc_count: 174250, - }, -]; - describe('Range Agg', () => { let aggTypesDependencies: RangeBucketAggDependencies; @@ -53,7 +32,6 @@ describe('Range Agg', () => { aggTypesDependencies = { getInternalStartServices: () => (({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), notifications: notificationServiceMock.createStartContract(), } as unknown) as InternalStartServices), }; @@ -99,23 +77,10 @@ describe('Range Agg', () => { ], { typesRegistry: mockAggTypesRegistry([getRangeBucketAgg(aggTypesDependencies)]), - fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, } ); }; - describe('formatting', () => { - test('formats bucket keys properly', () => { - const aggConfigs = getAggConfigs(); - const agg = aggConfigs.aggs[0]; - const format = (val: any) => agg.fieldFormatter()(agg.getKey(val)); - - expect(format(buckets[0])).toBe('≥ -∞ and < 1 KB'); - expect(format(buckets[1])).toBe('≥ 1 KB and < 2.5 KB'); - expect(format(buckets[2])).toBe('≥ 2.5 KB and < +∞'); - }); - }); - describe('getSerializedFormat', () => { test('generates a serialized field format in the expected shape', () => { const aggConfigs = getAggConfigs(); diff --git a/src/plugins/data/public/search/aggs/buckets/range.ts b/src/plugins/data/public/search/aggs/buckets/range.ts index 543e5d66b9fa8..92c5193d55a20 100644 --- a/src/plugins/data/public/search/aggs/buckets/range.ts +++ b/src/plugins/data/public/search/aggs/buckets/range.ts @@ -19,16 +19,13 @@ import { i18n } from '@kbn/i18n'; import { BucketAggType } from './bucket_agg_type'; -import { FieldFormat, KBN_FIELD_TYPES } from '../../../../common'; +import { KBN_FIELD_TYPES } from '../../../../common'; import { RangeKey } from './range_key'; import { createFilterRange } from './create_filter/range'; import { BUCKET_TYPES } from './bucket_agg_types'; import { GetInternalStartServicesFn } from '../../../types'; import { BaseAggParams } from '../types'; -const keyCaches = new WeakMap(); -const formats = new WeakMap(); - const rangeTitle = i18n.translate('data.search.aggs.buckets.rangeTitle', { defaultMessage: 'Range', }); @@ -45,12 +42,14 @@ export interface AggParamsRange extends BaseAggParams { }>; } -export const getRangeBucketAgg = ({ getInternalStartServices }: RangeBucketAggDependencies) => - new BucketAggType( +export const getRangeBucketAgg = ({ getInternalStartServices }: RangeBucketAggDependencies) => { + const keyCaches = new WeakMap(); + + return new BucketAggType( { name: BUCKET_TYPES.RANGE, title: rangeTitle, - createFilter: createFilterRange, + createFilter: createFilterRange(getInternalStartServices), makeLabel(aggConfig) { return i18n.translate('data.search.aggs.aggTypesLabel', { defaultMessage: '{fieldName} ranges', @@ -77,30 +76,6 @@ export const getRangeBucketAgg = ({ getInternalStartServices }: RangeBucketAggDe return key; }, - getFormat(agg) { - let aggFormat = formats.get(agg); - if (aggFormat) return aggFormat; - - const RangeFormat = FieldFormat.from((range: any) => { - const format = agg.fieldOwnFormatter(); - const gte = '\u2265'; - const lt = '\u003c'; - return i18n.translate('data.search.aggs.aggTypes.rangesFormatMessage', { - defaultMessage: '{gte} {from} and {lt} {to}', - values: { - gte, - from: format(range.gte), - lt, - to: format(range.lt), - }, - }); - }); - - aggFormat = new RangeFormat(); - - formats.set(agg, aggFormat); - return aggFormat; - }, getSerializedFormat(agg) { const format = agg.params.field ? agg.params.field.format.toJSON() : {}; return { @@ -132,3 +107,4 @@ export const getRangeBucketAgg = ({ getInternalStartServices }: RangeBucketAggDe }, { getInternalStartServices } ); +}; diff --git a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts index 156f7f8108482..8fe833aa99cb2 100644 --- a/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/significant_terms.test.ts @@ -72,7 +72,6 @@ describe('Significant Terms Agg', () => { typesRegistry: mockAggTypesRegistry([ getSignificantTermsBucketAgg(aggTypesDependencies), ]), - fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/terms.test.ts b/src/plugins/data/public/search/aggs/buckets/terms.test.ts index 9769efb6da749..2c5be00c8afea 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.test.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.test.ts @@ -20,11 +20,9 @@ import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; describe('Terms Agg', () => { describe('order agg editor UI', () => { - const fieldFormats = fieldFormatsServiceMock.createStartContract(); const getAggConfigs = (params: Record = {}) => { const indexPattern = { id: '1234', @@ -49,7 +47,7 @@ describe('Terms Agg', () => { type: BUCKET_TYPES.TERMS, }, ], - { typesRegistry: mockAggTypesRegistry(), fieldFormats } + { typesRegistry: mockAggTypesRegistry() } ); }; diff --git a/src/plugins/data/public/search/aggs/buckets/terms.ts b/src/plugins/data/public/search/aggs/buckets/terms.ts index 1e8e9ab4ef9d0..57a5ebf72316c 100644 --- a/src/plugins/data/public/search/aggs/buckets/terms.ts +++ b/src/plugins/data/public/search/aggs/buckets/terms.ts @@ -30,7 +30,7 @@ import { AggConfigSerialized, BaseAggParams, IAggConfigs } from '../types'; import { Adapters } from '../../../../../inspector/public'; import { ISearchSource } from '../../search_source'; -import { IFieldFormat, FieldFormatsContentType, KBN_FIELD_TYPES } from '../../../../common'; +import { KBN_FIELD_TYPES } from '../../../../common'; import { getRequestInspectorStats, getResponseInspectorStats } from '../../expressions'; import { @@ -88,22 +88,6 @@ export const getTermsBucketAgg = ({ getInternalStartServices }: TermsBucketAggDe const params = agg.params; return agg.getFieldDisplayName() + ': ' + params.order.text; }, - getFormat(bucket): IFieldFormat { - return { - getConverterFor: (type: FieldFormatsContentType) => { - return (val: any) => { - if (val === '__other__') { - return bucket.params.otherBucketLabel; - } - if (val === '__missing__') { - return bucket.params.missingBucketLabel; - } - - return bucket.params.field.format.convert(val, type); - }; - }, - } as IFieldFormat; - }, getSerializedFormat(agg) { const format = agg.params.field ? agg.params.field.format.toJSON() : {}; return { diff --git a/src/plugins/data/public/search/aggs/index.test.ts b/src/plugins/data/public/search/aggs/index.test.ts index 4864a8b9d013b..73068326ca97e 100644 --- a/src/plugins/data/public/search/aggs/index.test.ts +++ b/src/plugins/data/public/search/aggs/index.test.ts @@ -22,7 +22,6 @@ import { getAggTypes } from './index'; import { isBucketAggType } from './buckets/bucket_agg_type'; import { isMetricAggType } from './metrics/metric_agg_type'; -import { QueryStart } from '../../query'; import { FieldFormatsStart } from '../../field_formats'; import { InternalStartServices } from '../../types'; @@ -31,13 +30,13 @@ describe('AggTypesComponent', () => { const coreStart = coreMock.createSetup(); const aggTypes = getAggTypes({ - uiSettings: coreSetup.uiSettings, - query: {} as QueryStart, + calculateBounds: jest.fn(), getInternalStartServices: () => (({ notifications: coreStart.notifications, fieldFormats: {} as FieldFormatsStart, } as unknown) as InternalStartServices), + uiSettings: coreSetup.uiSettings, }); const { buckets, metrics } = aggTypes; diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts b/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts index 38312ec5cfa81..a30e426dd2390 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_avg.ts @@ -46,7 +46,7 @@ const averageBucketTitle = i18n.translate('data.search.aggs.metrics.averageBucke export const getBucketAvgMetricAgg = ({ getInternalStartServices, }: BucketAvgMetricAggDependencies) => { - const { subtype, params, getFormat, getSerializedFormat } = siblingPipelineAggHelper; + const { subtype, params, getSerializedFormat } = siblingPipelineAggHelper; return new MetricAggType( { @@ -55,7 +55,6 @@ export const getBucketAvgMetricAgg = ({ makeLabel: (agg) => makeNestedLabel(agg, overallAverageLabel), subtype, params: [...params()], - getFormat, getSerializedFormat, getValue(agg, bucket) { const customMetric = agg.getParam('customMetric'); diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_max.ts b/src/plugins/data/public/search/aggs/metrics/bucket_max.ts index e2c6a5105bac6..307c7ed3f9d38 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_max.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_max.ts @@ -45,7 +45,7 @@ const maxBucketTitle = i18n.translate('data.search.aggs.metrics.maxBucketTitle', export const getBucketMaxMetricAgg = ({ getInternalStartServices, }: BucketMaxMetricAggDependencies) => { - const { subtype, params, getFormat, getSerializedFormat } = siblingPipelineAggHelper; + const { subtype, params, getSerializedFormat } = siblingPipelineAggHelper; return new MetricAggType( { @@ -54,7 +54,6 @@ export const getBucketMaxMetricAgg = ({ makeLabel: (agg) => makeNestedLabel(agg, overallMaxLabel), subtype, params: [...params()], - getFormat, getSerializedFormat, }, { diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_min.ts b/src/plugins/data/public/search/aggs/metrics/bucket_min.ts index c46a3eb9425d1..bb4ed9d44b0b3 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_min.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_min.ts @@ -45,7 +45,7 @@ const minBucketTitle = i18n.translate('data.search.aggs.metrics.minBucketTitle', export const getBucketMinMetricAgg = ({ getInternalStartServices, }: BucketMinMetricAggDependencies) => { - const { subtype, params, getFormat, getSerializedFormat } = siblingPipelineAggHelper; + const { subtype, params, getSerializedFormat } = siblingPipelineAggHelper; return new MetricAggType( { @@ -54,7 +54,6 @@ export const getBucketMinMetricAgg = ({ makeLabel: (agg) => makeNestedLabel(agg, overallMinLabel), subtype, params: [...params()], - getFormat, getSerializedFormat, }, { diff --git a/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts b/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts index 57212ec9ff91b..dd065b52acd12 100644 --- a/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts +++ b/src/plugins/data/public/search/aggs/metrics/bucket_sum.ts @@ -45,7 +45,7 @@ const sumBucketTitle = i18n.translate('data.search.aggs.metrics.sumBucketTitle', export const getBucketSumMetricAgg = ({ getInternalStartServices, }: BucketSumMetricAggDependencies) => { - const { subtype, params, getFormat, getSerializedFormat } = siblingPipelineAggHelper; + const { subtype, params, getSerializedFormat } = siblingPipelineAggHelper; return new MetricAggType( { @@ -54,7 +54,6 @@ export const getBucketSumMetricAgg = ({ makeLabel: (agg) => makeNestedLabel(agg, overallSumLabel), subtype, params: [...params()], - getFormat, getSerializedFormat, }, { diff --git a/src/plugins/data/public/search/aggs/metrics/cardinality.ts b/src/plugins/data/public/search/aggs/metrics/cardinality.ts index 2855cc1b6b16e..efc79a8559566 100644 --- a/src/plugins/data/public/search/aggs/metrics/cardinality.ts +++ b/src/plugins/data/public/search/aggs/metrics/cardinality.ts @@ -49,11 +49,6 @@ export const getCardinalityMetricAgg = ({ values: { field: aggConfig.getFieldDisplayName() }, }); }, - getFormat() { - const { fieldFormats } = getInternalStartServices(); - - return fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); - }, getSerializedFormat(agg) { return { id: 'number', diff --git a/src/plugins/data/public/search/aggs/metrics/count.ts b/src/plugins/data/public/search/aggs/metrics/count.ts index 4c7b8139b0162..86faca053a9cf 100644 --- a/src/plugins/data/public/search/aggs/metrics/count.ts +++ b/src/plugins/data/public/search/aggs/metrics/count.ts @@ -20,7 +20,6 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../common'; import { GetInternalStartServicesFn } from '../../../types'; export interface CountMetricAggDependencies { @@ -40,11 +39,6 @@ export const getCountMetricAgg = ({ getInternalStartServices }: CountMetricAggDe defaultMessage: 'Count', }); }, - getFormat() { - const { fieldFormats } = getInternalStartServices(); - - return fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); - }, getSerializedFormat(agg) { return { id: 'number', diff --git a/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts b/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts index c392f44a7961e..3de88bb71740f 100644 --- a/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts +++ b/src/plugins/data/public/search/aggs/metrics/cumulative_sum.ts @@ -46,7 +46,7 @@ const cumulativeSumTitle = i18n.translate('data.search.aggs.metrics.cumulativeSu export const getCumulativeSumMetricAgg = ({ getInternalStartServices, }: CumulativeSumMetricAggDependencies) => { - const { subtype, params, getFormat, getSerializedFormat } = parentPipelineAggHelper; + const { subtype, params, getSerializedFormat } = parentPipelineAggHelper; return new MetricAggType( { @@ -55,7 +55,6 @@ export const getCumulativeSumMetricAgg = ({ makeLabel: (agg) => makeNestedLabel(agg, cumulativeSumLabel), subtype, params: [...params()], - getFormat, getSerializedFormat, }, { diff --git a/src/plugins/data/public/search/aggs/metrics/derivative.ts b/src/plugins/data/public/search/aggs/metrics/derivative.ts index f3c1cc9bc2977..c9507029080e6 100644 --- a/src/plugins/data/public/search/aggs/metrics/derivative.ts +++ b/src/plugins/data/public/search/aggs/metrics/derivative.ts @@ -46,7 +46,7 @@ const derivativeTitle = i18n.translate('data.search.aggs.metrics.derivativeTitle export const getDerivativeMetricAgg = ({ getInternalStartServices, }: DerivativeMetricAggDependencies) => { - const { subtype, params, getFormat, getSerializedFormat } = parentPipelineAggHelper; + const { subtype, params, getSerializedFormat } = parentPipelineAggHelper; return new MetricAggType( { @@ -57,7 +57,6 @@ export const getDerivativeMetricAgg = ({ }, subtype, params: [...params()], - getFormat, getSerializedFormat, }, { diff --git a/src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts b/src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts index 2a74a446ce84e..73acd26860076 100644 --- a/src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts +++ b/src/plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts @@ -18,14 +18,12 @@ */ import { i18n } from '@kbn/i18n'; -import { noop, identity } from 'lodash'; +import { noop } from 'lodash'; import { forwardModifyAggConfigOnSearchRequestStart } from './nested_agg_helpers'; import { IMetricAggConfig, MetricAggParam } from '../metric_agg_type'; import { parentPipelineAggWriter } from './parent_pipeline_agg_writer'; -import { FieldFormat } from '../../../../../common'; - const metricAggFilter = [ '!top_hits', '!percentiles', @@ -73,18 +71,6 @@ export const parentPipelineAggHelper = { ] as Array>; }, - getFormat(agg: IMetricAggConfig) { - let subAgg; - const customMetric = agg.getParam('customMetric'); - - if (customMetric) { - subAgg = customMetric; - } else { - subAgg = agg.aggConfigs.byId(agg.getParam('metricAgg')); - } - return subAgg ? subAgg.type.getFormat(subAgg) : new (FieldFormat.from(identity))(); - }, - getSerializedFormat(agg: IMetricAggConfig) { let subAgg; const customMetric = agg.getParam('customMetric'); diff --git a/src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts b/src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts index 8e3e0143bf915..347ac76b8e27b 100644 --- a/src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts +++ b/src/plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts @@ -17,12 +17,10 @@ * under the License. */ -import { identity } from 'lodash'; import { i18n } from '@kbn/i18n'; import { siblingPipelineAggWriter } from './sibling_pipeline_agg_writer'; import { forwardModifyAggConfigOnSearchRequestStart } from './nested_agg_helpers'; import { IMetricAggConfig, MetricAggParam } from '../metric_agg_type'; -import { FieldFormat } from '../../../../../common'; const metricAggFilter: string[] = [ '!top_hits', @@ -87,13 +85,6 @@ export const siblingPipelineAggHelper = { ] as Array>; }, - getFormat(agg: IMetricAggConfig) { - const customMetric = agg.getParam('customMetric'); - return customMetric - ? customMetric.type.getFormat(customMetric) - : new (FieldFormat.from(identity))(); - }, - getSerializedFormat(agg: IMetricAggConfig) { const customMetric = agg.getParam('customMetric'); return customMetric ? customMetric.type.getSerializedFormat(customMetric) : {}; diff --git a/src/plugins/data/public/search/aggs/metrics/median.test.ts b/src/plugins/data/public/search/aggs/metrics/median.test.ts index 71c48f04a3ca8..b3721e2c1e679 100644 --- a/src/plugins/data/public/search/aggs/metrics/median.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/median.test.ts @@ -63,7 +63,6 @@ describe('AggTypeMetricMedianProvider class', () => { ], { typesRegistry, - fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats, } ); }); diff --git a/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts b/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts index 83656db4ac547..5c4ff91258fb0 100644 --- a/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts +++ b/src/plugins/data/public/search/aggs/metrics/metric_agg_type.ts @@ -22,7 +22,6 @@ import { AggType, AggTypeConfig } from '../agg_type'; import { AggParamType } from '../param_types/agg'; import { AggConfig } from '../agg_config'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../common'; import { FieldTypes } from '../param_types'; import { GetInternalStartServicesFn } from '../../../types'; @@ -82,14 +81,6 @@ export class MetricAggType { - const { fieldFormats } = dependencies.getInternalStartServices(); - const field = agg.getField(); - return field ? field.format : fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER); - }); - this.subtype = config.subtype || i18n.translate('data.search.aggs.metrics.metricAggregationsSubtypeTitle', { diff --git a/src/plugins/data/public/search/aggs/metrics/moving_avg.ts b/src/plugins/data/public/search/aggs/metrics/moving_avg.ts index abad2782f9d20..61384ef1dc106 100644 --- a/src/plugins/data/public/search/aggs/metrics/moving_avg.ts +++ b/src/plugins/data/public/search/aggs/metrics/moving_avg.ts @@ -48,7 +48,7 @@ const movingAvgLabel = i18n.translate('data.search.aggs.metrics.movingAvgLabel', export const getMovingAvgMetricAgg = ({ getInternalStartServices, }: MovingAvgMetricAggDependencies) => { - const { subtype, params, getFormat, getSerializedFormat } = parentPipelineAggHelper; + const { subtype, params, getSerializedFormat } = parentPipelineAggHelper; return new MetricAggType( { @@ -57,7 +57,6 @@ export const getMovingAvgMetricAgg = ({ title: movingAvgTitle, makeLabel: (agg) => makeNestedLabel(agg, movingAvgLabel), subtype, - getFormat, getSerializedFormat, params: [ ...params(), diff --git a/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts b/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts index 2201366cdf78b..91149a3f29f94 100644 --- a/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts @@ -24,14 +24,12 @@ import { getSerialDiffMetricAgg } from './serial_diff'; import { AggConfigs } from '../agg_configs'; import { mockAggTypesRegistry } from '../test_helpers'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; -import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; import { GetInternalStartServicesFn, InternalStartServices } from '../../../types'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('parent pipeline aggs', function () { const getInternalStartServices: GetInternalStartServicesFn = () => (({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), notifications: notificationServiceMock.createStartContract(), } as unknown) as InternalStartServices); @@ -76,9 +74,7 @@ describe('parent pipeline aggs', function () { const field = { name: 'field', format: { - type: { - id: 'bytes', - }, + toJSON: () => ({ id: 'bytes' }), }, }; const indexPattern = { @@ -111,7 +107,7 @@ describe('parent pipeline aggs', function () { schema: 'metric', }, ], - { typesRegistry, fieldFormats: getInternalStartServices().fieldFormats } + { typesRegistry } ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) @@ -197,14 +193,14 @@ describe('parent pipeline aggs', function () { ); }); - it('should have correct formatter', () => { + it('should have correct serialized format', () => { init({ metricAgg: '3', }); - expect(metricAgg.getFormat(aggConfig).type.id).toBe('bytes'); + expect(metricAgg.getSerializedFormat(aggConfig).id).toBe('bytes'); }); - it('should have correct customMetric nested formatter', () => { + it('should have correct customMetric nested serialized format', () => { init({ metricAgg: 'custom', customMetric: { @@ -222,7 +218,7 @@ describe('parent pipeline aggs', function () { schema: 'orderAgg', }, }); - expect(metricAgg.getFormat(aggConfig).type.id).toBe('bytes'); + expect(metricAgg.getSerializedFormat(aggConfig).id).toBe('bytes'); }); it("should call modifyAggConfigOnSearchRequestStart for its customMetric's parameters", () => { diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts index df52c10a82495..39e371763ed4b 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts @@ -74,7 +74,7 @@ describe('AggTypesMetricsPercentileRanksProvider class', function () { }, }, ], - { typesRegistry, fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats } + { typesRegistry } ); }); diff --git a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts index c5aee380b9776..1953c81a99874 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentile_ranks.ts @@ -22,7 +22,7 @@ import { MetricAggType } from './metric_agg_type'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { getPercentileValue } from './percentiles_get_value'; import { METRIC_TYPES } from './metric_agg_types'; -import { FIELD_FORMAT_IDS, KBN_FIELD_TYPES } from '../../../../common'; +import { KBN_FIELD_TYPES } from '../../../../common'; import { GetInternalStartServicesFn } from '../../../types'; import { BaseAggParams } from '../types'; @@ -96,13 +96,6 @@ export const getPercentileRanksMetricAgg = ({ return values.map((value: any) => new ValueAggConfig(value)); }, - getFormat() { - const { fieldFormats } = getInternalStartServices(); - return ( - fieldFormats.getInstance(FIELD_FORMAT_IDS.PERCENT) || - fieldFormats.getDefaultInstance(KBN_FIELD_TYPES.NUMBER) - ); - }, getSerializedFormat(agg) { return { id: 'percent', diff --git a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts b/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts index 76382c01bcc10..52ab325ac5806 100644 --- a/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/percentiles.test.ts @@ -67,7 +67,7 @@ describe('AggTypesMetricsPercentilesProvider class', () => { }, }, ], - { typesRegistry, fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats } + { typesRegistry } ); }); diff --git a/src/plugins/data/public/search/aggs/metrics/serial_diff.ts b/src/plugins/data/public/search/aggs/metrics/serial_diff.ts index 7cb1b194fed1c..9ea01be11fe8f 100644 --- a/src/plugins/data/public/search/aggs/metrics/serial_diff.ts +++ b/src/plugins/data/public/search/aggs/metrics/serial_diff.ts @@ -46,7 +46,7 @@ const serialDiffLabel = i18n.translate('data.search.aggs.metrics.serialDiffLabel export const getSerialDiffMetricAgg = ({ getInternalStartServices, }: SerialDiffMetricAggDependencies) => { - const { subtype, params, getFormat, getSerializedFormat } = parentPipelineAggHelper; + const { subtype, params, getSerializedFormat } = parentPipelineAggHelper; return new MetricAggType( { @@ -55,7 +55,6 @@ export const getSerialDiffMetricAgg = ({ makeLabel: (agg) => makeNestedLabel(agg, serialDiffLabel), subtype, params: [...params()], - getFormat, getSerializedFormat, }, { diff --git a/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts b/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts index 56bd33b2b6345..f08e850caadce 100644 --- a/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts @@ -25,14 +25,12 @@ import { getBucketMaxMetricAgg } from './bucket_max'; import { AggConfigs } from '../agg_configs'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; import { mockAggTypesRegistry } from '../test_helpers'; -import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; import { GetInternalStartServicesFn, InternalStartServices } from '../../../types'; import { notificationServiceMock } from '../../../../../../../src/core/public/mocks'; describe('sibling pipeline aggs', () => { const getInternalStartServices: GetInternalStartServicesFn = () => (({ - fieldFormats: fieldFormatsServiceMock.createStartContract(), notifications: notificationServiceMock.createStartContract(), } as unknown) as InternalStartServices); @@ -71,9 +69,7 @@ describe('sibling pipeline aggs', () => { const field = { name: 'field', format: { - type: { - id: 'bytes', - }, + toJSON: () => ({ id: 'bytes' }), }, }; const indexPattern = { @@ -112,7 +108,7 @@ describe('sibling pipeline aggs', () => { }, }, ], - { typesRegistry, fieldFormats: getInternalStartServices().fieldFormats } + { typesRegistry } ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) @@ -155,7 +151,7 @@ describe('sibling pipeline aggs', () => { expect(aggDsl.parentAggs['2-bucket'].aggs['2-metric'].avg.field).toEqual('field'); }); - it('should have correct formatter', () => { + it('should have correct serialized field format', () => { init({ customMetric: { id: '5', @@ -171,7 +167,7 @@ describe('sibling pipeline aggs', () => { }, }); - expect(metricAgg.getFormat(aggConfig).type.id).toBe('bytes'); + expect(metricAgg.getSerializedFormat(aggConfig).id).toBe('bytes'); }); it("should call modifyAggConfigOnSearchRequestStart for nested aggs' parameters", () => { diff --git a/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts b/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts index 536764b2bcf0b..2fa207e62771e 100644 --- a/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/std_deviation.test.ts @@ -66,7 +66,7 @@ describe('AggTypeMetricStandardDeviationProvider class', () => { }, }, ], - { typesRegistry, fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats } + { typesRegistry } ); }; diff --git a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts index 49e0a3e4b349a..94a970a72a46f 100644 --- a/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts +++ b/src/plugins/data/public/search/aggs/metrics/top_hit.test.ts @@ -91,7 +91,7 @@ describe('Top hit metric', () => { params, }, ], - { typesRegistry, fieldFormats: aggTypesDependencies.getInternalStartServices().fieldFormats } + { typesRegistry } ); // Grab the aggConfig off the vis (we don't actually use the vis for anything else) diff --git a/src/plugins/data/public/search/aggs/mocks.ts b/src/plugins/data/public/search/aggs/mocks.ts index 16544fd8f46b0..7a5dcc9be4592 100644 --- a/src/plugins/data/public/search/aggs/mocks.ts +++ b/src/plugins/data/public/search/aggs/mocks.ts @@ -27,7 +27,6 @@ import { } from './'; import { SearchAggsSetup, SearchAggsStart } from './types'; import { mockAggTypesRegistry } from './test_helpers'; -import { fieldFormatsServiceMock } from '../../field_formats/mocks'; const aggTypeBaseParamMock = () => ({ name: 'some_param', @@ -73,7 +72,6 @@ export const searchAggsStartMock = (): SearchAggsStart => ({ createAggConfigs: jest.fn().mockImplementation((indexPattern, configStates = [], schemas) => { return new AggConfigs(indexPattern, configStates, { typesRegistry: mockAggTypesRegistry(), - fieldFormats: fieldFormatsServiceMock.createStartContract(), }); }), types: mockAggTypesRegistry(), diff --git a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts index 836aaad2cda0c..385d0cd6c6b39 100644 --- a/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts +++ b/src/plugins/data/public/search/aggs/test_helpers/mock_agg_types_registry.ts @@ -22,7 +22,6 @@ import { AggTypesRegistry, AggTypesRegistryStart } from '../agg_types_registry'; import { getAggTypes } from '../agg_types'; import { BucketAggType } from '../buckets/bucket_agg_type'; import { MetricAggType } from '../metrics/metric_agg_type'; -import { queryServiceMock } from '../../../query/mocks'; import { fieldFormatsServiceMock } from '../../../field_formats/mocks'; import { InternalStartServices } from '../../../types'; import { TimeBucketsConfig } from '../buckets/lib/time_buckets/time_buckets'; @@ -79,8 +78,7 @@ export function mockAggTypesRegistry | MetricAggTyp coreSetup.uiSettings.get = mockUiSettings; const aggTypes = getAggTypes({ - uiSettings: coreSetup.uiSettings, - query: queryServiceMock.createSetupContract(), + calculateBounds: jest.fn(), getInternalStartServices: () => (({ fieldFormats: fieldFormatsServiceMock.createStartContract(), @@ -88,6 +86,7 @@ export function mockAggTypesRegistry | MetricAggTyp uiSettings: coreStart.uiSettings, injectedMetadata: coreStart.injectedMetadata, } as unknown) as InternalStartServices), + uiSettings: coreSetup.uiSettings, }); aggTypes.buckets.forEach((type) => registrySetup.registerBucket(type)); diff --git a/src/plugins/data/public/search/expressions/build_tabular_inspector_data.ts b/src/plugins/data/public/search/expressions/build_tabular_inspector_data.ts index 906b8862d00aa..c4846a98f124f 100644 --- a/src/plugins/data/public/search/expressions/build_tabular_inspector_data.ts +++ b/src/plugins/data/public/search/expressions/build_tabular_inspector_data.ts @@ -19,6 +19,7 @@ import { set } from 'lodash'; import { FormattedData } from '../../../../../plugins/inspector/public'; +import { FormatFactory } from '../../../common/field_formats/utils'; import { TabbedTable } from '../tabify'; import { createFilter } from './create_filter'; @@ -38,14 +39,30 @@ import { createFilter } from './create_filter'; */ export async function buildTabularInspectorData( table: TabbedTable, - queryFilter: { addFilters: (filter: any) => void } + { + queryFilter, + deserializeFieldFormat, + }: { + queryFilter: { addFilters: (filter: any) => void }; + deserializeFieldFormat: FormatFactory; + } ) { const aggConfigs = table.columns.map((column) => column.aggConfig); const rows = table.rows.map((row) => { return table.columns.reduce>((prev, cur, colIndex) => { const value = row[cur.id]; - const fieldFormatter = cur.aggConfig.fieldFormatter('text'); - prev[`col-${colIndex}-${cur.aggConfig.id}`] = new FormattedData(value, fieldFormatter(value)); + + let format = cur.aggConfig.toSerializedFieldFormat(); + if (Object.keys(format).length < 1) { + // If no format exists, fall back to string as a default + format = { id: 'string' }; + } + const fieldFormatter = deserializeFieldFormat(format); + + prev[`col-${colIndex}-${cur.aggConfig.id}`] = new FormattedData( + value, + fieldFormatter.convert(value) + ); return prev; }, {}); }); diff --git a/src/plugins/data/public/search/expressions/create_filter.test.ts b/src/plugins/data/public/search/expressions/create_filter.test.ts index 51b5e175761bd..23da060cba203 100644 --- a/src/plugins/data/public/search/expressions/create_filter.test.ts +++ b/src/plugins/data/public/search/expressions/create_filter.test.ts @@ -22,12 +22,10 @@ import { AggConfigs, IAggConfig } from '../aggs'; import { TabbedTable } from '../tabify'; import { isRangeFilter, BytesFormat, FieldFormatsGetConfigFn } from '../../../common'; import { mockDataServices, mockAggTypesRegistry } from '../aggs/test_helpers'; -import { fieldFormatsServiceMock } from '../../field_formats/mocks'; describe('createFilter', () => { let table: TabbedTable; let aggConfig: IAggConfig; - const fieldFormats = fieldFormatsServiceMock.createStartContract(); const typesRegistry = mockAggTypesRegistry(); @@ -60,7 +58,7 @@ describe('createFilter', () => { params, }, ], - { typesRegistry, fieldFormats } + { typesRegistry } ); }; diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts index 77475cd3ce88b..e9daec0b4191b 100644 --- a/src/plugins/data/public/search/expressions/esaggs.ts +++ b/src/plugins/data/public/search/expressions/esaggs.ts @@ -32,9 +32,22 @@ import { Adapters } from '../../../../../plugins/inspector/public'; import { IAggConfigs } from '../aggs'; import { ISearchSource } from '../search_source'; import { tabifyAggResponse } from '../tabify'; -import { Filter, Query, TimeRange, IIndexPattern, isRangeFilter } from '../../../common'; -import { FilterManager, calculateBounds, getTime } from '../../query'; -import { getSearchService, getQueryService, getIndexPatterns } from '../../services'; +import { + calculateBounds, + Filter, + getTime, + IIndexPattern, + isRangeFilter, + Query, + TimeRange, +} from '../../../common'; +import { FilterManager } from '../../query'; +import { + getFieldFormats, + getIndexPatterns, + getQueryService, + getSearchService, +} from '../../services'; import { buildTabularInspectorData } from './build_tabular_inspector_data'; import { getRequestInspectorStats, getResponseInspectorStats, serializeAggConfig } from './utils'; @@ -220,7 +233,11 @@ const handleCourierRequest = async ({ } inspectorAdapters.data.setTabularLoader( - () => buildTabularInspectorData((searchSource as any).tabifiedResponse, filterManager), + () => + buildTabularInspectorData((searchSource as any).tabifiedResponse, { + queryFilter: filterManager, + deserializeFieldFormat: getFieldFormats().deserialize, + }), { returnsFormattedValues: true } ); diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index d5997c15817f6..134dc89c2421a 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -25,10 +25,11 @@ import { ExpressionsSetup } from '../../../../plugins/expressions/public'; import { createSearchSource, SearchSource, SearchSourceDependencies } from './search_source'; import { TStrategyTypes } from './strategy_types'; import { getEsClient, LegacyApiCaller } from './legacy'; +import { getForceNow } from '../query/timefilter/lib/get_force_now'; +import { calculateBounds, TimeRange } from '../../common/query'; import { ES_SEARCH_STRATEGY, DEFAULT_SEARCH_STRATEGY } from '../../common/search'; import { esSearchStrategyProvider } from './es_search'; import { IndexPatternsContract } from '../index_patterns/index_patterns'; -import { QuerySetup } from '../query'; import { GetInternalStartServicesFn } from '../types'; import { SearchInterceptor } from './search_interceptor'; import { @@ -38,19 +39,16 @@ import { AggConfigs, getCalculateAutoTimeExpression, } from './aggs'; -import { FieldFormatsStart } from '../field_formats'; import { ISearchGeneric } from './i_search'; interface SearchServiceSetupDependencies { expressions: ExpressionsSetup; getInternalStartServices: GetInternalStartServicesFn; packageInfo: PackageInfo; - query: QuerySetup; } interface SearchServiceStartDependencies { indexPatterns: IndexPatternsContract; - fieldFormats: FieldFormatsStart; } /** @@ -85,9 +83,16 @@ export class SearchService implements Plugin { return strategy; }; + /** + * getForceNow uses window.location, so we must have a separate implementation + * of calculateBounds on the client and the server. + */ + private calculateBounds = (timeRange: TimeRange) => + calculateBounds(timeRange, { forceNow: getForceNow() }); + public setup( core: CoreSetup, - { expressions, packageInfo, query, getInternalStartServices }: SearchServiceSetupDependencies + { expressions, packageInfo, getInternalStartServices }: SearchServiceSetupDependencies ): ISearchSetup { this.esClient = getEsClient(core.injectedMetadata, core.http, packageInfo); @@ -100,9 +105,9 @@ export class SearchService implements Plugin { // register each agg type const aggTypes = getAggTypes({ - query, - uiSettings: core.uiSettings, + calculateBounds: this.calculateBounds, getInternalStartServices, + uiSettings: core.uiSettings, }); aggTypes.buckets.forEach((b) => aggTypesSetup.registerBucket(b)); aggTypes.metrics.forEach((m) => aggTypesSetup.registerMetric(m)); @@ -158,7 +163,6 @@ export class SearchService implements Plugin { calculateAutoTimeExpression: getCalculateAutoTimeExpression(core.uiSettings), createAggConfigs: (indexPattern, configStates = [], schemas) => { return new AggConfigs(indexPattern, configStates, { - fieldFormats: dependencies.fieldFormats, typesRegistry: aggTypesStart, }); }, diff --git a/src/plugins/data/public/search/tabify/get_columns.test.ts b/src/plugins/data/public/search/tabify/get_columns.test.ts index b4498aca4079c..0c5551d95690f 100644 --- a/src/plugins/data/public/search/tabify/get_columns.test.ts +++ b/src/plugins/data/public/search/tabify/get_columns.test.ts @@ -21,7 +21,6 @@ import { tabifyGetColumns } from './get_columns'; import { TabbedAggColumn } from './types'; import { AggConfigs } from '../aggs'; import { mockAggTypesRegistry, mockDataServices } from '../aggs/test_helpers'; -import { fieldFormatsServiceMock } from '../../field_formats/mocks'; describe('get columns', () => { beforeEach(() => { @@ -29,7 +28,6 @@ describe('get columns', () => { }); const typesRegistry = mockAggTypesRegistry(); - const fieldFormats = fieldFormatsServiceMock.createStartContract(); const createAggConfigs = (aggs: any[] = []) => { const field = { @@ -45,10 +43,7 @@ describe('get columns', () => { }, } as any; - return new AggConfigs(indexPattern, aggs, { - typesRegistry, - fieldFormats, - }); + return new AggConfigs(indexPattern, aggs, { typesRegistry }); }; test('should inject the metric after each bucket if the vis is hierarchical', () => { diff --git a/src/plugins/data/public/search/tabify/response_writer.test.ts b/src/plugins/data/public/search/tabify/response_writer.test.ts index 3334d858ce54e..94473d23ccc39 100644 --- a/src/plugins/data/public/search/tabify/response_writer.test.ts +++ b/src/plugins/data/public/search/tabify/response_writer.test.ts @@ -21,7 +21,6 @@ import { TabbedAggResponseWriter } from './response_writer'; import { AggConfigs, BUCKET_TYPES } from '../aggs'; import { mockDataServices, mockAggTypesRegistry } from '../aggs/test_helpers'; import { TabbedResponseWriterOptions } from './types'; -import { fieldFormatsServiceMock } from '../../field_formats/mocks'; describe('TabbedAggResponseWriter class', () => { beforeEach(() => { @@ -31,7 +30,6 @@ describe('TabbedAggResponseWriter class', () => { let responseWriter: TabbedAggResponseWriter; const typesRegistry = mockAggTypesRegistry(); - const fieldFormats = fieldFormatsServiceMock.createStartContract(); const splitAggConfig = [ { @@ -73,17 +71,11 @@ describe('TabbedAggResponseWriter class', () => { }, } as any; - return new TabbedAggResponseWriter( - new AggConfigs(indexPattern, aggs, { - typesRegistry, - fieldFormats, - }), - { - metricsAtAllLevels: false, - partialRows: false, - ...opts, - } - ); + return new TabbedAggResponseWriter(new AggConfigs(indexPattern, aggs, { typesRegistry }), { + metricsAtAllLevels: false, + partialRows: false, + ...opts, + }); }; describe('Constructor', () => { diff --git a/src/plugins/data/public/search/tabify/tabify.test.ts b/src/plugins/data/public/search/tabify/tabify.test.ts index f6691360cec31..0a1e99c8bb200 100644 --- a/src/plugins/data/public/search/tabify/tabify.test.ts +++ b/src/plugins/data/public/search/tabify/tabify.test.ts @@ -22,11 +22,9 @@ import { IndexPattern } from '../../index_patterns'; import { AggConfigs, IAggConfig, IAggConfigs } from '../aggs'; import { mockAggTypesRegistry } from '../aggs/test_helpers'; import { metricOnly, threeTermBuckets } from 'fixtures/fake_hierarchical_data'; -import { fieldFormatsServiceMock } from '../../field_formats/mocks'; describe('tabifyAggResponse Integration', () => { const typesRegistry = mockAggTypesRegistry(); - const fieldFormats = fieldFormatsServiceMock.createStartContract(); const createAggConfigs = (aggs: IAggConfig[] = []) => { const field = { @@ -42,10 +40,7 @@ describe('tabifyAggResponse Integration', () => { }, } as unknown) as IndexPattern; - return new AggConfigs(indexPattern, aggs, { - typesRegistry, - fieldFormats, - }); + return new AggConfigs(indexPattern, aggs, { typesRegistry }); }; const mockAggConfig = (agg: any): IAggConfig => (agg as unknown) as IAggConfig; diff --git a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx index 8e8054ac204d9..719827a98cc63 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_editor/phrase_suggestor.tsx @@ -45,6 +45,7 @@ export class PhraseSuggestorUI extends React.Com PhraseSuggestorState > { private services = this.props.kibana.services; + private abortController?: AbortController; public state: PhraseSuggestorState = { suggestions: [], isLoading: false, @@ -54,6 +55,10 @@ export class PhraseSuggestorUI extends React.Com this.updateSuggestions(); } + public componentWillUnmount() { + if (this.abortController) this.abortController.abort(); + } + protected isSuggestingValues() { const shouldSuggestValues = this.services.uiSettings.get( UI_SETTINGS.FILTERS_EDITOR_SUGGEST_VALUES @@ -67,6 +72,8 @@ export class PhraseSuggestorUI extends React.Com }; protected updateSuggestions = debounce(async (query: string = '') => { + if (this.abortController) this.abortController.abort(); + this.abortController = new AbortController(); const { indexPattern, field } = this.props as PhraseSuggestorProps; if (!field || !this.isSuggestingValues()) { return; @@ -77,6 +84,7 @@ export class PhraseSuggestorUI extends React.Com indexPattern, field, query, + signal: this.abortController.signal, }); this.setState({ suggestions, isLoading: false }); diff --git a/src/plugins/data/public/ui/query_string_input/no_data_popover.test.tsx b/src/plugins/data/public/ui/query_string_input/no_data_popover.test.tsx new file mode 100644 index 0000000000000..27f924d98e6eb --- /dev/null +++ b/src/plugins/data/public/ui/query_string_input/no_data_popover.test.tsx @@ -0,0 +1,95 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { mountWithIntl as mount } from 'test_utils/enzyme_helpers'; +import { NoDataPopover } from './no_data_popover'; +import { EuiTourStep } from '@elastic/eui'; +import { act } from 'react-dom/test-utils'; + +describe('NoDataPopover', () => { + const createMockStorage = () => ({ + get: jest.fn(), + set: jest.fn(), + remove: jest.fn(), + clear: jest.fn(), + }); + + it('should hide popover if showNoDataPopover is set to false', () => { + const Child = () => ; + const instance = mount( + + + + ); + expect(instance.find(EuiTourStep).prop('isStepOpen')).toBe(false); + expect(instance.find(EuiTourStep).find(Child)).toHaveLength(1); + }); + + it('should hide popover if showNoDataPopover is set to true, but local storage flag is set', () => { + const child = ; + const storage = createMockStorage(); + storage.get.mockReturnValue(true); + const instance = mount( + + {child} + + ); + expect(instance.find(EuiTourStep).prop('isStepOpen')).toBe(false); + }); + + it('should render popover if showNoDataPopover is set to true and local storage flag is not set', () => { + const child = ; + const instance = mount( + + {child} + + ); + expect(instance.find(EuiTourStep).prop('isStepOpen')).toBe(true); + }); + + it('should hide popover if it is closed', async () => { + const props = { + children: , + showNoDataPopover: true, + storage: createMockStorage(), + }; + const instance = mount(); + act(() => { + instance.find(EuiTourStep).prop('closePopover')!(); + }); + instance.setProps({ ...props }); + expect(instance.find(EuiTourStep).prop('isStepOpen')).toBe(false); + }); + + it('should set local storage flag and hide on closing with button', () => { + const props = { + children: , + showNoDataPopover: true, + storage: createMockStorage(), + }; + const instance = mount(); + act(() => { + instance.find(EuiTourStep).prop('footerAction')!.props.onClick(); + }); + instance.setProps({ ...props }); + expect(props.storage.set).toHaveBeenCalledWith(expect.any(String), true); + expect(instance.find(EuiTourStep).prop('isStepOpen')).toBe(false); + }); +}); diff --git a/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx b/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx new file mode 100644 index 0000000000000..302477a5fff5e --- /dev/null +++ b/src/plugins/data/public/ui/query_string_input/no_data_popover.tsx @@ -0,0 +1,96 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ReactElement, useEffect, useState } from 'react'; +import React from 'react'; +import { EuiButtonEmpty, EuiText, EuiTourStep } from '@elastic/eui'; +import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; +import { i18n } from '@kbn/i18n'; + +const NO_DATA_POPOVER_STORAGE_KEY = 'data.noDataPopover'; + +export function NoDataPopover({ + showNoDataPopover, + storage, + children, +}: { + showNoDataPopover?: boolean; + storage: IStorageWrapper; + children: ReactElement; +}) { + const [noDataPopoverDismissed, setNoDataPopoverDismissed] = useState(() => + Boolean(storage.get(NO_DATA_POPOVER_STORAGE_KEY)) + ); + const [noDataPopoverVisible, setNoDataPopoverVisible] = useState(false); + + useEffect(() => { + if (showNoDataPopover && !noDataPopoverDismissed) { + setNoDataPopoverVisible(true); + } + }, [noDataPopoverDismissed, showNoDataPopover]); + + return ( + {}} + closePopover={() => { + setNoDataPopoverVisible(false); + }} + content={ + +

+ {i18n.translate('data.noDataPopover.content', { + defaultMessage: + "This time range doesn't contain any data. Increase or adjust the time range to see more fields and create charts", + })} +

+
+ } + minWidth={300} + anchorPosition="downCenter" + step={1} + stepsTotal={1} + isStepOpen={noDataPopoverVisible} + subtitle={i18n.translate('data.noDataPopover.title', { defaultMessage: 'Tip' })} + title="" + footerAction={ + { + storage.set(NO_DATA_POPOVER_STORAGE_KEY, true); + setNoDataPopoverDismissed(true); + setNoDataPopoverVisible(false); + }} + > + {i18n.translate('data.noDataPopover.dismissAction', { + defaultMessage: "Don't show again", + })} + + } + > +
{ + setNoDataPopoverVisible(false); + }} + > + {children} +
+
+ ); +} diff --git a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx index f65bf97e391e2..4b0dc579c39ce 100644 --- a/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_bar_top_row.tsx @@ -40,6 +40,7 @@ import { useKibana, toMountPoint } from '../../../../kibana_react/public'; import { QueryStringInput } from './query_string_input'; import { doesKueryExpressionHaveLuceneSyntaxError, UI_SETTINGS } from '../../../common'; import { PersistedLog, getQueryLog } from '../../query'; +import { NoDataPopover } from './no_data_popover'; interface Props { query?: Query; @@ -63,6 +64,7 @@ interface Props { customSubmitButton?: any; isDirty: boolean; timeHistory?: TimeHistoryContract; + indicateNoData?: boolean; } export function QueryBarTopRow(props: Props) { @@ -230,10 +232,12 @@ export function QueryBarTopRow(props: Props) { } return ( - - {renderDatePicker()} - {button} - + + + {renderDatePicker()} + {button} + + ); } diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 32295745ce217..120bbf3b68f7b 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -95,7 +95,7 @@ export class QueryStringInputUI extends Component { public inputRef: HTMLInputElement | null = null; private persistedLog: PersistedLog | undefined; - private abortController: AbortController | undefined; + private abortController?: AbortController; private services = this.props.kibana.services; private componentIsUnmounting = false; @@ -497,6 +497,7 @@ export class QueryStringInputUI extends Component { } public componentWillUnmount() { + if (this.abortController) this.abortController.abort(); this.updateSuggestions.cancel(); this.componentIsUnmounting = true; } diff --git a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx index 81e84e3198072..a0df7604f23aa 100644 --- a/src/plugins/data/public/ui/search_bar/create_search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/create_search_bar.tsx @@ -198,6 +198,7 @@ export function createSearchBar({ core, storage, data }: StatefulSearchBarDeps) showSaveQuery={props.showSaveQuery} screenTitle={props.screenTitle} indexPatterns={props.indexPatterns} + indicateNoData={props.indicateNoData} timeHistory={data.query.timefilter.history} dateRangeFrom={timeRange.from} dateRangeTo={timeRange.to} diff --git a/src/plugins/data/public/ui/search_bar/search_bar.tsx b/src/plugins/data/public/ui/search_bar/search_bar.tsx index a5ac227559115..2f740cc476087 100644 --- a/src/plugins/data/public/ui/search_bar/search_bar.tsx +++ b/src/plugins/data/public/ui/search_bar/search_bar.tsx @@ -75,6 +75,7 @@ export interface SearchBarOwnProps { onClearSavedQuery?: () => void; onRefresh?: (payload: { dateRange: TimeRange }) => void; + indicateNoData?: boolean; } export type SearchBarProps = SearchBarOwnProps & SearchBarInjectedDeps; @@ -402,6 +403,7 @@ class SearchBarUI extends Component { this.props.customSubmitButton ? this.props.customSubmitButton : undefined } dataTestSubj={this.props.dataTestSubj} + indicateNoData={this.props.indicateNoData} /> ); } diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 19d028c1d41e1..6a4eb38b552ff 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -202,6 +202,7 @@ export { castEsToKbnFieldTypeName, // query Filter, + getTime, Query, // timefilter RefreshInterval, diff --git a/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts index f563976cae9b8..ff9d67152e268 100644 --- a/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts +++ b/src/plugins/data/server/index_patterns/fetcher/index_patterns_fetcher.ts @@ -17,7 +17,7 @@ * under the License. */ -import { APICaller } from 'kibana/server'; +import { LegacyAPICaller } from 'kibana/server'; import { getFieldCapabilities, resolveTimePattern, createNoMatchingIndicesError } from './lib'; @@ -37,9 +37,9 @@ interface FieldSubType { } export class IndexPatternsFetcher { - private _callDataCluster: APICaller; + private _callDataCluster: LegacyAPICaller; - constructor(callDataCluster: APICaller) { + constructor(callDataCluster: LegacyAPICaller) { this._callDataCluster = callDataCluster; } diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts b/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts index 734e845e85769..0738a16034d46 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/es_api.ts @@ -17,7 +17,7 @@ * under the License. */ -import { APICaller } from 'kibana/server'; +import { LegacyAPICaller } from 'kibana/server'; import { convertEsError } from './errors'; import { FieldCapsResponse } from './field_capabilities'; @@ -46,7 +46,7 @@ export interface IndexAliasResponse { * @return {Promise} */ export async function callIndexAliasApi( - callCluster: APICaller, + callCluster: LegacyAPICaller, indices: string[] | string ): Promise { try { @@ -71,7 +71,7 @@ export async function callIndexAliasApi( * @param {Array|String} indices * @return {Promise} */ -export async function callFieldCapsApi(callCluster: APICaller, indices: string[] | string) { +export async function callFieldCapsApi(callCluster: LegacyAPICaller, indices: string[] | string) { try { return (await callCluster('fieldCaps', { index: indices, diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts index d8c9466432204..502364cdcba32 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_capabilities.ts @@ -19,7 +19,7 @@ import { defaults, indexBy, sortBy } from 'lodash'; -import { APICaller } from 'kibana/server'; +import { LegacyAPICaller } from 'kibana/server'; import { callFieldCapsApi } from '../es_api'; import { FieldCapsResponse, readFieldCapsResponse } from './field_caps_response'; import { mergeOverrides } from './overrides'; @@ -39,7 +39,7 @@ export function concatIfUniq(arr: T[], value: T) { * @return {Promise>} */ export async function getFieldCapabilities( - callCluster: APICaller, + callCluster: LegacyAPICaller, indices: string | string[] = [], metaFields: string[] = [] ) { diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts index 764307bef0ba6..a01d34dbe9df6 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/resolve_time_pattern.ts @@ -20,7 +20,7 @@ import { chain } from 'lodash'; import moment from 'moment'; -import { APICaller } from 'kibana/server'; +import { LegacyAPICaller } from 'kibana/server'; import { timePatternToWildcard } from './time_pattern_to_wildcard'; import { callIndexAliasApi, IndicesAliasResponse } from './es_api'; @@ -36,7 +36,7 @@ import { callIndexAliasApi, IndicesAliasResponse } from './es_api'; * and the indices that actually match the time * pattern (matches); */ -export async function resolveTimePattern(callCluster: APICaller, timePattern: string) { +export async function resolveTimePattern(callCluster: LegacyAPICaller, timePattern: string) { const aliases = await callIndexAliasApi(callCluster, timePatternToWildcard(timePattern)); const allIndexDetails = chain(aliases) diff --git a/src/plugins/data/server/index_patterns/index.ts b/src/plugins/data/server/index_patterns/index.ts index 33a37b28dedcf..683d1c445fd72 100644 --- a/src/plugins/data/server/index_patterns/index.ts +++ b/src/plugins/data/server/index_patterns/index.ts @@ -18,4 +18,4 @@ */ export * from './utils'; export { IndexPatternsFetcher, FieldDescriptor, shouldReadFieldFromDocValues } from './fetcher'; -export { IndexPatternsService } from './index_patterns_service'; +export { IndexPatternsService, IndexPatternsServiceStart } from './index_patterns_service'; diff --git a/src/plugins/visualize/public/application/help_menu/help_menu_util.js b/src/plugins/data/server/index_patterns/index_patterns_api_client.ts similarity index 62% rename from src/plugins/visualize/public/application/help_menu/help_menu_util.js rename to src/plugins/data/server/index_patterns/index_patterns_api_client.ts index c297326f2e264..2dc6f40c5a6f1 100644 --- a/src/plugins/visualize/public/application/help_menu/help_menu_util.js +++ b/src/plugins/data/server/index_patterns/index_patterns_api_client.ts @@ -17,18 +17,13 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; +import { GetFieldsOptions, IIndexPatternsApiClient } from '../../common/index_patterns/types'; -export function addHelpMenuToAppChrome(chrome, docLinks) { - chrome.setHelpExtension({ - appName: i18n.translate('visualize.helpMenu.appName', { - defaultMessage: 'Visualize', - }), - links: [ - { - linkType: 'documentation', - href: `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/visualize.html`, - }, - ], - }); +export class IndexPatternsApiServer implements IIndexPatternsApiClient { + async getFieldsForTimePattern(options: GetFieldsOptions = {}) { + throw new Error('IndexPatternsApiServer - getFieldsForTimePattern not defined'); + } + async getFieldsForWildcard(options: GetFieldsOptions = {}) { + throw new Error('IndexPatternsApiServer - getFieldsForWildcard not defined'); + } } diff --git a/src/plugins/data/server/index_patterns/index_patterns_service.ts b/src/plugins/data/server/index_patterns/index_patterns_service.ts index 3e31f8e8a566d..44699993dd135 100644 --- a/src/plugins/data/server/index_patterns/index_patterns_service.ts +++ b/src/plugins/data/server/index_patterns/index_patterns_service.ts @@ -17,12 +17,28 @@ * under the License. */ -import { CoreSetup, Plugin } from 'kibana/server'; +import { CoreSetup, CoreStart, Plugin, KibanaRequest, Logger } from 'kibana/server'; import { registerRoutes } from './routes'; import { indexPatternSavedObjectType } from '../saved_objects'; import { capabilitiesProvider } from './capabilities_provider'; +import { IndexPatternsService as IndexPatternsCommonService } from '../../common/index_patterns'; +import { FieldFormatsStart } from '../field_formats'; +import { UiSettingsServerToCommon } from './ui_settings_wrapper'; +import { IndexPatternsApiServer } from './index_patterns_api_client'; +import { SavedObjectsClientServerToCommon } from './saved_objects_client_wrapper'; -export class IndexPatternsService implements Plugin { +export interface IndexPatternsServiceStart { + indexPatternsServiceFactory: ( + kibanaRequest: KibanaRequest + ) => Promise; +} + +export interface IndexPatternsServiceStartDeps { + fieldFormats: FieldFormatsStart; + logger: Logger; +} + +export class IndexPatternsService implements Plugin { public setup(core: CoreSetup) { core.savedObjects.registerType(indexPatternSavedObjectType); core.capabilities.registerProvider(capabilitiesProvider); @@ -30,5 +46,28 @@ export class IndexPatternsService implements Plugin { registerRoutes(core.http); } - public start() {} + public start(core: CoreStart, { fieldFormats, logger }: IndexPatternsServiceStartDeps) { + const { uiSettings, savedObjects } = core; + + return { + indexPatternsServiceFactory: async (kibanaRequest: KibanaRequest) => { + const savedObjectsClient = savedObjects.getScopedClient(kibanaRequest); + const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); + const formats = await fieldFormats.fieldFormatServiceFactory(uiSettingsClient); + + return new IndexPatternsCommonService({ + uiSettings: new UiSettingsServerToCommon(uiSettingsClient), + savedObjectsClient: new SavedObjectsClientServerToCommon(savedObjectsClient), + apiClient: new IndexPatternsApiServer(), + fieldFormats: formats, + onError: (error) => { + logger.error(error); + }, + onNotification: ({ title, text }) => { + logger.warn(`${title} : ${text}`); + }, + }); + }, + }; + } } diff --git a/src/plugins/data/server/index_patterns/mocks.ts b/src/plugins/data/server/index_patterns/mocks.ts new file mode 100644 index 0000000000000..8f95afe3b3c9d --- /dev/null +++ b/src/plugins/data/server/index_patterns/mocks.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export function createIndexPatternsStartMock() { + return { + indexPatternsServiceFactory: jest.fn(), + }; +} diff --git a/src/plugins/data/server/index_patterns/saved_objects_client_wrapper.ts b/src/plugins/data/server/index_patterns/saved_objects_client_wrapper.ts new file mode 100644 index 0000000000000..c82695b7cb2ba --- /dev/null +++ b/src/plugins/data/server/index_patterns/saved_objects_client_wrapper.ts @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectsClientContract, SavedObject } from 'src/core/server'; +import { + SavedObjectsClientCommon, + SavedObjectsClientCommonFindArgs, +} from '../../common/index_patterns'; + +export class SavedObjectsClientServerToCommon implements SavedObjectsClientCommon { + private savedObjectClient: SavedObjectsClientContract; + constructor(savedObjectClient: SavedObjectsClientContract) { + this.savedObjectClient = savedObjectClient; + } + async find(options: SavedObjectsClientCommonFindArgs) { + const result = await this.savedObjectClient.find(options); + return result.saved_objects; + } + + async get(type: string, id: string) { + return await this.savedObjectClient.get(type, id); + } + async update( + type: string, + id: string, + attributes: Record, + options: Record + ) { + return (await this.savedObjectClient.update(type, id, attributes, options)) as SavedObject; + } + async create(type: string, attributes: Record, options: Record) { + return await this.savedObjectClient.create(type, attributes, options); + } + delete(type: string, id: string) { + return this.savedObjectClient.delete(type, id); + } +} diff --git a/src/plugins/data/server/index_patterns/ui_settings_wrapper.ts b/src/plugins/data/server/index_patterns/ui_settings_wrapper.ts new file mode 100644 index 0000000000000..34cdfdff0b80f --- /dev/null +++ b/src/plugins/data/server/index_patterns/ui_settings_wrapper.ts @@ -0,0 +1,43 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IUiSettingsClient } from 'src/core/server'; +import { UiSettingsCommon } from '../../common/index_patterns'; + +export class UiSettingsServerToCommon implements UiSettingsCommon { + private uiSettings: IUiSettingsClient; + constructor(uiSettings: IUiSettingsClient) { + this.uiSettings = uiSettings; + } + get(key: string) { + return this.uiSettings.get(key); + } + + getAll() { + return this.uiSettings.getAll(); + } + + set(key: string, value: any) { + return this.uiSettings.set(key, value); + } + + remove(key: string) { + return this.uiSettings.remove(key); + } +} diff --git a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts index 81e352fea51b2..35cee799ddb6a 100644 --- a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts +++ b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts @@ -18,7 +18,7 @@ */ import { fetchProvider } from './fetch'; -import { APICaller } from 'kibana/server'; +import { LegacyAPICaller } from 'kibana/server'; jest.mock('../../../common', () => ({ DEFAULT_QUERY_LANGUAGE: 'lucene', @@ -28,7 +28,7 @@ jest.mock('../../../common', () => ({ })); let fetch: ReturnType; -let callCluster: APICaller; +let callCluster: LegacyAPICaller; function setupMockCallCluster( optCount: { optInCount?: number; optOutCount?: number } | null, @@ -78,7 +78,7 @@ function setupMockCallCluster( } throw new Error('invalid call'); - }) as unknown) as APICaller; + }) as unknown) as LegacyAPICaller; } describe('makeKQLUsageCollector', () => { diff --git a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts index 157716b38f523..109d6f812334d 100644 --- a/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts +++ b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.ts @@ -18,13 +18,19 @@ */ import { get } from 'lodash'; -import { APICaller } from 'kibana/server'; +import { LegacyAPICaller } from 'kibana/server'; import { DEFAULT_QUERY_LANGUAGE, UI_SETTINGS } from '../../../common'; const defaultSearchQueryLanguageSetting = DEFAULT_QUERY_LANGUAGE; +export interface Usage { + optInCount: number; + optOutCount: number; + defaultQueryLanguage: string; +} + export function fetchProvider(index: string) { - return async (callCluster: APICaller) => { + return async (callCluster: LegacyAPICaller): Promise => { const [response, config] = await Promise.all([ callCluster('get', { index, @@ -38,7 +44,7 @@ export function fetchProvider(index: string) { }), ]); - const queryLanguageConfigValue = get( + const queryLanguageConfigValue: string | null | undefined = get( config, `hits.hits[0]._source.config.${UI_SETTINGS.SEARCH_QUERY_LANGUAGE}` ); diff --git a/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.ts b/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.ts index db4c9a8f0b4c7..6d0ca00122018 100644 --- a/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.ts +++ b/src/plugins/data/server/kql_telemetry/usage_collector/make_kql_usage_collector.ts @@ -17,18 +17,22 @@ * under the License. */ -import { fetchProvider } from './fetch'; +import { fetchProvider, Usage } from './fetch'; import { UsageCollectionSetup } from '../../../../usage_collection/server'; export async function makeKQLUsageCollector( usageCollection: UsageCollectionSetup, kibanaIndex: string ) { - const fetch = fetchProvider(kibanaIndex); - const kqlUsageCollector = usageCollection.makeUsageCollector({ + const kqlUsageCollector = usageCollection.makeUsageCollector({ type: 'kql', - fetch, + fetch: fetchProvider(kibanaIndex), isReady: () => true, + schema: { + optInCount: { type: 'long' }, + optOutCount: { type: 'long' }, + defaultQueryLanguage: { type: 'keyword' }, + }, }); usageCollection.registerCollector(kqlUsageCollector); diff --git a/src/plugins/data/server/mocks.ts b/src/plugins/data/server/mocks.ts index e2f2298234054..785e4a1ec41ab 100644 --- a/src/plugins/data/server/mocks.ts +++ b/src/plugins/data/server/mocks.ts @@ -19,6 +19,7 @@ import { createSearchSetupMock, createSearchStartMock } from './search/mocks'; import { createFieldFormatsSetupMock, createFieldFormatsStartMock } from './field_formats/mocks'; +import { createIndexPatternsStartMock } from './index_patterns/mocks'; function createSetupContract() { return { @@ -31,6 +32,7 @@ function createStartContract() { return { search: createSearchStartMock(), fieldFormats: createFieldFormatsStartMock(), + indexPatterns: createIndexPatternsStartMock(), }; } diff --git a/src/plugins/data/server/plugin.ts b/src/plugins/data/server/plugin.ts index 0edce458f1c6b..bcf1f4f8ab60b 100644 --- a/src/plugins/data/server/plugin.ts +++ b/src/plugins/data/server/plugin.ts @@ -17,9 +17,15 @@ * under the License. */ -import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../core/server'; +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + Logger, +} from '../../../core/server'; import { ConfigSchema } from '../config'; -import { IndexPatternsService } from './index_patterns'; +import { IndexPatternsService, IndexPatternsServiceStart } from './index_patterns'; import { ISearchSetup, ISearchStart } from './search'; import { SearchService } from './search/search_service'; import { QueryService } from './query/query_service'; @@ -38,6 +44,7 @@ export interface DataPluginSetup { export interface DataPluginStart { search: ISearchStart; fieldFormats: FieldFormatsStart; + indexPatterns: IndexPatternsServiceStart; } export interface DataPluginSetupDependencies { @@ -52,12 +59,14 @@ export class DataServerPlugin implements Plugin) { this.searchService = new SearchService(initializerContext); this.scriptsService = new ScriptsService(); this.kqlTelemetryService = new KqlTelemetryService(initializerContext); this.autocompleteService = new AutocompleteService(initializerContext); + this.logger = initializerContext.logger.get('data'); } public setup( @@ -79,9 +88,14 @@ export class DataServerPlugin implements Plugin { fieldFormats: { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; + indexPatterns: { + indexPatternsServiceFactory: (kibanaRequest: import("../../../core/server").KibanaRequest) => Promise; + }; }; // (undocumented) stop(): void; @@ -673,6 +694,10 @@ export interface PluginStart { // // (undocumented) fieldFormats: FieldFormatsStart; + // Warning: (ae-forgotten-export) The symbol "IndexPatternsServiceStart" needs to be exported by the entry point index.d.ts + // + // (undocumented) + indexPatterns: IndexPatternsServiceStart; // (undocumented) search: ISearchStart; } diff --git a/src/plugins/dev_tools/public/application.tsx b/src/plugins/dev_tools/public/application.tsx index 1a9d6bf4848f4..788ec1f145e2a 100644 --- a/src/plugins/dev_tools/public/application.tsx +++ b/src/plugins/dev_tools/public/application.tsx @@ -16,21 +16,21 @@ * specific language governing permissions and limitations * under the License. */ + +import React, { useEffect, useRef } from 'react'; +import ReactDOM from 'react-dom'; +import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom'; +import { EuiTab, EuiTabs, EuiToolTip } from '@elastic/eui'; import { I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { EuiTab, EuiTabs, EuiToolTip } from '@elastic/eui'; -import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom'; -import * as React from 'react'; -import ReactDOM from 'react-dom'; -import { useEffect, useRef } from 'react'; -import { AppMountContext, AppMountDeprecated, ScopedHistory } from 'kibana/public'; +import { ApplicationStart, ChromeStart, ScopedHistory } from 'src/core/public'; + import { DevToolApp } from './dev_tool'; interface DevToolsWrapperProps { devTools: readonly DevToolApp[]; activeDevTool: DevToolApp; - appMountContext: AppMountContext; updateRoute: (newRoute: string) => void; } @@ -40,12 +40,7 @@ interface MountedDevToolDescriptor { unmountHandler: () => void; } -function DevToolsWrapper({ - devTools, - activeDevTool, - appMountContext, - updateRoute, -}: DevToolsWrapperProps) { +function DevToolsWrapper({ devTools, activeDevTool, updateRoute }: DevToolsWrapperProps) { const mountedTool = useRef(null); useEffect( @@ -90,6 +85,7 @@ function DevToolsWrapper({ if (mountedTool.current) { mountedTool.current.unmountHandler(); } + const params = { element, appBasePath: '', @@ -97,9 +93,9 @@ function DevToolsWrapper({ // TODO: adapt to use Core's ScopedHistory history: {} as any, }; - const unmountHandler = isAppMountDeprecated(activeDevTool.mount) - ? await activeDevTool.mount(appMountContext, params) - : await activeDevTool.mount(params); + + const unmountHandler = await activeDevTool.mount(params); + mountedTool.current = { devTool: activeDevTool, mountpoint: element, @@ -112,19 +108,20 @@ function DevToolsWrapper({ ); } -function redirectOnMissingCapabilities(appMountContext: AppMountContext) { - if (!appMountContext.core.application.capabilities.dev_tools.show) { - appMountContext.core.application.navigateToApp('home'); +function redirectOnMissingCapabilities(application: ApplicationStart) { + if (!application.capabilities.dev_tools.show) { + application.navigateToApp('home'); return true; } return false; } -function setBadge(appMountContext: AppMountContext) { - if (appMountContext.core.application.capabilities.dev_tools.save) { +function setBadge(application: ApplicationStart, chrome: ChromeStart) { + if (application.capabilities.dev_tools.save) { return; } - appMountContext.core.chrome.setBadge({ + + chrome.setBadge({ text: i18n.translate('devTools.badge.readOnly.text', { defaultMessage: 'Read only', }), @@ -135,16 +132,16 @@ function setBadge(appMountContext: AppMountContext) { }); } -function setTitle(appMountContext: AppMountContext) { - appMountContext.core.chrome.docTitle.change( +function setTitle(chrome: ChromeStart) { + chrome.docTitle.change( i18n.translate('devTools.pageTitle', { defaultMessage: 'Dev Tools', }) ); } -function setBreadcrumbs(appMountContext: AppMountContext) { - appMountContext.core.chrome.setBreadcrumbs([ +function setBreadcrumbs(chrome: ChromeStart) { + chrome.setBreadcrumbs([ { text: i18n.translate('devTools.k7BreadcrumbsDevToolsLabel', { defaultMessage: 'Dev Tools', @@ -156,16 +153,19 @@ function setBreadcrumbs(appMountContext: AppMountContext) { export function renderApp( element: HTMLElement, - appMountContext: AppMountContext, + application: ApplicationStart, + chrome: ChromeStart, history: ScopedHistory, devTools: readonly DevToolApp[] ) { - if (redirectOnMissingCapabilities(appMountContext)) { + if (redirectOnMissingCapabilities(application)) { return () => {}; } - setBadge(appMountContext); - setBreadcrumbs(appMountContext); - setTitle(appMountContext); + + setBadge(application, chrome); + setBreadcrumbs(chrome); + setTitle(chrome); + ReactDOM.render( @@ -183,7 +183,6 @@ export function renderApp( updateRoute={props.history.push} activeDevTool={devTool} devTools={devTools} - appMountContext={appMountContext} /> )} /> @@ -208,8 +207,3 @@ export function renderApp( unlisten(); }; } - -function isAppMountDeprecated(mount: (...args: any[]) => any): mount is AppMountDeprecated { - // Mount functions with two arguments are assumed to expect deprecated `context` object. - return mount.length === 2; -} diff --git a/src/plugins/dev_tools/public/dev_tool.ts b/src/plugins/dev_tools/public/dev_tool.ts index 943cca286a722..932897cdd7861 100644 --- a/src/plugins/dev_tools/public/dev_tool.ts +++ b/src/plugins/dev_tools/public/dev_tool.ts @@ -16,7 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import { App } from 'kibana/public'; + +import { AppMount } from 'src/core/public'; /** * Descriptor for a dev tool. A dev tool works similar to an application @@ -38,7 +39,7 @@ export class DevToolApp { * This will be used as a label in the tab above the actual tool. */ public readonly title: string; - public readonly mount: App['mount']; + public readonly mount: AppMount; /** * Flag indicating to disable the tab of this dev tool. Navigating to a @@ -66,7 +67,7 @@ export class DevToolApp { constructor( id: string, title: string, - mount: App['mount'], + mount: AppMount, enableRouting: boolean, order: number, toolTipContent = '', diff --git a/src/plugins/dev_tools/public/plugin.ts b/src/plugins/dev_tools/public/plugin.ts index 130d07b441b83..3ee44aaa0816e 100644 --- a/src/plugins/dev_tools/public/plugin.ts +++ b/src/plugins/dev_tools/public/plugin.ts @@ -18,12 +18,14 @@ */ import { BehaviorSubject } from 'rxjs'; -import { AppUpdater, CoreSetup, Plugin } from 'kibana/public'; +import { Plugin, CoreSetup, AppMountParameters } from 'src/core/public'; +import { AppUpdater } from 'kibana/public'; import { i18n } from '@kbn/i18n'; import { sortBy } from 'lodash'; + +import { AppNavLinkStatus, DEFAULT_APP_CATEGORIES } from '../../../core/public'; import { KibanaLegacySetup } from '../../kibana_legacy/public'; import { CreateDevToolArgs, DevToolApp, createDevToolApp } from './dev_tool'; -import { AppNavLinkStatus, DEFAULT_APP_CATEGORIES } from '../../../core/public'; import './index.scss'; @@ -49,8 +51,10 @@ export class DevToolsPlugin implements Plugin { return sortBy([...this.devTools.values()], 'order'); } - public setup(core: CoreSetup, { kibanaLegacy }: { kibanaLegacy: KibanaLegacySetup }) { - core.application.register({ + public setup(coreSetup: CoreSetup, { kibanaLegacy }: { kibanaLegacy: KibanaLegacySetup }) { + const { application: applicationSetup, getStartServices } = coreSetup; + + applicationSetup.register({ id: 'dev_tools', title: i18n.translate('devTools.devToolsTitle', { defaultMessage: 'Dev Tools', @@ -59,15 +63,18 @@ export class DevToolsPlugin implements Plugin { euiIconType: 'devToolsApp', order: 9001, category: DEFAULT_APP_CATEGORIES.management, - mount: async (appMountContext, params) => { - if (!this.getSortedDevTools) { - throw new Error('not started yet'); - } + mount: async (params: AppMountParameters) => { + const { element, history } = params; + element.classList.add('devAppWrapper'); + + const [core] = await getStartServices(); + const { application, chrome } = core; + const { renderApp } = await import('./application'); - params.element.classList.add('devAppWrapper'); - return renderApp(params.element, appMountContext, params.history, this.getSortedDevTools()); + return renderApp(element, application, chrome, history, this.getSortedDevTools()); }, }); + kibanaLegacy.forwardApp('dev_tools', 'dev_tools'); return { diff --git a/src/plugins/discover/public/application/_discover.scss b/src/plugins/discover/public/application/_discover.scss index b0f3dfaf96c4f..1aaa0a24357ed 100644 --- a/src/plugins/discover/public/application/_discover.scss +++ b/src/plugins/discover/public/application/_discover.scss @@ -100,16 +100,6 @@ discover-app { .dscSkipButton { position: absolute; - left: -10000px; + right: $euiSizeM; top: $euiSizeXS; - width: 1px; - height: 1px; - overflow: hidden; - - &:focus { - left: initial; - right: $euiSize; - width: auto; - height: auto; - } } diff --git a/src/plugins/discover/public/application/angular/context_app.html b/src/plugins/discover/public/application/angular/context_app.html index 9c37fd3bfc5be..6adcaeeae94f5 100644 --- a/src/plugins/discover/public/application/angular/context_app.html +++ b/src/plugins/discover/public/application/angular/context_app.html @@ -12,44 +12,10 @@ -
-
-
- - -
- -
-
-
-
- -
-
-
-
+ +
{{screenTitle}}
- + {{screenTitle}} on-remove-column="removeColumn" > - +
{ + bottomMarker.focus(); + // The anchor tag is not technically empty (it's a hack to make Safari scroll) + // so the browser will show a highlight: remove the focus once scrolled + $timeout(() => { + bottomMarker.blur(); + }, 0); + }, 0); + }; + $scope.newQuery = function () { history.push('/'); }; @@ -995,7 +1011,11 @@ function discoverController( $scope.indexPattern.popularizeField(columnName, 1); } const columns = columnActions.removeColumn($scope.state.columns, columnName); - setAppState({ columns }); + // The state's sort property is an array of [sortByColumn,sortDirection] + const sort = $scope.state.sort.length + ? $scope.state.sort.filter((subArr) => subArr[0] !== columnName) + : []; + setAppState({ columns, sort }); }; $scope.moveColumn = function moveColumn(columnName, newIndex) { @@ -1007,17 +1027,6 @@ function discoverController( $window.scrollTo(0, 0); }; - $scope.scrollToBottom = function () { - // delay scrolling to after the rows have been rendered - $timeout(() => { - $element.find('#discoverBottomMarker').focus(); - }, 0); - }; - - $scope.showAllRows = function () { - $scope.minimumVisibleRows = $scope.hits; - }; - async function setupVisualization() { // If no timefield has been specified we don't create a histogram of messages if (!getTimeField()) return; diff --git a/src/plugins/discover/public/application/components/context_error_message/context_error_message.test.tsx b/src/plugins/discover/public/application/components/context_error_message/context_error_message.test.tsx new file mode 100644 index 0000000000000..1c9439bc34e58 --- /dev/null +++ b/src/plugins/discover/public/application/components/context_error_message/context_error_message.test.tsx @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { ReactWrapper } from 'enzyme'; +import { ContextErrorMessage } from './context_error_message'; +// @ts-ignore +import { FAILURE_REASONS, LOADING_STATUS } from '../../angular/context/query'; +// @ts-ignore +import { findTestSubject } from '@elastic/eui/lib/test'; + +describe('loading spinner', function () { + let component: ReactWrapper; + + it('ContextErrorMessage does not render on loading', () => { + component = mountWithIntl(); + expect(findTestSubject(component, 'contextErrorMessageTitle').length).toBe(0); + }); + + it('ContextErrorMessage does not render on success loading', () => { + component = mountWithIntl(); + expect(findTestSubject(component, 'contextErrorMessageTitle').length).toBe(0); + }); + + it('ContextErrorMessage renders just the title if the reason is not specifically handled', () => { + component = mountWithIntl(); + expect(findTestSubject(component, 'contextErrorMessageTitle').length).toBe(1); + expect(findTestSubject(component, 'contextErrorMessageBody').text()).toBe(''); + }); + + it('ContextErrorMessage renders the reason for unknown errors', () => { + component = mountWithIntl( + + ); + expect(findTestSubject(component, 'contextErrorMessageTitle').length).toBe(1); + expect(findTestSubject(component, 'contextErrorMessageBody').length).toBe(1); + }); +}); diff --git a/src/plugins/discover/public/application/components/context_error_message/context_error_message.tsx b/src/plugins/discover/public/application/components/context_error_message/context_error_message.tsx new file mode 100644 index 0000000000000..f73496c2eeada --- /dev/null +++ b/src/plugins/discover/public/application/components/context_error_message/context_error_message.tsx @@ -0,0 +1,64 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { EuiCallOut, EuiText } from '@elastic/eui'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; +// @ts-ignore +import { FAILURE_REASONS, LOADING_STATUS } from '../../angular/context/query'; + +export interface ContextErrorMessageProps { + /** + * the status of the loading action + */ + status: string; + /** + * the reason of the error + */ + reason?: string; +} + +export function ContextErrorMessage({ status, reason }: ContextErrorMessageProps) { + if (status !== LOADING_STATUS.FAILED) { + return null; + } + return ( + + + } + color="danger" + iconType="alert" + data-test-subj="contextErrorMessageTitle" + > + + {reason === FAILURE_REASONS.UNKNOWN && ( + + )} + + + + ); +} diff --git a/src/plugins/discover/public/application/components/context_error_message/context_error_message_directive.ts b/src/plugins/discover/public/application/components/context_error_message/context_error_message_directive.ts new file mode 100644 index 0000000000000..925d560761a84 --- /dev/null +++ b/src/plugins/discover/public/application/components/context_error_message/context_error_message_directive.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { ContextErrorMessage } from './context_error_message'; + +export function createContextErrorMessageDirective(reactDirective: any) { + return reactDirective(ContextErrorMessage, [ + ['status', { watchDepth: 'reference' }], + ['reason', { watchDepth: 'reference' }], + ]); +} diff --git a/src/plugins/discover/public/application/components/context_error_message/index.ts b/src/plugins/discover/public/application/components/context_error_message/index.ts new file mode 100644 index 0000000000000..f20f2ccf8afa0 --- /dev/null +++ b/src/plugins/discover/public/application/components/context_error_message/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { ContextErrorMessage } from './context_error_message'; +export { createContextErrorMessageDirective } from './context_error_message_directive'; diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_bucket.scss b/src/plugins/discover/public/application/components/sidebar/discover_field_bucket.scss new file mode 100644 index 0000000000000..90b645f70084e --- /dev/null +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_bucket.scss @@ -0,0 +1,4 @@ +.dscFieldDetails__barContainer { + // Constrains value to the flex item, and allows for truncation when necessary + min-width: 0; +} diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_bucket.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_bucket.tsx index 398a945e0f876..281fc9a392d7d 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_bucket.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_bucket.tsx @@ -17,11 +17,12 @@ * under the License. */ import React from 'react'; -import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { EuiText, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { StringFieldProgressBar } from './string_progress_bar'; import { Bucket } from './types'; import { IndexPatternField } from '../../../../../data/public'; +import './discover_field_bucket.scss'; interface Props { bucket: Bucket; @@ -47,18 +48,40 @@ export function DiscoverFieldBucket({ field, bucket, onAddFilter }: Props) { return ( <> - - - - {bucket.display === '' ? emptyTxt : bucket.display} - + + + + + + {bucket.display === '' ? emptyTxt : bucket.display} + + + + + {bucket.percent}% + + + + {field.filterable && (
onAddFilter(field, bucket.value, '+')} aria-label={addLabel} data-test-subj={`plus-${field.name}-${bucket.value}`} @@ -73,7 +96,7 @@ export function DiscoverFieldBucket({ field, bucket, onAddFilter }: Props) { /> onAddFilter(field, bucket.value, '-')} aria-label={removeLabel} data-test-subj={`minus-${field.name}-${bucket.value}`} @@ -90,7 +113,7 @@ export function DiscoverFieldBucket({ field, bucket, onAddFilter }: Props) { )} - + ); } diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx index b56f7ba8a852f..dd95a45f71626 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_details.tsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { EuiLink, EuiSpacer, EuiIconTip, EuiText } from '@elastic/eui'; +import { EuiLink, EuiIconTip, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { DiscoverFieldBucket } from './discover_field_bucket'; import { getWarnings } from './lib/get_warnings'; @@ -78,7 +78,6 @@ export function DiscoverFieldDetails({ {details.visualizeUrl && ( <> - { getServices().core.application.navigateToApp(details.visualizeUrl.app, { diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss index 9f7700c7f395c..ae7e915f09773 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss @@ -121,14 +121,6 @@ } } -/* - Fixes EUI known issue https://github.com/elastic/eui/issues/1749 -*/ -.dscProgressBarTooltip__anchor { - display: block; -} - - .dscFieldSearch { padding: $euiSizeS; } diff --git a/src/plugins/discover/public/application/components/sidebar/string_progress_bar.tsx b/src/plugins/discover/public/application/components/sidebar/string_progress_bar.tsx index 7ea41aa4bf270..c8693727b0725 100644 --- a/src/plugins/discover/public/application/components/sidebar/string_progress_bar.tsx +++ b/src/plugins/discover/public/application/components/sidebar/string_progress_bar.tsx @@ -17,35 +17,18 @@ * under the License. */ import React from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiText, EuiToolTip } from '@elastic/eui'; +import { EuiProgress } from '@elastic/eui'; interface Props { percent: number; count: number; + value: string; } -export function StringFieldProgressBar(props: Props) { +export function StringFieldProgressBar({ value, percent, count }: Props) { + const ariaLabel = `${value}: ${count} (${percent}%)`; + return ( - - - - - - - {props.percent}% - - - + ); } diff --git a/src/plugins/discover/public/application/components/skip_bottom_button/index.ts b/src/plugins/discover/public/application/components/skip_bottom_button/index.ts new file mode 100644 index 0000000000000..2feaa35e0d61f --- /dev/null +++ b/src/plugins/discover/public/application/components/skip_bottom_button/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { SkipBottomButton } from './skip_bottom_button'; +export { createSkipBottomButtonDirective } from './skip_bottom_button_directive'; diff --git a/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.test.tsx b/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.test.tsx new file mode 100644 index 0000000000000..bf417f9f1890b --- /dev/null +++ b/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.test.tsx @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { ReactWrapper } from 'enzyme'; +import { SkipBottomButton, SkipBottomButtonProps } from './skip_bottom_button'; +// @ts-ignore +import { findTestSubject } from '@elastic/eui/lib/test'; + +describe('Skip to Bottom Button', function () { + let props: SkipBottomButtonProps; + let component: ReactWrapper; + + beforeAll(() => { + props = { + onClick: jest.fn(), + }; + }); + + it('should be clickable', function () { + component = mountWithIntl(); + component.simulate('click'); + expect(props.onClick).toHaveBeenCalled(); + }); +}); diff --git a/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.tsx b/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.tsx new file mode 100644 index 0000000000000..ccf05ca031a8d --- /dev/null +++ b/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button.tsx @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { EuiSkipLink } from '@elastic/eui'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; + +export interface SkipBottomButtonProps { + /** + * Action to perform on click + */ + onClick: () => void; +} + +export function SkipBottomButton({ onClick }: SkipBottomButtonProps) { + return ( + + { + // prevent the anchor to reload the page on click + event.preventDefault(); + // The destinationId prop cannot be leveraged here as the table needs + // to be updated first (angular logic) + onClick(); + }} + className="dscSkipButton" + destinationId="" + > + + + + ); +} diff --git a/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button_directive.ts b/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button_directive.ts new file mode 100644 index 0000000000000..27f17b25fd447 --- /dev/null +++ b/src/plugins/discover/public/application/components/skip_bottom_button/skip_bottom_button_directive.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { SkipBottomButton } from './skip_bottom_button'; + +export function createSkipBottomButtonDirective(reactDirective: any) { + return reactDirective(SkipBottomButton, [['onClick', { watchDepth: 'reference' }]]); +} diff --git a/src/plugins/discover/public/get_inner_angular.ts b/src/plugins/discover/public/get_inner_angular.ts index 2b4705645cfcc..0b3c2fad8d45b 100644 --- a/src/plugins/discover/public/get_inner_angular.ts +++ b/src/plugins/discover/public/get_inner_angular.ts @@ -61,8 +61,10 @@ import { createDiscoverSidebarDirective } from './application/components/sidebar import { createHitsCounterDirective } from '././application/components/hits_counter'; import { createLoadingSpinnerDirective } from '././application/components/loading_spinner/loading_spinner'; import { createTimechartHeaderDirective } from './application/components/timechart_header'; +import { createContextErrorMessageDirective } from './application/components/context_error_message'; import { DiscoverStartPlugins } from './plugin'; import { getScopedHistory } from './kibana_services'; +import { createSkipBottomButtonDirective } from './application/components/skip_bottom_button'; /** * returns the main inner angular module, it contains all the parts of Angular Discover @@ -155,9 +157,11 @@ export function initializeInnerAngularModule( .directive('fixedScroll', FixedScrollProvider) .directive('renderComplete', createRenderCompleteDirective) .directive('discoverSidebar', createDiscoverSidebarDirective) + .directive('skipBottomButton', createSkipBottomButtonDirective) .directive('hitsCounter', createHitsCounterDirective) .directive('loadingSpinner', createLoadingSpinnerDirective) .directive('timechartHeader', createTimechartHeaderDirective) + .directive('contextErrorMessage', createContextErrorMessageDirective) .service('debounce', ['$timeout', DebounceProviderTimeout]); } diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index f19974942c43d..6960550b59d1c 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -48,8 +48,8 @@ export { EmbeddableOutput, EmbeddablePanel, EmbeddableRoot, - ValueClickTriggerContext, - RangeSelectTriggerContext, + ValueClickContext, + RangeSelectContext, ErrorEmbeddable, IContainer, IEmbeddable, diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts index 5bb96a708b7ac..ccba5cf771088 100644 --- a/src/plugins/embeddable/public/lib/triggers/triggers.ts +++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts @@ -25,7 +25,7 @@ export interface EmbeddableContext { embeddable: IEmbeddable; } -export interface ValueClickTriggerContext { +export interface ValueClickContext { embeddable?: T; data: { data: Array<{ @@ -39,7 +39,7 @@ export interface ValueClickTriggerContext { }; } -export interface RangeSelectTriggerContext { +export interface RangeSelectContext { embeddable?: T; data: { table: KibanaDatatable; @@ -50,16 +50,16 @@ export interface RangeSelectTriggerContext } export type ChartActionContext = - | ValueClickTriggerContext - | RangeSelectTriggerContext; + | ValueClickContext + | RangeSelectContext; export const isValueClickTriggerContext = ( context: ChartActionContext -): context is ValueClickTriggerContext => context.data && 'data' in context.data; +): context is ValueClickContext => context.data && 'data' in context.data; export const isRangeSelectTriggerContext = ( context: ChartActionContext -): context is RangeSelectTriggerContext => context.data && 'range' in context.data; +): context is RangeSelectContext => context.data && 'range' in context.data; export const CONTEXT_MENU_TRIGGER = 'CONTEXT_MENU_TRIGGER'; export const contextMenuTrigger: Trigger<'CONTEXT_MENU_TRIGGER'> = { diff --git a/src/plugins/expressions/server/legacy.ts b/src/plugins/expressions/server/legacy.ts index 4dd9419e59e2d..8ff08542f18f8 100644 --- a/src/plugins/expressions/server/legacy.ts +++ b/src/plugins/expressions/server/legacy.ts @@ -26,7 +26,7 @@ import { register, registryFactory, Registry, Fn } from '@kbn/interpreter/common import Boom from 'boom'; import { schema } from '@kbn/config-schema'; -import { CoreSetup, Logger, APICaller } from 'src/core/server'; +import { CoreSetup, Logger, LegacyAPICaller } from 'src/core/server'; import { ExpressionsServerSetupDependencies } from './plugin'; import { typeSpecs, ExpressionType } from '../common'; import { serializeProvider } from '../common'; @@ -98,7 +98,7 @@ export const createLegacyServerEndpoints = ( * @param {*} fnCall - Describes the function being run `{ functionName, args, context }` */ async function runFunction( - handlers: { environment: string; elasticsearchClient: APICaller }, + handlers: { environment: string; elasticsearchClient: LegacyAPICaller }, fnCall: any ) { const { functionName, args, context } = fnCall; diff --git a/src/plugins/home/server/capabilities_provider.ts b/src/plugins/home/server/capabilities_provider.ts index 1c662e2301e16..5e86e0ca54bf7 100644 --- a/src/plugins/home/server/capabilities_provider.ts +++ b/src/plugins/home/server/capabilities_provider.ts @@ -24,6 +24,6 @@ export const capabilitiesProvider = () => ({ visualize: true, console: true, advanced_settings: true, - index_patterns: true, + indexPatterns: true, }, }); diff --git a/src/plugins/home/server/services/sample_data/usage/collector.ts b/src/plugins/home/server/services/sample_data/usage/collector.ts index 19ceceb4cba14..d819d67a8d432 100644 --- a/src/plugins/home/server/services/sample_data/usage/collector.ts +++ b/src/plugins/home/server/services/sample_data/usage/collector.ts @@ -19,7 +19,7 @@ import { PluginInitializerContext } from 'kibana/server'; import { first } from 'rxjs/operators'; -import { fetchProvider } from './collector_fetch'; +import { fetchProvider, TelemetryResponse } from './collector_fetch'; import { UsageCollectionSetup } from '../../../../../usage_collection/server'; export async function makeSampleDataUsageCollector( @@ -33,10 +33,18 @@ export async function makeSampleDataUsageCollector( } catch (err) { return; // kibana plugin is not enabled (test environment) } - const collector = usageCollection.makeUsageCollector({ + const collector = usageCollection.makeUsageCollector({ type: 'sample-data', fetch: fetchProvider(index), isReady: () => true, + schema: { + installed: { type: 'keyword' }, + last_install_date: { type: 'date' }, + last_install_set: { type: 'keyword' }, + last_uninstall_date: { type: 'date' }, + last_uninstall_set: { type: 'keyword' }, + uninstalled: { type: 'keyword' }, + }, }); usageCollection.registerCollector(collector); diff --git a/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts b/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts index 4c7316c853018..d43458cfc64db 100644 --- a/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts +++ b/src/plugins/home/server/services/sample_data/usage/collector_fetch.ts @@ -31,7 +31,7 @@ interface SearchHit { }; } -interface TelemetryResponse { +export interface TelemetryResponse { installed: string[]; uninstalled: string[]; last_install_date: moment.Moment | null; diff --git a/src/plugins/index_pattern_management/public/plugin.ts b/src/plugins/index_pattern_management/public/plugin.ts index a98cc05a0a80a..6e93d23f8469c 100644 --- a/src/plugins/index_pattern_management/public/plugin.ts +++ b/src/plugins/index_pattern_management/public/plugin.ts @@ -27,7 +27,7 @@ import { IndexPatternManagementServiceStart, } from './service'; -import { ManagementSetup, ManagementApp, ManagementSectionId } from '../../management/public'; +import { ManagementSetup, ManagementSectionId } from '../../management/public'; export interface IndexPatternManagementSetupDependencies { management: ManagementSetup; @@ -57,7 +57,6 @@ export class IndexPatternManagementPlugin IndexPatternManagementStartDependencies > { private readonly indexPatternManagementService = new IndexPatternManagementService(); - private managementApp?: ManagementApp; constructor(initializerContext: PluginInitializerContext) {} @@ -80,7 +79,7 @@ export class IndexPatternManagementPlugin return pathInApp && `/patterns${pathInApp}`; }); - this.managementApp = kibanaSection.registerApp({ + kibanaSection.registerApp({ id: IPM_APP_ID, title: sectionsHeader, order: 0, @@ -95,10 +94,6 @@ export class IndexPatternManagementPlugin } public start(core: CoreStart, plugins: IndexPatternManagementStartDependencies) { - if (!core.application.capabilities.management.kibana.index_patterns) { - this.managementApp!.disable(); - } - return this.indexPatternManagementService.start(); } diff --git a/src/plugins/input_control_vis/public/input_control_vis_type.ts b/src/plugins/input_control_vis/public/input_control_vis_type.ts index 8114dbf110f8b..2af53ea4d28e8 100644 --- a/src/plugins/input_control_vis/public/input_control_vis_type.ts +++ b/src/plugins/input_control_vis/public/input_control_vis_type.ts @@ -23,7 +23,6 @@ import { createInputControlVisController } from './vis_controller'; import { getControlsTab } from './components/editor/controls_tab'; import { OptionsTab } from './components/editor/options_tab'; import { InputControlVisDependencies } from './plugin'; -import { defaultFeedbackMessage } from '../../kibana_utils/public'; export function createInputControlVisTypeDefinition(deps: InputControlVisDependencies) { const InputControlVisController = createInputControlVisController(deps); @@ -39,7 +38,6 @@ export function createInputControlVisTypeDefinition(deps: InputControlVisDepende defaultMessage: 'Create interactive controls for easy dashboard manipulation.', }), stage: 'experimental', - feedbackMessage: defaultFeedbackMessage, visualization: InputControlVisController, visConfig: { defaults: { diff --git a/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx b/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx index 8187e70b1bbd1..63b9b48ec809e 100644 --- a/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx +++ b/src/plugins/kibana_react/public/validated_range/validated_dual_range.tsx @@ -17,7 +17,7 @@ * under the License. */ import { i18n } from '@kbn/i18n'; -import React, { Component, createRef } from 'react'; +import React, { Component } from 'react'; import { EuiFormRow, EuiDualRange } from '@elastic/eui'; import { EuiFormRowDisplayKeys } from '@elastic/eui/src/components/form/form_row/form_row'; import { EuiDualRangeProps } from '@elastic/eui/src/components/form/range/dual_range'; @@ -72,18 +72,6 @@ export class ValidatedDualRange extends Component { return null; } - // Can remove after eui#3412 is resolved - componentDidMount() { - if (this.trackRef.current) { - const track = this.trackRef.current.querySelector('.euiRangeTrack'); - if (track) { - track.setAttribute('aria-hidden', 'true'); - } - } - } - - trackRef = createRef(); - // @ts-ignore state populated by getDerivedStateFromProps state: State = {}; @@ -119,34 +107,32 @@ export class ValidatedDualRange extends Component { } = this.props; return ( -
- + - - -
+ value={this.state.value} + onChange={this._onChange} + minInputProps={{ + 'aria-label': i18n.translate('kibana-react.dualRangeControl.minInputAriaLabel', { + defaultMessage: 'Range minimum', + }), + }} + maxInputProps={{ + 'aria-label': i18n.translate('kibana-react.dualRangeControl.maxInputAriaLabel', { + defaultMessage: 'Range maximum', + }), + }} + {...rest} + /> + ); } } diff --git a/src/plugins/kibana_usage_collection/common/constants.ts b/src/plugins/kibana_usage_collection/common/constants.ts index df0adfc52184b..c4e7eaac51cf4 100644 --- a/src/plugins/kibana_usage_collection/common/constants.ts +++ b/src/plugins/kibana_usage_collection/common/constants.ts @@ -20,27 +20,6 @@ export const PLUGIN_ID = 'kibanaUsageCollection'; export const PLUGIN_NAME = 'kibana_usage_collection'; -/** - * UI metric usage type - */ -export const UI_METRIC_USAGE_TYPE = 'ui_metric'; - -/** - * Application Usage type - */ -export const APPLICATION_USAGE_TYPE = 'application_usage'; - -/** - * The type name used within the Monitoring index to publish management stats. - */ -export const KIBANA_STACK_MANAGEMENT_STATS_TYPE = 'stack_management'; - -/** - * The type name used to publish Kibana usage stats. - * NOTE: this string shows as-is in the stats API as a field name for the kibana usage stats - */ -export const KIBANA_USAGE_TYPE = 'kibana'; - /** * The type name used to publish Kibana usage stats in the formatted as bulk. */ diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts index f52687038bbbc..1f22ab0100101 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.ts @@ -20,7 +20,6 @@ import moment from 'moment'; import { ISavedObjectsRepository, SavedObjectsServiceSetup } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { APPLICATION_USAGE_TYPE } from '../../../common/constants'; import { findAll } from '../find_all'; import { ApplicationUsageTotal, @@ -62,7 +61,7 @@ export function registerApplicationUsageCollector( registerMappings(registerType); const collector = usageCollection.makeUsageCollector({ - type: APPLICATION_USAGE_TYPE, + type: 'application_usage', isReady: () => typeof getSavedObjectsClient() !== 'undefined', fetch: async () => { const savedObjectsClient = getSavedObjectsClient(); diff --git a/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts b/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts index 804c8b0ed2026..1adc0dc6896fd 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts @@ -27,7 +27,7 @@ */ import { snakeCase } from 'lodash'; -import { APICaller } from 'kibana/server'; +import { LegacyAPICaller } from 'kibana/server'; const TYPES = [ 'dashboard', @@ -45,7 +45,7 @@ export interface KibanaSavedObjectCounts { } export async function getSavedObjectsCounts( - callCluster: APICaller, + callCluster: LegacyAPICaller, kibanaIndex: string // Typically '.kibana'. We might need a way to obtain it from the SavedObjects client (or the SavedObjects client to provide a way to run aggregations?) ): Promise { const savedObjectCountSearchParams = { diff --git a/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts index d0da6fcc523cc..9cc079a9325d5 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts @@ -21,7 +21,7 @@ import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; import { SharedGlobalConfig } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { KIBANA_STATS_TYPE, KIBANA_USAGE_TYPE } from '../../../common/constants'; +import { KIBANA_STATS_TYPE } from '../../../common/constants'; import { getSavedObjectsCounts } from './get_saved_object_counts'; export function getKibanaUsageCollector( @@ -29,7 +29,7 @@ export function getKibanaUsageCollector( legacyConfig$: Observable ) { return usageCollection.makeUsageCollector({ - type: KIBANA_USAGE_TYPE, + type: 'kibana', isReady: () => true, async fetch(callCluster) { const { diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts index 39cd351884955..3a777beebd90a 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts @@ -19,7 +19,6 @@ import { IUiSettingsClient } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { KIBANA_STACK_MANAGEMENT_STATS_TYPE } from '../../../common/constants'; export type UsageStats = Record; @@ -47,7 +46,7 @@ export function registerManagementUsageCollector( getUiSettingsClient: () => IUiSettingsClient | undefined ) { const collector = usageCollection.makeUsageCollector({ - type: KIBANA_STACK_MANAGEMENT_STATS_TYPE, + type: 'stack_management', isReady: () => typeof getUiSettingsClient() !== 'undefined', fetch: createCollectorFetch(getUiSettingsClient), }); diff --git a/src/plugins/kibana_usage_collection/server/collectors/ui_metric/telemetry_ui_metric_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/ui_metric/telemetry_ui_metric_collector.ts index 603742f612a6b..ec2f1bfdfc25f 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/ui_metric/telemetry_ui_metric_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/ui_metric/telemetry_ui_metric_collector.ts @@ -23,7 +23,6 @@ import { SavedObjectsServiceSetup, } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { UI_METRIC_USAGE_TYPE } from '../../../common/constants'; import { findAll } from '../find_all'; interface UIMetricsSavedObjects extends SavedObjectAttributes { @@ -49,7 +48,7 @@ export function registerUiMetricUsageCollector( }); const collector = usageCollection.makeUsageCollector({ - type: UI_METRIC_USAGE_TYPE, + type: 'ui_metric', fetch: async () => { const savedObjectsClient = getSavedObjectsClient(); if (typeof savedObjectsClient === 'undefined') { diff --git a/src/plugins/kibana_utils/common/index.ts b/src/plugins/kibana_utils/common/index.ts index 99daed98dbe64..c94021872b4e1 100644 --- a/src/plugins/kibana_utils/common/index.ts +++ b/src/plugins/kibana_utils/common/index.ts @@ -28,4 +28,3 @@ export { distinctUntilChangedWithInitialValue } from './distinct_until_changed_w export { url } from './url'; export { now } from './now'; export { calculateObjectHash } from './calculate_object_hash'; -export { defaultFeedbackMessage } from './default_feedback_message'; diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 6f61e2c228970..2911a9ae75689 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -31,7 +31,6 @@ export { UiComponentInstance, url, createGetterSetter, - defaultFeedbackMessage, } from '../common'; export * from './core'; export * from '../common/errors'; diff --git a/src/plugins/management/public/management_sections_service.test.ts b/src/plugins/management/public/management_sections_service.test.ts index 2c5d04883235c..fd56dd8a6ee27 100644 --- a/src/plugins/management/public/management_sections_service.test.ts +++ b/src/plugins/management/public/management_sections_service.test.ts @@ -27,9 +27,15 @@ describe('ManagementService', () => { managementService = new ManagementSectionsService(); }); + const capabilities = { + navLinks: {}, + catalogue: {}, + management: {}, + }; + test('Provides default sections', () => { managementService.setup(); - const start = managementService.start(); + const start = managementService.start({ capabilities }); expect(start.getAllSections().length).toEqual(6); expect(start.getSection(ManagementSectionId.Ingest)).toBeDefined(); @@ -48,7 +54,7 @@ describe('ManagementService', () => { expect(setup.getSection('test-section')).not.toBeUndefined(); // Start phase: - const start = managementService.start(); + const start = managementService.start({ capabilities }); expect(start.getSectionsEnabled().length).toEqual(7); @@ -56,4 +62,38 @@ describe('ManagementService', () => { expect(start.getSectionsEnabled().length).toEqual(6); }); + + test('Disables items that are not allowed by Capabilities', () => { + // Setup phase: + const setup = managementService.setup(); + const testSection = setup.register({ id: 'test-section', title: 'Test Section' }); + testSection.registerApp({ id: 'test-app-1', title: 'Test App 1', mount: jest.fn() }); + testSection.registerApp({ id: 'test-app-2', title: 'Test App 2', mount: jest.fn() }); + testSection.registerApp({ id: 'test-app-3', title: 'Test App 3', mount: jest.fn() }); + + expect(setup.getSection('test-section')).not.toBeUndefined(); + + // Start phase: + managementService.start({ + capabilities: { + navLinks: {}, + catalogue: {}, + management: { + ['test-section']: { + 'test-app-1': true, + 'test-app-2': false, + // test-app-3 intentionally left undefined. Should be enabled by default + }, + }, + }, + }); + + expect(testSection.apps).toHaveLength(3); + expect(testSection.getAppsEnabled().map((app) => app.id)).toMatchInlineSnapshot(` + Array [ + "test-app-1", + "test-app-3", + ] + `); + }); }); diff --git a/src/plugins/management/public/management_sections_service.ts b/src/plugins/management/public/management_sections_service.ts index 08a87b3e89f2b..d8d148a9247ff 100644 --- a/src/plugins/management/public/management_sections_service.ts +++ b/src/plugins/management/public/management_sections_service.ts @@ -21,7 +21,12 @@ import { ReactElement } from 'react'; import { ManagementSection, RegisterManagementSectionArgs } from './utils'; import { managementSections } from './components/management_sections'; -import { ManagementSectionId, SectionsServiceSetup, SectionsServiceStart } from './types'; +import { + ManagementSectionId, + SectionsServiceSetup, + SectionsServiceStart, + SectionsServiceStartDeps, +} from './types'; export class ManagementSectionsService { private sections: Map = new Map(); @@ -55,7 +60,18 @@ export class ManagementSectionsService { }; } - start(): SectionsServiceStart { + start({ capabilities }: SectionsServiceStartDeps): SectionsServiceStart { + this.getAllSections().forEach((section) => { + if (capabilities.management.hasOwnProperty(section.id)) { + const sectionCapabilities = capabilities.management[section.id]; + section.apps.forEach((app) => { + if (sectionCapabilities.hasOwnProperty(app.id) && sectionCapabilities[app.id] !== true) { + app.disable(); + } + }); + } + }); + return { getSection: this.getSection, getAllSections: this.getAllSections, diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index 71656d7c0b83b..dada4636e6add 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -82,7 +82,7 @@ export class ManagementPlugin implements Plugin ManagementSection; + register: (args: Omit) => ManagementSection; getSection: (sectionId: ManagementSectionId | string) => ManagementSection; } diff --git a/src/plugins/management/server/capabilities_provider.ts b/src/plugins/management/server/capabilities_provider.ts index 9a69749c8233b..254ac3ed04dd7 100644 --- a/src/plugins/management/server/capabilities_provider.ts +++ b/src/plugins/management/server/capabilities_provider.ts @@ -25,7 +25,7 @@ export const capabilitiesProvider = () => ({ */ kibana: { settings: true, - index_patterns: true, + indexPatterns: true, objects: true, }, }, diff --git a/src/plugins/maps_legacy/public/index.ts b/src/plugins/maps_legacy/public/index.ts index cbe8b9213d577..6b9c7d1c52db9 100644 --- a/src/plugins/maps_legacy/public/index.ts +++ b/src/plugins/maps_legacy/public/index.ts @@ -18,12 +18,10 @@ */ // @ts-ignore -import { CoreSetup, PluginInitializerContext } from 'kibana/public'; +import { PluginInitializerContext } from 'kibana/public'; // @ts-ignore import { L } from './leaflet'; -// @ts-ignore -import { KibanaMap } from './map/kibana_map'; -import { bindSetupCoreAndPlugins, MapsLegacyPlugin } from './plugin'; +import { MapsLegacyPlugin } from './plugin'; // @ts-ignore import * as colorUtil from './map/color_util'; // @ts-ignore @@ -32,8 +30,6 @@ import { KibanaMapLayer } from './map/kibana_map_layer'; import { convertToGeoJson } from './map/convert_to_geojson'; // @ts-ignore import { scaleBounds, getPrecision, geoContains } from './map/decode_geo_hash'; -// @ts-ignore -import { BaseMapsVisualizationProvider } from './map/base_maps_visualization'; import { VectorLayer, FileLayerField, @@ -75,20 +71,6 @@ export { L, }; -// Due to a leaflet/leaflet-draw bug, it's not possible to consume leaflet maps w/ draw control -// through a pipeline leveraging angular. For this reason, client plugins need to -// init kibana map and the basemaps visualization directly rather than consume through -// the usual plugin interface -export function getKibanaMapFactoryProvider(core: CoreSetup) { - bindSetupCoreAndPlugins(core); - return (...args: any) => new KibanaMap(...args); -} - -export function getBaseMapsVis(core: CoreSetup, serviceSettings: IServiceSettings) { - const getKibanaMap = getKibanaMapFactoryProvider(core); - return new BaseMapsVisualizationProvider(getKibanaMap, serviceSettings); -} - export * from './common/types'; export { ORIGIN } from './common/constants/origin'; diff --git a/src/plugins/maps_legacy/public/kibana_services.js b/src/plugins/maps_legacy/public/kibana_services.js index e0a6a6e21ab00..256b5f386d5f7 100644 --- a/src/plugins/maps_legacy/public/kibana_services.js +++ b/src/plugins/maps_legacy/public/kibana_services.js @@ -25,6 +25,12 @@ let uiSettings; export const setUiSettings = (coreUiSettings) => (uiSettings = coreUiSettings); export const getUiSettings = () => uiSettings; -let getInjectedVar; -export const setInjectedVarFunc = (getInjectedVarFunc) => (getInjectedVar = getInjectedVarFunc); -export const getInjectedVarFunc = () => getInjectedVar; +let kibanaVersion; +export const setKibanaVersion = (version) => (kibanaVersion = version); +export const getKibanaVersion = () => kibanaVersion; + +let mapsLegacyConfig; +export const setMapsLegacyConfig = (config) => (mapsLegacyConfig = config); +export const getMapsLegacyConfig = () => mapsLegacyConfig; + +export const getEmsTileLayerId = () => getMapsLegacyConfig().emsTileLayerId; diff --git a/src/plugins/maps_legacy/public/map/base_maps_visualization.js b/src/plugins/maps_legacy/public/map/base_maps_visualization.js index 2d1a45beb5d87..2d78fdc246e19 100644 --- a/src/plugins/maps_legacy/public/map/base_maps_visualization.js +++ b/src/plugins/maps_legacy/public/map/base_maps_visualization.js @@ -21,7 +21,7 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import * as Rx from 'rxjs'; import { filter, first } from 'rxjs/operators'; -import { getInjectedVarFunc, getUiSettings, getToasts } from '../kibana_services'; +import { getEmsTileLayerId, getUiSettings, getToasts } from '../kibana_services'; const WMS_MINZOOM = 0; const WMS_MAXZOOM = 22; //increase this to 22. Better for WMS @@ -129,7 +129,7 @@ export function BaseMapsVisualizationProvider(getKibanaMap, mapServiceSettings) } async _updateBaseLayer() { - const emsTileLayerId = getInjectedVarFunc()('emsTileLayerId', true); + const emsTileLayerId = getEmsTileLayerId(); if (!this._kibanaMap) { return; diff --git a/src/plugins/maps_legacy/public/map/service_settings.js b/src/plugins/maps_legacy/public/map/service_settings.js index 7c2b841e4adf3..f4f88bd5807d5 100644 --- a/src/plugins/maps_legacy/public/map/service_settings.js +++ b/src/plugins/maps_legacy/public/map/service_settings.js @@ -21,22 +21,20 @@ import _ from 'lodash'; import MarkdownIt from 'markdown-it'; import { EMSClient } from '@elastic/ems-client'; import { i18n } from '@kbn/i18n'; -import { getInjectedVarFunc } from '../kibana_services'; +import { getKibanaVersion } from '../kibana_services'; import { ORIGIN } from '../common/constants/origin'; const TMS_IN_YML_ID = 'TMS in config/kibana.yml'; export class ServiceSettings { constructor(mapConfig, tilemapsConfig) { - const getInjectedVar = getInjectedVarFunc(); this._mapConfig = mapConfig; this._tilemapsConfig = tilemapsConfig; - const kbnVersion = getInjectedVar('version'); this._showZoomMessage = true; this._emsClient = new EMSClient({ language: i18n.getLocale(), - appVersion: kbnVersion, + appVersion: getKibanaVersion(), appName: 'kibana', fileApiUrl: this._mapConfig.emsFileApiUrl, tileApiUrl: this._mapConfig.emsTileApiUrl, diff --git a/src/plugins/maps_legacy/public/plugin.ts b/src/plugins/maps_legacy/public/plugin.ts index 78c2498b9ee90..6b4e06fec9ccc 100644 --- a/src/plugins/maps_legacy/public/plugin.ts +++ b/src/plugins/maps_legacy/public/plugin.ts @@ -20,13 +20,17 @@ // @ts-ignore import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'kibana/public'; // @ts-ignore -import { setToasts, setUiSettings, setInjectedVarFunc } from './kibana_services'; +import { setToasts, setUiSettings, setKibanaVersion, setMapsLegacyConfig } from './kibana_services'; // @ts-ignore import { ServiceSettings } from './map/service_settings'; // @ts-ignore import { getPrecision, getZoomPrecision } from './map/precision'; +// @ts-ignore +import { KibanaMap } from './map/kibana_map'; import { MapsLegacyConfigType, MapsLegacyPluginSetup, MapsLegacyPluginStart } from './index'; import { ConfigSchema } from '../config'; +// @ts-ignore +import { BaseMapsVisualizationProvider } from './map/base_maps_visualization'; /** * These are the interfaces with your public contracts. You should export these @@ -34,10 +38,15 @@ import { ConfigSchema } from '../config'; * @public */ -export const bindSetupCoreAndPlugins = (core: CoreSetup) => { +export const bindSetupCoreAndPlugins = ( + core: CoreSetup, + config: MapsLegacyConfigType, + kibanaVersion: string +) => { setToasts(core.notifications.toasts); setUiSettings(core.uiSettings); - setInjectedVarFunc(core.injectedMetadata.getInjectedVar); + setKibanaVersion(kibanaVersion); + setMapsLegacyConfig(config); }; // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -53,15 +62,23 @@ export class MapsLegacyPlugin implements Plugin(); + const kibanaVersion = this._initializerContext.env.packageInfo.version; + + bindSetupCoreAndPlugins(core, config, kibanaVersion); + + const serviceSettings = new ServiceSettings(config, config.tilemap); + const getKibanaMapFactoryProvider = (...args: any) => new KibanaMap(...args); + const getBaseMapsVis = () => + new BaseMapsVisualizationProvider(getKibanaMapFactoryProvider, serviceSettings); return { - serviceSettings: new ServiceSettings(config, config.tilemap), + serviceSettings, getZoomPrecision, getPrecision, config, + getKibanaMapFactoryProvider, + getBaseMapsVis, }; } diff --git a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx index 2b7466ffd6ab3..a1653c5289255 100644 --- a/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx +++ b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx @@ -19,7 +19,7 @@ import { ButtonIconSide } from '@elastic/eui'; -export type TopNavMenuAction = (anchorElement: EventTarget) => void; +export type TopNavMenuAction = (anchorElement: HTMLElement) => void; export interface TopNavMenuData { id?: string; @@ -29,7 +29,7 @@ export interface TopNavMenuData { testId?: string; className?: string; disableButton?: boolean | (() => boolean); - tooltip?: string | (() => string); + tooltip?: string | (() => string | undefined); emphasize?: boolean; iconType?: string; iconSide?: ButtonIconSide; diff --git a/src/plugins/region_map/public/__tests__/region_map_visualization.js b/src/plugins/region_map/public/__tests__/region_map_visualization.js index 3dcfc7c2fc6fa..0a2a18c7cef4f 100644 --- a/src/plugins/region_map/public/__tests__/region_map_visualization.js +++ b/src/plugins/region_map/public/__tests__/region_map_visualization.js @@ -52,10 +52,11 @@ import { ExprVis } from '../../../visualizations/public/expressions/vis'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { BaseVisType } from '../../../visualizations/public/vis_types/base_vis_type'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { setInjectedVarFunc } from '../../../maps_legacy/public/kibana_services'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ServiceSettings } from '../../../maps_legacy/public/map/service_settings'; -import { getBaseMapsVis } from '../../../maps_legacy/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { BaseMapsVisualizationProvider } from '../../../maps_legacy/public/map/base_maps_visualization'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { KibanaMap } from '../../../maps_legacy/public/map/kibana_map'; const THRESHOLD = 0.45; const PIXEL_DIFF = 96; @@ -118,14 +119,6 @@ describe('RegionMapsVisualizationTests', function () { }, }, }; - setInjectedVarFunc((injectedVar) => { - switch (injectedVar) { - case 'version': - return '123'; - default: - return 'not found'; - } - }); const serviceSettings = new ServiceSettings(mapConfig, tilemapsConfig); const regionmapsConfig = { includeElasticMapsService: true, @@ -142,7 +135,10 @@ describe('RegionMapsVisualizationTests', function () { getInjectedVar: () => {}, }, }; - const BaseMapsVisualization = getBaseMapsVis(coreSetupMock, serviceSettings); + const BaseMapsVisualization = new BaseMapsVisualizationProvider( + (...args) => new KibanaMap(...args), + serviceSettings + ); dependencies = { serviceSettings, diff --git a/src/plugins/region_map/public/plugin.ts b/src/plugins/region_map/public/plugin.ts index 6b31de758a4ca..04a2ba2f23f4e 100644 --- a/src/plugins/region_map/public/plugin.ts +++ b/src/plugins/region_map/public/plugin.ts @@ -30,7 +30,7 @@ import { VisualizationsSetup } from '../../visualizations/public'; import { createRegionMapFn } from './region_map_fn'; // @ts-ignore import { createRegionMapTypeDefinition } from './region_map_type'; -import { getBaseMapsVis, IServiceSettings, MapsLegacyPluginSetup } from '../../maps_legacy/public'; +import { IServiceSettings, MapsLegacyPluginSetup } from '../../maps_legacy/public'; import { setFormatService, setNotifications, setKibanaLegacy } from './kibana_services'; import { DataPublicPluginStart } from '../../data/public'; import { RegionMapsConfigType } from './index'; @@ -94,7 +94,7 @@ export class RegionMapPlugin implements Plugin} */ - async get(id?: string) { + async get(opts?: Record | string) { + // can accept object as argument in accordance to SavedVis class + // see src/plugins/saved_objects/public/saved_object/saved_object_loader.ts // @ts-ignore - const obj = new this.Class(id); + const obj = new this.Class(opts); return obj.init(); } diff --git a/src/plugins/saved_objects/public/types.ts b/src/plugins/saved_objects/public/types.ts index 973a493c0a15e..6db72b396a86a 100644 --- a/src/plugins/saved_objects/public/types.ts +++ b/src/plugins/saved_objects/public/types.ts @@ -63,6 +63,7 @@ export interface SavedObjectSaveOpts { confirmOverwrite?: boolean; isTitleDuplicateConfirmed?: boolean; onTitleDuplicate?: () => void; + returnToOrigin?: boolean; } export interface SavedObjectCreationOpts { diff --git a/src/plugins/saved_objects_management/public/lib/in_app_url.test.ts b/src/plugins/saved_objects_management/public/lib/in_app_url.test.ts index 09e08e6ec333b..e83952e82c953 100644 --- a/src/plugins/saved_objects_management/public/lib/in_app_url.test.ts +++ b/src/plugins/saved_objects_management/public/lib/in_app_url.test.ts @@ -70,7 +70,7 @@ describe('canViewInApp', () => { let uiCapabilities = createCapabilities({ management: { kibana: { - index_patterns: true, + indexPatterns: true, }, }, }); @@ -81,7 +81,7 @@ describe('canViewInApp', () => { uiCapabilities = createCapabilities({ management: { kibana: { - index_patterns: false, + indexPatterns: false, }, }, }); diff --git a/src/plugins/saved_objects_management/public/lib/in_app_url.ts b/src/plugins/saved_objects_management/public/lib/in_app_url.ts index 82146554afa6f..0fcf52cba254b 100644 --- a/src/plugins/saved_objects_management/public/lib/in_app_url.ts +++ b/src/plugins/saved_objects_management/public/lib/in_app_url.ts @@ -30,7 +30,7 @@ export function canViewInApp(uiCapabilities: Capabilities, type: string): boolea case 'index-pattern': case 'index-patterns': case 'indexPatterns': - return uiCapabilities.management.kibana.index_patterns as boolean; + return uiCapabilities.management.kibana.indexPatterns as boolean; case 'dashboard': case 'dashboards': return uiCapabilities.dashboard.show as boolean; diff --git a/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx b/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx index fd7967f4128c3..50358c17e058c 100644 --- a/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx +++ b/src/plugins/saved_objects_management/public/management_section/object_view/components/field.tsx @@ -18,14 +18,7 @@ */ import React, { PureComponent } from 'react'; -import { - EuiFieldNumber, - EuiFieldText, - EuiFormRow, - EuiSwitch, - // @ts-ignore - EuiCodeEditor, -} from '@elastic/eui'; +import { EuiFieldNumber, EuiFieldText, EuiFormRow, EuiSwitch, EuiCodeEditor } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { FieldState, FieldType } from '../../types'; diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap index 884f39b97e233..7d43158ad8878 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/__snapshots__/saved_objects_table.test.tsx.snap @@ -304,7 +304,7 @@ exports[`SavedObjectsTable should render normally 1`] = ` "icon": "indexPatternApp", "inAppUrl": Object { "path": "/management/kibana/indexPatterns/patterns/1", - "uiCapabilitiesPath": "management.kibana.index_patterns", + "uiCapabilitiesPath": "management.kibana.indexPatterns", }, "title": "MyIndexPattern*", }, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/relationships.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/relationships.test.tsx.snap index 6eb9e36394ee3..15e5cb89b622c 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/relationships.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/relationships.test.tsx.snap @@ -456,7 +456,7 @@ exports[`Relationships should render searches normally 1`] = ` "icon": "indexPatternApp", "inAppUrl": Object { "path": "/app/management/kibana/indexPatterns/patterns/1", - "uiCapabilitiesPath": "management.kibana.index_patterns", + "uiCapabilitiesPath": "management.kibana.indexPatterns", }, "title": "My Index Pattern", }, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/table.test.tsx.snap b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/table.test.tsx.snap index 92e4e637e0d3f..56fddc075a50c 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/table.test.tsx.snap +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/__snapshots__/table.test.tsx.snap @@ -178,7 +178,7 @@ exports[`Table prevents saved objects from being deleted 1`] = ` "icon": "indexPatternApp", "inAppUrl": Object { "path": "/management/kibana/indexPatterns/patterns/1", - "uiCapabilitiesPath": "management.kibana.index_patterns", + "uiCapabilitiesPath": "management.kibana.indexPatterns", }, "title": "MyIndexPattern*", }, @@ -393,7 +393,7 @@ exports[`Table should render normally 1`] = ` "icon": "indexPatternApp", "inAppUrl": Object { "path": "/management/kibana/indexPatterns/patterns/1", - "uiCapabilitiesPath": "management.kibana.index_patterns", + "uiCapabilitiesPath": "management.kibana.indexPatterns", }, "title": "MyIndexPattern*", }, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx index 6e7397d1058bf..aac799da6ea67 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/flyout.tsx @@ -31,7 +31,6 @@ import { EuiForm, EuiFormRow, EuiSwitch, - // @ts-ignore EuiFilePicker, EuiInMemoryTable, EuiSelect, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx index 9277d9b00305b..2e545b372f781 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx @@ -77,7 +77,7 @@ describe('Relationships', () => { editUrl: '#/management/kibana/indexPatterns/patterns/1', inAppUrl: { path: '/management/kibana/indexPatterns/patterns/1', - uiCapabilitiesPath: 'management.kibana.index_patterns', + uiCapabilitiesPath: 'management.kibana.indexPatterns', }, }, }, @@ -113,7 +113,7 @@ describe('Relationships', () => { icon: 'indexPatternApp', inAppUrl: { path: '/app/management/kibana/indexPatterns/patterns/1', - uiCapabilitiesPath: 'management.kibana.index_patterns', + uiCapabilitiesPath: 'management.kibana.indexPatterns', }, title: 'My Index Pattern', }, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx index b9dad983cb42d..6b209a62e1b98 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.test.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { shallowWithI18nProvider, mountWithI18nProvider } from 'test_utils/enzyme_helpers'; -// @ts-ignore +// @ts-expect-error import { findTestSubject } from '@elastic/eui/lib/test'; import { keyCodes } from '@elastic/eui'; import { httpServiceMock } from '../../../../../../core/public/mocks'; @@ -41,7 +41,7 @@ const defaultProps: TableProps = { editUrl: '#/management/kibana/indexPatterns/patterns/1', inAppUrl: { path: '/management/kibana/indexPatterns/patterns/1', - uiCapabilitiesPath: 'management.kibana.index_patterns', + uiCapabilitiesPath: 'management.kibana.indexPatterns', }, }, }, @@ -68,7 +68,7 @@ const defaultProps: TableProps = { editUrl: '#/management/kibana/indexPatterns/patterns/1', inAppUrl: { path: '/management/kibana/indexPatterns/patterns/1', - uiCapabilitiesPath: 'management.kibana.index_patterns', + uiCapabilitiesPath: 'management.kibana.indexPatterns', }, }, }, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx index 51e7525d0e00a..719729cee2602 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/components/table.tsx @@ -20,7 +20,6 @@ import { IBasePath } from 'src/core/public'; import React, { PureComponent, Fragment } from 'react'; import { - // @ts-ignore EuiSearchBar, EuiBasicTable, EuiButton, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx index e3456240dc340..3719dac24e6e7 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx @@ -158,7 +158,7 @@ describe('SavedObjectsTable', () => { editUrl: '#/management/kibana/indexPatterns/patterns/1', inAppUrl: { path: '/management/kibana/indexPatterns/patterns/1', - uiCapabilitiesPath: 'management.kibana.index_patterns', + uiCapabilitiesPath: 'management.kibana.indexPatterns', }, }, }, diff --git a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx index 54bc649c33b60..340c0e3237f91 100644 --- a/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx +++ b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.tsx @@ -19,7 +19,7 @@ import React, { Component } from 'react'; import { debounce } from 'lodash'; -// @ts-ignore +// @ts-expect-error import { saveAs } from '@elastic/filesaver'; import { EuiSpacer, diff --git a/src/plugins/telemetry/common/constants.ts b/src/plugins/telemetry/common/constants.ts index 53c79b738f750..fc77332c18fc9 100644 --- a/src/plugins/telemetry/common/constants.ts +++ b/src/plugins/telemetry/common/constants.ts @@ -56,11 +56,6 @@ export const PATH_TO_ADVANCED_SETTINGS = 'management/kibana/settings'; */ export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/privacy-statement`; -/** - * The type name used to publish telemetry plugin stats. - */ -export const TELEMETRY_STATS_TYPE = 'telemetry'; - /** * The endpoint version when hitting the remote telemetry service */ diff --git a/src/plugins/telemetry/schema/legacy_oss_plugins.json b/src/plugins/telemetry/schema/legacy_oss_plugins.json new file mode 100644 index 0000000000000..e660ccac9dc36 --- /dev/null +++ b/src/plugins/telemetry/schema/legacy_oss_plugins.json @@ -0,0 +1,17 @@ +{ + "properties": { + "csp": { + "properties": { + "strict": { + "type": "boolean" + }, + "warnLegacyBrowsers": { + "type": "boolean" + }, + "rulesChangedFromDefault": { + "type": "boolean" + } + } + } + } +} diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json new file mode 100644 index 0000000000000..a5172c01b1dad --- /dev/null +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -0,0 +1,59 @@ +{ + "properties": { + "kql": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + }, + "defaultQueryLanguage": { + "type": "keyword" + } + } + }, + "sample-data": { + "properties": { + "installed": { + "type": "keyword" + }, + "last_install_date": { + "type": "date" + }, + "last_install_set": { + "type": "keyword" + }, + "last_uninstall_date": { + "type": "date" + }, + "last_uninstall_set": { + "type": "keyword" + }, + "uninstalled": { + "type": "keyword" + } + } + }, + "telemetry": { + "properties": { + "opt_in_status": { + "type": "boolean" + }, + "usage_fetcher": { + "type": "keyword" + }, + "last_reported": { + "type": "long" + } + } + }, + "tsvb-validation": { + "properties": { + "failed_validations": { + "type": "long" + } + } + } + } +} diff --git a/src/plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts b/src/plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts index ab90935266d69..05836b8448a68 100644 --- a/src/plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts +++ b/src/plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts @@ -20,7 +20,6 @@ import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; import { ISavedObjectsRepository, SavedObjectsClient } from '../../../../../core/server'; -import { TELEMETRY_STATS_TYPE } from '../../../common/constants'; import { getTelemetrySavedObject, TelemetrySavedObject } from '../../telemetry_repository'; import { getTelemetryOptIn, getTelemetrySendUsageFrom } from '../../../common/telemetry_config'; import { UsageCollectionSetup } from '../../../../usage_collection/server'; @@ -81,10 +80,15 @@ export function registerTelemetryPluginUsageCollector( usageCollection: UsageCollectionSetup, options: TelemetryPluginUsageCollectorOptions ) { - const collector = usageCollection.makeUsageCollector({ - type: TELEMETRY_STATS_TYPE, + const collector = usageCollection.makeUsageCollector({ + type: 'telemetry', isReady: () => typeof options.getSavedObjectsClient() !== 'undefined', fetch: createCollectorFetch(options), + schema: { + opt_in_status: { type: 'boolean' }, + usage_fetcher: { type: 'keyword' }, + last_reported: { type: 'long' }, + }, }); usageCollection.registerCollector(collector); diff --git a/src/plugins/telemetry/server/fetcher.ts b/src/plugins/telemetry/server/fetcher.ts index 9422df6030518..75cfac721bcd3 100644 --- a/src/plugins/telemetry/server/fetcher.ts +++ b/src/plugins/telemetry/server/fetcher.ts @@ -29,7 +29,7 @@ import { SavedObjectsClientContract, SavedObjectsClient, CoreStart, - ICustomClusterClient, + ILegacyCustomClusterClient, } from '../../../core/server'; import { getTelemetryOptIn, @@ -63,7 +63,7 @@ export class FetcherTask { private isSending = false; private internalRepository?: SavedObjectsClientContract; private telemetryCollectionManager?: TelemetryCollectionManagerPluginStart; - private elasticsearchClient?: ICustomClusterClient; + private elasticsearchClient?: ILegacyCustomClusterClient; constructor(initializerContext: PluginInitializerContext) { this.config$ = initializerContext.config.create(); diff --git a/src/plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js b/src/plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js index 29076537e9ae8..e78b92498e6e7 100644 --- a/src/plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js +++ b/src/plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js @@ -19,11 +19,12 @@ import expect from '@kbn/expect'; import sinon from 'sinon'; +import { merge, omit } from 'lodash'; +import { TIMEOUT } from '../constants'; import { mockGetClusterInfo } from './get_cluster_info'; import { mockGetClusterStats } from './get_cluster_stats'; -import { omit } from 'lodash'; import { getLocalStats, handleLocalStats } from '../get_local_stats'; const mockUsageCollection = (kibanaUsage = {}) => ({ @@ -51,10 +52,26 @@ const getMockServer = (getCluster = sinon.stub()) => ({ elasticsearch: { getCluster }, }, }); +function mockGetNodesUsage(callCluster, nodesUsage, req) { + callCluster + .withArgs( + req, + { + method: 'GET', + path: '/_nodes/usage', + query: { + timeout: TIMEOUT, + }, + }, + 'transport.request' + ) + .returns(nodesUsage); +} -function mockGetLocalStats(callCluster, clusterInfo, clusterStats, req) { +function mockGetLocalStats(callCluster, clusterInfo, clusterStats, nodesUsage, req) { mockGetClusterInfo(callCluster, clusterInfo, req); mockGetClusterStats(callCluster, clusterStats, req); + mockGetNodesUsage(callCluster, nodesUsage, req); } describe('get_local_stats', () => { @@ -68,6 +85,28 @@ describe('get_local_stats', () => { number: version, }, }; + const nodesUsage = [ + { + node_id: 'some_node_id', + timestamp: 1588617023177, + since: 1588616945163, + rest_actions: { + nodes_usage_action: 1, + create_index_action: 1, + document_get_action: 1, + search_action: 19, + nodes_info_action: 36, + }, + aggregations: { + terms: { + bytes: 2, + }, + scripted_metric: { + other: 7, + }, + }, + }, + ]; const clusterStats = { _nodes: { failed: 123 }, cluster_name: 'real-cool', @@ -75,6 +114,7 @@ describe('get_local_stats', () => { nodes: { yup: 'abc' }, random: 123, }; + const kibana = { kibana: { great: 'googlymoogly', @@ -97,12 +137,16 @@ describe('get_local_stats', () => { snow: { chances: 0 }, }; + const clusterStatsWithNodesUsage = { + ...clusterStats, + nodes: merge(clusterStats.nodes, { usage: nodesUsage }), + }; const combinedStatsResult = { collection: 'local', cluster_uuid: clusterUuid, cluster_name: clusterName, version, - cluster_stats: omit(clusterStats, '_nodes', 'cluster_name'), + cluster_stats: omit(clusterStatsWithNodesUsage, '_nodes', 'cluster_name'), stack_stats: { kibana: { great: 'googlymoogly', @@ -135,7 +179,7 @@ describe('get_local_stats', () => { describe('handleLocalStats', () => { it('returns expected object without xpack and kibana data', () => { - const result = handleLocalStats(clusterInfo, clusterStats, void 0, context); + const result = handleLocalStats(clusterInfo, clusterStatsWithNodesUsage, void 0, context); expect(result.cluster_uuid).to.eql(combinedStatsResult.cluster_uuid); expect(result.cluster_name).to.eql(combinedStatsResult.cluster_name); expect(result.cluster_stats).to.eql(combinedStatsResult.cluster_stats); @@ -146,7 +190,7 @@ describe('get_local_stats', () => { }); it('returns expected object with xpack', () => { - const result = handleLocalStats(clusterInfo, clusterStats, void 0, context); + const result = handleLocalStats(clusterInfo, clusterStatsWithNodesUsage, void 0, context); const { stack_stats: stack, ...cluster } = result; expect(cluster.collection).to.be(combinedStatsResult.collection); expect(cluster.cluster_uuid).to.be(combinedStatsResult.cluster_uuid); @@ -167,7 +211,8 @@ describe('get_local_stats', () => { mockGetLocalStats( callClusterUsageFailed, Promise.resolve(clusterInfo), - Promise.resolve(clusterStats) + Promise.resolve(clusterStats), + Promise.resolve(nodesUsage) ); const result = await getLocalStats([], { server: getMockServer(), @@ -177,6 +222,7 @@ describe('get_local_stats', () => { expect(result.cluster_uuid).to.eql(combinedStatsResult.cluster_uuid); expect(result.cluster_name).to.eql(combinedStatsResult.cluster_name); expect(result.cluster_stats).to.eql(combinedStatsResult.cluster_stats); + expect(result.cluster_stats.nodes).to.eql(combinedStatsResult.cluster_stats.nodes); expect(result.version).to.be('2.3.4'); expect(result.collection).to.be('local'); @@ -188,7 +234,12 @@ describe('get_local_stats', () => { it('returns expected object with xpack and kibana data', async () => { const callCluster = sinon.stub(); const usageCollection = mockUsageCollection(kibana); - mockGetLocalStats(callCluster, Promise.resolve(clusterInfo), Promise.resolve(clusterStats)); + mockGetLocalStats( + callCluster, + Promise.resolve(clusterInfo), + Promise.resolve(clusterStats), + Promise.resolve(nodesUsage) + ); const result = await getLocalStats([], { server: getMockServer(callCluster), diff --git a/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.ts b/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.ts index d5f0d2d8c9598..4a33356ee9761 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.ts @@ -17,7 +17,7 @@ * under the License. */ -import { APICaller } from 'kibana/server'; +import { LegacyAPICaller } from 'kibana/server'; // This can be removed when the ES client improves the types export interface ESClusterInfo { @@ -43,6 +43,6 @@ export interface ESClusterInfo { * * @param {function} callCluster The callWithInternalUser handler (exposed for testing) */ -export function getClusterInfo(callCluster: APICaller) { +export function getClusterInfo(callCluster: LegacyAPICaller) { return callCluster('info'); } diff --git a/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts b/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts index 89e2cae777985..d7c0110a99c6f 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_cluster_stats.ts @@ -18,14 +18,14 @@ */ import { ClusterDetailsGetter } from 'src/plugins/telemetry_collection_manager/server'; -import { APICaller } from 'kibana/server'; +import { LegacyAPICaller } from 'kibana/server'; import { TIMEOUT } from './constants'; /** * Get the cluster stats from the connected cluster. * * This is the equivalent to GET /_cluster/stats?timeout=30s. */ -export async function getClusterStats(callCluster: APICaller) { +export async function getClusterStats(callCluster: LegacyAPICaller) { return await callCluster('cluster.stats', { timeout: TIMEOUT, }); diff --git a/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts b/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts index 645c5a4be8a6c..5d27774a630a5 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_kibana.ts @@ -19,7 +19,7 @@ import { omit } from 'lodash'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { APICaller } from 'kibana/server'; +import { LegacyAPICaller } from 'kibana/server'; import { StatsCollectionContext } from 'src/plugins/telemetry_collection_manager/server'; export interface KibanaUsageStats { @@ -83,7 +83,7 @@ export function handleKibanaStats( export async function getKibana( usageCollection: UsageCollectionSetup, - callWithInternalUser: APICaller + callWithInternalUser: LegacyAPICaller ): Promise { const usage = await usageCollection.bulkFetch(callWithInternalUser); return usageCollection.toObject(usage); diff --git a/src/plugins/telemetry/server/telemetry_collection/get_local_license.ts b/src/plugins/telemetry/server/telemetry_collection/get_local_license.ts index 0ba6fcc154d8b..d41904c6d8e0e 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_local_license.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_local_license.ts @@ -17,12 +17,12 @@ * under the License. */ -import { APICaller } from 'kibana/server'; +import { LegacyAPICaller } from 'kibana/server'; import { ESLicense, LicenseGetter } from 'src/plugins/telemetry_collection_manager/server'; let cachedLicense: ESLicense | undefined; -function fetchLicense(callCluster: APICaller, local: boolean) { +function fetchLicense(callCluster: LegacyAPICaller, local: boolean) { return callCluster<{ license: ESLicense }>('transport.request', { method: 'GET', path: '/_license', @@ -41,7 +41,7 @@ function fetchLicense(callCluster: APICaller, local: boolean) { * * Like any X-Pack related API, X-Pack must installed for this to work. */ -async function getLicenseFromLocalOrMaster(callCluster: APICaller) { +async function getLicenseFromLocalOrMaster(callCluster: LegacyAPICaller) { // Fetching the local license is cheaper than getting it from the master and good enough const { license } = await fetchLicense(callCluster, true).catch(async (err) => { if (cachedLicense) { diff --git a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts index b77d01c5b431f..b42edde2f55ca 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts +++ b/src/plugins/telemetry/server/telemetry_collection/get_local_stats.ts @@ -24,6 +24,7 @@ import { import { getClusterInfo, ESClusterInfo } from './get_cluster_info'; import { getClusterStats } from './get_cluster_stats'; import { getKibana, handleKibanaStats, KibanaUsageStats } from './get_kibana'; +import { getNodesUsage } from './get_nodes_usage'; /** * Handle the separate local calls by combining them into a single object response that looks like the @@ -67,12 +68,21 @@ export const getLocalStats: StatsGetter<{}, TelemetryLocalStats> = async ( return await Promise.all( clustersDetails.map(async (clustersDetail) => { - const [clusterInfo, clusterStats, kibana] = await Promise.all([ + const [clusterInfo, clusterStats, nodesUsage, kibana] = await Promise.all([ getClusterInfo(callCluster), // cluster info getClusterStats(callCluster), // cluster stats (not to be confused with cluster _state_) + getNodesUsage(callCluster), // nodes_usage info getKibana(usageCollection, callCluster), ]); - return handleLocalStats(clusterInfo, clusterStats, kibana, context); + return handleLocalStats( + clusterInfo, + { + ...clusterStats, + nodes: { ...clusterStats.nodes, usage: nodesUsage }, + }, + kibana, + context + ); }) ); }; diff --git a/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.test.ts b/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.test.ts new file mode 100644 index 0000000000000..4e4b0e11b7979 --- /dev/null +++ b/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.test.ts @@ -0,0 +1,80 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { getNodesUsage } from './get_nodes_usage'; +import { TIMEOUT } from './constants'; + +const mockedNodesFetchResponse = { + cluster_name: 'test cluster', + nodes: { + some_node_id: { + timestamp: 1588617023177, + since: 1588616945163, + rest_actions: { + nodes_usage_action: 1, + create_index_action: 1, + document_get_action: 1, + search_action: 19, + nodes_info_action: 36, + }, + aggregations: { + terms: { + bytes: 2, + }, + scripted_metric: { + other: 7, + }, + }, + }, + }, +}; +describe('get_nodes_usage', () => { + it('calls fetchNodesUsage', async () => { + const callCluster = jest.fn(); + callCluster.mockResolvedValueOnce(mockedNodesFetchResponse); + await getNodesUsage(callCluster); + expect(callCluster).toHaveBeenCalledWith('transport.request', { + path: '/_nodes/usage', + method: 'GET', + query: { + timeout: TIMEOUT, + }, + }); + }); + it('returns a modified array of node usage data', async () => { + const callCluster = jest.fn(); + callCluster.mockResolvedValueOnce(mockedNodesFetchResponse); + const result = await getNodesUsage(callCluster); + expect(result.nodes).toEqual([ + { + aggregations: { scripted_metric: { other: 7 }, terms: { bytes: 2 } }, + node_id: 'some_node_id', + rest_actions: { + create_index_action: 1, + document_get_action: 1, + nodes_info_action: 36, + nodes_usage_action: 1, + search_action: 19, + }, + since: 1588616945163, + timestamp: 1588617023177, + }, + ]); + }); +}); diff --git a/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts b/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts new file mode 100644 index 0000000000000..c5c110fbb4149 --- /dev/null +++ b/src/plugins/telemetry/server/telemetry_collection/get_nodes_usage.ts @@ -0,0 +1,81 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { LegacyAPICaller } from 'kibana/server'; +import { TIMEOUT } from './constants'; + +export interface NodeAggregation { + [key: string]: number; +} + +// we set aggregations as an optional type because it was only added in v7.8.0 +export interface NodeObj { + node_id?: string; + timestamp: number; + since: number; + rest_actions: { + [key: string]: number; + }; + aggregations?: { + [key: string]: NodeAggregation; + }; +} + +export interface NodesFeatureUsageResponse { + cluster_name: string; + nodes: { + [key: string]: NodeObj; + }; +} + +export type NodesUsageGetter = ( + callCluster: LegacyAPICaller +) => Promise<{ nodes: NodeObj[] | Array<{}> }>; +/** + * Get the nodes usage data from the connected cluster. + * + * This is the equivalent to GET /_nodes/usage?timeout=30s. + * + * The Nodes usage API was introduced in v6.0.0 + */ +export async function fetchNodesUsage( + callCluster: LegacyAPICaller +): Promise { + const response = await callCluster('transport.request', { + method: 'GET', + path: '/_nodes/usage', + query: { + timeout: TIMEOUT, + }, + }); + return response; +} + +/** + * Get the nodes usage from the connected cluster + * @param callCluster APICaller + * @returns Object containing array of modified usage information with the node_id nested within the data for that node. + */ +export const getNodesUsage: NodesUsageGetter = async (callCluster) => { + const result = await fetchNodesUsage(callCluster); + const transformedNodes = Object.entries(result?.nodes || {}).map(([key, value]) => ({ + ...(value as NodeObj), + node_id: key, + })); + return { nodes: transformedNodes }; +}; diff --git a/src/plugins/telemetry/server/telemetry_collection/register_collection.ts b/src/plugins/telemetry/server/telemetry_collection/register_collection.ts index 833fd9f7fd5bc..438fcadad9255 100644 --- a/src/plugins/telemetry/server/telemetry_collection/register_collection.ts +++ b/src/plugins/telemetry/server/telemetry_collection/register_collection.ts @@ -36,7 +36,7 @@ * under the License. */ -import { IClusterClient } from 'kibana/server'; +import { ILegacyClusterClient } from 'kibana/server'; import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server'; import { getLocalStats } from './get_local_stats'; import { getClusterUuids } from './get_cluster_stats'; @@ -44,7 +44,7 @@ import { getLocalLicense } from './get_local_license'; export function registerCollection( telemetryCollectionManager: TelemetryCollectionManagerPluginSetup, - esCluster: IClusterClient + esCluster: ILegacyClusterClient ) { telemetryCollectionManager.setCollection({ esCluster, diff --git a/src/plugins/telemetry_collection_manager/server/types.ts b/src/plugins/telemetry_collection_manager/server/types.ts index d3a47694d38a7..16f96c07fd8ea 100644 --- a/src/plugins/telemetry_collection_manager/server/types.ts +++ b/src/plugins/telemetry_collection_manager/server/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { APICaller, Logger, KibanaRequest, IClusterClient } from 'kibana/server'; +import { LegacyAPICaller, Logger, KibanaRequest, ILegacyClusterClient } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { TelemetryCollectionManagerPlugin } from './plugin'; @@ -64,7 +64,7 @@ export interface ClusterDetails { export interface StatsCollectionConfig { usageCollection: UsageCollectionSetup; - callCluster: APICaller; + callCluster: LegacyAPICaller; start: string | number; end: string | number; } @@ -129,7 +129,7 @@ export interface CollectionConfig< > { title: string; priority: number; - esCluster: IClusterClient; + esCluster: ILegacyClusterClient; statsGetter: StatsGetter; clusterDetailsGetter: ClusterDetailsGetter; licenseGetter: LicenseGetter; @@ -144,6 +144,6 @@ export interface Collection< statsGetter: StatsGetter; licenseGetter: LicenseGetter; clusterDetailsGetter: ClusterDetailsGetter; - esCluster: IClusterClient; + esCluster: ILegacyClusterClient; title: string; } diff --git a/src/plugins/tile_map/public/__tests__/coordinate_maps_visualization.js b/src/plugins/tile_map/public/__tests__/coordinate_maps_visualization.js index 11c8fb9c00ef1..9ff25ce674d3d 100644 --- a/src/plugins/tile_map/public/__tests__/coordinate_maps_visualization.js +++ b/src/plugins/tile_map/public/__tests__/coordinate_maps_visualization.js @@ -52,8 +52,9 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ServiceSettings } from '../../../maps_legacy/public/map/service_settings'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { setInjectedVarFunc } from '../../../maps_legacy/public/kibana_services'; -import { getBaseMapsVis } from '../../../maps_legacy/public'; +import { KibanaMap } from '../../../maps_legacy/public/map/kibana_map'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { BaseMapsVisualizationProvider } from '../../../maps_legacy/public/map/base_maps_visualization'; function mockRawData() { const stack = [dummyESResponse]; @@ -105,26 +106,12 @@ describe('CoordinateMapsVisualizationTest', function () { }, }, }; - setInjectedVarFunc((injectedVar) => { - switch (injectedVar) { - case 'version': - return '123'; - default: - return 'not found'; - } - }); - const coreSetupMock = { - notifications: { - toasts: {}, - }, - uiSettings: {}, - injectedMetadata: { - getInjectedVar: () => {}, - }, - }; const serviceSettings = new ServiceSettings(mapConfig, tilemapsConfig); - const BaseMapsVisualization = getBaseMapsVis(coreSetupMock, serviceSettings); + const BaseMapsVisualization = new BaseMapsVisualizationProvider( + (...args) => new KibanaMap(...args), + serviceSettings + ); const uiSettings = $injector.get('config'); dependencies = { diff --git a/src/plugins/tile_map/public/plugin.ts b/src/plugins/tile_map/public/plugin.ts index 20a45c586074a..1f79104b183ee 100644 --- a/src/plugins/tile_map/public/plugin.ts +++ b/src/plugins/tile_map/public/plugin.ts @@ -32,7 +32,7 @@ import 'angular-sanitize'; import { createTileMapFn } from './tile_map_fn'; // @ts-ignore import { createTileMapTypeDefinition } from './tile_map_type'; -import { getBaseMapsVis, MapsLegacyPluginSetup } from '../../maps_legacy/public'; +import { MapsLegacyPluginSetup } from '../../maps_legacy/public'; import { DataPublicPluginStart } from '../../data/public'; import { setFormatService, setQueryService } from './services'; import { setKibanaLegacy } from './services'; @@ -85,7 +85,7 @@ export class TileMapPlugin implements Plugin = { getZoomPrecision, getPrecision, - BaseMapsVisualization: getBaseMapsVis(core, mapsLegacy.serviceSettings), + BaseMapsVisualization: mapsLegacy.getBaseMapsVis(), uiSettings: core.uiSettings, }; diff --git a/src/plugins/ui_actions/public/types.ts b/src/plugins/ui_actions/public/types.ts index 85c87306cc4f9..9fcd8a32881df 100644 --- a/src/plugins/ui_actions/public/types.ts +++ b/src/plugins/ui_actions/public/types.ts @@ -22,7 +22,7 @@ import { TriggerInternal } from './triggers/trigger_internal'; import { Filter } from '../../data/public'; import { SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, APPLY_FILTER_TRIGGER } from './triggers'; import { IEmbeddable } from '../../embeddable/public'; -import { RangeSelectTriggerContext, ValueClickTriggerContext } from '../../embeddable/public'; +import { RangeSelectContext, ValueClickContext } from '../../embeddable/public'; export type TriggerRegistry = Map>; export type ActionRegistry = Map; @@ -37,8 +37,8 @@ export type TriggerContext = BaseContext; export interface TriggerContextMapping { [DEFAULT_TRIGGER]: TriggerContext; - [SELECT_RANGE_TRIGGER]: RangeSelectTriggerContext; - [VALUE_CLICK_TRIGGER]: ValueClickTriggerContext; + [SELECT_RANGE_TRIGGER]: RangeSelectContext; + [VALUE_CLICK_TRIGGER]: ValueClickContext; [APPLY_FILTER_TRIGGER]: { embeddable: IEmbeddable; filters: Filter[]; diff --git a/src/plugins/usage_collection/README.md b/src/plugins/usage_collection/README.md index 99075d5d48f59..9520dfc03cfa4 100644 --- a/src/plugins/usage_collection/README.md +++ b/src/plugins/usage_collection/README.md @@ -8,7 +8,7 @@ To integrate with the telemetry services for usage collection of your feature, t ## Creating and Registering Usage Collector -All you need to provide is a `type` for organizing your fields, and a `fetch` method for returning your usage data. Then you need to make the Telemetry service aware of the collector by registering it. +All you need to provide is a `type` for organizing your fields, `schema` field to define the expected types of usage fields reported, and a `fetch` method for returning your usage data. Then you need to make the Telemetry service aware of the collector by registering it. ### New Platform @@ -45,6 +45,12 @@ All you need to provide is a `type` for organizing your fields, and a `fetch` me import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { APICluster } from 'kibana/server'; + interface Usage { + my_objects: { + total: number, + }, + } + export function registerMyPluginUsageCollector(usageCollection?: UsageCollectionSetup): void { // usageCollection is an optional dependency, so make sure to return if it is not registered. if (!usageCollection) { @@ -52,8 +58,13 @@ All you need to provide is a `type` for organizing your fields, and a `fetch` me } // create usage collector - const myCollector = usageCollection.makeUsageCollector({ + const myCollector = usageCollection.makeUsageCollector({ type: MY_USAGE_TYPE, + schema: { + my_objects: { + total: 'long', + }, + }, fetch: async (callCluster: APICluster) => { // query ES and get some data @@ -98,10 +109,8 @@ class Plugin { ```ts // server/collectors/register.ts import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { ISavedObjectsRepository } from 'kibana/server'; export function registerMyPluginUsageCollector( - getSavedObjectsRepository: () => ISavedObjectsRepository | undefined, usageCollection?: UsageCollectionSetup ): void { // usageCollection is an optional dependency, so make sure to return if it is not registered. @@ -110,22 +119,52 @@ export function registerMyPluginUsageCollector( } // create usage collector - const myCollector = usageCollection.makeUsageCollector({ - type: MY_USAGE_TYPE, - isReady: () => typeof getSavedObjectsRepository() !== 'undefined', - fetch: async () => { - const savedObjectsRepository = getSavedObjectsRepository()!; - // get something from the savedObjects - - return { my_objects }; - }, - }); + const myCollector = usageCollection.makeUsageCollector(...) // register usage collector usageCollection.registerCollector(myCollector); } ``` +## Schema Field + +The `schema` field is a proscribed data model assists with detecting changes in usage collector payloads. To define the collector schema add a schema field that specifies every possible field reported when registering the collector. Whenever the `schema` field is set or changed please run `node scripts/telemetry_check.js --fix` to update the stored schema json files. + +### Allowed Schema Types + +The `AllowedSchemaTypes` is the list of allowed schema types for the usage fields getting reported: + +``` +'keyword', 'text', 'number', 'boolean', 'long', 'date', 'float' +``` + +### Example + +```ts +export const myCollector = makeUsageCollector({ + type: 'my_working_collector', + isReady: () => true, + fetch() { + return { + my_greeting: 'hello', + some_obj: { + total: 123, + }, + }; + }, + schema: { + my_greeting: { + type: 'keyword', + }, + some_obj: { + total: { + type: 'number', + }, + }, + }, +}); +``` + ## Update the telemetry payload and telemetry cluster field mappings There is a module in the telemetry service that creates the payload of data that gets sent up to the telemetry cluster. diff --git a/src/plugins/usage_collection/public/index.ts b/src/plugins/usage_collection/public/index.ts index 712e6a76152a2..c6c6ba64e6630 100644 --- a/src/plugins/usage_collection/public/index.ts +++ b/src/plugins/usage_collection/public/index.ts @@ -21,7 +21,7 @@ import { PluginInitializerContext } from '../../../core/public'; import { UsageCollectionPlugin } from './plugin'; export { METRIC_TYPE } from '@kbn/analytics'; -export { UsageCollectionSetup } from './plugin'; +export { UsageCollectionSetup, UsageCollectionStart } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new UsageCollectionPlugin(initializerContext); diff --git a/src/plugins/usage_collection/server/collector/collector.ts b/src/plugins/usage_collection/server/collector/collector.ts index b4f86f67e798d..9ae63b9f50e42 100644 --- a/src/plugins/usage_collection/server/collector/collector.ts +++ b/src/plugins/usage_collection/server/collector/collector.ts @@ -17,14 +17,38 @@ * under the License. */ -import { Logger, APICaller } from 'kibana/server'; +import { Logger, LegacyAPICaller } from 'kibana/server'; export type CollectorFormatForBulkUpload = (result: T) => { type: string; payload: U }; +export type AllowedSchemaTypes = + | 'keyword' + | 'text' + | 'number' + | 'boolean' + | 'long' + | 'date' + | 'float'; + +export interface SchemaField { + type: string; +} + +type Purify = { [P in T]: T }[T]; + +export type MakeSchemaFrom = { + [Key in Purify>]: Base[Key] extends Array + ? { type: AllowedSchemaTypes } + : Base[Key] extends object + ? MakeSchemaFrom + : { type: AllowedSchemaTypes }; +}; + export interface CollectorOptions { type: string; init?: Function; - fetch: (callCluster: APICaller) => Promise | T; + schema?: MakeSchemaFrom; + fetch: (callCluster: LegacyAPICaller) => Promise | T; /* * A hook for allowing the fetched data payload to be organized into a typed * data model for internal bulk upload. See defaultFormatterForBulkUpload for diff --git a/src/plugins/usage_collection/server/collector/collector_set.ts b/src/plugins/usage_collection/server/collector/collector_set.ts index e8791138c5e26..b6308d6603388 100644 --- a/src/plugins/usage_collection/server/collector/collector_set.ts +++ b/src/plugins/usage_collection/server/collector/collector_set.ts @@ -18,7 +18,7 @@ */ import { snakeCase } from 'lodash'; -import { Logger, APICaller } from 'kibana/server'; +import { Logger, LegacyAPICaller } from 'kibana/server'; import { Collector, CollectorOptions } from './collector'; import { UsageCollector } from './usage_collector'; @@ -42,7 +42,7 @@ export class CollectorSet { public makeStatsCollector = (options: CollectorOptions) => { return new Collector(this.logger, options); }; - public makeUsageCollector = (options: CollectorOptions) => { + public makeUsageCollector = (options: CollectorOptions) => { return new UsageCollector(this.logger, options); }; @@ -112,7 +112,7 @@ export class CollectorSet { }; public bulkFetch = async ( - callCluster: APICaller, + callCluster: LegacyAPICaller, collectors: Array> = this.collectors ) => { const responses = []; @@ -140,7 +140,7 @@ export class CollectorSet { return this.makeCollectorSetFromArray(filtered); }; - public bulkFetchUsage = async (callCluster: APICaller) => { + public bulkFetchUsage = async (callCluster: LegacyAPICaller) => { const usageCollectors = this.getFilteredCollectorSet((c) => c instanceof UsageCollector); return await this.bulkFetch(callCluster, usageCollectors.collectors); }; diff --git a/src/plugins/usage_collection/server/collector/index.ts b/src/plugins/usage_collection/server/collector/index.ts index 0d3939e1dc681..1816e845b4d66 100644 --- a/src/plugins/usage_collection/server/collector/index.ts +++ b/src/plugins/usage_collection/server/collector/index.ts @@ -18,5 +18,11 @@ */ export { CollectorSet } from './collector_set'; -export { Collector } from './collector'; +export { + Collector, + AllowedSchemaTypes, + SchemaField, + MakeSchemaFrom, + CollectorOptions, +} from './collector'; export { UsageCollector } from './usage_collector'; diff --git a/src/plugins/usage_collection/server/index.ts b/src/plugins/usage_collection/server/index.ts index a2769c8b4b405..87761bca9a507 100644 --- a/src/plugins/usage_collection/server/index.ts +++ b/src/plugins/usage_collection/server/index.ts @@ -20,6 +20,13 @@ import { PluginInitializerContext } from 'kibana/server'; import { UsageCollectionPlugin } from './plugin'; +export { + AllowedSchemaTypes, + MakeSchemaFrom, + SchemaField, + CollectorOptions, + Collector, +} from './collector'; export { UsageCollectionSetup } from './plugin'; export { config } from './config'; export const plugin = (initializerContext: PluginInitializerContext) => diff --git a/src/plugins/vis_default_editor/public/components/sidebar/sidebar.tsx b/src/plugins/vis_default_editor/public/components/sidebar/sidebar.tsx index 837dd9bff2c6d..c41315e7bc0dc 100644 --- a/src/plugins/vis_default_editor/public/components/sidebar/sidebar.tsx +++ b/src/plugins/vis_default_editor/public/components/sidebar/sidebar.tsx @@ -23,9 +23,13 @@ import { i18n } from '@kbn/i18n'; import { keyCodes, EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { EventEmitter } from 'events'; -import { Vis, PersistedState } from 'src/plugins/visualizations/public'; -import { SavedSearch } from 'src/plugins/discover/public'; +import { + Vis, + PersistedState, + VisualizeEmbeddableContract, +} from 'src/plugins/visualizations/public'; import { TimeRange } from 'src/plugins/data/public'; +import { SavedObject } from 'src/plugins/saved_objects/public'; import { DefaultEditorNavBar, OptionTab } from './navbar'; import { DefaultEditorControls } from './controls'; import { setStateParamValue, useEditorReducer, useEditorFormState, discardChanges } from './state'; @@ -34,6 +38,7 @@ import { SidebarTitle } from './sidebar_title'; import { Schema } from '../../schemas'; interface DefaultEditorSideBarProps { + embeddableHandler: VisualizeEmbeddableContract; isCollapsed: boolean; onClickCollapse: () => void; optionTabs: OptionTab[]; @@ -41,11 +46,12 @@ interface DefaultEditorSideBarProps { vis: Vis; isLinkedSearch: boolean; eventEmitter: EventEmitter; - savedSearch?: SavedSearch; + savedSearch?: SavedObject; timeRange: TimeRange; } function DefaultEditorSideBar({ + embeddableHandler, isCollapsed, onClickCollapse, optionTabs, @@ -104,12 +110,12 @@ function DefaultEditorSideBar({ aggs: state.data.aggs ? (state.data.aggs.aggs.map((agg) => agg.toJSON()) as any) : [], }, }); - eventEmitter.emit('updateVis'); + embeddableHandler.reload(); eventEmitter.emit('dirtyStateChange', { isDirty: false, }); setTouched(false); - }, [vis, state, formState.invalid, setTouched, isDirty, eventEmitter]); + }, [vis, state, formState.invalid, setTouched, isDirty, eventEmitter, embeddableHandler]); const onSubmit: KeyboardEventHandler = useCallback( (event) => { diff --git a/src/plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx b/src/plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx index ebc92170c8735..6713c2ce2391b 100644 --- a/src/plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx +++ b/src/plugins/vis_default_editor/public/components/sidebar/sidebar_title.tsx @@ -36,17 +36,17 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { Vis } from 'src/plugins/visualizations/public'; -import { SavedSearch } from 'src/plugins/discover/public'; +import { SavedObject } from 'src/plugins/saved_objects/public'; import { useKibana } from '../../../../kibana_react/public'; interface LinkedSearchProps { - savedSearch: SavedSearch; + savedSearch: SavedObject; eventEmitter: EventEmitter; } interface SidebarTitleProps { isLinkedSearch: boolean; - savedSearch?: SavedSearch; + savedSearch?: SavedObject; vis: Vis; eventEmitter: EventEmitter; } diff --git a/src/plugins/vis_default_editor/public/default_editor.tsx b/src/plugins/vis_default_editor/public/default_editor.tsx index 731358bdcbdec..60b6ebab5ad8e 100644 --- a/src/plugins/vis_default_editor/public/default_editor.tsx +++ b/src/plugins/vis_default_editor/public/default_editor.tsx @@ -59,7 +59,7 @@ function DefaultEditor({ embeddableHandler.render(visRef.current); setTimeout(() => { - eventEmitter.emit('apply'); + eventEmitter.emit('embeddableRendered'); }); return () => embeddableHandler.destroy(); @@ -102,6 +102,7 @@ function DefaultEditor({ initialWidth={editorInitialWidth} > get(col.aggConfig.type.getFormat(col.aggConfig), 'type.id') === 'number') + .filter((col) => get(col.aggConfig.toSerializedFieldFormat(), 'id') === 'number') .map(({ name }) => ({ value: name, text: name })), ], [aggs] diff --git a/src/plugins/vis_type_timelion/server/plugin.ts b/src/plugins/vis_type_timelion/server/plugin.ts index 435ec9027eef2..605c6be0a85df 100644 --- a/src/plugins/vis_type_timelion/server/plugin.ts +++ b/src/plugins/vis_type_timelion/server/plugin.ts @@ -20,11 +20,9 @@ import { i18n } from '@kbn/i18n'; import { first } from 'rxjs/operators'; import { TypeOf } from '@kbn/config-schema'; -import { - CoreSetup, - PluginInitializerContext, - RecursiveReadonly, -} from '../../../../src/core/server'; +import { RecursiveReadonly } from '@kbn/utility-types'; + +import { CoreSetup, PluginInitializerContext } from '../../../../src/core/server'; import { deepFreeze } from '../../../../src/core/server'; import { configSchema } from '../config'; import loadFunctions from './lib/load_functions'; diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js index a96890d4d1502..300e70f3ae0c0 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js @@ -80,7 +80,7 @@ export class VisEditor extends Component { updateVisState = debounce(() => { this.props.vis.params = this.state.model; - this.props.eventEmitter.emit('updateVis'); + this.props.embeddableHandler.reload(); this.props.eventEmitter.emit('dirtyStateChange', { isDirty: false, }); @@ -187,6 +187,7 @@ export class VisEditor extends Component { autoApply={this.state.autoApply} model={model} embeddableHandler={this.props.embeddableHandler} + eventEmitter={this.props.eventEmitter} vis={this.props.vis} timeRange={this.props.timeRange} uiState={this.uiState} diff --git a/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js b/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js index 0ae1c86ae3117..23a9555da2452 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js +++ b/src/plugins/vis_type_timeseries/public/application/components/vis_editor_visualization.js @@ -73,6 +73,7 @@ class VisEditorVisualizationUI extends Component { this._handler = embeddableHandler; await this._handler.render(this._visEl.current); + this.props.eventEmitter.emit('embeddableRendered'); this._subscription = this._handler.handler.data$.subscribe((data) => { this.setPanelInterval(data.value.visData); @@ -279,6 +280,7 @@ VisEditorVisualizationUI.propTypes = { uiState: PropTypes.object, onToggleAutoApply: PropTypes.func, embeddableHandler: PropTypes.object, + eventEmitter: PropTypes.object, timeRange: PropTypes.object, dirty: PropTypes.bool, autoApply: PropTypes.bool, diff --git a/src/plugins/vis_type_timeseries/public/metrics_type.ts b/src/plugins/vis_type_timeseries/public/metrics_type.ts index c06f94efb3c49..649ee765cc642 100644 --- a/src/plugins/vis_type_timeseries/public/metrics_type.ts +++ b/src/plugins/vis_type_timeseries/public/metrics_type.ts @@ -24,7 +24,6 @@ import { metricsRequestHandler } from './request_handler'; import { EditorController } from './application'; // @ts-ignore import { PANEL_TYPES } from '../common/panel_types'; -import { defaultFeedbackMessage } from '../../kibana_utils/public'; import { VisEditor } from './application/components/vis_editor_lazy'; export const metricsVisDefinition = { @@ -34,7 +33,6 @@ export const metricsVisDefinition = { defaultMessage: 'Build time-series using a visual pipeline interface', }), icon: 'visVisualBuilder', - feedbackMessage: defaultFeedbackMessage, visConfig: { defaults: { id: '61ca57f0-469d-11e7-af02-69e470af7417', diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts index a2146e493d4ed..0b1c6e6e20414 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts @@ -18,7 +18,7 @@ */ import { - APICaller, + LegacyAPICaller, FakeRequest, IUiSettingsClient, SavedObjectsClientContract, @@ -53,12 +53,12 @@ export type ReqFacade = FakeRequest & { }; export class AbstractSearchStrategy { - public getCallWithRequestInstance: (req: ReqFacade) => APICaller; + public getCallWithRequestInstance: (req: ReqFacade) => LegacyAPICaller; public getSearchRequest: (req: ReqFacade) => any; constructor( server: any, - callWithRequestFactory: (server: any, req: ReqFacade) => APICaller, + callWithRequestFactory: (server: any, req: ReqFacade) => LegacyAPICaller, SearchRequest: any ) { this.getCallWithRequestInstance = (req) => callWithRequestFactory(server, req); diff --git a/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts index 505816d48af52..a5f095a4c4f3d 100644 --- a/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts +++ b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts @@ -17,13 +17,16 @@ * under the License. */ -import { APICaller, CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; +import { LegacyAPICaller, CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; import { UsageCollectionSetup } from '../../../usage_collection/server'; import { tsvbTelemetrySavedObjectType } from '../saved_objects'; export interface ValidationTelemetryServiceSetup { logFailedValidation: () => void; } +export interface Usage { + failed_validations: number; +} export class ValidationTelemetryService implements Plugin { private kibanaIndex: string = ''; @@ -43,10 +46,10 @@ export class ValidationTelemetryService implements Plugin({ type: 'tsvb-validation', isReady: () => this.kibanaIndex !== '', - fetch: async (callCluster: APICaller) => { + fetch: async (callCluster: LegacyAPICaller) => { try { const response = await callCluster('get', { index: this.kibanaIndex, @@ -63,6 +66,9 @@ export class ValidationTelemetryService implements Plugin, void> { emsTileLayerId: core.injectedMetadata.getInjectedVar('emsTileLayerId', true), }); setUISettings(core.uiSettings); - setKibanaMapFactory(getKibanaMapFactoryProvider(core)); + setKibanaMapFactory(mapsLegacy.getKibanaMapFactoryProvider); setMapsLegacyConfig(mapsLegacy.config); const visualizationDependencies: Readonly = { diff --git a/src/plugins/vis_type_vega/public/vega_type.ts b/src/plugins/vis_type_vega/public/vega_type.ts index c864553c118b9..55ad134c05301 100644 --- a/src/plugins/vis_type_vega/public/vega_type.ts +++ b/src/plugins/vis_type_vega/public/vega_type.ts @@ -21,7 +21,6 @@ import { i18n } from '@kbn/i18n'; import { DefaultEditorSize } from '../../vis_default_editor/public'; import { VegaVisualizationDependencies } from './plugin'; import { VegaVisEditor } from './components'; -import { defaultFeedbackMessage } from '../../kibana_utils/public'; import { createVegaRequestHandler } from './vega_request_handler'; // @ts-ignore @@ -56,6 +55,5 @@ export const createVegaTypeDefinition = (dependencies: VegaVisualizationDependen showFilterBar: true, }, stage: 'experimental', - feedbackMessage: defaultFeedbackMessage, }; }; diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx index eb4b66401820f..b81ff5c166183 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable_factory.tsx @@ -30,7 +30,7 @@ import { import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualize_embeddable'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; -import { Vis } from '../vis'; +import { SerializedVis, Vis } from '../vis'; import { getCapabilities, getTypes, @@ -124,13 +124,20 @@ export class VisualizeEmbeddableFactory } } - public async create() { + public async create(input: VisualizeInput & { savedVis?: SerializedVis }, parent?: IContainer) { // TODO: This is a bit of a hack to preserve the original functionality. Ideally we will clean this up // to allow for in place creation of visualizations without having to navigate away to a new URL. - showNewVisModal({ - originatingApp: await this.getCurrentAppId(), - outsideVisualizeApp: true, - }); - return undefined; + if (input.savedVis) { + const visState = input.savedVis; + const vis = new Vis(visState.type, visState); + await vis.setState(visState); + return createVisEmbeddableFromObject(this.deps)(vis, input, parent); + } else { + showNewVisModal({ + originatingApp: await this.getCurrentAppId(), + outsideVisualizeApp: true, + }); + return undefined; + } } } diff --git a/src/plugins/visualizations/public/index.ts b/src/plugins/visualizations/public/index.ts index 0bbf862216ed5..2ac53c2c81acc 100644 --- a/src/plugins/visualizations/public/index.ts +++ b/src/plugins/visualizations/public/index.ts @@ -51,4 +51,5 @@ export { VisSavedObject, VisResponseValue, } from './types'; +export { VisualizationListItem } from './vis_types/vis_type_alias_registry'; export { VISUALIZE_ENABLE_LABS_SETTING } from '../common/constants'; diff --git a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts index eb00dce8aba2f..8edf494ddc0ec 100644 --- a/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts +++ b/src/plugins/visualizations/public/saved_visualizations/_saved_vis.ts @@ -62,6 +62,7 @@ export const convertFromSerializedVis = (vis: SerializedVis): ISavedVis => { title: vis.title, description: vis.description, visState: { + title: vis.title, type: vis.type, aggs: vis.data.aggs, params: vis.params, diff --git a/src/plugins/visualizations/public/saved_visualizations/saved_visualizations.ts b/src/plugins/visualizations/public/saved_visualizations/saved_visualizations.ts index c6a25df7615a2..d44fc2f4a75af 100644 --- a/src/plugins/visualizations/public/saved_visualizations/saved_visualizations.ts +++ b/src/plugins/visualizations/public/saved_visualizations/saved_visualizations.ts @@ -56,7 +56,7 @@ export function createSavedVisLoader(services: SavedObjectKibanaServicesWithVisu source.icon = source.type.icon; source.image = source.type.image; source.typeTitle = source.type.title; - source.editUrl = `#/edit/${id}`; + source.editUrl = `/edit/${id}`; return source; }; diff --git a/src/plugins/visualizations/public/types.ts b/src/plugins/visualizations/public/types.ts index 3455d88b6ce9e..daf275297fb82 100644 --- a/src/plugins/visualizations/public/types.ts +++ b/src/plugins/visualizations/public/types.ts @@ -35,6 +35,7 @@ export type VisualizationControllerConstructor = new ( ) => VisualizationController; export interface SavedVisState { + title: string; type: string; params: VisParams; aggs: AggConfigOptions[]; diff --git a/src/plugins/visualizations/public/vis.ts b/src/plugins/visualizations/public/vis.ts index aaab0566af65e..e8ae48cdce145 100644 --- a/src/plugins/visualizations/public/vis.ts +++ b/src/plugins/visualizations/public/vis.ts @@ -29,6 +29,7 @@ import { isFunction, defaults, cloneDeep } from 'lodash'; import { Assign } from '@kbn/utility-types'; +import { i18n } from '@kbn/i18n'; import { PersistedState } from './persisted_state'; import { getTypes, getAggs, getSearch, getSavedSearchLoader } from './services'; import { VisType } from './vis_types'; @@ -105,7 +106,13 @@ export class Vis { private getType(visType: string) { const type = getTypes().get(visType); if (!type) { - throw new Error(`Invalid type "${visType}"`); + const errorMessage = i18n.translate('visualizations.visualizationTypeInvalidMessage', { + defaultMessage: 'Invalid visualization type "{visType}"', + values: { + visType, + }, + }); + throw new Error(errorMessage); } return type; } @@ -150,7 +157,13 @@ export class Vis { const configStates = this.initializeDefaultsFromSchemas(aggs, this.type.schemas.all || []); if (!this.data.indexPattern) { if (aggs.length) { - throw new Error('trying to initialize aggs without index pattern'); + const errorMessage = i18n.translate( + 'visualizations.initializeWithoutIndexPatternErrorMessage', + { + defaultMessage: 'Trying to initialize aggs without index pattern', + } + ); + throw new Error(errorMessage); } return; } diff --git a/src/plugins/visualizations/public/vis_types/base_vis_type.ts b/src/plugins/visualizations/public/vis_types/base_vis_type.ts index 2464bb72d2695..44b76a52b34fe 100644 --- a/src/plugins/visualizations/public/vis_types/base_vis_type.ts +++ b/src/plugins/visualizations/public/vis_types/base_vis_type.ts @@ -27,7 +27,6 @@ export interface BaseVisTypeOptions { icon?: string; image?: string; stage?: 'experimental' | 'beta' | 'production'; - feedbackMessage?: string; options?: Record; visualization: VisualizationControllerConstructor; visConfig?: Record; @@ -48,7 +47,7 @@ export class BaseVisType { icon?: string; image?: string; stage: 'experimental' | 'beta' | 'production'; - feedbackMessage: string; + isExperimental: boolean; options: Record; visualization: VisualizationControllerConstructor; visConfig: Record; @@ -87,7 +86,7 @@ export class BaseVisType { this.editorConfig = _.defaultsDeep({}, opts.editorConfig, { collections: {} }); this.options = _.defaultsDeep({}, opts.options, defaultOptions); this.stage = opts.stage || 'production'; - this.feedbackMessage = opts.feedbackMessage || ''; + this.isExperimental = opts.stage === 'experimental'; this.hidden = opts.hidden || false; this.requestHandler = opts.requestHandler || 'courier'; this.responseHandler = opts.responseHandler || 'none'; @@ -97,10 +96,6 @@ export class BaseVisType { this.useCustomNoDataScreen = opts.useCustomNoDataScreen || false; } - shouldMarkAsExperimentalInUI() { - return this.stage === 'experimental'; - } - public get schemas() { if (this.editorConfig && this.editorConfig.schemas) { return this.editorConfig.schemas; diff --git a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts index 73e3360004e5a..bc80d549c81e6 100644 --- a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts +++ b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts @@ -27,6 +27,7 @@ export interface VisualizationListItem { title: string; description?: string; typeTitle: string; + image?: string; } export interface VisualizationsAppExtension { diff --git a/src/plugins/visualize/config.ts b/src/plugins/visualize/config.ts new file mode 100644 index 0000000000000..ee79a37717f26 --- /dev/null +++ b/src/plugins/visualize/config.ts @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + showNewVisualizeFlow: schema.boolean({ defaultValue: false }), +}); + +export type ConfigSchema = TypeOf; diff --git a/src/plugins/visualize/kibana.json b/src/plugins/visualize/kibana.json index cda45f3acc102..c27cfec24b332 100644 --- a/src/plugins/visualize/kibana.json +++ b/src/plugins/visualize/kibana.json @@ -9,7 +9,6 @@ "navigation", "savedObjects", "visualizations", - "dashboard", "embeddable" ], "optionalPlugins": ["home", "share"] diff --git a/src/plugins/visualize/public/application/_app.scss b/src/plugins/visualize/public/application/_app.scss deleted file mode 100644 index 8a52ebf4cc088..0000000000000 --- a/src/plugins/visualize/public/application/_app.scss +++ /dev/null @@ -1,5 +0,0 @@ -.visAppWrapper { - display: flex; - flex-direction: column; - flex-grow: 1; -} diff --git a/src/plugins/visualize/public/application/index.scss b/src/plugins/visualize/public/application/app.scss similarity index 67% rename from src/plugins/visualize/public/application/index.scss rename to src/plugins/visualize/public/application/app.scss index 620380a77ba46..f7f68fbc2c359 100644 --- a/src/plugins/visualize/public/application/index.scss +++ b/src/plugins/visualize/public/application/app.scss @@ -5,6 +5,8 @@ // visChart__legend--small // visChart__legend-isLoading -@import 'app'; -@import 'editor/index'; -@import 'listing/index'; +.visAppWrapper { + display: flex; + flex-direction: column; + flex-grow: 1; +} diff --git a/src/plugins/visualize/public/application/app.tsx b/src/plugins/visualize/public/application/app.tsx new file mode 100644 index 0000000000000..0e71d72a3d4c7 --- /dev/null +++ b/src/plugins/visualize/public/application/app.tsx @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import './app.scss'; +import React, { useEffect } from 'react'; +import { Route, Switch, useLocation } from 'react-router-dom'; + +import { syncQueryStateWithUrl } from '../../../data/public'; +import { useKibana } from '../../../kibana_react/public'; +import { VisualizeServices } from './types'; +import { VisualizeEditor, VisualizeListing, VisualizeNoMatch } from './components'; +import { VisualizeConstants } from './visualize_constants'; + +export const VisualizeApp = () => { + const { + services: { + data: { query }, + kbnUrlStateStorage, + }, + } = useKibana(); + const { pathname } = useLocation(); + + useEffect(() => { + // syncs `_g` portion of url with query services + const { stop } = syncQueryStateWithUrl(query, kbnUrlStateStorage); + + return () => stop(); + + // this effect should re-run when pathname is changed to preserve querystring part, + // so the global state is always preserved + }, [query, kbnUrlStateStorage, pathname]); + + return ( + + + + + + + + + + ); +}; diff --git a/src/plugins/visualize/public/application/application.ts b/src/plugins/visualize/public/application/application.ts deleted file mode 100644 index 60bb73d6de2cc..0000000000000 --- a/src/plugins/visualize/public/application/application.ts +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import './index.scss'; - -import angular, { IModule } from 'angular'; - -// required for i18nIdDirective -import 'angular-sanitize'; -// required for ngRoute -import 'angular-route'; - -import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; - -import { AppMountContext } from 'kibana/public'; -import { NavigationPublicPluginStart as NavigationStart } from 'src/plugins/navigation/public'; -import { - configureAppAngularModule, - createTopNavDirective, - createTopNavHelper, -} from '../../../kibana_legacy/public'; - -// @ts-ignore -import { initVisualizeApp } from './legacy_app'; -import { VisualizeKibanaServices } from '../kibana_services'; - -let angularModuleInstance: IModule | null = null; - -export const renderApp = ( - element: HTMLElement, - appBasePath: string, - deps: VisualizeKibanaServices -) => { - if (!angularModuleInstance) { - angularModuleInstance = createLocalAngularModule(deps.core, deps.navigation); - // global routing stuff - configureAppAngularModule( - angularModuleInstance, - { core: deps.core, env: deps.pluginInitializerContext.env }, - true, - deps.scopedHistory - ); - initVisualizeApp(angularModuleInstance, deps); - } - const $injector = mountVisualizeApp(appBasePath, element); - return () => $injector.get('$rootScope').$destroy(); -}; - -const mainTemplate = (basePath: string) => `
- -
-`; - -const moduleName = 'app/visualize'; - -const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react']; - -function mountVisualizeApp(appBasePath: string, element: HTMLElement) { - const mountpoint = document.createElement('div'); - mountpoint.setAttribute('class', 'visAppWrapper'); - mountpoint.innerHTML = mainTemplate(appBasePath); - // bootstrap angular into detached element and attach it later to - // make angular-within-angular possible - const $injector = angular.bootstrap(mountpoint, [moduleName]); - // initialize global state handler - element.appendChild(mountpoint); - return $injector; -} - -function createLocalAngularModule(core: AppMountContext['core'], navigation: NavigationStart) { - createLocalI18nModule(); - createLocalTopNavModule(navigation); - - const visualizeAngularModule: IModule = angular.module(moduleName, [ - ...thirdPartyAngularDependencies, - 'app/visualize/I18n', - 'app/visualize/TopNav', - ]); - return visualizeAngularModule; -} - -function createLocalTopNavModule(navigation: NavigationStart) { - angular - .module('app/visualize/TopNav', ['react']) - .directive('kbnTopNav', createTopNavDirective) - .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); -} - -function createLocalI18nModule() { - angular - .module('app/visualize/I18n', []) - .provider('i18n', I18nProvider) - .filter('i18n', i18nFilter) - .directive('i18nId', i18nDirective); -} diff --git a/src/plugins/visualize/public/application/components/experimental_vis_info.tsx b/src/plugins/visualize/public/application/components/experimental_vis_info.tsx new file mode 100644 index 0000000000000..51abb3ca530a4 --- /dev/null +++ b/src/plugins/visualize/public/application/components/experimental_vis_info.tsx @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { memo } from 'react'; +import { EuiCallOut, EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +export const InfoComponent = () => { + const title = ( + <> + {' '} + + GitHub + + {'.'} + + ); + + return ( + + ); +}; + +export const ExperimentalVisInfo = memo(InfoComponent); diff --git a/src/plugins/visualize/public/application/components/index.ts b/src/plugins/visualize/public/application/components/index.ts new file mode 100644 index 0000000000000..a3a7fde1d6569 --- /dev/null +++ b/src/plugins/visualize/public/application/components/index.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { VisualizeListing } from './visualize_listing'; +export { VisualizeEditor } from './visualize_editor'; +export { VisualizeNoMatch } from './visualize_no_match'; diff --git a/src/plugins/visualize/public/application/editor/_editor.scss b/src/plugins/visualize/public/application/components/visualize_editor.scss similarity index 100% rename from src/plugins/visualize/public/application/editor/_editor.scss rename to src/plugins/visualize/public/application/components/visualize_editor.scss diff --git a/src/plugins/visualize/public/application/components/visualize_editor.tsx b/src/plugins/visualize/public/application/components/visualize_editor.tsx new file mode 100644 index 0000000000000..c571a5fb078bc --- /dev/null +++ b/src/plugins/visualize/public/application/components/visualize_editor.tsx @@ -0,0 +1,115 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import './visualize_editor.scss'; +import React, { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { EventEmitter } from 'events'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiScreenReaderOnly } from '@elastic/eui'; + +import { useKibana } from '../../../../kibana_react/public'; +import { + useChromeVisibility, + useSavedVisInstance, + useVisualizeAppState, + useEditorUpdates, + useLinkedSearchUpdates, +} from '../utils'; +import { VisualizeServices } from '../types'; +import { ExperimentalVisInfo } from './experimental_vis_info'; +import { VisualizeTopNav } from './visualize_top_nav'; + +export const VisualizeEditor = () => { + const { id: visualizationIdFromUrl } = useParams(); + const [originatingApp, setOriginatingApp] = useState(); + const { services } = useKibana(); + const [eventEmitter] = useState(new EventEmitter()); + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(!visualizationIdFromUrl); + + const isChromeVisible = useChromeVisibility(services.chrome); + const { savedVisInstance, visEditorRef, visEditorController } = useSavedVisInstance( + services, + eventEmitter, + isChromeVisible, + visualizationIdFromUrl + ); + const { appState, hasUnappliedChanges } = useVisualizeAppState( + services, + eventEmitter, + savedVisInstance + ); + const { isEmbeddableRendered, currentAppState } = useEditorUpdates( + services, + eventEmitter, + setHasUnsavedChanges, + appState, + savedVisInstance, + visEditorController + ); + useLinkedSearchUpdates(services, eventEmitter, appState, savedVisInstance); + + useEffect(() => { + const { originatingApp: value } = + services.embeddable.getStateTransfer(services.scopedHistory).getIncomingEditorState() || {}; + setOriginatingApp(value); + }, [services]); + + useEffect(() => { + // clean up all registered listeners if any is left + return () => { + eventEmitter.removeAllListeners(); + }; + }, [eventEmitter]); + + return ( +
+ {savedVisInstance && appState && currentAppState && ( + + )} + {savedVisInstance?.vis?.type?.isExperimental && } + {savedVisInstance && ( + +

+ +

+
+ )} +
+
+ ); +}; diff --git a/src/plugins/visualize/public/application/listing/_listing.scss b/src/plugins/visualize/public/application/components/visualize_listing.scss similarity index 100% rename from src/plugins/visualize/public/application/listing/_listing.scss rename to src/plugins/visualize/public/application/components/visualize_listing.scss diff --git a/src/plugins/visualize/public/application/components/visualize_listing.tsx b/src/plugins/visualize/public/application/components/visualize_listing.tsx new file mode 100644 index 0000000000000..cbfbd6e0e3ab6 --- /dev/null +++ b/src/plugins/visualize/public/application/components/visualize_listing.tsx @@ -0,0 +1,154 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import './visualize_listing.scss'; + +import React, { useCallback, useRef, useMemo, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { useUnmount, useMount } from 'react-use'; +import { useLocation } from 'react-router-dom'; + +import { useKibana, TableListView } from '../../../../kibana_react/public'; +import { VISUALIZE_ENABLE_LABS_SETTING } from '../../../../visualizations/public'; +import { VisualizeServices } from '../types'; +import { VisualizeConstants } from '../visualize_constants'; +import { getTableColumns, getNoItemsMessage } from '../utils'; + +export const VisualizeListing = () => { + const { + services: { + application, + chrome, + history, + savedVisualizations, + toastNotifications, + visualizations, + savedObjects, + savedObjectsPublic, + uiSettings, + visualizeCapabilities, + }, + } = useKibana(); + const { pathname } = useLocation(); + const closeNewVisModal = useRef(() => {}); + const listingLimit = savedObjectsPublic.settings.getListingLimit(); + + useEffect(() => { + if (pathname === '/new') { + // In case the user navigated to the page via the /visualize/new URL we start the dialog immediately + closeNewVisModal.current = visualizations.showNewVisModal({ + onClose: () => { + // In case the user came via a URL to this page, change the URL to the regular landing page URL after closing the modal + history.push(VisualizeConstants.LANDING_PAGE_PATH); + }, + }); + } else { + // close modal window if exists + closeNewVisModal.current(); + } + }, [history, pathname, visualizations]); + + useMount(() => { + chrome.setBreadcrumbs([ + { + text: i18n.translate('visualize.visualizeListingBreadcrumbsTitle', { + defaultMessage: 'Visualize', + }), + }, + ]); + chrome.docTitle.change( + i18n.translate('visualize.listingPageTitle', { defaultMessage: 'Visualize' }) + ); + }); + useUnmount(() => closeNewVisModal.current()); + + const createNewVis = useCallback(() => { + closeNewVisModal.current = visualizations.showNewVisModal(); + }, [visualizations]); + + const editItem = useCallback( + ({ editUrl, editApp }) => { + if (editApp) { + application.navigateToApp(editApp, { path: editUrl }); + return; + } + // for visualizations the edit and view URLs are the same + history.push(editUrl); + }, + [application, history] + ); + + const noItemsFragment = useMemo(() => getNoItemsMessage(createNewVis), [createNewVis]); + const tableColumns = useMemo(() => getTableColumns(application, history), [application, history]); + + const fetchItems = useCallback( + (filter) => { + const isLabsEnabled = uiSettings.get(VISUALIZE_ENABLE_LABS_SETTING); + return savedVisualizations + .findListItems(filter, listingLimit) + .then(({ total, hits }: { total: number; hits: object[] }) => ({ + total, + hits: hits.filter((result: any) => isLabsEnabled || result.type.stage !== 'experimental'), + })); + }, + [listingLimit, savedVisualizations, uiSettings] + ); + + const deleteItems = useCallback( + async (selectedItems: object[]) => { + await Promise.all( + selectedItems.map((item: any) => savedObjects.client.delete(item.savedObjectType, item.id)) + ).catch((error) => { + toastNotifications.addError(error, { + title: i18n.translate('visualize.visualizeListingDeleteErrorTitle', { + defaultMessage: 'Error deleting visualization', + }), + }); + }); + }, + [savedObjects.client, toastNotifications] + ); + + return ( + + ); +}; diff --git a/src/plugins/visualize/public/application/components/visualize_no_match.tsx b/src/plugins/visualize/public/application/components/visualize_no_match.tsx new file mode 100644 index 0000000000000..7776c5e8ce486 --- /dev/null +++ b/src/plugins/visualize/public/application/components/visualize_no_match.tsx @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiCallOut, EuiLink } from '@elastic/eui'; + +import { useKibana, toMountPoint } from '../../../../kibana_react/public'; +import { VisualizeServices } from '../types'; +import { VisualizeConstants } from '../visualize_constants'; + +let bannerId: string; + +export const VisualizeNoMatch = () => { + const { services } = useKibana(); + + useEffect(() => { + services.restorePreviousUrl(); + + const { navigated } = services.kibanaLegacy.navigateToLegacyKibanaUrl( + services.history.location.pathname + ); + + if (!navigated) { + const bannerMessage = i18n.translate('visualize.noMatchRoute.bannerTitleText', { + defaultMessage: 'Page not found', + }); + + bannerId = services.overlays.banners.replace( + bannerId, + toMountPoint( + +

+ + {services.history.location.pathname} + + ), + }} + /> +

+
+ ) + ); + + // hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around + setTimeout(() => { + services.overlays.banners.remove(bannerId); + }, 15000); + + services.history.replace(VisualizeConstants.LANDING_PAGE_PATH); + } + }, [services]); + + return null; +}; diff --git a/src/plugins/visualize/public/application/components/visualize_top_nav.tsx b/src/plugins/visualize/public/application/components/visualize_top_nav.tsx new file mode 100644 index 0000000000000..2e7dba46487ad --- /dev/null +++ b/src/plugins/visualize/public/application/components/visualize_top_nav.tsx @@ -0,0 +1,178 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { memo, useCallback, useMemo, useState, useEffect } from 'react'; +import { isEqual } from 'lodash'; + +import { OverlayRef } from 'kibana/public'; +import { Query } from 'src/plugins/data/public'; +import { useKibana } from '../../../../kibana_react/public'; +import { + VisualizeServices, + VisualizeAppState, + VisualizeAppStateContainer, + SavedVisInstance, +} from '../types'; +import { APP_NAME } from '../visualize_constants'; +import { getTopNavConfig } from '../utils'; + +interface VisualizeTopNavProps { + currentAppState: VisualizeAppState; + isChromeVisible?: boolean; + isEmbeddableRendered: boolean; + hasUnsavedChanges: boolean; + setHasUnsavedChanges: (value: boolean) => void; + hasUnappliedChanges: boolean; + originatingApp?: string; + savedVisInstance: SavedVisInstance; + stateContainer: VisualizeAppStateContainer; + visualizationIdFromUrl?: string; +} + +const TopNav = ({ + currentAppState, + isChromeVisible, + isEmbeddableRendered, + hasUnsavedChanges, + setHasUnsavedChanges, + hasUnappliedChanges, + originatingApp, + savedVisInstance, + stateContainer, + visualizationIdFromUrl, +}: VisualizeTopNavProps) => { + const { services } = useKibana(); + const { TopNavMenu } = services.navigation.ui; + const { embeddableHandler, vis } = savedVisInstance; + const [inspectorSession, setInspectorSession] = useState(); + const openInspector = useCallback(() => { + const session = embeddableHandler.openInspector(); + setInspectorSession(session); + }, [embeddableHandler]); + + const updateQuery = useCallback( + ({ query }: { query?: Query }) => { + if (!isEqual(currentAppState.query, query)) { + stateContainer.transitions.set('query', query || currentAppState.query); + } else { + savedVisInstance.embeddableHandler.reload(); + } + }, + [currentAppState.query, savedVisInstance.embeddableHandler, stateContainer.transitions] + ); + + const config = useMemo(() => { + if (isEmbeddableRendered) { + return getTopNavConfig( + { + hasUnsavedChanges, + setHasUnsavedChanges, + hasUnappliedChanges, + openInspector, + originatingApp, + savedVisInstance, + stateContainer, + visualizationIdFromUrl, + }, + services + ); + } + }, [ + isEmbeddableRendered, + hasUnsavedChanges, + setHasUnsavedChanges, + hasUnappliedChanges, + openInspector, + originatingApp, + savedVisInstance, + stateContainer, + visualizationIdFromUrl, + services, + ]); + const [indexPattern, setIndexPattern] = useState(vis.data.indexPattern); + const showDatePicker = () => { + // tsvb loads without an indexPattern initially (TODO investigate). + // hide timefilter only if timeFieldName is explicitly undefined. + const hasTimeField = vis.data.indexPattern ? !!vis.data.indexPattern.timeFieldName : true; + return vis.type.options.showTimePicker && hasTimeField; + }; + const showFilterBar = vis.type.options.showFilterBar; + const showQueryInput = vis.type.requiresSearch && vis.type.options.showQueryBar; + + useEffect(() => { + return () => { + if (inspectorSession) { + // Close the inspector if this scope is destroyed (e.g. because the user navigates away). + inspectorSession.close(); + } + }; + }, [inspectorSession]); + + useEffect(() => { + if (!vis.data.indexPattern) { + services.data.indexPatterns.getDefault().then((index) => { + if (index) { + setIndexPattern(index); + } + }); + } + }, [services.data.indexPatterns, vis.data.indexPattern]); + + return isChromeVisible ? ( + /** + * Most visualizations have all search bar components enabled. + * Some visualizations have fewer options, but all visualizations have the search bar. + * That's is why the showSearchBar prop is set. + * All visualizations also have the timepicker\autorefresh component, + * it is enabled by default in the TopNavMenu component. + */ + + ) : showFilterBar ? ( + /** + * The top nav is hidden in embed mode, but the filter bar must still be present so + * we show the filter bar on its own here if the chrome is not visible. + */ + + ) : null; +}; + +export const VisualizeTopNav = memo(TopNav); diff --git a/src/plugins/visualize/public/application/editor/_index.scss b/src/plugins/visualize/public/application/editor/_index.scss deleted file mode 100644 index 9d3ca4b539947..0000000000000 --- a/src/plugins/visualize/public/application/editor/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'editor'; diff --git a/src/plugins/visualize/public/application/editor/editor.html b/src/plugins/visualize/public/application/editor/editor.html deleted file mode 100644 index 3c3455fb34f18..0000000000000 --- a/src/plugins/visualize/public/application/editor/editor.html +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - -
-
-

-
-
- - - -

-

- - -
diff --git a/src/plugins/visualize/public/application/editor/editor.js b/src/plugins/visualize/public/application/editor/editor.js deleted file mode 100644 index d7c7828c58f23..0000000000000 --- a/src/plugins/visualize/public/application/editor/editor.js +++ /dev/null @@ -1,763 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import angular from 'angular'; -import _ from 'lodash'; -import { Subscription } from 'rxjs'; -import { map } from 'rxjs/operators'; -import { i18n } from '@kbn/i18n'; -import { EventEmitter } from 'events'; - -import React from 'react'; -import { makeStateful, useVisualizeAppState } from './lib'; -import { VisualizeConstants } from '../visualize_constants'; -import { getEditBreadcrumbs } from '../breadcrumbs'; - -import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; -import { unhashUrl } from '../../../../kibana_utils/public'; -import { MarkdownSimple, toMountPoint } from '../../../../kibana_react/public'; -import { - addFatalError, - subscribeWithScope, - migrateLegacyQuery, -} from '../../../../kibana_legacy/public'; -import { showSaveModal, SavedObjectSaveModalOrigin } from '../../../../saved_objects/public'; -import { - esFilters, - connectToQueryState, - syncQueryStateWithUrl, - UI_SETTINGS, -} from '../../../../data/public'; - -import { initVisEditorDirective } from './visualization_editor'; -import { initVisualizationDirective } from './visualization'; - -import { getServices } from '../../kibana_services'; -import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../visualizations/public'; - -export function initEditorDirective(app, deps) { - app.directive('visualizeApp', function () { - return { - restrict: 'E', - controllerAs: 'visualizeApp', - controller: VisualizeAppController, - }; - }); - - initVisEditorDirective(app, deps); - initVisualizationDirective(app, deps); -} - -function VisualizeAppController($scope, $route, $injector, $timeout, kbnUrlStateStorage, history) { - const { - localStorage, - visualizeCapabilities, - share, - data: { query: queryService, indexPatterns }, - toastNotifications, - chrome, - core: { docLinks, fatalErrors, uiSettings, application }, - I18nContext, - setActiveUrl, - visualizations, - embeddable, - scopedHistory, - } = getServices(); - - const { - filterManager, - timefilter: { timefilter }, - } = queryService; - - // starts syncing `_g` portion of url with query services - const { stop: stopSyncingQueryServiceStateWithUrl } = syncQueryStateWithUrl( - queryService, - kbnUrlStateStorage - ); - - // Retrieve the resolved SavedVis instance. - const { vis, savedVis, savedSearch, embeddableHandler } = $route.current.locals.resolved; - $scope.eventEmitter = new EventEmitter(); - const _applyVis = () => { - $scope.$apply(); - }; - // This will trigger a digest cycle. This is needed when vis is updated from a global angular like in visualize_embeddable.js. - $scope.eventEmitter.on('apply', _applyVis); - // vis is instance of src/legacy/ui/public/vis/vis.js. - // SearchSource is a promise-based stream of search results that can inherit from other search sources. - const searchSource = vis.data.searchSource; - - $scope.vis = vis; - $scope.savedSearch = savedSearch; - - const $appStatus = { - dirty: !savedVis.id, - }; - - const defaultQuery = { - query: '', - language: - localStorage.get('kibana.userQueryLanguage') || - uiSettings.get(UI_SETTINGS.SEARCH_QUERY_LANGUAGE), - }; - - const { originatingApp } = - embeddable.getStateTransfer(scopedHistory()).getIncomingEditorState() || {}; - $scope.getOriginatingApp = () => originatingApp; - - const visStateToEditorState = () => { - const savedVisState = visualizations.convertFromSerializedVis(vis.serialize()); - return { - uiState: vis.uiState.toJSON(), - query: vis.data.searchSource.getOwnField('query') || defaultQuery, - filters: vis.data.searchSource.getOwnField('filter') || [], - vis: { ...savedVisState.visState, title: vis.title }, - linked: !!savedVis.savedSearchId, - }; - }; - - const stateDefaults = visStateToEditorState(); - - const { stateContainer, stopStateSync } = useVisualizeAppState({ - stateDefaults, - kbnUrlStateStorage, - }); - - $scope.eventEmitter.on('dirtyStateChange', ({ isDirty }) => { - if (!isDirty) { - stateContainer.transitions.updateVisState(visStateToEditorState().vis); - } - $timeout(() => { - $scope.dirty = isDirty; - }); - }); - - $scope.eventEmitter.on('updateVis', () => { - embeddableHandler.reload(); - }); - - $scope.embeddableHandler = embeddableHandler; - - $scope.topNavMenu = [ - ...($scope.getOriginatingApp() && savedVis.id - ? [ - { - id: 'saveAndReturn', - label: i18n.translate('visualize.topNavMenu.saveAndReturnVisualizationButtonLabel', { - defaultMessage: 'Save and return', - }), - emphasize: true, - iconType: 'check', - description: i18n.translate( - 'visualize.topNavMenu.saveAndReturnVisualizationButtonAriaLabel', - { - defaultMessage: 'Finish editing visualization and return to the last app', - } - ), - testId: 'visualizesaveAndReturnButton', - disableButton() { - return Boolean($scope.dirty); - }, - tooltip() { - if ($scope.dirty) { - return i18n.translate( - 'visualize.topNavMenu.saveAndReturnVisualizationDisabledButtonTooltip', - { - defaultMessage: 'Apply or Discard your changes before finishing', - } - ); - } - }, - run: async () => { - const saveOptions = { - confirmOverwrite: false, - returnToOrigin: true, - }; - return doSave(saveOptions); - }, - }, - ] - : []), - ...(visualizeCapabilities.save - ? [ - { - id: 'save', - label: - savedVis.id && $scope.getOriginatingApp() - ? i18n.translate('visualize.topNavMenu.saveVisualizationAsButtonLabel', { - defaultMessage: 'save as', - }) - : i18n.translate('visualize.topNavMenu.saveVisualizationButtonLabel', { - defaultMessage: 'save', - }), - emphasize: !savedVis.id || !$scope.getOriginatingApp(), - description: i18n.translate('visualize.topNavMenu.saveVisualizationButtonAriaLabel', { - defaultMessage: 'Save Visualization', - }), - testId: 'visualizeSaveButton', - disableButton() { - return Boolean($scope.dirty); - }, - tooltip() { - if ($scope.dirty) { - return i18n.translate( - 'visualize.topNavMenu.saveVisualizationDisabledButtonTooltip', - { - defaultMessage: 'Apply or Discard your changes before saving', - } - ); - } - }, - run: async () => { - const onSave = ({ - newTitle, - newCopyOnSave, - isTitleDuplicateConfirmed, - onTitleDuplicate, - newDescription, - returnToOrigin, - }) => { - const currentTitle = savedVis.title; - savedVis.title = newTitle; - savedVis.copyOnSave = newCopyOnSave; - savedVis.description = newDescription; - const saveOptions = { - confirmOverwrite: false, - isTitleDuplicateConfirmed, - onTitleDuplicate, - returnToOrigin, - }; - return doSave(saveOptions).then((response) => { - // If the save wasn't successful, put the original values back. - if (!response.id || response.error) { - savedVis.title = currentTitle; - } - return response; - }); - }; - - const saveModal = ( - {}} - originatingApp={$scope.getOriginatingApp()} - /> - ); - showSaveModal(saveModal, I18nContext); - }, - }, - ] - : []), - { - id: 'share', - label: i18n.translate('visualize.topNavMenu.shareVisualizationButtonLabel', { - defaultMessage: 'share', - }), - description: i18n.translate('visualize.topNavMenu.shareVisualizationButtonAriaLabel', { - defaultMessage: 'Share Visualization', - }), - testId: 'shareTopNavButton', - run: (anchorElement) => { - const hasUnappliedChanges = $scope.dirty; - const hasUnsavedChanges = $appStatus.dirty; - share.toggleShareContextMenu({ - anchorElement, - allowEmbed: true, - allowShortUrl: visualizeCapabilities.createShortUrl, - shareableUrl: unhashUrl(window.location.href), - objectId: savedVis.id, - objectType: 'visualization', - sharingData: { - title: savedVis.title, - }, - isDirty: hasUnappliedChanges || hasUnsavedChanges, - }); - }, - // disable the Share button if no action specified - disableButton: !share, - }, - { - id: 'inspector', - label: i18n.translate('visualize.topNavMenu.openInspectorButtonLabel', { - defaultMessage: 'inspect', - }), - description: i18n.translate('visualize.topNavMenu.openInspectorButtonAriaLabel', { - defaultMessage: 'Open Inspector for visualization', - }), - testId: 'openInspectorButton', - disableButton() { - return !embeddableHandler.hasInspector || !embeddableHandler.hasInspector(); - }, - run() { - const inspectorSession = embeddableHandler.openInspector(); - - if (inspectorSession) { - // Close the inspector if this scope is destroyed (e.g. because the user navigates away). - const removeWatch = $scope.$on('$destroy', () => inspectorSession.close()); - // Remove that watch in case the user closes the inspector session herself. - inspectorSession.onClose.finally(removeWatch); - } - }, - tooltip() { - if (!embeddableHandler.hasInspector || !embeddableHandler.hasInspector()) { - return i18n.translate('visualize.topNavMenu.openInspectorDisabledButtonTooltip', { - defaultMessage: `This visualization doesn't support any inspectors.`, - }); - } - }, - }, - ]; - - if (savedVis.id) { - chrome.docTitle.change(savedVis.title); - } - - // sync initial app filters from state to filterManager - filterManager.setAppFilters(_.cloneDeep(stateContainer.getState().filters)); - // setup syncing of app filters between appState and filterManager - const stopSyncingAppFilters = connectToQueryState( - queryService, - { - set: ({ filters }) => stateContainer.transitions.set('filters', filters), - get: () => ({ filters: stateContainer.getState().filters }), - state$: stateContainer.state$.pipe(map((state) => ({ filters: state.filters }))), - }, - { - filters: esFilters.FilterStateStore.APP_STATE, - } - ); - - const stopAllSyncing = () => { - stopStateSync(); - stopSyncingQueryServiceStateWithUrl(); - stopSyncingAppFilters(); - }; - - // The savedVis is pulled from elasticsearch, but the appState is pulled from the url, with the - // defaults applied. If the url was from a previous session which included modifications to the - // appState then they won't be equal. - if (!_.isEqual(stateContainer.getState().vis, stateDefaults.vis)) { - try { - const { aggs, ...visState } = stateContainer.getState().vis; - vis.setState({ ...visState, data: { aggs } }); - } catch (error) { - // stop syncing url updtes with the state to prevent extra syncing - stopAllSyncing(); - - toastNotifications.addWarning({ - title: i18n.translate('visualize.visualizationTypeInvalidNotificationMessage', { - defaultMessage: 'Invalid visualization type', - }), - text: toMountPoint({error.message}), - }); - - history.replace(`${VisualizeConstants.LANDING_PAGE_PATH}?notFound=visualization`); - - // prevent further controller execution - return; - } - } - - $scope.filters = filterManager.getFilters(); - - $scope.onFiltersUpdated = (filters) => { - // The filters will automatically be set when the filterManager emits an update event (see below) - filterManager.setFilters(filters); - }; - - $scope.showSaveQuery = visualizeCapabilities.saveQuery; - - $scope.$watch( - () => visualizeCapabilities.saveQuery, - (newCapability) => { - $scope.showSaveQuery = newCapability; - } - ); - - const updateSavedQueryFromUrl = (savedQueryId) => { - if (!savedQueryId) { - delete $scope.savedQuery; - - return; - } - - if ($scope.savedQuery && $scope.savedQuery.id === savedQueryId) { - return; - } - - queryService.savedQueries.getSavedQuery(savedQueryId).then((savedQuery) => { - $scope.$evalAsync(() => { - $scope.updateSavedQuery(savedQuery); - }); - }); - }; - - function init() { - if (vis.data.indexPattern) { - $scope.indexPattern = vis.data.indexPattern; - } else { - indexPatterns.getDefault().then((defaultIndexPattern) => { - $scope.indexPattern = defaultIndexPattern; - }); - } - - const initialState = stateContainer.getState(); - - const handleLinkedSearch = (linked) => { - if (linked && !savedVis.savedSearchId && savedSearch) { - savedVis.savedSearchId = savedSearch.id; - vis.data.savedSearchId = savedSearch.id; - searchSource.setParent(savedSearch.searchSource); - } else if (!linked && savedVis.savedSearchId) { - delete savedVis.savedSearchId; - delete vis.data.savedSearchId; - } - }; - - // Create a PersistedState instance for uiState. - const { persistedState, unsubscribePersisted, persistOnChange } = makeStateful( - 'uiState', - stateContainer - ); - vis.uiState = persistedState; - vis.uiState.on('reload', embeddableHandler.reload); - $scope.uiState = persistedState; - $scope.savedVis = savedVis; - $scope.query = initialState.query; - $scope.searchSource = searchSource; - $scope.refreshInterval = timefilter.getRefreshInterval(); - handleLinkedSearch(initialState.linked); - - $scope.showFilterBar = () => { - return vis.type.options.showFilterBar; - }; - - $scope.showQueryInput = () => { - return vis.type.requiresSearch && vis.type.options.showQueryBar; - }; - - $scope.showQueryBarTimePicker = () => { - // tsvb loads without an indexPattern initially (TODO investigate). - // hide timefilter only if timeFieldName is explicitly undefined. - const hasTimeField = vis.data.indexPattern ? !!vis.data.indexPattern.timeFieldName : true; - return vis.type.options.showTimePicker && hasTimeField; - }; - - $scope.timeRange = timefilter.getTime(); - - const unsubscribeStateUpdates = stateContainer.subscribe((state) => { - const newQuery = migrateLegacyQuery(state.query); - if (!_.isEqual(state.query, newQuery)) { - stateContainer.transitions.set('query', newQuery); - } - persistOnChange(state); - updateSavedQueryFromUrl(state.savedQuery); - - // if the browser history was changed manually we need to reflect changes in the editor - if ( - !_.isEqual( - { - ...visualizations.convertFromSerializedVis(vis.serialize()).visState, - title: vis.title, - }, - state.vis - ) - ) { - const { aggs, ...visState } = state.vis; - vis.setState({ - ...visState, - data: { - aggs, - }, - }); - embeddableHandler.reload(); - $scope.eventEmitter.emit('updateEditor'); - } - - $appStatus.dirty = true; - $scope.fetch(); - }); - - const updateTimeRange = () => { - $scope.timeRange = timefilter.getTime(); - $scope.$broadcast('render'); - }; - - // update the query if savedQuery is stored - updateSavedQueryFromUrl(initialState.savedQuery); - - const subscriptions = new Subscription(); - - subscriptions.add( - subscribeWithScope( - $scope, - timefilter.getRefreshIntervalUpdate$(), - { - next: () => { - $scope.refreshInterval = timefilter.getRefreshInterval(); - }, - }, - (error) => addFatalError(fatalErrors, error) - ) - ); - subscriptions.add( - subscribeWithScope( - $scope, - timefilter.getTimeUpdate$(), - { - next: updateTimeRange, - }, - (error) => addFatalError(fatalErrors, error) - ) - ); - - subscriptions.add( - chrome.getIsVisible$().subscribe((isVisible) => { - $scope.$evalAsync(() => { - $scope.isVisible = isVisible; - }); - }) - ); - - // update the searchSource when query updates - $scope.fetch = function () { - const { query, linked, filters } = stateContainer.getState(); - $scope.query = query; - handleLinkedSearch(linked); - vis.data.searchSource.setField('query', query); - vis.data.searchSource.setField('filter', filters); - $scope.$broadcast('render'); - }; - - // update the searchSource when filters update - subscriptions.add( - subscribeWithScope( - $scope, - filterManager.getUpdates$(), - { - next: () => { - $scope.filters = filterManager.getFilters(); - }, - }, - (error) => addFatalError(fatalErrors, error) - ) - ); - subscriptions.add( - subscribeWithScope( - $scope, - filterManager.getFetches$(), - { - next: $scope.fetch, - }, - (error) => addFatalError(fatalErrors, error) - ) - ); - - $scope.$on('$destroy', () => { - if ($scope._handler) { - $scope._handler.destroy(); - } - savedVis.destroy(); - subscriptions.unsubscribe(); - $scope.eventEmitter.off('apply', _applyVis); - - unsubscribePersisted(); - vis.uiState.off('reload', embeddableHandler.reload); - unsubscribeStateUpdates(); - - stopAllSyncing(); - }); - - $timeout(() => { - $scope.$broadcast('render'); - }); - } - - $scope.updateQueryAndFetch = function ({ query, dateRange }) { - const isUpdate = - (query && !_.isEqual(query, stateContainer.getState().query)) || - (dateRange && !_.isEqual(dateRange, $scope.timeRange)); - - stateContainer.transitions.set('query', query); - timefilter.setTime(dateRange); - - // If nothing has changed, trigger the fetch manually, otherwise it will happen as a result of the changes - if (!isUpdate) { - embeddableHandler.reload(); - } - }; - - $scope.onRefreshChange = function ({ isPaused, refreshInterval }) { - timefilter.setRefreshInterval({ - pause: isPaused, - value: refreshInterval ? refreshInterval : $scope.refreshInterval.value, - }); - }; - - $scope.onClearSavedQuery = () => { - delete $scope.savedQuery; - stateContainer.transitions.removeSavedQuery(defaultQuery); - filterManager.setFilters(filterManager.getGlobalFilters()); - }; - - const updateStateFromSavedQuery = (savedQuery) => { - stateContainer.transitions.updateFromSavedQuery(savedQuery); - - const savedQueryFilters = savedQuery.attributes.filters || []; - const globalFilters = filterManager.getGlobalFilters(); - filterManager.setFilters([...globalFilters, ...savedQueryFilters]); - - if (savedQuery.attributes.timefilter) { - timefilter.setTime({ - from: savedQuery.attributes.timefilter.from, - to: savedQuery.attributes.timefilter.to, - }); - if (savedQuery.attributes.timefilter.refreshInterval) { - timefilter.setRefreshInterval(savedQuery.attributes.timefilter.refreshInterval); - } - } - }; - - $scope.updateSavedQuery = (savedQuery) => { - $scope.savedQuery = savedQuery; - updateStateFromSavedQuery(savedQuery); - }; - - /** - * Called when the user clicks "Save" button. - */ - function doSave(saveOptions) { - // vis.title was not bound and it's needed to reflect title into visState - const newlyCreated = !Boolean(savedVis.id) || savedVis.copyOnSave; - stateContainer.transitions.setVis({ - title: savedVis.title, - type: savedVis.type || stateContainer.getState().vis.type, - }); - savedVis.searchSourceFields = searchSource.getSerializedFields(); - savedVis.visState = stateContainer.getState().vis; - savedVis.uiStateJSON = angular.toJson($scope.uiState.toJSON()); - $appStatus.dirty = false; - - return savedVis.save(saveOptions).then( - function (id) { - $scope.$evalAsync(() => { - if (id) { - toastNotifications.addSuccess({ - title: i18n.translate( - 'visualize.topNavMenu.saveVisualization.successNotificationText', - { - defaultMessage: `Saved '{visTitle}'`, - values: { - visTitle: savedVis.title, - }, - } - ), - 'data-test-subj': 'saveVisualizationSuccess', - }); - - if ($scope.getOriginatingApp() && saveOptions.returnToOrigin) { - const appPath = `${VisualizeConstants.EDIT_PATH}/${encodeURIComponent(savedVis.id)}`; - - // Manually insert a new url so the back button will open the saved visualization. - history.replace(appPath); - setActiveUrl(appPath); - if (newlyCreated && embeddable) { - embeddable - .getStateTransfer() - .navigateToWithEmbeddablePackage($scope.getOriginatingApp(), { - state: { id: savedVis.id, type: VISUALIZE_EMBEDDABLE_TYPE }, - }); - } else { - application.navigateToApp($scope.getOriginatingApp()); - } - } else if (savedVis.id === $route.current.params.id) { - chrome.docTitle.change(savedVis.lastSavedTitle); - chrome.setBreadcrumbs($injector.invoke(getEditBreadcrumbs)); - savedVis.vis.title = savedVis.title; - savedVis.vis.description = savedVis.description; - } else { - history.replace({ - ...history.location, - pathname: `${VisualizeConstants.EDIT_PATH}/${savedVis.id}`, - }); - } - } - }); - return { id }; - }, - (error) => { - // eslint-disable-next-line - console.error(error); - toastNotifications.addDanger({ - title: i18n.translate('visualize.topNavMenu.saveVisualization.failureNotificationText', { - defaultMessage: `Error on saving '{visTitle}'`, - values: { - visTitle: savedVis.title, - }, - }), - text: error.message, - 'data-test-subj': 'saveVisualizationError', - }); - return { error }; - } - ); - } - - const unlinkFromSavedSearch = () => { - const searchSourceParent = savedSearch.searchSource; - const searchSourceGrandparent = searchSourceParent.getParent(); - const currentIndex = searchSourceParent.getField('index'); - - searchSource.setField('index', currentIndex); - searchSource.setParent(searchSourceGrandparent); - - stateContainer.transitions.unlinkSavedSearch({ - query: searchSourceParent.getField('query'), - parentFilters: searchSourceParent.getOwnField('filter'), - }); - - toastNotifications.addSuccess( - i18n.translate('visualize.linkedToSearch.unlinkSuccessNotificationText', { - defaultMessage: `Unlinked from saved search '{searchTitle}'`, - values: { - searchTitle: savedSearch.title, - }, - }) - ); - }; - - $scope.getAdditionalMessage = () => { - return ( - '' + - i18n.translate('visualize.experimentalVisInfoText', { - defaultMessage: 'This visualization is marked as experimental.', - }) + - ' ' + - vis.type.feedbackMessage - ); - }; - - $scope.eventEmitter.on('unlinkFromSavedSearch', unlinkFromSavedSearch); - - addHelpMenuToAppChrome(chrome, docLinks); - - init(); -} diff --git a/src/plugins/visualize/public/application/editor/lib/make_stateful.ts b/src/plugins/visualize/public/application/editor/lib/make_stateful.ts deleted file mode 100644 index c7163f9b7705d..0000000000000 --- a/src/plugins/visualize/public/application/editor/lib/make_stateful.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { PersistedState } from '../../../../../visualizations/public'; -import { ReduxLikeStateContainer } from '../../../../../kibana_utils/public'; -import { VisualizeAppState, VisualizeAppStateTransitions } from '../../types'; - -/** - * @returns Create a PersistedState instance, initialize state changes subscriber/unsubscriber - */ -export function makeStateful( - prop: keyof VisualizeAppState, - stateContainer: ReduxLikeStateContainer -) { - // set up the persistedState state - const persistedState = new PersistedState(); - - // update the appState when the stateful instance changes - const updateOnChange = function () { - stateContainer.transitions.set(prop, persistedState.getChanges()); - }; - - const handlerOnChange = (method: 'on' | 'off') => - persistedState[method]('change', updateOnChange); - - handlerOnChange('on'); - const unsubscribePersisted = () => handlerOnChange('off'); - - // update the stateful object when the app state changes - const persistOnChange = function (state: VisualizeAppState) { - if (state[prop]) { - persistedState.set(state[prop]); - } - }; - - const appState = stateContainer.getState(); - - // if the thing we're making stateful has an appState value, write to persisted state - if (appState[prop]) persistedState.setSilent(appState[prop]); - - return { persistedState, unsubscribePersisted, persistOnChange }; -} diff --git a/src/plugins/visualize/public/application/editor/visualization.js b/src/plugins/visualize/public/application/editor/visualization.js deleted file mode 100644 index 26f61f3f0a2c2..0000000000000 --- a/src/plugins/visualize/public/application/editor/visualization.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export function initVisualizationDirective(app) { - app.directive('visualizationEmbedded', function ($timeout) { - return { - restrict: 'E', - scope: { - embeddableHandler: '=', - uiState: '=?', - timeRange: '=', - filters: '=', - query: '=', - appState: '=', - }, - link: function ($scope, element) { - $scope.renderFunction = async () => { - if (!$scope.rendered) { - $scope.embeddableHandler.render(element[0]); - $scope.rendered = true; - } - - $scope.embeddableHandler.updateInput({ - timeRange: $scope.timeRange, - filters: $scope.filters || [], - query: $scope.query, - }); - }; - - $scope.$on('render', (event) => { - event.preventDefault(); - $timeout(() => { - $scope.renderFunction(); - }); - }); - - $scope.$on('$destroy', () => { - if ($scope.embeddableHandler) { - $scope.embeddableHandler.destroy(); - } - }); - }, - }; - }); -} diff --git a/src/plugins/visualize/public/application/editor/visualization_editor.js b/src/plugins/visualize/public/application/editor/visualization_editor.js deleted file mode 100644 index 4963d9bc5ed72..0000000000000 --- a/src/plugins/visualize/public/application/editor/visualization_editor.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { DefaultEditorController } from '../../../../vis_default_editor/public'; - -export function initVisEditorDirective(app, deps) { - app.directive('visualizationEditor', function ($timeout) { - return { - restrict: 'E', - scope: { - vis: '=', - uiState: '=?', - timeRange: '=', - filters: '=', - query: '=', - savedSearch: '=', - embeddableHandler: '=', - eventEmitter: '=', - }, - link: function ($scope, element) { - const Editor = $scope.vis.type.editor || DefaultEditorController; - const editor = new Editor( - element[0], - $scope.vis, - $scope.eventEmitter, - $scope.embeddableHandler - ); - - $scope.renderFunction = () => { - editor.render({ - core: deps.core, - data: deps.data, - uiState: $scope.uiState, - timeRange: $scope.timeRange, - filters: $scope.filters, - query: $scope.query, - linked: !!$scope.vis.data.savedSearchId, - savedSearch: $scope.savedSearch, - }); - }; - - $scope.$on('render', (event) => { - event.preventDefault(); - $timeout(() => { - $scope.renderFunction(); - }); - }); - - $scope.$on('$destroy', () => { - editor.destroy(); - }); - }, - }; - }); -} diff --git a/src/plugins/visualize/public/application/index.tsx b/src/plugins/visualize/public/application/index.tsx new file mode 100644 index 0000000000000..4bec244e6efc9 --- /dev/null +++ b/src/plugins/visualize/public/application/index.tsx @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Router } from 'react-router-dom'; + +import { AppMountParameters } from 'kibana/public'; +import { KibanaContextProvider } from '../../../kibana_react/public'; +import { VisualizeApp } from './app'; +import { VisualizeServices } from './types'; +import { addHelpMenuToAppChrome, addBadgeToAppChrome } from './utils'; + +export const renderApp = ({ element }: AppMountParameters, services: VisualizeServices) => { + // add help link to visualize docs into app chrome menu + addHelpMenuToAppChrome(services.chrome, services.docLinks); + // add readonly badge if saving restricted + if (!services.visualizeCapabilities.save) { + addBadgeToAppChrome(services.chrome); + } + + const app = ( + + + + + + + + ); + + ReactDOM.render(app, element); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/src/plugins/visualize/public/application/legacy_app.js b/src/plugins/visualize/public/application/legacy_app.js deleted file mode 100644 index 452118f8097da..0000000000000 --- a/src/plugins/visualize/public/application/legacy_app.js +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { find } from 'lodash'; -import { i18n } from '@kbn/i18n'; -import { createHashHistory } from 'history'; - -import { createKbnUrlStateStorage, redirectWhenMissing } from '../../../kibana_utils/public'; -import { createSavedSearchesLoader } from '../../../discover/public'; - -import editorTemplate from './editor/editor.html'; -import visualizeListingTemplate from './listing/visualize_listing.html'; - -import { initVisualizeAppDirective } from './visualize_app'; -import { VisualizeConstants } from './visualize_constants'; -import { VisualizeListingController } from './listing/visualize_listing'; - -import { - getLandingBreadcrumbs, - getWizardStep1Breadcrumbs, - getCreateBreadcrumbs, - getEditBreadcrumbs, -} from './breadcrumbs'; - -const getResolvedResults = (deps) => { - const { core, data, visualizations, createVisEmbeddableFromObject } = deps; - - const results = {}; - - return (savedVis) => { - results.savedVis = savedVis; - const serializedVis = visualizations.convertToSerializedVis(savedVis); - return visualizations - .createVis(serializedVis.type, serializedVis) - .then((vis) => { - if (vis.type.setup) { - return vis.type.setup(vis).catch(() => vis); - } - return vis; - }) - .then((vis) => { - results.vis = vis; - return createVisEmbeddableFromObject(vis, { - timeRange: data.query.timefilter.timefilter.getTime(), - filters: data.query.filterManager.getFilters(), - }); - }) - .then((embeddableHandler) => { - results.embeddableHandler = embeddableHandler; - - embeddableHandler.getOutput$().subscribe((output) => { - if (output.error) { - core.notifications.toasts.addError(output.error, { - title: i18n.translate('visualize.error.title', { - defaultMessage: 'Visualization error', - }), - }); - } - }); - - if (results.vis.data.savedSearchId) { - return createSavedSearchesLoader({ - savedObjectsClient: core.savedObjects.client, - indexPatterns: data.indexPatterns, - search: data.search, - chrome: core.chrome, - overlays: core.overlays, - }).get(results.vis.data.savedSearchId); - } - }) - .then((savedSearch) => { - if (savedSearch) { - results.savedSearch = savedSearch; - } - return results; - }); - }; -}; - -export function initVisualizeApp(app, deps) { - initVisualizeAppDirective(app, deps); - - app.factory('history', () => createHashHistory()); - app.factory('kbnUrlStateStorage', (history) => - createKbnUrlStateStorage({ - history, - useHash: deps.core.uiSettings.get('state:storeInSessionStorage'), - }) - ); - - app.config(function ($routeProvider) { - const defaults = { - reloadOnSearch: false, - requireUICapability: 'visualize.show', - badge: () => { - if (deps.visualizeCapabilities.save) { - return undefined; - } - - return { - text: i18n.translate('visualize.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('visualize.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save visualizations', - }), - iconType: 'glasses', - }; - }, - }; - - $routeProvider - .when(VisualizeConstants.LANDING_PAGE_PATH, { - ...defaults, - template: visualizeListingTemplate, - k7Breadcrumbs: getLandingBreadcrumbs, - controller: VisualizeListingController, - controllerAs: 'listingController', - resolve: { - createNewVis: () => false, - hasDefaultIndex: (history) => deps.data.indexPatterns.ensureDefaultIndexPattern(history), - }, - }) - .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { - ...defaults, - template: visualizeListingTemplate, - k7Breadcrumbs: getWizardStep1Breadcrumbs, - controller: VisualizeListingController, - controllerAs: 'listingController', - resolve: { - createNewVis: () => true, - hasDefaultIndex: (history) => deps.data.indexPatterns.ensureDefaultIndexPattern(history), - }, - }) - .when(VisualizeConstants.CREATE_PATH, { - ...defaults, - template: editorTemplate, - k7Breadcrumbs: getCreateBreadcrumbs, - resolve: { - resolved: function ($route, history) { - const { data, savedVisualizations, visualizations, toastNotifications } = deps; - const visTypes = visualizations.all(); - const visType = find(visTypes, { name: $route.current.params.type }); - const shouldHaveIndex = visType.requiresSearch && visType.options.showIndexSelection; - const hasIndex = - $route.current.params.indexPattern || $route.current.params.savedSearchId; - if (shouldHaveIndex && !hasIndex) { - throw new Error( - i18n.translate( - 'visualize.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage', - { - defaultMessage: 'You must provide either an indexPattern or a savedSearchId', - } - ) - ); - } - - // This delay is needed to prevent some navigation issues in Firefox/Safari. - // see https://github.com/elastic/kibana/issues/65161 - const delay = (res) => { - return new Promise((resolve) => { - setTimeout(() => resolve(res), 0); - }); - }; - - return data.indexPatterns - .ensureDefaultIndexPattern(history) - .then(() => savedVisualizations.get($route.current.params)) - .then((savedVis) => { - savedVis.searchSourceFields = { index: $route.current.params.indexPattern }; - return savedVis; - }) - .then(getResolvedResults(deps)) - .then(delay) - .catch( - redirectWhenMissing({ - history, - mapping: VisualizeConstants.LANDING_PAGE_PATH, - toastNotifications, - }) - ); - }, - }, - }) - .when(`${VisualizeConstants.EDIT_PATH}/:id`, { - ...defaults, - template: editorTemplate, - k7Breadcrumbs: getEditBreadcrumbs, - resolve: { - resolved: function ($route, history) { - const { chrome, data, savedVisualizations, toastNotifications } = deps; - - return data.indexPatterns - .ensureDefaultIndexPattern(history) - .then(() => savedVisualizations.get($route.current.params.id)) - .then((savedVis) => { - chrome.recentlyAccessed.add(savedVis.getFullPath(), savedVis.title, savedVis.id); - return savedVis; - }) - .then(getResolvedResults(deps)) - .catch( - redirectWhenMissing({ - history, - navigateToApp: deps.core.application.navigateToApp, - basePath: deps.core.http.basePath, - mapping: { - visualization: VisualizeConstants.LANDING_PAGE_PATH, - search: { - app: 'management', - path: 'kibana/objects/savedVisualizations/' + $route.current.params.id, - }, - 'index-pattern': { - app: 'management', - path: 'kibana/objects/savedVisualizations/' + $route.current.params.id, - }, - 'index-pattern-field': { - app: 'management', - path: 'kibana/objects/savedVisualizations/' + $route.current.params.id, - }, - }, - toastNotifications, - onBeforeRedirect() { - deps.setActiveUrl(VisualizeConstants.LANDING_PAGE_PATH); - }, - }) - ); - }, - }, - }) - .otherwise({ - resolveRedirectTo: function ($rootScope) { - const path = window.location.hash.substr(1); - deps.restorePreviousUrl(); - $rootScope.$applyAsync(() => { - const { navigated } = deps.kibanaLegacy.navigateToLegacyKibanaUrl(path); - if (!navigated) { - deps.kibanaLegacy.navigateToDefaultApp(); - } - }); - // prevent angular from completing the navigation - return new Promise(() => {}); - }, - }); - }); -} diff --git a/src/plugins/visualize/public/application/listing/_index.scss b/src/plugins/visualize/public/application/listing/_index.scss deleted file mode 100644 index 924c164e467d8..0000000000000 --- a/src/plugins/visualize/public/application/listing/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import 'listing'; diff --git a/src/plugins/visualize/public/application/listing/visualize_listing.html b/src/plugins/visualize/public/application/listing/visualize_listing.html deleted file mode 100644 index 8838348e0b679..0000000000000 --- a/src/plugins/visualize/public/application/listing/visualize_listing.html +++ /dev/null @@ -1,13 +0,0 @@ -
- -
diff --git a/src/plugins/visualize/public/application/listing/visualize_listing.js b/src/plugins/visualize/public/application/listing/visualize_listing.js deleted file mode 100644 index e8e8d92034113..0000000000000 --- a/src/plugins/visualize/public/application/listing/visualize_listing.js +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; -import { withI18nContext } from './visualize_listing_table'; - -import { VisualizeConstants } from '../visualize_constants'; -import { i18n } from '@kbn/i18n'; - -import { getServices } from '../../kibana_services'; -import { syncQueryStateWithUrl } from '../../../../data/public'; -import { VISUALIZE_ENABLE_LABS_SETTING } from '../../../../visualizations/public'; - -import { EuiLink } from '@elastic/eui'; -import React from 'react'; - -export function initListingDirective(app, I18nContext) { - app.directive('visualizeListingTable', (reactDirective) => - reactDirective(withI18nContext(I18nContext)) - ); -} - -export function VisualizeListingController($scope, createNewVis, kbnUrlStateStorage, history) { - const { - addBasePath, - chrome, - savedObjectsClient, - savedVisualizations, - data: { query }, - toastNotifications, - visualizations, - core: { docLinks, savedObjects, uiSettings, application }, - savedObjects: savedObjectsPublic, - } = getServices(); - - chrome.docTitle.change( - i18n.translate('visualize.listingPageTitle', { defaultMessage: 'Visualize' }) - ); - - // syncs `_g` portion of url with query services - const { stop: stopSyncingQueryServiceStateWithUrl } = syncQueryStateWithUrl( - query, - kbnUrlStateStorage - ); - - const { - timefilter: { timefilter }, - } = query; - - timefilter.disableAutoRefreshSelector(); - timefilter.disableTimeRangeSelector(); - - this.addBasePath = addBasePath; - this.uiSettings = uiSettings; - this.savedObjects = savedObjects; - - this.createNewVis = () => { - this.closeNewVisModal = visualizations.showNewVisModal(); - }; - - this.editItem = ({ editUrl, editApp }) => { - if (editApp) { - application.navigateToApp(editApp, { path: editUrl }); - return; - } - // for visualizations the edit and view URLs are the same - window.location.href = addBasePath(editUrl); - }; - - this.getViewElement = (field, record) => { - const dataTestSubj = `visListingTitleLink-${record.title.split(' ').join('-')}`; - if (record.editApp) { - return ( - { - application.navigateToApp(record.editApp, { path: record.editUrl }); - }} - data-test-subj={dataTestSubj} - > - {field} - - ); - } else if (record.editUrl) { - return ( - - {field} - - ); - } else { - return {field}; - } - }; - - if (createNewVis) { - // In case the user navigated to the page via the /visualize/new URL we start the dialog immediately - this.closeNewVisModal = visualizations.showNewVisModal({ - onClose: () => { - // In case the user came via a URL to this page, change the URL to the regular landing page URL after closing the modal - history.push({ - // Should preserve querystring part so the global state is preserved. - ...history.location, - pathname: VisualizeConstants.LANDING_PAGE_PATH, - }); - }, - }); - } - - this.fetchItems = (filter) => { - const isLabsEnabled = uiSettings.get(VISUALIZE_ENABLE_LABS_SETTING); - return savedVisualizations - .findListItems(filter, savedObjectsPublic.settings.getListingLimit()) - .then((result) => { - this.totalItems = result.total; - - return { - total: result.total, - hits: result.hits.filter( - (result) => isLabsEnabled || result.type.stage !== 'experimental' - ), - }; - }); - }; - - this.deleteSelectedItems = function deleteSelectedItems(selectedItems) { - return Promise.all( - selectedItems.map((item) => { - return savedObjectsClient.delete(item.savedObjectType, item.id); - }) - ).catch((error) => { - toastNotifications.addError(error, { - title: i18n.translate('visualize.visualizeListingDeleteErrorTitle', { - defaultMessage: 'Error deleting visualization', - }), - }); - }); - }; - - chrome.setBreadcrumbs([ - { - text: i18n.translate('visualize.visualizeListingBreadcrumbsTitle', { - defaultMessage: 'Visualize', - }), - }, - ]); - - this.listingLimit = savedObjectsPublic.settings.getListingLimit(); - this.initialPageSize = savedObjectsPublic.settings.getPerPage(); - - addHelpMenuToAppChrome(chrome, docLinks); - - $scope.$on('$destroy', () => { - if (this.closeNewVisModal) { - this.closeNewVisModal(); - } - - stopSyncingQueryServiceStateWithUrl(); - }); -} diff --git a/src/plugins/visualize/public/application/listing/visualize_listing_table.js b/src/plugins/visualize/public/application/listing/visualize_listing_table.js deleted file mode 100644 index fcd62d7ddee73..0000000000000 --- a/src/plugins/visualize/public/application/listing/visualize_listing_table.js +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React, { Component, Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import { TableListView } from '../../../../kibana_react/public'; - -import { EuiIcon, EuiBetaBadge, EuiButton, EuiEmptyPrompt } from '@elastic/eui'; - -import { getServices } from '../../kibana_services'; - -class VisualizeListingTable extends Component { - constructor(props) { - super(props); - } - - render() { - const { visualizeCapabilities, core, toastNotifications } = getServices(); - return ( - item.canDelete} - initialFilter={''} - noItemsFragment={this.getNoItemsMessage()} - entityName={i18n.translate('visualize.listing.table.entityName', { - defaultMessage: 'visualization', - })} - entityNamePlural={i18n.translate('visualize.listing.table.entityNamePlural', { - defaultMessage: 'visualizations', - })} - tableListTitle={i18n.translate('visualize.listing.table.listTitle', { - defaultMessage: 'Visualizations', - })} - toastNotifications={toastNotifications} - uiSettings={core.uiSettings} - /> - ); - } - - getTableColumns() { - const tableColumns = [ - { - field: 'title', - name: i18n.translate('visualize.listing.table.titleColumnName', { - defaultMessage: 'Title', - }), - sortable: true, - render: (field, record) => this.props.getViewElement(field, record), - }, - { - field: 'typeTitle', - name: i18n.translate('visualize.listing.table.typeColumnName', { - defaultMessage: 'Type', - }), - sortable: true, - render: (field, record) => ( - - {this.renderItemTypeIcon(record)} - {record.typeTitle} - {this.getBadge(record)} - - ), - }, - { - field: 'description', - name: i18n.translate('visualize.listing.table.descriptionColumnName', { - defaultMessage: 'Description', - }), - sortable: true, - render: (field, record) => {record.description}, - }, - ]; - - return tableColumns; - } - - getNoItemsMessage() { - if (this.props.hideWriteControls) { - return ( -
- - - - } - /> -
- ); - } - - return ( -
- - - - } - body={ - -

- -

-
- } - actions={ - - - - } - /> -
- ); - } - - renderItemTypeIcon(item) { - let icon; - if (item.image) { - icon = ( - - ); - } else { - icon = ( -