diff --git a/.eslintrc.js b/.eslintrc.js index 8d5b4525d51ba5..ffc49a60d5bca0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -888,7 +888,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', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e6f6e83253c8bb..47f9942162f75c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -170,6 +170,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 +178,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 00000000000000..30643a104c1cd2 --- /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 75ce5f56c96c65..bc5e1ccc1dd55e 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 00000000000000..860ab66f22f4e2 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 eb4fb790afd7fd..65f7a378ec2443 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/development/core/public/kibana-plugin-core-public.publicappinfo.md b/docs/development/core/public/kibana-plugin-core-public.publicappinfo.md index c70f3a97a8882f..4b3b103c92731d 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 cc3e9de3193cb8..051638daabd12f 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/server/kibana-plugin-core-server.callapioptions.md b/docs/development/core/server/kibana-plugin-core-server.callapioptions.md deleted file mode 100644 index 03f74424acf99b..00000000000000 --- 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 d4b2f80443acb6..00000000000000 --- 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 dd1dcffc5a969f..00000000000000 --- 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 ae9a9b46b52b6b..00000000000000 --- 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 0f6b2512a6d94c..00000000000000 --- 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 d2b07e8d116bab..00000000000000 --- 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 42079c02689c53..00000000000000 --- 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 d85ae4f9ab5303..00000000000000 --- 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 e8c4c63dc6a964..c683f0ba33189f 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 c1e23527e95163..0dd41a6154a1e8 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 667a36091f2326..5f346d7887c2af 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 39c794af2c881f..e059acdbd52fa2 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 28fbdf17cf31c9..c70a5ac07c6ad8 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 3d379e1231cb15..a3cb8f13150212 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 378836ee461f38..1263b85acb4983 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 4da23b3f821c5b..e6c2878d2b3556 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 00000000000000..9ebe2fc57a54bf --- /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 8b35924826f151..7d795a59e41a54 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 2a5b8054819a3a..38fac54db77a42 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 00000000000000..823f34bd7dd237 --- /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 00000000000000..1c25fc1d072b61 --- /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 00000000000000..2e235485711c6e --- /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 68a1bd4b9d7cf2..88a5ffce5bb17c 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 00000000000000..4f218ae552c99b --- /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 703b07808aca3b..62b0f216c863c0 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 00000000000000..05530ceb0d568b --- /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 00000000000000..f760780504e55f --- /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 2b2eeceb4e3c9d..bd802a39e9339f 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 00000000000000..f647916149458e --- /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 e9c205e3dfa2c4..e20dcd4ed253eb 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 00000000000000..bd1cd1e9f3d9b3 --- /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 68c0f8e6be1be0..9130f9adde76e0 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 e769fd692a4d1d..bf95782299e720 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 00000000000000..f3d8a69b8ed053 --- /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 74422c82fc9ea7..7e777d51f147f7 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) | | 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 7b887d6d421e4c..b09fb121b8a632 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 99be0676bcda3d..55d6e931ac1584 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 d4d1d442a57f98..00000000000000 --- 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 058d65b587dbed..00000000000000 --- 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/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 00000000000000..54e7cf92f500cf --- /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.indexpatternsfetcher._constructor_.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternsfetcher._constructor_.md index f9bbcc5a356e13..d36ebd0745e8d0 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 f492ba2843a697..c80112fb17dde5 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/package.json b/package.json index 10eaef8ed5dc74..c225435b4e4ffd 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", @@ -376,8 +377,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", diff --git a/packages/kbn-telemetry-tools/README.md b/packages/kbn-telemetry-tools/README.md new file mode 100644 index 00000000000000..ccd092c76a17c4 --- /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/src/plugins/kibana_utils/common/default_feedback_message.ts b/packages/kbn-telemetry-tools/babel.config.js similarity index 69% rename from src/plugins/kibana_utils/common/default_feedback_message.ts rename to packages/kbn-telemetry-tools/babel.config.js index f61f36bc8810cc..3b09c7d74ccb56 100644 --- a/src/plugins/kibana_utils/common/default_feedback_message.ts +++ b/packages/kbn-telemetry-tools/babel.config.js @@ -17,12 +17,7 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; - -export const defaultFeedbackMessage = i18n.translate('kibana_utils.defaultFeedbackMessage', { - defaultMessage: 'Have feedback? Please create an issue in {link}.', - values: { - link: - 'GitHub', - }, -}); +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 00000000000000..5593a72ecd965a --- /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 00000000000000..116c484a5c36af --- /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 00000000000000..27a406a4e216d2 --- /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 78589383925fb9..3a018a9b3002c3 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 00000000000000..885fe0e38dacfe --- /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 00000000000000..fe45f6b7f30427 --- /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 00000000000000..48702520829502 --- /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/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_imported_usage_interface.ts b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_imported_usage_interface.ts new file mode 100644 index 00000000000000..42ed2140b5208a --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/__fixture__/parsed_imported_usage_interface.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 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 00000000000000..ed727c15b7c86e --- /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 00000000000000..25e49ea221c941 --- /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 00000000000000..44a12dfa9030cc --- /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 00000000000000..5b1b3d9d352990 --- /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 00000000000000..6083593431d9b3 --- /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 00000000000000..824132b05732ce --- /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/packages/kbn-telemetry-tools/src/tools/config.test.ts b/packages/kbn-telemetry-tools/src/tools/config.test.ts new file mode 100644 index 00000000000000..51ca0493cbb5a4 --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/config.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 path from 'path'; +import { parseTelemetryRC } from './config'; + +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 00000000000000..5724b869e8f5ea --- /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/src/plugins/kibana_utils/common/default_feedback_message.test.ts b/packages/kbn-telemetry-tools/src/tools/constants.ts similarity index 68% rename from src/plugins/kibana_utils/common/default_feedback_message.test.ts rename to packages/kbn-telemetry-tools/src/tools/constants.ts index 5c1afa4634b71e..8635b1a2e2528e 100644 --- a/src/plugins/kibana_utils/common/default_feedback_message.test.ts +++ b/packages/kbn-telemetry-tools/src/tools/constants.ts @@ -17,10 +17,4 @@ * under the License. */ -import { defaultFeedbackMessage } from './default_feedback_message'; - -test('default feedback message with link', () => { - expect(defaultFeedbackMessage).toMatchInlineSnapshot( - `"Have feedback? Please create an issue in GitHub."` - ); -}); +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 00000000000000..1b4ed21a1635cf --- /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 00000000000000..a638fde0214580 --- /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 00000000000000..8f4bfc66b32aeb --- /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 00000000000000..d422837140d802 --- /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 00000000000000..9475574a442192 --- /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 00000000000000..bce5dd7f58643b --- /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 00000000000000..dae4d0f1ad168a --- /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 00000000000000..a1f23bcd44c765 --- /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/packages/kbn-telemetry-tools/src/tools/tasks/error_reporter.ts b/packages/kbn-telemetry-tools/src/tools/tasks/error_reporter.ts new file mode 100644 index 00000000000000..246d659667281e --- /dev/null +++ b/packages/kbn-telemetry-tools/src/tools/tasks/error_reporter.ts @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import chalk from 'chalk'; +import { normalizePath } from '../utils'; + +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 00000000000000..834ec71e220320 --- /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 00000000000000..f6d15c7127d4eb --- /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 00000000000000..cbe74aeb483e41 --- /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 00000000000000..00b319006e2ee3 --- /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 00000000000000..78d0b7fbd6c2d7 --- /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 00000000000000..fcfc09db65426f --- /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 00000000000000..b7ca33a7bcd743 --- /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 00000000000000..6af8450f5a2e8c --- /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 00000000000000..f5cf74ae35e456 --- /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 00000000000000..13ce8ef2bad60b --- /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 88e84fc87ae533..aaa46ab74714f1 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')); diff --git a/packages/kbn-ui-shared-deps/index.js b/packages/kbn-ui-shared-deps/index.js index 301d1765558479..596c31820e80d0 100644 --- a/packages/kbn-ui-shared-deps/index.js +++ b/packages/kbn-ui-shared-deps/index.js @@ -42,6 +42,7 @@ exports.externals = { 'react-intl': '__kbnSharedDeps__.ReactIntl', 'react-router': '__kbnSharedDeps__.ReactRouter', 'react-router-dom': '__kbnSharedDeps__.ReactRouterDom', + 'styled-components': '__kbnSharedDeps__.StyledComponents', '@kbn/monaco': '__kbnSharedDeps__.KbnMonaco', // this is how plugins/consumers from npm load monaco 'monaco-editor/esm/vs/editor/editor.api': '__kbnSharedDeps__.MonacoBarePluginApi', diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 1cc04afad56696..0e3bb235c3d9f5 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/scripts/telemetry_check.js b/scripts/telemetry_check.js new file mode 100644 index 00000000000000..06b3ed46bdba6a --- /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 00000000000000..051bee26537b9b --- /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/core/public/application/types.ts b/src/core/public/application/types.ts index 6926b6acf24115..cd2dd99c30c116 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -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 1dc9ec70590017..92d25fa468c4a5 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/public.api.md b/src/core/public/public.api.md index d10e351f4d13ed..a65b9dd9d242a7 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -1143,11 +1143,16 @@ 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 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 75627f311d9a58..00000000000000 --- 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 ccd5fd0c7a5718..648eaaf8a97f27 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 55e60f5987604e..fdfc48fa9f754b 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 e87913a3c5e4ff..c30230a7847a0e 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 26001bf83924fb..f47b33dd410f6c 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 2e45f710c4dcfd..f5f5f5cc7b6f88 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 5f2b61eb4935fd..b9699ab290e3fc 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 6873117aecc35e..08585c2305e9bf 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 820272bdf14b81..9ab4862a1ab4c5 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 2352677b8d3e0a..8e2d20a8972fc9 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 77d1e41c9ad833..d6b65da7726d26 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 287d835c40351b..260a60ac74f118 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 6852b8632ebf12..f81903d76547a3 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 00000000000000..165980b9f45223 --- /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 18ffa95048c4d9..62789a4fe952d7 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 88% rename from src/core/server/elasticsearch/retry_call_cluster.ts rename to src/core/server/elasticsearch/legacy/retry_call_cluster.ts index aa3e39d948593a..b12ecc889eb2d7 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$) => @@ -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 04c13f85a1ef7a..2eb8cefb564ae6 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 4b64bfba151907..e62409ff00c7ed 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 6fef08fc298ffa..2b4ba4b0a0a550 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 776298e5869a0d..3f562dac22a028 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/index.ts b/src/core/server/http/index.ts index ca9dfde2e71dce..65d633260a7911 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 8e782970e2a8e4..6f9b4b96eae9d6 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/index.ts b/src/core/server/index.ts index 7520111bf33ac5..91c33fac41646b 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, @@ -356,7 +356,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 +371,7 @@ export interface RequestHandlerContext { }; elasticsearch: { legacy: { - client: IScopedClusterClient; + client: ILegacyScopedClusterClient; }; }; uiSettings: { diff --git a/src/core/server/logging/logging_system.test.ts b/src/core/server/logging/logging_system.test.ts index f73e40fe320dc3..ac52973081106e 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 0bab9534d2d053..8aadab83bf7163 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/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index 48b1e12fc187ed..c2d4f49d7ee2ac 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/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 40c5282a77e499..f24195c0f295e3 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 108826ad61aa26..5973e300e098e1 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -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: { @@ -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) @@ -1679,7 +1690,7 @@ export interface RequestHandlerContext { }; elasticsearch: { legacy: { - client: IScopedClusterClient; + client: ILegacyScopedClusterClient; }; }; uiSettings: { @@ -2323,7 +2334,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 +2489,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; 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 486abd5a0c7908..ea462291059a51 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/dev/jest/config.js b/src/dev/jest/config.js index 18fc7ebaa2af44..da343aa0f0672d 100644 --- a/src/dev/jest/config.js +++ b/src/dev/jest/config.js @@ -43,6 +43,7 @@ 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', @@ -51,6 +52,7 @@ export default { '!packages/kbn-ui-framework/src/services/**/*/index.js', 'src/legacy/core_plugins/**/*.{js,jsx,ts,tsx}', '!src/legacy/core_plugins/**/{__test__,__snapshots__}/**/*', + '!src/legacy/core_plugins/tests_bundle/**', ], moduleNameMapper: { '@elastic/eui$': '/node_modules/@elastic/eui/test-env', diff --git a/src/fixtures/telemetry_collectors/.telemetryrc.json b/src/fixtures/telemetry_collectors/.telemetryrc.json new file mode 100644 index 00000000000000..31203149c9b579 --- /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 00000000000000..4aac9e66cdbdb3 --- /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 00000000000000..00a8d643e27b33 --- /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/plugins/data/common/timefilter/index.ts b/src/fixtures/telemetry_collectors/file_with_no_collector.ts similarity index 94% rename from src/plugins/data/common/timefilter/index.ts rename to src/fixtures/telemetry_collectors/file_with_no_collector.ts index e0c509e119fda1..2e1870e486269d 100644 --- a/src/plugins/data/common/timefilter/index.ts +++ b/src/fixtures/telemetry_collectors/file_with_no_collector.ts @@ -17,4 +17,4 @@ * under the License. */ -export { isTimeRange } from './is_time_range'; +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 00000000000000..66d04700642d17 --- /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 00000000000000..a4a0f4ae1b3c4a --- /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 00000000000000..bde89fe4a70603 --- /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/plugins/visualize/public/application/help_menu/help_menu_util.js b/src/fixtures/telemetry_collectors/unmapped_collector.ts similarity index 62% rename from src/plugins/visualize/public/application/help_menu/help_menu_util.js rename to src/fixtures/telemetry_collectors/unmapped_collector.ts index c297326f2e264e..1ea360fcd9e960 100644 --- a/src/plugins/visualize/public/application/help_menu/help_menu_util.js +++ b/src/fixtures/telemetry_collectors/unmapped_collector.ts @@ -16,19 +16,24 @@ * 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 { i18n } from '@kbn/i18n'; +const { makeUsageCollector } = new CollectorSet({ + logger: loggerMock.create(), + maximumWaitTimeForAllCollectorsInS: 0, +}); -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`, - }, - ], - }); +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 00000000000000..d70a247c61e70a --- /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 a595fffb3c2350..0e7692f6be7555 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/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 395cb605878328..63c2cbec21b579 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 6622ed4bef478e..9c124a90e66eb4 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 2784b74ab726cf..8b3347f8d88f0e 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/data/common/field_formats/mocks.ts b/src/plugins/data/common/field_formats/mocks.ts index 394d4c383032fb..9bbaefe2d146a8 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 b40e02b709d301..0fb45fcc739d45 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/query/index.ts b/src/plugins/data/common/query/index.ts index 4e90f6f8bb83ec..b0dfbbb82355e0 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 3706972ce4a2ef..6e4eda95accc73 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/src/plugins/data/common/query/timefilter/index.ts b/src/plugins/data/common/query/timefilter/index.ts new file mode 100644 index 00000000000000..55739511a0ef54 --- /dev/null +++ b/src/plugins/data/common/query/timefilter/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 * 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 b197b16e67dd11..60008ce6054e1b 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 61b5d5b2b7b4ad..6b34a1baf293bc 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 93629c3dbaf626..e2ec1a031b0ca7 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/index.ts b/src/plugins/data/public/index.ts index efce8d2c021c99..89b0d7e0303b98 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/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 e32a8e023cf402..7ed6525db6350e 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/plugin.ts b/src/plugins/data/public/plugin.ts index 493733daf261b2..51f96f10aa7c72 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -146,7 +146,6 @@ export class DataPublicPlugin implements Plugin { - 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 8b8deea43f808e..d47a9cbb3bd609 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 95e0b2cd27186b..18a86d767c2de2 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 a2b74eca584766..8650f5920e5202 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 6e6fb3350d901f..121bb29f6f8edb 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 6cc03be292d7b5..b272dfd3c7468e 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 cc45b935d45b54..99b1c4d8612192 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 e909cd8134e83f..13655afdf469ea 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 2af29d36002460..68542b66e6c358 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 44d99375bbd302..8e862b5692ca39 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 2f03b8ec671123..518bdbfe0c1352 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 0d66d9cfcdca2c..08f39e10b6006f 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 0fdb07cc4198a5..c5ad1a61e6676f 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 396d515f3b5809..3f9f5dd5672f0a 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 f8e7747d49147c..f3626bc9130ad3 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 990adde5f8a0bd..89a5fd65778912 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 564e7b4763c8d5..238e5737d8f474 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 cbad8742bfab60..f9db2973af1369 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 36e4bef025ef9b..a45087916a395c 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 fc42d43b2fea81..e4c4bc0cedc3c4 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 e1881c3bbc7f4c..0829d6e9cdc9f7 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 3e14ab422ccbee..dbcf5517fb08fd 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 69157edad4f680..d048df9f0e43e8 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 295e740a2a7809..7554e4081726cc 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 877a817984dc6f..d4c0a5b3288445 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 be339de5d7faea..fbe7c76681f816 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 4756669f5b4b31..787603ee143613 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 c1fad17f488db2..ce275e2dc1639a 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 b3e90bdf9b56a1..018fcb365b5850 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 2b8a36f2fbdbcb..fea5572b75795c 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 543e5d66b9fa81..92c5193d55a209 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 156f7f8108482c..8fe833aa99cb2e 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 9769efb6da7494..2c5be00c8afea5 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 1e8e9ab4ef9d09..57a5ebf72316c6 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 4864a8b9d013bb..73068326ca97ee 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 38312ec5cfa81c..a30e426dd23901 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 e2c6a5105bac66..307c7ed3f9d386 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 c46a3eb9425d1f..bb4ed9d44b0b33 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 57212ec9ff91b2..dd065b52acd129 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 2855cc1b6b16eb..efc79a85595661 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 4c7b8139b01628..86faca053a9cf7 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 c392f44a7961ed..3de88bb71740f1 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 f3c1cc9bc29773..c9507029080e61 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 2a74a446ce84e0..73acd268600763 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 8e3e0143bf9154..347ac76b8e27b9 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 71c48f04a3ca8a..b3721e2c1e679b 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 83656db4ac5474..5c4ff91258fb00 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 abad2782f9d201..61384ef1dc1066 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 2201366cdf78b5..91149a3f29f94a 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 df52c10a82495b..39e371763ed4b1 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 c5aee380b97762..1953c81a998740 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 76382c01bcc108..52ab325ac5806b 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 7cb1b194fed1c1..9ea01be11fe8f8 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 56bd33b2b6345b..f08e850caadcec 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 536764b2bcf0b8..2fa207e62771e3 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 49e0a3e4b349ab..94a970a72a46f5 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 16544fd8f46b08..7a5dcc9be45929 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 836aaad2cda0c3..385d0cd6c6b396 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 906b8862d00aab..c4846a98f124fd 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 51b5e175761bd7..23da060cba2032 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 77475cd3ce88b9..e9daec0b4191b6 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 d5997c15817f69..134dc89c2421a0 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 b4498aca4079cf..0c5551d95690f2 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 3334d858ce54e9..94473d23ccc398 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 f6691360cec312..0a1e99c8bb2001 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/server/index.ts b/src/plugins/data/server/index.ts index 19d028c1d41e1b..6a4eb38b552ffb 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 f563976cae9b82..ff9d67152e268d 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 734e845e857693..0738a16034d467 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 d8c94664322049..502364cdcba327 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 764307bef0ba64..a01d34dbe9df6c 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/kql_telemetry/usage_collector/fetch.test.ts b/src/plugins/data/server/kql_telemetry/usage_collector/fetch.test.ts index 81e352fea51b26..35cee799ddb6aa 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 157716b38f5234..109d6f812334df 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 db4c9a8f0b4c79..6d0ca00122018f 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/saved_objects/index_patterns.ts b/src/plugins/data/server/saved_objects/index_patterns.ts index ee02f38427914f..902cf2988f429f 100644 --- a/src/plugins/data/server/saved_objects/index_patterns.ts +++ b/src/plugins/data/server/saved_objects/index_patterns.ts @@ -37,7 +37,7 @@ export const indexPatternSavedObjectType: SavedObjectsType = { getInAppUrl(obj) { return { path: `/app/management/kibana/indexPatterns/patterns/${encodeURIComponent(obj.id)}`, - uiCapabilitiesPath: 'management.kibana.index_patterns', + uiCapabilitiesPath: 'management.kibana.indexPatterns', }; }, }, diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 136d960b52c347..1eaab2550645f8 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -4,7 +4,6 @@ ```ts -import { APICaller as APICaller_2 } from 'kibana/server'; import Boom from 'boom'; import { BulkIndexDocumentsParams } from 'elasticsearch'; import { CatAliasesParams } from 'elasticsearch'; @@ -92,6 +91,7 @@ import { IngestGetPipelineParams } from 'elasticsearch'; import { IngestPutPipelineParams } from 'elasticsearch'; import { IngestSimulateParams } from 'elasticsearch'; import { KibanaConfigType as KibanaConfigType_2 } from 'src/core/server/kibana_config'; +import { LegacyAPICaller as LegacyAPICaller_2 } from 'kibana/server'; import { Logger as Logger_2 } from 'kibana/server'; import { MGetParams } from 'elasticsearch'; import { MGetResponse } from 'elasticsearch'; @@ -335,6 +335,14 @@ export function getDefaultSearchParams(config: SharedGlobalConfig): { restTotalHitsAsInt: boolean; }; +// Warning: (ae-missing-release-tag) "getTime" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export function getTime(indexPattern: IIndexPattern | undefined, timeRange: TimeRange, options?: { + forceNow?: Date; + fieldName?: string; +}): import("../..").RangeFilter | undefined; + // @internal export function getTotalLoaded({ total, failed, successful }: ShardsResponse): { total: number; @@ -477,7 +485,7 @@ export const indexPatterns: { // // @public (undocumented) export class IndexPatternsFetcher { - constructor(callDataCluster: APICaller_2); + constructor(callDataCluster: LegacyAPICaller_2); getFieldsForTimePattern(options: { pattern: string; metaFields: string[]; diff --git a/src/plugins/expressions/server/legacy.ts b/src/plugins/expressions/server/legacy.ts index 4dd9419e59e2dd..8ff08542f18f85 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 1c662e2301e16c..5e86e0ca54bf79 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 19ceceb4cba143..d819d67a8d4324 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 4c7316c8530181..d43458cfc64db8 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 a98cc05a0a80af..6e93d23f8469ca 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 8114dbf110f8b3..2af53ea4d28e83 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 8187e70b1bbd1a..63b9b48ec809eb 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 df0adfc52184b5..c4e7eaac51cf46 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 f52687038bbbca..1f22ab01001010 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 804c8b0ed2026e..1adc0dc6896fda 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 d0da6fcc523cc4..9cc079a9325d53 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 39cd3518849550..3a777beebd90a7 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 603742f612a6bd..ec2f1bfdfc25f9 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 99daed98dbe640..c94021872b4e10 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 6f61e2c228970d..2911a9ae75689d 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 2c5d04883235c7..fd56dd8a6ee274 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 08a87b3e89f2ba..d8d148a9247fff 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 71656d7c0b83ba..dada4636e6add9 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 9a69749c8233b4..254ac3ed04dd73 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/navigation/public/top_nav_menu/top_nav_menu_data.tsx b/src/plugins/navigation/public/top_nav_menu/top_nav_menu_data.tsx index 2b7466ffd6ab37..a1653c52892554 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/saved_objects/public/saved_object/saved_object_loader.ts b/src/plugins/saved_objects/public/saved_object/saved_object_loader.ts index 53ef1f3f04ad9b..9e7346f3b673cd 100644 --- a/src/plugins/saved_objects/public/saved_object/saved_object_loader.ts +++ b/src/plugins/saved_objects/public/saved_object/saved_object_loader.ts @@ -51,14 +51,17 @@ export class SavedObjectLoader { } /** - * Retrieve a saved object by id. Returns a promise that completes when the object finishes + * Retrieve a saved object by id or create new one. + * Returns a promise that completes when the object finishes * initializing. - * @param id + * @param opts * @returns {Promise} */ - 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 973a493c0a15e9..6db72b396a86ae 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 09e08e6ec333b0..e83952e82c953e 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 82146554afa6ff..0fcf52cba254b5 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/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 884f39b97e2335..7d43158ad8878e 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 6eb9e36394ee31..15e5cb89b622c6 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 92e4e637e0d3fe..56fddc075a50c9 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/relationships.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/components/relationships.test.tsx index 9277d9b00305b1..2e545b372f781b 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 b9dad983cb42d4..6b25a1b0c1f256 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 @@ -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/saved_objects_table.test.tsx b/src/plugins/saved_objects_management/public/management_section/objects_table/saved_objects_table.test.tsx index e3456240dc340f..3719dac24e6e70 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/telemetry/common/constants.ts b/src/plugins/telemetry/common/constants.ts index 53c79b738f750e..fc77332c18fc90 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 00000000000000..e660ccac9dc36b --- /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 00000000000000..a5172c01b1dad2 --- /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 ab90935266d69e..05836b8448a688 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 9422df60305184..75cfac721bcd3b 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/get_cluster_info.ts b/src/plugins/telemetry/server/telemetry_collection/get_cluster_info.ts index d5f0d2d8c9598d..4a33356ee97614 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 89e2cae7779856..d7c0110a99c6fd 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 645c5a4be8a6cb..5d27774a630a59 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 0ba6fcc154d8bd..d41904c6d8e0e8 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/register_collection.ts b/src/plugins/telemetry/server/telemetry_collection/register_collection.ts index 833fd9f7fd5bca..438fcadad92555 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 d3a47694d38a76..16f96c07fd8ead 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/usage_collection/README.md b/src/plugins/usage_collection/README.md index 99075d5d48f596..9520dfc03cfa47 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/server/collector/collector.ts b/src/plugins/usage_collection/server/collector/collector.ts index b4f86f67e798d0..9ae63b9f50e429 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 e8791138c5e265..b6308d66033885 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 0d3939e1dc681b..1816e845b4d666 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 a2769c8b4b405f..87761bca9a507a 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 837dd9bff2c6de..c41315e7bc0dca 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 ebc92170c87350..6713c2ce2391be 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 731358bdcbdec2..60b6ebab5ad8eb 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_timeseries/public/application/components/vis_editor.js b/src/plugins/vis_type_timeseries/public/application/components/vis_editor.js index a96890d4d15029..300e70f3ae0c00 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 0ae1c86ae31175..23a9555da2452c 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 c06f94efb3c493..649ee765cc6428 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 a2146e493d4edf..0b1c6e6e204141 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 505816d48af528..a5f095a4c4f3d1 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 { 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 c6a25df7615a2b..d44fc2f4a75af0 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 3455d88b6ce9e1..daf275297fb822 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 aaab0566af65e6..e8ae48cdce1452 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 2464bb72d26957..44b76a52b34fef 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 73e3360004e5a7..bc80d549c81e6f 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/kibana.json b/src/plugins/visualize/kibana.json index cda45f3acc102d..c27cfec24b332d 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 8a52ebf4cc0883..00000000000000 --- 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 620380a77ba46c..f7f68fbc2c3597 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 00000000000000..0e71d72a3d4c72 --- /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 60bb73d6de2ccb..00000000000000 --- 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 00000000000000..51abb3ca530a4c --- /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 00000000000000..a3a7fde1d6569f --- /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 00000000000000..c571a5fb078bc5 --- /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 00000000000000..cbfbd6e0e3ab6b --- /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 00000000000000..7776c5e8ce4866 --- /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 00000000000000..2e7dba46487ad0 --- /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 9d3ca4b5399472..00000000000000 --- 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 3c3455fb34f18c..00000000000000 --- 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 d7c7828c58f23f..00000000000000 --- 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 c7163f9b7705d4..00000000000000 --- 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 26f61f3f0a2c2b..00000000000000 --- 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 4963d9bc5ed72e..00000000000000 --- 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 00000000000000..4bec244e6efc94 --- /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 452118f8097da0..00000000000000 --- 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 924c164e467d88..00000000000000 --- 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 8838348e0b6796..00000000000000 --- 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 e8e8d92034113a..00000000000000 --- 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 fcd62d7ddee732..00000000000000 --- 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 = ( -