diff --git a/.eslintignore b/.eslintignore index cf13fc28467d94..90155ca9cb681e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -8,6 +8,7 @@ bower_components /plugins /built_assets /html_docs +/src/plugins/data/common/es_query/kuery/ast/_generated_/** /src/fixtures/vislib/mock_data /src/legacy/ui/public/angular-bootstrap /src/legacy/ui/public/flot-charts @@ -19,7 +20,6 @@ bower_components /src/core/lib/kbn_internal_native_observable /packages/*/target /packages/eslint-config-kibana -/packages/kbn-es-query/src/kuery/ast/kuery.js /packages/kbn-pm/dist /packages/kbn-plugin-generator/sao_template/template /packages/kbn-ui-framework/dist diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e208dc73c7b4ba..d567f267afa9d1 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -77,6 +77,7 @@ /src/dev/i18n @elastic/kibana-stack-services /packages/kbn-analytics/ @elastic/kibana-stack-services /src/legacy/core_plugins/ui_metric/ @elastic/kibana-stack-services +/src/plugins/usage_collection/ @elastic/kibana-stack-services /x-pack/legacy/plugins/telemetry @elastic/kibana-stack-services /x-pack/legacy/plugins/alerting @elastic/kibana-stack-services /x-pack/legacy/plugins/actions @elastic/kibana-stack-services diff --git a/.i18nrc.json b/.i18nrc.json index 2cdf7d2b039c6c..e5ba6762da154d 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -16,7 +16,6 @@ "interpreter": "src/legacy/core_plugins/interpreter", "kbn": "src/legacy/core_plugins/kibana", "kbnDocViews": "src/legacy/core_plugins/kbn_doc_views", - "kbnESQuery": "packages/kbn-es-query", "kbnVislibVisTypes": "src/legacy/core_plugins/kbn_vislib_vis_types", "kibana_react": "src/legacy/core_plugins/kibana_react", "kibana-react": "src/plugins/kibana_react", diff --git a/docs/api/role-management/put.asciidoc b/docs/api/role-management/put.asciidoc index 67ec15892afe42..a00fedf7e7ac4c 100644 --- a/docs/api/role-management/put.asciidoc +++ b/docs/api/role-management/put.asciidoc @@ -26,7 +26,9 @@ To use the create or update role API, you must have the `manage_security` cluste (Optional, object) In the `metadata` object, keys that begin with `_` are reserved for system usage. `elasticsearch`:: - (Optional, object) {es} cluster and index privileges. Valid keys include `cluster`, `indices`, and `run_as`. For more information, see {xpack-ref}/defining-roles.html[Defining Roles]. + (Optional, object) {es} cluster and index privileges. Valid keys include + `cluster`, `indices`, and `run_as`. For more information, see + {ref}/defining-roles.html[Defining roles]. `kibana`:: (list) Objects that specify the <> for the role: diff --git a/docs/developer/security/rbac.asciidoc b/docs/developer/security/rbac.asciidoc index b967dabf0684f6..02b8233a9a3df2 100644 --- a/docs/developer/security/rbac.asciidoc +++ b/docs/developer/security/rbac.asciidoc @@ -1,7 +1,14 @@ [[development-security-rbac]] === Role-based access control -Role-based access control (RBAC) in {kib} relies upon the {xpack-ref}/security-privileges.html#application-privileges[application privileges] that Elasticsearch exposes. This allows {kib} to define the privileges that {kib} wishes to grant to users, assign them to the relevant users using roles, and then authorize the user to perform a specific action. This is handled within a secured instance of the `SavedObjectsClient` and available transparently to consumers when using `request.getSavedObjectsClient()` or `savedObjects.getScopedSavedObjectsClient()`. +Role-based access control (RBAC) in {kib} relies upon the +{ref}/security-privileges.html#application-privileges[application privileges] +that Elasticsearch exposes. This allows {kib} to define the privileges that +{kib} wishes to grant to users, assign them to the relevant users using roles, +and then authorize the user to perform a specific action. This is handled within +a secured instance of the `SavedObjectsClient` and available transparently to +consumers when using `request.getSavedObjectsClient()` or +`savedObjects.getScopedSavedObjectsClient()`. [[development-rbac-privileges]] ==== {kib} Privileges diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md index 866755e78648af..cecceb04240e60 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.find.md @@ -9,5 +9,5 @@ Search for objects Signature: ```typescript -find: (options: Pick) => Promise>; +find: (options: Pick) => Promise>; ``` diff --git a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md index 50451b813a61c0..c4ceb47f66e1bb 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md @@ -20,7 +20,7 @@ export declare class SavedObjectsClient | [bulkGet](./kibana-plugin-public.savedobjectsclient.bulkget.md) | | (objects?: {
id: string;
type: string;
}[]) => Promise<SavedObjectsBatchResponse<SavedObjectAttributes>> | Returns an array of objects by id | | [create](./kibana-plugin-public.savedobjectsclient.create.md) | | <T extends SavedObjectAttributes>(type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise<SimpleSavedObject<T>> | Persists an object | | [delete](./kibana-plugin-public.savedobjectsclient.delete.md) | | (type: string, id: string) => Promise<{}> | Deletes an object | -| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "page" | "perPage" | "fields">) => Promise<SavedObjectsFindResponsePublic<T>> | Search for objects | +| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "fields" | "searchFields" | "defaultSearchOperator" | "hasReference" | "sortField" | "page" | "perPage">) => Promise<SavedObjectsFindResponsePublic<T>> | Search for objects | | [get](./kibana-plugin-public.savedobjectsclient.get.md) | | <T extends SavedObjectAttributes>(type: string, id: string) => Promise<SimpleSavedObject<T>> | Fetches a single object | ## Methods diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 360675b3490c26..13e0ea3645f26a 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -115,6 +115,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsResolveImportErrorsOptions](./kibana-plugin-server.savedobjectsresolveimporterrorsoptions.md) | Options to control the "resolve import" operation. | | [SavedObjectsUpdateOptions](./kibana-plugin-server.savedobjectsupdateoptions.md) | | | [SavedObjectsUpdateResponse](./kibana-plugin-server.savedobjectsupdateresponse.md) | | +| [SessionCookieValidationResult](./kibana-plugin-server.sessioncookievalidationresult.md) | Return type from a function to validate cookie contents. | | [SessionStorage](./kibana-plugin-server.sessionstorage.md) | Provides an interface to store and retrieve data across requests. | | [SessionStorageCookieOptions](./kibana-plugin-server.sessionstoragecookieoptions.md) | Configuration used to create HTTP session storage based on top of cookie mechanism. | | [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | SessionStorage factory to bind one to an incoming request | diff --git a/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.isvalid.md b/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.isvalid.md new file mode 100644 index 00000000000000..6e5f6acca2eb90 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.isvalid.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionCookieValidationResult](./kibana-plugin-server.sessioncookievalidationresult.md) > [isValid](./kibana-plugin-server.sessioncookievalidationresult.isvalid.md) + +## SessionCookieValidationResult.isValid property + +Whether the cookie is valid or not. + +Signature: + +```typescript +isValid: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.md b/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.md new file mode 100644 index 00000000000000..6d32c4cca3dd6a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionCookieValidationResult](./kibana-plugin-server.sessioncookievalidationresult.md) + +## SessionCookieValidationResult interface + +Return type from a function to validate cookie contents. + +Signature: + +```typescript +export interface SessionCookieValidationResult +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [isValid](./kibana-plugin-server.sessioncookievalidationresult.isvalid.md) | boolean | Whether the cookie is valid or not. | +| [path](./kibana-plugin-server.sessioncookievalidationresult.path.md) | string | The "Path" attribute of the cookie; if the cookie is invalid, this is used to clear it. | + diff --git a/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.path.md b/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.path.md new file mode 100644 index 00000000000000..8ca6d452213aac --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.sessioncookievalidationresult.path.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SessionCookieValidationResult](./kibana-plugin-server.sessioncookievalidationresult.md) > [path](./kibana-plugin-server.sessioncookievalidationresult.path.md) + +## SessionCookieValidationResult.path property + +The "Path" attribute of the cookie; if the cookie is invalid, this is used to clear it. + +Signature: + +```typescript +path?: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md index 167ab03d7567f5..ef65735e7bdbab 100644 --- a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md +++ b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md @@ -4,7 +4,7 @@ ## SessionStorageCookieOptions.encryptionKey property -A key used to encrypt a cookie value. Should be at least 32 characters long. +A key used to encrypt a cookie's value. Should be at least 32 characters long. Signature: diff --git a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.md b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.md index de412818142f25..778dc27a190d91 100644 --- a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.md +++ b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.md @@ -16,8 +16,8 @@ export interface SessionStorageCookieOptions | Property | Type | Description | | --- | --- | --- | -| [encryptionKey](./kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md) | string | A key used to encrypt a cookie value. Should be at least 32 characters long. | +| [encryptionKey](./kibana-plugin-server.sessionstoragecookieoptions.encryptionkey.md) | string | A key used to encrypt a cookie's value. Should be at least 32 characters long. | | [isSecure](./kibana-plugin-server.sessionstoragecookieoptions.issecure.md) | boolean | Flag indicating whether the cookie should be sent only via a secure connection. | | [name](./kibana-plugin-server.sessionstoragecookieoptions.name.md) | string | Name of the session cookie. | -| [validate](./kibana-plugin-server.sessionstoragecookieoptions.validate.md) | (sessionValue: T) => boolean | Promise<boolean> | Function called to validate a cookie content. | +| [validate](./kibana-plugin-server.sessionstoragecookieoptions.validate.md) | (sessionValue: T | T[]) => SessionCookieValidationResult | Function called to validate a cookie's decrypted value. | diff --git a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.validate.md b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.validate.md index f3cbfc0d84e18e..effa4b6bbc077c 100644 --- a/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.validate.md +++ b/docs/development/core/server/kibana-plugin-server.sessionstoragecookieoptions.validate.md @@ -4,10 +4,10 @@ ## SessionStorageCookieOptions.validate property -Function called to validate a cookie content. +Function called to validate a cookie's decrypted value. Signature: ```typescript -validate: (sessionValue: T) => boolean | Promise; +validate: (sessionValue: T | T[]) => SessionCookieValidationResult; ``` diff --git a/docs/getting-started/tutorial-full-experience.asciidoc b/docs/getting-started/tutorial-full-experience.asciidoc index eafbb7d8f7c918..a05205fceab4ad 100644 --- a/docs/getting-started/tutorial-full-experience.asciidoc +++ b/docs/getting-started/tutorial-full-experience.asciidoc @@ -91,7 +91,7 @@ and whether it's _tokenized_, or broken up into separate words. NOTE: If security is enabled, you must have the `all` Kibana privilege to run this tutorial. You must also have the `create`, `manage` `read`, `write,` and `delete` -index privileges. See {xpack-ref}/security-privileges.html[Security Privileges] +index privileges. See {ref}/security-privileges.html[Security privileges] for more information. In Kibana *Dev Tools > Console*, set up a mapping for the Shakespeare data set: diff --git a/docs/getting-started/tutorial-sample-data.asciidoc b/docs/getting-started/tutorial-sample-data.asciidoc index 24cc176d5daf9f..f41c648a3d492d 100644 --- a/docs/getting-started/tutorial-sample-data.asciidoc +++ b/docs/getting-started/tutorial-sample-data.asciidoc @@ -12,8 +12,8 @@ with Kibana sample data and learn to: NOTE: If security is enabled, you must have `read`, `write`, and `manage` privileges -on the `kibana_sample_data_*` indices. See {xpack-ref}/security-privileges.html[Security Privileges] -for more information. +on the `kibana_sample_data_*` indices. See +{ref}/security-privileges.html[Security privileges] for more information. [float] diff --git a/docs/management/managing-indices.asciidoc b/docs/management/managing-indices.asciidoc index 4a736e3ddab59d..4c7f6c2aee6e6f 100644 --- a/docs/management/managing-indices.asciidoc +++ b/docs/management/managing-indices.asciidoc @@ -22,7 +22,7 @@ If security is enabled, you must have the `monitor` cluster privilege and the `view_index_metadata` and `manage` index privileges to view the data. For index templates, you must have the `manage_index_templates` cluster privilege. -See {xpack-ref}/security-privileges.html[Security Privileges] for more +See {ref}/security-privileges.html[Security privileges] for more information. Before using this feature, you should be familiar with index management diff --git a/docs/setup/install.asciidoc b/docs/setup/install.asciidoc index b0893a6e789454..286fed34f64c56 100644 --- a/docs/setup/install.asciidoc +++ b/docs/setup/install.asciidoc @@ -54,8 +54,8 @@ Formulae are available from the Elastic Homebrew tap for installing {kib} on mac <> IMPORTANT: If your Elasticsearch installation is protected by -{xpack-ref}/elasticsearch-security.html[{security}] see -{kibana-ref}/using-kibana-with-security.html[Configuring Security in Kibana] for +{ref}/elasticsearch-security.html[{security}] see +{kibana-ref}/using-kibana-with-security.html[Configuring security in Kibana] for additional setup instructions. include::install/targz.asciidoc[] diff --git a/docs/user/security/authentication/index.asciidoc b/docs/user/security/authentication/index.asciidoc index e6b70fa059fc28..32f341a9c1b7cc 100644 --- a/docs/user/security/authentication/index.asciidoc +++ b/docs/user/security/authentication/index.asciidoc @@ -14,16 +14,17 @@ - <> [[basic-authentication]] -==== Basic Authentication +==== Basic authentication Basic authentication requires a username and password to successfully log in to {kib}. It is enabled by default and based on the Native security realm provided by {es}. The basic authentication provider uses a Kibana provided login form, and supports authentication using the `Authorization` request header's `Basic` scheme. The session cookies that are issued by the basic authentication provider are stateless. Therefore, logging out of Kibana when using the basic authentication provider clears the session cookies from the browser but does not invalidate the session cookie for reuse. -For more information about basic authentication and built-in users, see {xpack-ref}/setting-up-authentication.html[Setting Up User Authentication]. +For more information about basic authentication and built-in users, see +{ref}/setting-up-authentication.html[User authentication]. [[token-authentication]] -==== Token Authentication +==== Token authentication Token authentication allows users to login using the same Kibana provided login form as basic authentication. The token authentication provider is built on {es}'s token APIs. The bearer tokens returned by {es}'s {ref}/security-api-get-token.html[get token API] can be used directly with Kibana using the `Authorization` request header with the `Bearer` scheme. @@ -46,7 +47,7 @@ xpack.security.authc.providers: [token, basic] -------------------------------------------------------------------------------- [[pki-authentication]] -==== Public Key Infrastructure (PKI) Authentication +==== Public key infrastructure (PKI) authentication [IMPORTANT] ============================================================================ @@ -76,9 +77,9 @@ xpack.security.authc.providers: [pki, basic] Note that with `server.ssl.clientAuthentication` set to `required`, users are asked to provide a valid client certificate, even if they want to authenticate with username and password. Depending on the security policies, it may or may not be desired. If not, `server.ssl.clientAuthentication` can be set to `optional`. In this case, {kib} still requests a client certificate, but the client won't be required to present one. The `optional` client authentication mode might also be needed in other cases, for example, when PKI authentication is used in conjunction with Reporting. [[saml]] -==== SAML Single Sign-On +==== SAML single sign-on -SAML authentication allows users to log in to {kib} with an external Identity Provider, such as Okta or Auth0. Make sure that SAML is enabled and configured in {es} before setting it up in {kib}. See {xpack-ref}/saml-guide.html[Configuring SAML Single-Sign-On on the Elastic Stack]. +SAML authentication allows users to log in to {kib} with an external Identity Provider, such as Okta or Auth0. Make sure that SAML is enabled and configured in {es} before setting it up in {kib}. See {ref}/saml-guide.html[Configuring SAML single sign-on on the Elastic Stack]. Set the configuration values in `kibana.yml` as follows: @@ -106,7 +107,7 @@ server.xsrf.whitelist: [/api/security/saml/callback] Users will be able to log in to {kib} via SAML Single Sign-On by navigating directly to the {kib} URL. Users who aren't authenticated are redirected to the Identity Provider for login. Most Identity Providers maintain a long-lived session—users who logged in to a different application using the same Identity Provider in the same browser are automatically authenticated. An exception is if {es} or the Identity Provider is configured to force user to re-authenticate. This login scenario is called _Service Provider initiated login_. [float] -===== SAML and Basic Authentication +===== SAML and basic authentication SAML support in {kib} is designed to be the primary (or sole) authentication method for users of that {kib} instance. However, you can configure both SAML and Basic authentication for the same {kib} instance: @@ -135,7 +136,7 @@ xpack.security.authc.saml.maxRedirectURLSize: 1kb -------------------------------------------------------------------------------- [[oidc]] -==== OpenID Connect Single Sign-On +==== OpenID Connect single sign-on Similar to SAML, authentication with OpenID Connect allows users to log in to {kib} using an OpenID Connect Provider such as Google, or Okta. OpenID Connect should also be configured in {es}. For more details, see {ref}/oidc-guide.html[Configuring single sign-on to the {stack} using OpenID Connect]. @@ -166,7 +167,7 @@ server.xsrf.whitelist: [/api/security/v1/oidc] -------------------------------------------------------------------------------- [float] -===== OpenID Connect and Basic Authentication +===== OpenID Connect and basic authentication Similar to SAML, OpenID Connect support in {kib} is designed to be the primary (or sole) authentication method for users of that {kib} instance. However, you can configure both OpenID Connect and Basic authentication for the same {kib} instance: @@ -179,12 +180,12 @@ xpack.security.authc.providers: [oidc, basic] Users will be able to access the login page and use Basic authentication by navigating to the `/login` URL. [float] -==== Single Sign-On provider details +==== Single sign-on provider details The following sections apply both to <> and <> [float] -===== Access and Refresh Tokens +===== Access and refresh tokens Once the user logs in to {kib} Single Sign-On, either using SAML or OpenID Connect, {es} issues access and refresh tokens that {kib} encrypts and stores them in its own session cookie. This way, the user isn't redirected to the Identity Provider @@ -202,7 +203,7 @@ If {kib} can't redirect the user to the external authentication provider (for ex indicates that both access and refresh tokens are expired. Reloading the current {kib} page fixes the error. [float] -===== Local and Global Logout +===== Local and global logout During logout, both the {kib} session cookie and access/refresh token pair are invalidated. Even if the cookie has been leaked, it can't be re-used after logout. This is known as "local" logout. diff --git a/docs/user/security/reporting.asciidoc b/docs/user/security/reporting.asciidoc index fb40dc17c0abd9..aaba60ca4b3cac 100644 --- a/docs/user/security/reporting.asciidoc +++ b/docs/user/security/reporting.asciidoc @@ -8,7 +8,7 @@ user actions in {kib}. To use {reporting} with {security} enabled, you need to <>. If you are automatically generating reports with -{xpack-ref}/xpack-alerting.html[{watcher}], you also need to configure {watcher} +{ref}/xpack-alerting.html[{watcher}], you also need to configure {watcher} to trust the {kib} server's certificate. For more information, see <>. @@ -35,7 +35,7 @@ POST /_security/user/reporter * If you are using an LDAP or Active Directory realm, you can either assign roles on a per user basis, or assign roles to groups of users. By default, role mappings are configured in -{xpack-ref}/mapping-roles.html[`config/shield/role_mapping.yml`]. +{ref}/mapping-roles.html[`config/shield/role_mapping.yml`]. For example, the following snippet assigns the user named Bill Murray the `kibana_user` and `reporting_user` roles: + @@ -55,7 +55,7 @@ In a production environment, you should restrict access to the {reporting} endpoints to authorized users. This requires that you: . Enable {security} on your {es} cluster. For more information, -see {xpack-ref}/security-getting-started.html[Getting Started with Security]. +see {ref}/security-getting-started.html[Getting Started with Security]. . Configure an SSL certificate for Kibana. For more information, see <>. . Configure {watcher} to trust the Kibana server's certificate by adding it to @@ -83,4 +83,4 @@ includes a watch that submits requests as the built-in `elastic` user: <>. For more information about configuring watches, see -{xpack-ref}/how-watcher-works.html[How Watcher Works]. +{ref}/how-watcher-works.html[How Watcher works]. diff --git a/docs/user/security/securing-kibana.asciidoc b/docs/user/security/securing-kibana.asciidoc index 2fbc6ba4f1ee64..60f5473f43b9df 100644 --- a/docs/user/security/securing-kibana.asciidoc +++ b/docs/user/security/securing-kibana.asciidoc @@ -121,7 +121,7 @@ TIP: You can define as many different roles for your {kib} users as you need. For example, create roles that have `read` and `view_index_metadata` privileges on specific index patterns. For more information, see -{xpack-ref}/authorization.html[Configuring Role-based Access Control]. +{ref}/authorization.html[User authorization]. -- diff --git a/package.json b/package.json index a4f7b869aef6f6..45a376a2913591 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,10 @@ "**/graphql-toolkit/lodash": "^4.17.13", "**/isomorphic-git/**/base64-js": "^1.2.1", "**/image-diff/gm/debug": "^2.6.9", - "**/deepmerge": "^4.2.2" + "**/deepmerge": "^4.2.2", + "**/react": "16.8.6", + "**/react-dom": "16.8.6", + "**/react-test-renderer": "16.8.6" }, "workspaces": { "packages": [ @@ -121,7 +124,6 @@ "@kbn/babel-code-parser": "1.0.0", "@kbn/babel-preset": "1.0.0", "@kbn/config-schema": "1.0.0", - "@kbn/es-query": "1.0.0", "@kbn/i18n": "1.0.0", "@kbn/interpreter": "1.0.0", "@kbn/pm": "1.0.0", @@ -214,10 +216,10 @@ "pug": "^2.0.3", "querystring-browser": "1.0.4", "raw-loader": "3.1.0", - "react": "^16.8.0", + "react": "^16.8.6", "react-addons-shallow-compare": "15.6.2", "react-color": "^2.13.8", - "react-dom": "^16.8.0", + "react-dom": "^16.8.6", "react-grid-layout": "^0.16.2", "react-hooks-testing-library": "^0.5.0", "react-input-range": "^1.3.0", @@ -349,8 +351,8 @@ "@types/uuid": "^3.4.4", "@types/vinyl-fs": "^2.4.11", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^2.8.0", - "@typescript-eslint/parser": "^2.8.0", + "@typescript-eslint/eslint-plugin": "^2.9.0", + "@typescript-eslint/parser": "^2.9.0", "angular-mocks": "^1.7.8", "archiver": "^3.1.1", "axe-core": "^3.3.2", diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json index 71517bc10404d6..ee65a1cf79148d 100644 --- a/packages/eslint-config-kibana/package.json +++ b/packages/eslint-config-kibana/package.json @@ -15,8 +15,8 @@ }, "homepage": "https://github.com/elastic/eslint-config-kibana#readme", "peerDependencies": { - "@typescript-eslint/eslint-plugin": "^2.8.0", - "@typescript-eslint/parser": "^2.8.0", + "@typescript-eslint/eslint-plugin": "^2.9.0", + "@typescript-eslint/parser": "^2.9.0", "babel-eslint": "^10.0.3", "eslint": "^6.5.1", "eslint-plugin-babel": "^5.3.0", diff --git a/packages/kbn-es-query/README.md b/packages/kbn-es-query/README.md deleted file mode 100644 index fc403447877d81..00000000000000 --- a/packages/kbn-es-query/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# kbn-es-query - -This module is responsible for generating Elasticsearch queries for Kibana. See explanations below for each of the subdirectories. - -## es_query - -This folder contains the code that combines Lucene/KQL queries and filters into an Elasticsearch query. - -```javascript -buildEsQuery(indexPattern, queries, filters, config) -``` - -Generates the Elasticsearch query DSL from combining the queries and filters provided. - -```javascript -buildQueryFromFilters(filters, indexPattern) -``` - -Generates the Elasticsearch query DSL from the given filters. - -```javascript -luceneStringToDsl(query) -``` - -Generates the Elasticsearch query DSL from the given Lucene query. - -```javascript -migrateFilter(filter, indexPattern) -``` - -Migrates a filter from a previous version of Elasticsearch to the current version. - -```javascript -decorateQuery(query, queryStringOptions) -``` - -Decorates an Elasticsearch query_string query with the given options. - -## filters - -This folder contains the code related to Kibana Filter objects, including their definitions, and helper functions to create them. Filters in Kibana always contain a `meta` property which describes which `index` the filter corresponds to, as well as additional data about the specific filter. - -The object that is created by each of the following functions corresponds to a Filter object in the `lib` directory (e.g. `PhraseFilter`, `RangeFilter`, etc.) - -```javascript -buildExistsFilter(field, indexPattern) -``` - -Creates a filter (`ExistsFilter`) where the given field exists. - -```javascript -buildPhraseFilter(field, value, indexPattern) -``` - -Creates an filter (`PhraseFilter`) where the given field matches the given value. - -```javascript -buildPhrasesFilter(field, params, indexPattern) -``` - -Creates a filter (`PhrasesFilter`) where the given field matches one or more of the given values. `params` should be an array of values. - -```javascript -buildQueryFilter(query, index) -``` - -Creates a filter (`CustomFilter`) corresponding to a raw Elasticsearch query DSL object. - -```javascript -buildRangeFilter(field, params, indexPattern) -``` - -Creates a filter (`RangeFilter`) where the value for the given field is in the given range. `params` should contain `lt`, `lte`, `gt`, and/or `gte`. - -## kuery - -This folder contains the code corresponding to generating Elasticsearch queries using the Kibana query language. - -In general, you will only need to worry about the following functions from the `ast` folder: - -```javascript -fromExpression(expression) -``` - -Generates an abstract syntax tree corresponding to the raw Kibana query `expression`. - -```javascript -toElasticsearchQuery(node, indexPattern) -``` - -Takes an abstract syntax tree (generated from the previous method) and generates the Elasticsearch query DSL using the given `indexPattern`. Note that if no `indexPattern` is provided, then an Elasticsearch query DSL will still be generated, ignoring things like the index pattern scripted fields, field types, etc. - diff --git a/packages/kbn-es-query/package.json b/packages/kbn-es-query/package.json deleted file mode 100644 index 2cd2a8f53d2eed..00000000000000 --- a/packages/kbn-es-query/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@kbn/es-query", - "main": "target/server/index.js", - "browser": "target/public/index.js", - "version": "1.0.0", - "license": "Apache-2.0", - "private": true, - "scripts": { - "build": "node scripts/build", - "kbn:bootstrap": "node scripts/build --source-maps", - "kbn:watch": "node scripts/build --source-maps --watch" - }, - "dependencies": { - "lodash": "npm:@elastic/lodash@3.10.1-kibana3", - "moment-timezone": "^0.5.27", - "@kbn/i18n": "1.0.0" - }, - "devDependencies": { - "@babel/cli": "^7.5.5", - "@babel/core": "^7.5.5", - "@kbn/babel-preset": "1.0.0", - "@kbn/dev-utils": "1.0.0", - "@kbn/expect": "1.0.0", - "del": "^5.1.0", - "getopts": "^2.2.4", - "supports-color": "^7.0.0" - } -} diff --git a/packages/kbn-es-query/src/__fixtures__/filter_skeleton.json b/packages/kbn-es-query/src/__fixtures__/filter_skeleton.json deleted file mode 100644 index 1799d04a0fbd89..00000000000000 --- a/packages/kbn-es-query/src/__fixtures__/filter_skeleton.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "meta": { - "index": "logstash-*" - } -} diff --git a/packages/kbn-es-query/src/__fixtures__/index_pattern_response.json b/packages/kbn-es-query/src/__fixtures__/index_pattern_response.json deleted file mode 100644 index 588e6ada69cfe4..00000000000000 --- a/packages/kbn-es-query/src/__fixtures__/index_pattern_response.json +++ /dev/null @@ -1,303 +0,0 @@ -{ - "id": "logstash-*", - "title": "logstash-*", - "fields": [ - { - "name": "bytes", - "type": "number", - "esTypes": ["long"], - "count": 10, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "ssl", - "type": "boolean", - "esTypes": ["boolean"], - "count": 20, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "@timestamp", - "type": "date", - "esTypes": ["date"], - "count": 30, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "time", - "type": "date", - "esTypes": ["date"], - "count": 30, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "@tags", - "type": "string", - "esTypes": ["keyword"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "utc_time", - "type": "date", - "esTypes": ["date"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "phpmemory", - "type": "number", - "esTypes": ["integer"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "ip", - "type": "ip", - "esTypes": ["ip"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "request_body", - "type": "attachment", - "esTypes": ["attachment"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "point", - "type": "geo_point", - "esTypes": ["geo_point"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "area", - "type": "geo_shape", - "esTypes": ["geo_shape"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "hashed", - "type": "murmur3", - "esTypes": ["murmur3"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": false, - "readFromDocValues": false - }, - { - "name": "geo.coordinates", - "type": "geo_point", - "esTypes": ["geo_point"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "extension", - "type": "string", - "esTypes": ["keyword"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "machine.os", - "type": "string", - "esTypes": ["text"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "machine.os.raw", - "type": "string", - "esTypes": ["keyword"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true, - "subType": { "multi": { "parent": "machine.os" } } - }, - { - "name": "geo.src", - "type": "string", - "esTypes": ["keyword"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "_id", - "type": "string", - "esTypes": ["_id"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "_type", - "type": "string", - "esTypes": ["_type"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "_source", - "type": "_source", - "esTypes": ["_source"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "non-filterable", - "type": "string", - "esTypes": ["text"], - "count": 0, - "scripted": false, - "searchable": false, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "non-sortable", - "type": "string", - "esTypes": ["text"], - "count": 0, - "scripted": false, - "searchable": false, - "aggregatable": false, - "readFromDocValues": false - }, - { - "name": "custom_user_field", - "type": "conflict", - "esTypes": ["long", "text"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": true, - "readFromDocValues": true - }, - { - "name": "script string", - "type": "string", - "count": 0, - "scripted": true, - "script": "'i am a string'", - "lang": "expression", - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "script number", - "type": "number", - "count": 0, - "scripted": true, - "script": "1234", - "lang": "expression", - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "script date", - "type": "date", - "count": 0, - "scripted": true, - "script": "1234", - "lang": "painless", - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "script murmur3", - "type": "murmur3", - "count": 0, - "scripted": true, - "script": "1234", - "lang": "expression", - "searchable": true, - "aggregatable": true, - "readFromDocValues": false - }, - { - "name": "nestedField.child", - "type": "string", - "esTypes": ["text"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": false, - "readFromDocValues": false, - "subType": { "nested": { "path": "nestedField" } } - }, - { - "name": "nestedField.nestedChild.doublyNestedChild", - "type": "string", - "esTypes": ["text"], - "count": 0, - "scripted": false, - "searchable": true, - "aggregatable": false, - "readFromDocValues": false, - "subType": { "nested": { "path": "nestedField.nestedChild" } } - } - ] -} diff --git a/packages/kbn-es-query/src/index.d.ts b/packages/kbn-es-query/src/index.d.ts deleted file mode 100644 index 79e6903b186448..00000000000000 --- a/packages/kbn-es-query/src/index.d.ts +++ /dev/null @@ -1,20 +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 * from './kuery'; diff --git a/packages/kbn-es-query/src/kuery/ast/__tests__/ast.js b/packages/kbn-es-query/src/kuery/ast/__tests__/ast.js deleted file mode 100644 index 3cbe1203bc5330..00000000000000 --- a/packages/kbn-es-query/src/kuery/ast/__tests__/ast.js +++ /dev/null @@ -1,415 +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 * as ast from '../ast'; -import expect from '@kbn/expect'; -import { nodeTypes } from '../../node_types/index'; -import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; - -let indexPattern; - -describe('kuery AST API', function () { - - - beforeEach(() => { - indexPattern = indexPatternResponse; - }); - - describe('fromKueryExpression', function () { - - it('should return a match all "is" function for whitespace', function () { - const expected = nodeTypes.function.buildNode('is', '*', '*'); - const actual = ast.fromKueryExpression(' '); - expect(actual).to.eql(expected); - }); - - it('should return an "is" function with a null field for single literals', function () { - const expected = nodeTypes.function.buildNode('is', null, 'foo'); - const actual = ast.fromKueryExpression('foo'); - expect(actual).to.eql(expected); - }); - - it('should ignore extraneous whitespace at the beginning and end of the query', function () { - const expected = nodeTypes.function.buildNode('is', null, 'foo'); - const actual = ast.fromKueryExpression(' foo '); - expect(actual).to.eql(expected); - }); - - it('should not split on whitespace', function () { - const expected = nodeTypes.function.buildNode('is', null, 'foo bar'); - const actual = ast.fromKueryExpression('foo bar'); - expect(actual).to.eql(expected); - }); - - it('should support "and" as a binary operator', function () { - const expected = nodeTypes.function.buildNode('and', [ - nodeTypes.function.buildNode('is', null, 'foo'), - nodeTypes.function.buildNode('is', null, 'bar'), - ]); - const actual = ast.fromKueryExpression('foo and bar'); - expect(actual).to.eql(expected); - }); - - it('should support "or" as a binary operator', function () { - const expected = nodeTypes.function.buildNode('or', [ - nodeTypes.function.buildNode('is', null, 'foo'), - nodeTypes.function.buildNode('is', null, 'bar'), - ]); - const actual = ast.fromKueryExpression('foo or bar'); - expect(actual).to.eql(expected); - }); - - it('should support negation of queries with a "not" prefix', function () { - const expected = nodeTypes.function.buildNode('not', - nodeTypes.function.buildNode('or', [ - nodeTypes.function.buildNode('is', null, 'foo'), - nodeTypes.function.buildNode('is', null, 'bar'), - ]) - ); - const actual = ast.fromKueryExpression('not (foo or bar)'); - expect(actual).to.eql(expected); - }); - - it('"and" should have a higher precedence than "or"', function () { - const expected = nodeTypes.function.buildNode('or', [ - nodeTypes.function.buildNode('is', null, 'foo'), - nodeTypes.function.buildNode('or', [ - nodeTypes.function.buildNode('and', [ - nodeTypes.function.buildNode('is', null, 'bar'), - nodeTypes.function.buildNode('is', null, 'baz'), - ]), - nodeTypes.function.buildNode('is', null, 'qux'), - ]) - ]); - const actual = ast.fromKueryExpression('foo or bar and baz or qux'); - expect(actual).to.eql(expected); - }); - - it('should support grouping to override default precedence', function () { - const expected = nodeTypes.function.buildNode('and', [ - nodeTypes.function.buildNode('or', [ - nodeTypes.function.buildNode('is', null, 'foo'), - nodeTypes.function.buildNode('is', null, 'bar'), - ]), - nodeTypes.function.buildNode('is', null, 'baz'), - ]); - const actual = ast.fromKueryExpression('(foo or bar) and baz'); - expect(actual).to.eql(expected); - }); - - it('should support matching against specific fields', function () { - const expected = nodeTypes.function.buildNode('is', 'foo', 'bar'); - const actual = ast.fromKueryExpression('foo:bar'); - expect(actual).to.eql(expected); - }); - - it('should also not split on whitespace when matching specific fields', function () { - const expected = nodeTypes.function.buildNode('is', 'foo', 'bar baz'); - const actual = ast.fromKueryExpression('foo:bar baz'); - expect(actual).to.eql(expected); - }); - - it('should treat quoted values as phrases', function () { - const expected = nodeTypes.function.buildNode('is', 'foo', 'bar baz', true); - const actual = ast.fromKueryExpression('foo:"bar baz"'); - expect(actual).to.eql(expected); - }); - - it('should support a shorthand for matching multiple values against a single field', function () { - const expected = nodeTypes.function.buildNode('or', [ - nodeTypes.function.buildNode('is', 'foo', 'bar'), - nodeTypes.function.buildNode('is', 'foo', 'baz'), - ]); - const actual = ast.fromKueryExpression('foo:(bar or baz)'); - expect(actual).to.eql(expected); - }); - - it('should support "and" and "not" operators and grouping in the shorthand as well', function () { - const expected = nodeTypes.function.buildNode('and', [ - nodeTypes.function.buildNode('or', [ - nodeTypes.function.buildNode('is', 'foo', 'bar'), - nodeTypes.function.buildNode('is', 'foo', 'baz'), - ]), - nodeTypes.function.buildNode('not', - nodeTypes.function.buildNode('is', 'foo', 'qux') - ), - ]); - const actual = ast.fromKueryExpression('foo:((bar or baz) and not qux)'); - expect(actual).to.eql(expected); - }); - - it('should support exclusive range operators', function () { - const expected = nodeTypes.function.buildNode('and', [ - nodeTypes.function.buildNode('range', 'bytes', { - gt: 1000, - }), - nodeTypes.function.buildNode('range', 'bytes', { - lt: 8000, - }), - ]); - const actual = ast.fromKueryExpression('bytes > 1000 and bytes < 8000'); - expect(actual).to.eql(expected); - }); - - it('should support inclusive range operators', function () { - const expected = nodeTypes.function.buildNode('and', [ - nodeTypes.function.buildNode('range', 'bytes', { - gte: 1000, - }), - nodeTypes.function.buildNode('range', 'bytes', { - lte: 8000, - }), - ]); - const actual = ast.fromKueryExpression('bytes >= 1000 and bytes <= 8000'); - expect(actual).to.eql(expected); - }); - - it('should support wildcards in field names', function () { - const expected = nodeTypes.function.buildNode('is', 'machine*', 'osx'); - const actual = ast.fromKueryExpression('machine*:osx'); - expect(actual).to.eql(expected); - }); - - it('should support wildcards in values', function () { - const expected = nodeTypes.function.buildNode('is', 'foo', 'ba*'); - const actual = ast.fromKueryExpression('foo:ba*'); - expect(actual).to.eql(expected); - }); - - it('should create an exists "is" query when a field is given and "*" is the value', function () { - const expected = nodeTypes.function.buildNode('is', 'foo', '*'); - const actual = ast.fromKueryExpression('foo:*'); - expect(actual).to.eql(expected); - }); - - it('should support nested queries indicated by curly braces', () => { - const expected = nodeTypes.function.buildNode( - 'nested', - 'nestedField', - nodeTypes.function.buildNode('is', 'childOfNested', 'foo') - ); - const actual = ast.fromKueryExpression('nestedField:{ childOfNested: foo }'); - expect(actual).to.eql(expected); - }); - - it('should support nested subqueries and subqueries inside nested queries', () => { - const expected = nodeTypes.function.buildNode( - 'and', - [ - nodeTypes.function.buildNode('is', 'response', '200'), - nodeTypes.function.buildNode( - 'nested', - 'nestedField', - nodeTypes.function.buildNode('or', [ - nodeTypes.function.buildNode('is', 'childOfNested', 'foo'), - nodeTypes.function.buildNode('is', 'childOfNested', 'bar'), - ]) - )]); - const actual = ast.fromKueryExpression('response:200 and nestedField:{ childOfNested:foo or childOfNested:bar }'); - expect(actual).to.eql(expected); - }); - - it('should support nested sub-queries inside paren groups', () => { - const expected = nodeTypes.function.buildNode( - 'and', - [ - nodeTypes.function.buildNode('is', 'response', '200'), - nodeTypes.function.buildNode('or', [ - nodeTypes.function.buildNode( - 'nested', - 'nestedField', - nodeTypes.function.buildNode('is', 'childOfNested', 'foo') - ), - nodeTypes.function.buildNode( - 'nested', - 'nestedField', - nodeTypes.function.buildNode('is', 'childOfNested', 'bar') - ), - ]) - ]); - const actual = ast.fromKueryExpression('response:200 and ( nestedField:{ childOfNested:foo } or nestedField:{ childOfNested:bar } )'); - expect(actual).to.eql(expected); - }); - - it('should support nested groups inside other nested groups', () => { - const expected = nodeTypes.function.buildNode( - 'nested', - 'nestedField', - nodeTypes.function.buildNode( - 'nested', - 'nestedChild', - nodeTypes.function.buildNode('is', 'doublyNestedChild', 'foo') - ) - ); - const actual = ast.fromKueryExpression('nestedField:{ nestedChild:{ doublyNestedChild:foo } }'); - expect(actual).to.eql(expected); - }); - }); - - describe('fromLiteralExpression', function () { - - it('should create literal nodes for unquoted values with correct primitive types', function () { - const stringLiteral = nodeTypes.literal.buildNode('foo'); - const booleanFalseLiteral = nodeTypes.literal.buildNode(false); - const booleanTrueLiteral = nodeTypes.literal.buildNode(true); - const numberLiteral = nodeTypes.literal.buildNode(42); - - expect(ast.fromLiteralExpression('foo')).to.eql(stringLiteral); - expect(ast.fromLiteralExpression('true')).to.eql(booleanTrueLiteral); - expect(ast.fromLiteralExpression('false')).to.eql(booleanFalseLiteral); - expect(ast.fromLiteralExpression('42')).to.eql(numberLiteral); - }); - - it('should allow escaping of special characters with a backslash', function () { - const expected = nodeTypes.literal.buildNode('\\():<>"*'); - // yo dawg - const actual = ast.fromLiteralExpression('\\\\\\(\\)\\:\\<\\>\\"\\*'); - expect(actual).to.eql(expected); - }); - - it('should support double quoted strings that do not need escapes except for quotes', function () { - const expected = nodeTypes.literal.buildNode('\\():<>"*'); - const actual = ast.fromLiteralExpression('"\\():<>\\"*"'); - expect(actual).to.eql(expected); - }); - - it('should support escaped backslashes inside quoted strings', function () { - const expected = nodeTypes.literal.buildNode('\\'); - const actual = ast.fromLiteralExpression('"\\\\"'); - expect(actual).to.eql(expected); - }); - - it('should detect wildcards and build wildcard AST nodes', function () { - const expected = nodeTypes.wildcard.buildNode('foo*bar'); - const actual = ast.fromLiteralExpression('foo*bar'); - expect(actual).to.eql(expected); - }); - }); - - describe('toElasticsearchQuery', function () { - - it('should return the given node type\'s ES query representation', function () { - const node = nodeTypes.function.buildNode('exists', 'response'); - const expected = nodeTypes.function.toElasticsearchQuery(node, indexPattern); - const result = ast.toElasticsearchQuery(node, indexPattern); - expect(result).to.eql(expected); - }); - - it('should return an empty "and" function for undefined nodes and unknown node types', function () { - const expected = nodeTypes.function.toElasticsearchQuery(nodeTypes.function.buildNode('and', [])); - - expect(ast.toElasticsearchQuery()).to.eql(expected); - - const noTypeNode = nodeTypes.function.buildNode('exists', 'foo'); - delete noTypeNode.type; - expect(ast.toElasticsearchQuery(noTypeNode)).to.eql(expected); - - const unknownTypeNode = nodeTypes.function.buildNode('exists', 'foo'); - unknownTypeNode.type = 'notValid'; - expect(ast.toElasticsearchQuery(unknownTypeNode)).to.eql(expected); - }); - - it('should return the given node type\'s ES query representation including a time zone parameter when one is provided', function () { - const config = { dateFormatTZ: 'America/Phoenix' }; - const node = nodeTypes.function.buildNode('is', '@timestamp', '"2018-04-03T19:04:17"'); - const expected = nodeTypes.function.toElasticsearchQuery(node, indexPattern, config); - const result = ast.toElasticsearchQuery(node, indexPattern, config); - expect(result).to.eql(expected); - }); - - }); - - describe('doesKueryExpressionHaveLuceneSyntaxError', function () { - it('should return true for Lucene ranges', function () { - const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar: [1 TO 10]'); - expect(result).to.eql(true); - }); - - it('should return false for KQL ranges', function () { - const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar < 1'); - expect(result).to.eql(false); - }); - - it('should return true for Lucene exists', function () { - const result = ast.doesKueryExpressionHaveLuceneSyntaxError('_exists_: bar'); - expect(result).to.eql(true); - }); - - it('should return false for KQL exists', function () { - const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar:*'); - expect(result).to.eql(false); - }); - - it('should return true for Lucene wildcards', function () { - const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar: ba?'); - expect(result).to.eql(true); - }); - - it('should return false for KQL wildcards', function () { - const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar: ba*'); - expect(result).to.eql(false); - }); - - it('should return true for Lucene regex', function () { - const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar: /ba.*/'); - expect(result).to.eql(true); - }); - - it('should return true for Lucene fuzziness', function () { - const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar: ba~'); - expect(result).to.eql(true); - }); - - it('should return true for Lucene proximity', function () { - const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar: "ba"~2'); - expect(result).to.eql(true); - }); - - it('should return true for Lucene boosting', function () { - const result = ast.doesKueryExpressionHaveLuceneSyntaxError('bar: ba^2'); - expect(result).to.eql(true); - }); - - it('should return true for Lucene + operator', function () { - const result = ast.doesKueryExpressionHaveLuceneSyntaxError('+foo: bar'); - expect(result).to.eql(true); - }); - - it('should return true for Lucene - operators', function () { - const result = ast.doesKueryExpressionHaveLuceneSyntaxError('-foo: bar'); - expect(result).to.eql(true); - }); - - it('should return true for Lucene && operators', function () { - const result = ast.doesKueryExpressionHaveLuceneSyntaxError('foo: bar && baz: qux'); - expect(result).to.eql(true); - }); - - it('should return true for Lucene || operators', function () { - const result = ast.doesKueryExpressionHaveLuceneSyntaxError('foo: bar || baz: qux'); - expect(result).to.eql(true); - }); - - it('should return true for mixed KQL/Lucene queries', function () { - const result = ast.doesKueryExpressionHaveLuceneSyntaxError('foo: bar and (baz: qux || bag)'); - expect(result).to.eql(true); - }); - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/ast/ast.d.ts b/packages/kbn-es-query/src/kuery/ast/ast.d.ts deleted file mode 100644 index ef3d0ee8288746..00000000000000 --- a/packages/kbn-es-query/src/kuery/ast/ast.d.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { JsonObject } from '..'; - -/** - * WARNING: these typings are incomplete - */ - -export type KueryNode = any; - -export type DslQuery = any; - -export interface KueryParseOptions { - helpers: { - [key: string]: any; - }; - startRule: string; - allowLeadingWildcards: boolean; -} - -export function fromKueryExpression( - expression: string | DslQuery, - parseOptions?: Partial -): KueryNode; - -export function toElasticsearchQuery( - node: KueryNode, - indexPattern?: any, - config?: Record, - context?: Record -): JsonObject; - -export function doesKueryExpressionHaveLuceneSyntaxError(expression: string): boolean; diff --git a/packages/kbn-es-query/src/kuery/ast/index.d.ts b/packages/kbn-es-query/src/kuery/ast/index.d.ts deleted file mode 100644 index 9e68d01d046cca..00000000000000 --- a/packages/kbn-es-query/src/kuery/ast/index.d.ts +++ /dev/null @@ -1,20 +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 * from '../ast/ast'; diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/geo_bounding_box.js b/packages/kbn-es-query/src/kuery/functions/__tests__/geo_bounding_box.js deleted file mode 100644 index 7afa0fcce1bfeb..00000000000000 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/geo_bounding_box.js +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import * as geoBoundingBox from '../geo_bounding_box'; -import { nodeTypes } from '../../node_types'; -import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; - -let indexPattern; -const params = { - bottomRight: { - lat: 50.73, - lon: -135.35 - }, - topLeft: { - lat: 73.12, - lon: -174.37 - } -}; - -describe('kuery functions', function () { - describe('geoBoundingBox', function () { - - beforeEach(() => { - indexPattern = indexPatternResponse; - }); - - describe('buildNodeParams', function () { - - it('should return an "arguments" param', function () { - const result = geoBoundingBox.buildNodeParams('geo', params); - expect(result).to.only.have.keys('arguments'); - }); - - it('arguments should contain the provided fieldName as a literal', function () { - const result = geoBoundingBox.buildNodeParams('geo', params); - const { arguments: [ fieldName ] } = result; - - expect(fieldName).to.have.property('type', 'literal'); - expect(fieldName).to.have.property('value', 'geo'); - }); - - it('arguments should contain the provided params as named arguments with "lat, lon" string values', function () { - const result = geoBoundingBox.buildNodeParams('geo', params); - const { arguments: [ , ...args ] } = result; - - args.map((param) => { - expect(param).to.have.property('type', 'namedArg'); - expect(['bottomRight', 'topLeft'].includes(param.name)).to.be(true); - expect(param.value.type).to.be('literal'); - - const expectedParam = params[param.name]; - const expectedLatLon = `${expectedParam.lat}, ${expectedParam.lon}`; - expect(param.value.value).to.be(expectedLatLon); - }); - }); - - }); - - describe('toElasticsearchQuery', function () { - - it('should return an ES geo_bounding_box query representing the given node', function () { - const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params); - const result = geoBoundingBox.toElasticsearchQuery(node, indexPattern); - expect(result).to.have.property('geo_bounding_box'); - expect(result.geo_bounding_box.geo).to.have.property('top_left', '73.12, -174.37'); - expect(result.geo_bounding_box.geo).to.have.property('bottom_right', '50.73, -135.35'); - }); - - it('should return an ES geo_bounding_box query without an index pattern', function () { - const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params); - const result = geoBoundingBox.toElasticsearchQuery(node); - expect(result).to.have.property('geo_bounding_box'); - expect(result.geo_bounding_box.geo).to.have.property('top_left', '73.12, -174.37'); - expect(result.geo_bounding_box.geo).to.have.property('bottom_right', '50.73, -135.35'); - }); - - it('should use the ignore_unmapped parameter', function () { - const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params); - const result = geoBoundingBox.toElasticsearchQuery(node, indexPattern); - expect(result.geo_bounding_box.ignore_unmapped).to.be(true); - }); - - it('should throw an error for scripted fields', function () { - const node = nodeTypes.function.buildNode('geoBoundingBox', 'script number', params); - expect(geoBoundingBox.toElasticsearchQuery) - .withArgs(node, indexPattern).to.throwException(/Geo bounding box query does not support scripted fields/); - }); - - it('should use a provided nested context to create a full field name', function () { - const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params); - const result = geoBoundingBox.toElasticsearchQuery( - node, - indexPattern, - {}, - { nested: { path: 'nestedField' } } - ); - expect(result).to.have.property('geo_bounding_box'); - expect(result.geo_bounding_box).to.have.property('nestedField.geo'); - }); - - }); - }); -}); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/geo_polygon.js b/packages/kbn-es-query/src/kuery/functions/__tests__/geo_polygon.js deleted file mode 100644 index c1f2fae0bb3e1f..00000000000000 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/geo_polygon.js +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import * as geoPolygon from '../geo_polygon'; -import { nodeTypes } from '../../node_types'; -import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; - - -let indexPattern; -const points = [ - { - lat: 69.77, - lon: -171.56 - }, - { - lat: 50.06, - lon: -169.10 - }, - { - lat: 69.16, - lon: -125.85 - } -]; - -describe('kuery functions', function () { - - describe('geoPolygon', function () { - - - beforeEach(() => { - indexPattern = indexPatternResponse; - }); - - describe('buildNodeParams', function () { - - it('should return an "arguments" param', function () { - const result = geoPolygon.buildNodeParams('geo', points); - expect(result).to.only.have.keys('arguments'); - }); - - it('arguments should contain the provided fieldName as a literal', function () { - const result = geoPolygon.buildNodeParams('geo', points); - const { arguments: [ fieldName ] } = result; - - expect(fieldName).to.have.property('type', 'literal'); - expect(fieldName).to.have.property('value', 'geo'); - }); - - it('arguments should contain the provided points literal "lat, lon" string values', function () { - const result = geoPolygon.buildNodeParams('geo', points); - const { arguments: [ , ...args ] } = result; - - args.forEach((param, index) => { - expect(param).to.have.property('type', 'literal'); - const expectedPoint = points[index]; - const expectedLatLon = `${expectedPoint.lat}, ${expectedPoint.lon}`; - expect(param.value).to.be(expectedLatLon); - }); - }); - - }); - - describe('toElasticsearchQuery', function () { - - it('should return an ES geo_polygon query representing the given node', function () { - const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points); - const result = geoPolygon.toElasticsearchQuery(node, indexPattern); - expect(result).to.have.property('geo_polygon'); - expect(result.geo_polygon.geo).to.have.property('points'); - - result.geo_polygon.geo.points.forEach((point, index) => { - const expectedLatLon = `${points[index].lat}, ${points[index].lon}`; - expect(point).to.be(expectedLatLon); - }); - }); - - it('should return an ES geo_polygon query without an index pattern', function () { - const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points); - const result = geoPolygon.toElasticsearchQuery(node); - expect(result).to.have.property('geo_polygon'); - expect(result.geo_polygon.geo).to.have.property('points'); - - result.geo_polygon.geo.points.forEach((point, index) => { - const expectedLatLon = `${points[index].lat}, ${points[index].lon}`; - expect(point).to.be(expectedLatLon); - }); - }); - - it('should use the ignore_unmapped parameter', function () { - const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points); - const result = geoPolygon.toElasticsearchQuery(node, indexPattern); - expect(result.geo_polygon.ignore_unmapped).to.be(true); - }); - - it('should throw an error for scripted fields', function () { - const node = nodeTypes.function.buildNode('geoPolygon', 'script number', points); - expect(geoPolygon.toElasticsearchQuery) - .withArgs(node, indexPattern).to.throwException(/Geo polygon query does not support scripted fields/); - }); - - it('should use a provided nested context to create a full field name', function () { - const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points); - const result = geoPolygon.toElasticsearchQuery( - node, - indexPattern, - {}, - { nested: { path: 'nestedField' } } - ); - expect(result).to.have.property('geo_polygon'); - expect(result.geo_polygon).to.have.property('nestedField.geo'); - }); - }); - }); -}); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/is.js b/packages/kbn-es-query/src/kuery/functions/__tests__/is.js deleted file mode 100644 index b2f3d7ec16a658..00000000000000 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/is.js +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import * as is from '../is'; -import { nodeTypes } from '../../node_types'; -import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; - -let indexPattern; - -describe('kuery functions', function () { - - describe('is', function () { - - - beforeEach(() => { - indexPattern = indexPatternResponse; - }); - - describe('buildNodeParams', function () { - - it('fieldName and value should be required arguments', function () { - expect(is.buildNodeParams).to.throwException(/fieldName is a required argument/); - expect(is.buildNodeParams).withArgs('foo').to.throwException(/value is a required argument/); - }); - - it('arguments should contain the provided fieldName and value as literals', function () { - const { arguments: [fieldName, value] } = is.buildNodeParams('response', 200); - - expect(fieldName).to.have.property('type', 'literal'); - expect(fieldName).to.have.property('value', 'response'); - - expect(value).to.have.property('type', 'literal'); - expect(value).to.have.property('value', 200); - }); - - it('should detect wildcards in the provided arguments', function () { - const { arguments: [fieldName, value] } = is.buildNodeParams('machine*', 'win*'); - - expect(fieldName).to.have.property('type', 'wildcard'); - expect(value).to.have.property('type', 'wildcard'); - }); - - it('should default to a non-phrase query', function () { - const { arguments: [, , isPhrase] } = is.buildNodeParams('response', 200); - expect(isPhrase.value).to.be(false); - }); - - it('should allow specification of a phrase query', function () { - const { arguments: [, , isPhrase] } = is.buildNodeParams('response', 200, true); - expect(isPhrase.value).to.be(true); - }); - }); - - describe('toElasticsearchQuery', function () { - - it('should return an ES match_all query when fieldName and value are both "*"', function () { - const expected = { - match_all: {} - }; - - const node = nodeTypes.function.buildNode('is', '*', '*'); - const result = is.toElasticsearchQuery(node, indexPattern); - expect(result).to.eql(expected); - }); - - it('should return an ES multi_match query using default_field when fieldName is null', function () { - const expected = { - multi_match: { - query: 200, - type: 'best_fields', - lenient: true, - } - }; - - const node = nodeTypes.function.buildNode('is', null, 200); - const result = is.toElasticsearchQuery(node, indexPattern); - expect(result).to.eql(expected); - }); - - it('should return an ES query_string query using default_field when fieldName is null and value contains a wildcard', function () { - const expected = { - query_string: { - query: 'jpg*', - } - }; - - const node = nodeTypes.function.buildNode('is', null, 'jpg*'); - const result = is.toElasticsearchQuery(node, indexPattern); - expect(result).to.eql(expected); - }); - - it('should return an ES bool query with a sub-query for each field when fieldName is "*"', function () { - const node = nodeTypes.function.buildNode('is', '*', 200); - const result = is.toElasticsearchQuery(node, indexPattern); - expect(result).to.have.property('bool'); - expect(result.bool.should).to.have.length(indexPattern.fields.length); - }); - - it('should return an ES exists query when value is "*"', function () { - const expected = { - bool: { - should: [ - { exists: { field: 'extension' } }, - ], - minimum_should_match: 1 - } - }; - - const node = nodeTypes.function.buildNode('is', 'extension', '*'); - const result = is.toElasticsearchQuery(node, indexPattern); - expect(result).to.eql(expected); - }); - - it('should return an ES match query when a concrete fieldName and value are provided', function () { - const expected = { - bool: { - should: [ - { match: { extension: 'jpg' } }, - ], - minimum_should_match: 1 - } - }; - - const node = nodeTypes.function.buildNode('is', 'extension', 'jpg'); - const result = is.toElasticsearchQuery(node, indexPattern); - expect(result).to.eql(expected); - }); - - it('should return an ES match query when a concrete fieldName and value are provided without an index pattern', function () { - const expected = { - bool: { - should: [ - { match: { extension: 'jpg' } }, - ], - minimum_should_match: 1 - } - }; - - const node = nodeTypes.function.buildNode('is', 'extension', 'jpg'); - const result = is.toElasticsearchQuery(node); - expect(result).to.eql(expected); - }); - - it('should support creation of phrase queries', function () { - const expected = { - bool: { - should: [ - { match_phrase: { extension: 'jpg' } }, - ], - minimum_should_match: 1 - } - }; - - const node = nodeTypes.function.buildNode('is', 'extension', 'jpg', true); - const result = is.toElasticsearchQuery(node, indexPattern); - expect(result).to.eql(expected); - }); - - it('should create a query_string query for wildcard values', function () { - const expected = { - bool: { - should: [ - { - query_string: { - fields: ['extension'], - query: 'jpg*' - } - }, - ], - minimum_should_match: 1 - } - }; - - const node = nodeTypes.function.buildNode('is', 'extension', 'jpg*'); - const result = is.toElasticsearchQuery(node, indexPattern); - expect(result).to.eql(expected); - }); - - it('should support scripted fields', function () { - const node = nodeTypes.function.buildNode('is', 'script string', 'foo'); - const result = is.toElasticsearchQuery(node, indexPattern); - expect(result.bool.should[0]).to.have.key('script'); - }); - - it('should support date fields without a dateFormat provided', function () { - const expected = { - bool: { - should: [ - { - range: { - '@timestamp': { - gte: '2018-04-03T19:04:17', - lte: '2018-04-03T19:04:17', - } - } - } - ], - minimum_should_match: 1 - } - }; - - const node = nodeTypes.function.buildNode('is', '@timestamp', '"2018-04-03T19:04:17"'); - const result = is.toElasticsearchQuery(node, indexPattern); - expect(result).to.eql(expected); - }); - - it('should support date fields with a dateFormat provided', function () { - const config = { dateFormatTZ: 'America/Phoenix' }; - const expected = { - bool: { - should: [ - { - range: { - '@timestamp': { - gte: '2018-04-03T19:04:17', - lte: '2018-04-03T19:04:17', - time_zone: 'America/Phoenix', - } - } - } - ], - minimum_should_match: 1 - } - }; - - const node = nodeTypes.function.buildNode('is', '@timestamp', '"2018-04-03T19:04:17"'); - const result = is.toElasticsearchQuery(node, indexPattern, config); - expect(result).to.eql(expected); - }); - - it('should use a provided nested context to create a full field name', function () { - const expected = { - bool: { - should: [ - { match: { 'nestedField.extension': 'jpg' } }, - ], - minimum_should_match: 1 - } - }; - - const node = nodeTypes.function.buildNode('is', 'extension', 'jpg'); - const result = is.toElasticsearchQuery( - node, - indexPattern, - {}, - { nested: { path: 'nestedField' } } - ); - expect(result).to.eql(expected); - }); - - it('should support wildcard field names', function () { - const expected = { - bool: { - should: [ - { match: { extension: 'jpg' } }, - ], - minimum_should_match: 1 - } - }; - - const node = nodeTypes.function.buildNode('is', 'ext*', 'jpg'); - const result = is.toElasticsearchQuery(node, indexPattern); - expect(result).to.eql(expected); - }); - - it('should automatically add a nested query when a wildcard field name covers a nested field', () => { - const expected = { - bool: { - should: [ - { - nested: { - path: 'nestedField.nestedChild', - query: { - match: { - 'nestedField.nestedChild.doublyNestedChild': 'foo' - } - }, - score_mode: 'none' - } - } - ], - minimum_should_match: 1 - } - }; - - - const node = nodeTypes.function.buildNode('is', '*doublyNested*', 'foo'); - const result = is.toElasticsearchQuery(node, indexPattern); - expect(result).to.eql(expected); - }); - }); - }); -}); diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/utils/get_full_field_name_node.js b/packages/kbn-es-query/src/kuery/functions/__tests__/utils/get_full_field_name_node.js deleted file mode 100644 index dae15979a161cd..00000000000000 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/utils/get_full_field_name_node.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { nodeTypes } from '../../../node_types'; -import indexPatternResponse from '../../../../__fixtures__/index_pattern_response.json'; -import { getFullFieldNameNode } from '../../utils/get_full_field_name_node'; - -let indexPattern; - -describe('getFullFieldNameNode', function () { - - beforeEach(() => { - indexPattern = indexPatternResponse; - }); - - it('should return unchanged name node if no nested path is passed in', () => { - const nameNode = nodeTypes.literal.buildNode('notNested'); - const result = getFullFieldNameNode(nameNode, indexPattern); - expect(result).to.eql(nameNode); - }); - - it('should add the nested path if it is valid according to the index pattern', () => { - const nameNode = nodeTypes.literal.buildNode('child'); - const result = getFullFieldNameNode(nameNode, indexPattern, 'nestedField'); - expect(result).to.eql(nodeTypes.literal.buildNode('nestedField.child')); - }); - - it('should throw an error if a path is provided for a non-nested field', () => { - const nameNode = nodeTypes.literal.buildNode('os'); - expect(getFullFieldNameNode) - .withArgs(nameNode, indexPattern, 'machine') - .to - .throwException(/machine.os is not a nested field but is in nested group "machine" in the KQL expression/); - }); - - it('should throw an error if a nested field is not passed with a path', () => { - const nameNode = nodeTypes.literal.buildNode('nestedField.child'); - expect(getFullFieldNameNode) - .withArgs(nameNode, indexPattern) - .to - .throwException(/nestedField.child is a nested field, but is not in a nested group in the KQL expression./); - }); - - it('should throw an error if a nested field is passed with the wrong path', () => { - const nameNode = nodeTypes.literal.buildNode('nestedChild.doublyNestedChild'); - expect(getFullFieldNameNode) - .withArgs(nameNode, indexPattern, 'nestedField') - .to - // eslint-disable-next-line max-len - .throwException(/Nested field nestedField.nestedChild.doublyNestedChild is being queried with the incorrect nested path. The correct path is nestedField.nestedChild/); - }); - - it('should skip error checking for wildcard names', () => { - const nameNode = nodeTypes.wildcard.buildNode('nested*'); - const result = getFullFieldNameNode(nameNode, indexPattern); - expect(result).to.eql(nameNode); - }); - - it('should skip error checking if no index pattern is passed in', () => { - const nameNode = nodeTypes.literal.buildNode('os'); - expect(getFullFieldNameNode) - .withArgs(nameNode, null, 'machine') - .to - .not - .throwException(); - - const result = getFullFieldNameNode(nameNode, null, 'machine'); - expect(result).to.eql(nodeTypes.literal.buildNode('machine.os')); - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/node_types/__tests__/function.js b/packages/kbn-es-query/src/kuery/node_types/__tests__/function.js deleted file mode 100644 index de00c083fc8304..00000000000000 --- a/packages/kbn-es-query/src/kuery/node_types/__tests__/function.js +++ /dev/null @@ -1,80 +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 * as functionType from '../function'; -import _ from 'lodash'; -import expect from '@kbn/expect'; -import * as isFunction from '../../functions/is'; -import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; - -import { nodeTypes } from '../../node_types'; - -describe('kuery node types', function () { - - describe('function', function () { - - let indexPattern; - - beforeEach(() => { - indexPattern = indexPatternResponse; - }); - - describe('buildNode', function () { - - it('should return a node representing the given kuery function', function () { - const result = functionType.buildNode('is', 'extension', 'jpg'); - expect(result).to.have.property('type', 'function'); - expect(result).to.have.property('function', 'is'); - expect(result).to.have.property('arguments'); - }); - - }); - - describe('buildNodeWithArgumentNodes', function () { - - it('should return a function node with the given argument list untouched', function () { - const fieldNameLiteral = nodeTypes.literal.buildNode('extension'); - const valueLiteral = nodeTypes.literal.buildNode('jpg'); - const argumentNodes = [fieldNameLiteral, valueLiteral]; - const result = functionType.buildNodeWithArgumentNodes('is', argumentNodes); - - expect(result).to.have.property('type', 'function'); - expect(result).to.have.property('function', 'is'); - expect(result).to.have.property('arguments'); - expect(result.arguments).to.be(argumentNodes); - expect(result.arguments).to.eql(argumentNodes); - }); - - }); - - describe('toElasticsearchQuery', function () { - - it('should return the given function type\'s ES query representation', function () { - const node = functionType.buildNode('is', 'extension', 'jpg'); - const expected = isFunction.toElasticsearchQuery(node, indexPattern); - const result = functionType.toElasticsearchQuery(node, indexPattern); - expect(_.isEqual(expected, result)).to.be(true); - }); - - }); - - - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/node_types/__tests__/named_arg.js b/packages/kbn-es-query/src/kuery/node_types/__tests__/named_arg.js deleted file mode 100644 index cfb8f6d5274dbe..00000000000000 --- a/packages/kbn-es-query/src/kuery/node_types/__tests__/named_arg.js +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import * as namedArg from '../named_arg'; -import { nodeTypes } from '../../node_types'; - -describe('kuery node types', function () { - - describe('named arg', function () { - - describe('buildNode', function () { - - it('should return a node representing a named argument with the given value', function () { - const result = namedArg.buildNode('fieldName', 'foo'); - expect(result).to.have.property('type', 'namedArg'); - expect(result).to.have.property('name', 'fieldName'); - expect(result).to.have.property('value'); - - const literalValue = result.value; - expect(literalValue).to.have.property('type', 'literal'); - expect(literalValue).to.have.property('value', 'foo'); - }); - - it('should support literal nodes as values', function () { - const value = nodeTypes.literal.buildNode('foo'); - const result = namedArg.buildNode('fieldName', value); - expect(result.value).to.be(value); - expect(result.value).to.eql(value); - }); - - }); - - describe('toElasticsearchQuery', function () { - - it('should return the argument value represented by the given node', function () { - const node = namedArg.buildNode('fieldName', 'foo'); - const result = namedArg.toElasticsearchQuery(node); - expect(result).to.be('foo'); - }); - - }); - - }); - -}); diff --git a/packages/kbn-es-query/src/kuery/node_types/__tests__/wildcard.js b/packages/kbn-es-query/src/kuery/node_types/__tests__/wildcard.js deleted file mode 100644 index 0c4379378c6d6f..00000000000000 --- a/packages/kbn-es-query/src/kuery/node_types/__tests__/wildcard.js +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import * as wildcard from '../wildcard'; - -describe('kuery node types', function () { - - describe('wildcard', function () { - - describe('buildNode', function () { - - it('should accept a string argument representing a wildcard string', function () { - const wildcardValue = `foo${wildcard.wildcardSymbol}bar`; - const result = wildcard.buildNode(wildcardValue); - expect(result).to.have.property('type', 'wildcard'); - expect(result).to.have.property('value', wildcardValue); - }); - - it('should accept and parse a wildcard string', function () { - const result = wildcard.buildNode('foo*bar'); - expect(result).to.have.property('type', 'wildcard'); - expect(result.value).to.be(`foo${wildcard.wildcardSymbol}bar`); - }); - - }); - - describe('toElasticsearchQuery', function () { - - it('should return the string representation of the wildcard literal', function () { - const node = wildcard.buildNode('foo*bar'); - const result = wildcard.toElasticsearchQuery(node); - expect(result).to.be('foo*bar'); - }); - - }); - - describe('toQueryStringQuery', function () { - - it('should return the string representation of the wildcard literal', function () { - const node = wildcard.buildNode('foo*bar'); - const result = wildcard.toQueryStringQuery(node); - expect(result).to.be('foo*bar'); - }); - - it('should escape query_string query special characters other than wildcard', function () { - const node = wildcard.buildNode('+foo*bar'); - const result = wildcard.toQueryStringQuery(node); - expect(result).to.be('\\+foo*bar'); - }); - - }); - - describe('test', function () { - - it('should return a boolean indicating whether the string matches the given wildcard node', function () { - const node = wildcard.buildNode('foo*bar'); - expect(wildcard.test(node, 'foobar')).to.be(true); - expect(wildcard.test(node, 'foobazbar')).to.be(true); - expect(wildcard.test(node, 'foobar')).to.be(true); - - expect(wildcard.test(node, 'fooqux')).to.be(false); - expect(wildcard.test(node, 'bazbar')).to.be(false); - }); - - it('should return a true even when the string has newlines or tabs', function () { - const node = wildcard.buildNode('foo*bar'); - expect(wildcard.test(node, 'foo\nbar')).to.be(true); - expect(wildcard.test(node, 'foo\tbar')).to.be(true); - }); - }); - - describe('hasLeadingWildcard', function () { - it('should determine whether a wildcard node contains a leading wildcard', function () { - const node = wildcard.buildNode('foo*bar'); - expect(wildcard.hasLeadingWildcard(node)).to.be(false); - - const leadingWildcardNode = wildcard.buildNode('*foobar'); - expect(wildcard.hasLeadingWildcard(leadingWildcardNode)).to.be(true); - }); - - // Lone wildcards become exists queries, so we aren't worried about their performance - it('should not consider a lone wildcard to be a leading wildcard', function () { - const leadingWildcardNode = wildcard.buildNode('*'); - expect(wildcard.hasLeadingWildcard(leadingWildcardNode)).to.be(false); - }); - }); - - }); - -}); diff --git a/packages/kbn-es-query/src/utils/filters.js b/packages/kbn-es-query/src/utils/filters.js deleted file mode 100644 index 6e4f5c342688ce..00000000000000 --- a/packages/kbn-es-query/src/utils/filters.js +++ /dev/null @@ -1,133 +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 { pick, get, reduce, map } from 'lodash'; - -/** @deprecated - * @see src/plugins/data/public/es_query/filters/phrase_filter.ts - * Code was already moved into src/plugins/data/public. - * This method will be removed after moving 'es_query' into new platform - * */ -export const getConvertedValueForField = (field, value) => { - if (typeof value !== 'boolean' && field.type === 'boolean') { - if ([1, 'true'].includes(value)) { - return true; - } else if ([0, 'false'].includes(value)) { - return false; - } else { - throw new Error(`${value} is not a valid boolean value for boolean field ${field.name}`); - } - } - return value; -}; - -/** @deprecated - * @see src/plugins/data/public/es_query/filters/phrase_filter.ts - * Code was already moved into src/plugins/data/public. - * This method will be removed after moving 'es_query' into new platform - * */ -export const buildInlineScriptForPhraseFilter = (scriptedField) => { - // We must wrap painless scripts in a lambda in case they're more than a simple expression - if (scriptedField.lang === 'painless') { - return ( - `boolean compare(Supplier s, def v) {return s.get() == v;}` + - `compare(() -> { ${scriptedField.script} }, params.value);` - ); - } else { - return `(${scriptedField.script}) == value`; - } -}; - -/** @deprecated - * @see src/plugins/data/public/es_query/filters/phrase_filter.ts - * Code was already moved into src/plugins/data/public. - * This method will be removed after moving 'es_query' into new platform - * */ -export function getPhraseScript(field, value) { - const convertedValue = getConvertedValueForField(field, value); - const script = buildInlineScriptForPhraseFilter(field); - - return { - script: { - source: script, - lang: field.lang, - params: { - value: convertedValue, - }, - }, - }; -} - -/** @deprecated - * @see src/plugins/data/public/es_query/filters/range_filter.ts - * Code was already moved into src/plugins/data/public. - * This method will be removed after moving 'kuery' into new platform - * */ -export function getRangeScript(field, params) { - const operators = { - gt: '>', - gte: '>=', - lte: '<=', - lt: '<', - }; - const comparators = { - gt: 'boolean gt(Supplier s, def v) {return s.get() > v}', - gte: 'boolean gte(Supplier s, def v) {return s.get() >= v}', - lte: 'boolean lte(Supplier s, def v) {return s.get() <= v}', - lt: 'boolean lt(Supplier s, def v) {return s.get() < v}', - }; - - const dateComparators = { - gt: 'boolean gt(Supplier s, def v) {return s.get().toInstant().isAfter(Instant.parse(v))}', - gte: 'boolean gte(Supplier s, def v) {return !s.get().toInstant().isBefore(Instant.parse(v))}', - lte: 'boolean lte(Supplier s, def v) {return !s.get().toInstant().isAfter(Instant.parse(v))}', - lt: 'boolean lt(Supplier s, def v) {return s.get().toInstant().isBefore(Instant.parse(v))}', - }; - - const knownParams = pick(params, (val, key) => { - return key in operators; - }); - let script = map(knownParams, (val, key) => { - return '(' + field.script + ')' + get(operators, key) + key; - }).join(' && '); - - // We must wrap painless scripts in a lambda in case they're more than a simple expression - if (field.lang === 'painless') { - const comp = field.type === 'date' ? dateComparators : comparators; - const currentComparators = reduce( - knownParams, - (acc, val, key) => acc.concat(get(comp, key)), - [] - ).join(' '); - - const comparisons = map(knownParams, (val, key) => { - return `${key}(() -> { ${field.script} }, params.${key})`; - }).join(' && '); - - script = `${currentComparators}${comparisons}`; - } - - return { - script: { - source: script, - params: knownParams, - lang: field.lang, - }, - }; -} diff --git a/packages/kbn-es-query/src/utils/index.js b/packages/kbn-es-query/src/utils/index.js deleted file mode 100644 index 27f51c1f44cf2f..00000000000000 --- a/packages/kbn-es-query/src/utils/index.js +++ /dev/null @@ -1,20 +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 * from './get_time_zone_from_settings'; diff --git a/packages/kbn-es-query/tasks/build_cli.js b/packages/kbn-es-query/tasks/build_cli.js deleted file mode 100644 index 2a43c4d10e0070..00000000000000 --- a/packages/kbn-es-query/tasks/build_cli.js +++ /dev/null @@ -1,103 +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. - */ - -const { resolve } = require('path'); - -const getopts = require('getopts'); -const del = require('del'); -const supportsColor = require('supports-color'); -const { ToolingLog, withProcRunner, pickLevelFromFlags } = require('@kbn/dev-utils'); - -const ROOT_DIR = resolve(__dirname, '..'); -const BUILD_DIR = resolve(ROOT_DIR, 'target'); - -const padRight = (width, str) => - str.length >= width ? str : `${str}${' '.repeat(width - str.length)}`; - -const unknownFlags = []; -const flags = getopts(process.argv, { - boolean: ['watch', 'help', 'source-maps'], - unknown(name) { - unknownFlags.push(name); - }, -}); - -const log = new ToolingLog({ - level: pickLevelFromFlags(flags), - writeTo: process.stdout, -}); - -if (unknownFlags.length) { - log.error(`Unknown flag(s): ${unknownFlags.join(', ')}`); - flags.help = true; - process.exitCode = 1; -} - -if (flags.help) { - log.info(` - Simple build tool for @kbn/es-query package - - --watch Run in watch mode - --source-maps Include sourcemaps - --help Show this message - `); - process.exit(); -} - -withProcRunner(log, async proc => { - log.info('Deleting old output'); - await del(BUILD_DIR); - - const cwd = ROOT_DIR; - const env = { ...process.env }; - if (supportsColor.stdout) { - env.FORCE_COLOR = 'true'; - } - - log.info(`Starting babel and typescript${flags.watch ? ' in watch mode' : ''}`); - await Promise.all([ - ...['public', 'server'].map(subTask => - proc.run(padRight(12, `babel:${subTask}`), { - cmd: 'babel', - args: [ - 'src', - '--config-file', - require.resolve('../babel.config.js'), - '--out-dir', - resolve(BUILD_DIR, subTask), - '--extensions', - '.js,.ts,.tsx', - ...(flags.watch ? ['--watch'] : ['--quiet']), - ...(flags['source-maps'] ? ['--source-map', 'inline'] : []), - ], - wait: true, - cwd, - env: { - ...env, - BABEL_ENV: subTask, - }, - }) - ), - ]); - - log.success('Complete'); -}).catch(error => { - log.error(error); - process.exit(1); -}); diff --git a/packages/kbn-es-query/tsconfig.browser.json b/packages/kbn-es-query/tsconfig.browser.json deleted file mode 100644 index 4a914074712661..00000000000000 --- a/packages/kbn-es-query/tsconfig.browser.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../../tsconfig.browser.json", - "compilerOptions": { - "rootDir": "./src", - "outDir": "./target/public" - }, - "include": [ - "index.d.ts", - "src/**/*.ts" - ] -} diff --git a/packages/kbn-es-query/tsconfig.json b/packages/kbn-es-query/tsconfig.json deleted file mode 100644 index 05f51bbccd2ff3..00000000000000 --- a/packages/kbn-es-query/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "rootDir": "./src", - "outDir": "./target/server" - }, - "include": [ - "index.d.ts", - "src/**/*.ts" - ] -} diff --git a/packages/kbn-i18n/package.json b/packages/kbn-i18n/package.json index 591faff64711d8..0146111941044f 100644 --- a/packages/kbn-i18n/package.json +++ b/packages/kbn-i18n/package.json @@ -28,7 +28,7 @@ "intl-messageformat": "^2.2.0", "intl-relativeformat": "^2.1.0", "prop-types": "^15.6.2", - "react": "^16.8.0", + "react": "^16.8.6", "react-intl": "^2.8.0" } } diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json index 472c801721ecfe..ee5424370fb06a 100644 --- a/packages/kbn-ui-framework/package.json +++ b/packages/kbn-ui-framework/package.json @@ -19,7 +19,7 @@ "focus-trap-react": "^3.1.1", "lodash": "npm:@elastic/lodash@3.10.1-kibana3", "prop-types": "15.6.0", - "react": "^16.8.0", + "react": "^16.8.6", "react-ace": "^5.9.0", "react-color": "^2.13.8", "tabbable": "1.1.3", @@ -57,7 +57,7 @@ "postcss": "^7.0.5", "postcss-loader": "^3.0.0", "raw-loader": "^3.1.0", - "react-dom": "^16.8.0", + "react-dom": "^16.8.6", "react-redux": "^5.0.6", "react-router": "^3.2.0", "react-router-redux": "^4.0.8", diff --git a/src/core/CONVENTIONS.md b/src/core/CONVENTIONS.md index 11a5f33a1b2d89..fbe2740b961088 100644 --- a/src/core/CONVENTIONS.md +++ b/src/core/CONVENTIONS.md @@ -210,3 +210,40 @@ export class Plugin { } } ``` + +### Usage Collection + +For creating and registering a Usage Collector. Collectors would be defined in a separate directory `server/collectors/register.ts`. You can read more about usage collectors on `src/plugins/usage_collection/README.md`. + +```ts +// server/collectors/register.ts +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; + +export function registerMyPluginUsageCollector(usageCollection?: UsageCollectionSetup): void { + // usageCollection is an optional dependency, so make sure to return if it is not registered. + if (!usageCollection) { + return; + } + + // create usage collector + const myCollector = usageCollection.makeUsageCollector({ + type: MY_USAGE_TYPE, + fetch: async (callCluster: CallCluster) => { + + // query ES and get some data + // summarize the data into a model + // return the modeled object that includes whatever you want to track + + return { + my_objects: { + total: SOME_NUMBER + } + }; + }, + }); + + // register usage collector + usageCollection.registerCollector(myCollector); +} +``` diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index c5e04c3cfb53ac..e88f1675114bc4 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -1169,7 +1169,7 @@ import { setup, start } from '../core_plugins/visualizations/public/legacy'; | ------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `import 'ui/apply_filters'` | `import { applyFiltersPopover } from '../data/public'` | Directive is deprecated. | | `import 'ui/filter_bar'` | `import { FilterBar } from '../data/public'` | Directive is deprecated. | -| `import 'ui/query_bar'` | `import { QueryBarInput } from '../data/public'` | Directives are deprecated. | +| `import 'ui/query_bar'` | `import { QueryStringInput } from '../data/public'` | Directives are deprecated. | | `import 'ui/search_bar'` | `import { SearchBar } from '../data/public'` | Directive is deprecated. | | `import 'ui/kbn_top_nav'` | `import { TopNavMenu } from '../navigation/public'` | Directive is still available in `ui/kbn_top_nav`. | | `ui/saved_objects/components/saved_object_finder` | `import { SavedObjectFinder } from '../kibana_react/public'` | | diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 16a634b2d32872..30a98c9046ff50 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -846,7 +846,7 @@ export class SavedObjectsClient { bulkUpdate(objects?: SavedObjectsBulkUpdateObject[]): Promise>; create: (type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise>; delete: (type: string, id: string) => Promise<{}>; - find: (options: Pick) => Promise>; + find: (options: Pick) => Promise>; get: (type: string, id: string) => Promise>; update(type: string, id: string, attributes: T, { version, migrationVersion, references }?: SavedObjectsUpdateOptions): Promise>; } diff --git a/src/core/server/http/cookie_session_storage.ts b/src/core/server/http/cookie_session_storage.ts index 8a1b56d87fb4c9..25b463140bfbc2 100644 --- a/src/core/server/http/cookie_session_storage.ts +++ b/src/core/server/http/cookie_session_storage.ts @@ -34,19 +34,34 @@ export interface SessionStorageCookieOptions { */ name: string; /** - * A key used to encrypt a cookie value. Should be at least 32 characters long. + * A key used to encrypt a cookie's value. Should be at least 32 characters long. */ encryptionKey: string; /** - * Function called to validate a cookie content. + * Function called to validate a cookie's decrypted value. */ - validate: (sessionValue: T) => boolean | Promise; + validate: (sessionValue: T | T[]) => SessionCookieValidationResult; /** * Flag indicating whether the cookie should be sent only via a secure connection. */ isSecure: boolean; } +/** + * Return type from a function to validate cookie contents. + * @public + */ +export interface SessionCookieValidationResult { + /** + * Whether the cookie is valid or not. + */ + isValid: boolean; + /** + * The "Path" attribute of the cookie; if the cookie is invalid, this is used to clear it. + */ + path?: string; +} + class ScopedCookieSessionStorage> implements SessionStorage { constructor( private readonly log: Logger, @@ -98,15 +113,31 @@ export async function createCookieSessionStorageFactory( cookieOptions: SessionStorageCookieOptions, basePath?: string ): Promise> { + function clearInvalidCookie(req: Request | undefined, path: string = basePath || '/') { + // if the cookie did not include the 'path' attribute in the session value, it is a legacy cookie + // we will assume that the cookie was created with the current configuration + log.debug(`Clearing invalid session cookie`); + // need to use Hapi toolkit to clear cookie with defined options + if (req) { + (req.cookieAuth as any).h.unstate(cookieOptions.name, { path }); + } + } + await server.register({ plugin: hapiAuthCookie }); server.auth.strategy('security-cookie', 'cookie', { cookie: cookieOptions.name, password: cookieOptions.encryptionKey, - validateFunc: async (req, session: T) => ({ valid: await cookieOptions.validate(session) }), + validateFunc: async (req, session: T | T[]) => { + const result = cookieOptions.validate(session); + if (!result.isValid) { + clearInvalidCookie(req, result.path); + } + return { valid: result.isValid }; + }, isSecure: cookieOptions.isSecure, path: basePath, - clearInvalid: true, + clearInvalid: false, isHttpOnly: true, isSameSite: false, }); diff --git a/src/core/server/http/cookie_sesson_storage.test.ts b/src/core/server/http/cookie_sesson_storage.test.ts index 5cd2fbaa1ebe8a..bf0585ad280d50 100644 --- a/src/core/server/http/cookie_sesson_storage.test.ts +++ b/src/core/server/http/cookie_sesson_storage.test.ts @@ -80,6 +80,7 @@ interface User { interface Storage { value: User; expires: number; + path: string; } function retrieveSessionCookie(cookies: string) { @@ -92,13 +93,21 @@ function retrieveSessionCookie(cookies: string) { const userData = { id: '42' }; const sessionDurationMs = 1000; +const path = '/'; +const sessVal = () => ({ value: userData, expires: Date.now() + sessionDurationMs, path }); const delay = (ms: number) => new Promise(res => setTimeout(res, ms)); const cookieOptions = { name: 'sid', encryptionKey: 'something_at_least_32_characters', - validate: (session: Storage) => session.expires > Date.now(), + validate: (session: Storage | Storage[]) => { + if (Array.isArray(session)) { + session = session[0]; + } + const isValid = session.path === path && session.expires > Date.now(); + return { isValid, path: session.path }; + }, isSecure: false, - path: '/', + path, }; describe('Cookie based SessionStorage', () => { @@ -107,9 +116,9 @@ describe('Cookie based SessionStorage', () => { const { server: innerServer, createRouter } = await server.setup(setupDeps); const router = createRouter(''); - router.get({ path: '/', validate: false }, (context, req, res) => { + router.get({ path, validate: false }, (context, req, res) => { const sessionStorage = factory.asScoped(req); - sessionStorage.set({ value: userData, expires: Date.now() + sessionDurationMs }); + sessionStorage.set(sessVal()); return res.ok({}); }); @@ -136,6 +145,7 @@ describe('Cookie based SessionStorage', () => { expect(sessionCookie.httpOnly).toBe(true); }); }); + describe('#get()', () => { it('reads from session storage', async () => { const { server: innerServer, createRouter } = await server.setup(setupDeps); @@ -145,7 +155,7 @@ describe('Cookie based SessionStorage', () => { const sessionStorage = factory.asScoped(req); const sessionValue = await sessionStorage.get(); if (!sessionValue) { - sessionStorage.set({ value: userData, expires: Date.now() + sessionDurationMs }); + sessionStorage.set(sessVal()); return res.ok(); } return res.ok({ body: { value: sessionValue.value } }); @@ -173,6 +183,7 @@ describe('Cookie based SessionStorage', () => { .set('Cookie', `${sessionCookie.key}=${sessionCookie.value}`) .expect(200, { value: userData }); }); + it('returns null for empty session', async () => { const { server: innerServer, createRouter } = await server.setup(setupDeps); @@ -198,7 +209,7 @@ describe('Cookie based SessionStorage', () => { expect(cookies).not.toBeDefined(); }); - it('returns null for invalid session & clean cookies', async () => { + it('returns null for invalid session (expired) & clean cookies', async () => { const { server: innerServer, createRouter } = await server.setup(setupDeps); const router = createRouter(''); @@ -208,7 +219,7 @@ describe('Cookie based SessionStorage', () => { const sessionStorage = factory.asScoped(req); if (!setOnce) { setOnce = true; - sessionStorage.set({ value: userData, expires: Date.now() + sessionDurationMs }); + sessionStorage.set(sessVal()); return res.ok({ body: { value: userData } }); } const sessionValue = await sessionStorage.get(); @@ -242,6 +253,50 @@ describe('Cookie based SessionStorage', () => { 'sid=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Path=/', ]); }); + + it('returns null for invalid session (incorrect path) & clean cookies accurately', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + + const router = createRouter(''); + + let setOnce = false; + router.get({ path: '/', validate: false }, async (context, req, res) => { + const sessionStorage = factory.asScoped(req); + if (!setOnce) { + setOnce = true; + sessionStorage.set({ ...sessVal(), path: '/foo' }); + return res.ok({ body: { value: userData } }); + } + const sessionValue = await sessionStorage.get(); + return res.ok({ body: { value: sessionValue } }); + }); + + const factory = await createCookieSessionStorageFactory( + logger.get(), + innerServer, + cookieOptions + ); + await server.start(); + + const response = await supertest(innerServer.listener) + .get('/') + .expect(200, { value: userData }); + + const cookies = response.get('set-cookie'); + expect(cookies).toBeDefined(); + + const sessionCookie = retrieveSessionCookie(cookies[0]); + const response2 = await supertest(innerServer.listener) + .get('/') + .set('Cookie', `${sessionCookie.key}=${sessionCookie.value}`) + .expect(200, { value: null }); + + const cookies2 = response2.get('set-cookie'); + expect(cookies2).toEqual([ + 'sid=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Path=/foo', + ]); + }); + // use mocks to simplify test setup it('returns null if multiple session cookies are detected.', async () => { const mockServer = { @@ -342,7 +397,7 @@ describe('Cookie based SessionStorage', () => { sessionStorage.clear(); return res.ok({}); } - sessionStorage.set({ value: userData, expires: Date.now() + sessionDurationMs }); + sessionStorage.set(sessVal()); return res.ok({}); }); diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index acae9d8ff0e704..ceecfcfea1449c 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -34,7 +34,7 @@ import { HttpServer } from './http_server'; const cookieOptions = { name: 'sid', encryptionKey: 'something_at_least_32_characters', - validate: () => true, + validate: () => ({ isValid: true }), isSecure: false, }; diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts index 2fa67750f64063..bed76201bb4f99 100644 --- a/src/core/server/http/index.ts +++ b/src/core/server/http/index.ts @@ -60,6 +60,9 @@ export { } from './lifecycle/auth'; export { OnPostAuthHandler, OnPostAuthToolkit } from './lifecycle/on_post_auth'; export { SessionStorageFactory, SessionStorage } from './session_storage'; -export { SessionStorageCookieOptions } from './cookie_session_storage'; +export { + SessionStorageCookieOptions, + SessionCookieValidationResult, +} from './cookie_session_storage'; export * from './types'; export { BasePath, IBasePath } from './base_path_service'; diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts index 00629b811b28fc..f3867faa2ae75f 100644 --- a/src/core/server/http/integration_tests/core_services.test.ts +++ b/src/core/server/http/integration_tests/core_services.test.ts @@ -39,7 +39,7 @@ describe('http service', () => { const cookieOptions = { name: 'sid', encryptionKey: 'something_at_least_32_characters', - validate: (session: StorageData) => true, + validate: () => ({ isValid: true }), isSecure: false, path: '/', }; diff --git a/src/core/server/http/integration_tests/lifecycle.test.ts b/src/core/server/http/integration_tests/lifecycle.test.ts index 4592a646b7f041..7c4a0097456ca8 100644 --- a/src/core/server/http/integration_tests/lifecycle.test.ts +++ b/src/core/server/http/integration_tests/lifecycle.test.ts @@ -408,7 +408,7 @@ describe('Auth', () => { const cookieOptions = { name: 'sid', encryptionKey: 'something_at_least_32_characters', - validate: () => true, + validate: () => ({ isValid: true }), isSecure: false, }; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 31dec2c9b96ffe..b53f04d601ff42 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -117,6 +117,7 @@ export { RouteRegistrar, SessionStorage, SessionStorageCookieOptions, + SessionCookieValidationResult, SessionStorageFactory, } from './http'; export { Logger, LoggerFactory, LogMeta, LogRecord, LogLevel } from './logging'; diff --git a/src/core/server/saved_objects/service/lib/filter_utils.test.ts b/src/core/server/saved_objects/service/lib/filter_utils.test.ts index 4c4f321695d706..13a132ab9dd67b 100644 --- a/src/core/server/saved_objects/service/lib/filter_utils.test.ts +++ b/src/core/server/saved_objects/service/lib/filter_utils.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { fromKueryExpression } from '@kbn/es-query'; +import { esKuery } from '../../../../../plugins/data/server'; import { validateFilterKueryNode, validateConvertFilterToKueryNode } from './filter_utils'; @@ -64,7 +64,7 @@ describe('Filter Utils', () => { test('Validate a simple filter', () => { expect( validateConvertFilterToKueryNode(['foo'], 'foo.attributes.title: "best"', mockMappings) - ).toEqual(fromKueryExpression('foo.title: "best"')); + ).toEqual(esKuery.fromKueryExpression('foo.title: "best"')); }); test('Assemble filter kuery node saved object attributes with one saved object type', () => { expect( @@ -74,7 +74,7 @@ describe('Filter Utils', () => { mockMappings ) ).toEqual( - fromKueryExpression( + esKuery.fromKueryExpression( '(type: foo and updatedAt: 5678654567) and foo.bytes > 1000 and foo.bytes < 8000 and foo.title: "best" and (foo.description: t* or foo.description :*)' ) ); @@ -88,7 +88,7 @@ describe('Filter Utils', () => { mockMappings ) ).toEqual( - fromKueryExpression( + esKuery.fromKueryExpression( '(type: foo and updatedAt: 5678654567) and foo.bytes > 1000 and foo.bytes < 8000 and foo.title: "best" and (foo.description: t* or foo.description :*)' ) ); @@ -102,7 +102,7 @@ describe('Filter Utils', () => { mockMappings ) ).toEqual( - fromKueryExpression( + esKuery.fromKueryExpression( '((type: bar and updatedAt: 5678654567) or (type: foo and updatedAt: 5678654567)) and foo.bytes > 1000 and foo.bytes < 8000 and foo.title: "best" and (foo.description: t* or bar.description :*)' ) ); @@ -130,7 +130,7 @@ describe('Filter Utils', () => { describe('#validateFilterKueryNode', () => { test('Validate filter query through KueryNode - happy path', () => { const validationObject = validateFilterKueryNode( - fromKueryExpression( + esKuery.fromKueryExpression( 'foo.updatedAt: 5678654567 and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or foo.attributes.description :*)' ), ['foo'], @@ -185,7 +185,7 @@ describe('Filter Utils', () => { test('Return Error if key is not wrapper by a saved object type', () => { const validationObject = validateFilterKueryNode( - fromKueryExpression( + esKuery.fromKueryExpression( 'updatedAt: 5678654567 and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or foo.attributes.description :*)' ), ['foo'], @@ -240,7 +240,7 @@ describe('Filter Utils', () => { test('Return Error if key of a saved object type is not wrapped with attributes', () => { const validationObject = validateFilterKueryNode( - fromKueryExpression( + esKuery.fromKueryExpression( 'foo.updatedAt: 5678654567 and foo.attributes.bytes > 1000 and foo.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or foo.description :*)' ), ['foo'], @@ -297,7 +297,7 @@ describe('Filter Utils', () => { test('Return Error if filter is not using an allowed type', () => { const validationObject = validateFilterKueryNode( - fromKueryExpression( + esKuery.fromKueryExpression( 'bar.updatedAt: 5678654567 and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.title: "best" and (foo.attributes.description: t* or foo.attributes.description :*)' ), ['foo'], @@ -352,7 +352,7 @@ describe('Filter Utils', () => { test('Return Error if filter is using an non-existing key in the index patterns of the saved object type', () => { const validationObject = validateFilterKueryNode( - fromKueryExpression( + esKuery.fromKueryExpression( 'foo.updatedAt33: 5678654567 and foo.attributes.bytes > 1000 and foo.attributes.bytes < 8000 and foo.attributes.header: "best" and (foo.attributes.description: t* or foo.attributes.description :*)' ), ['foo'], @@ -408,7 +408,7 @@ describe('Filter Utils', () => { test('Return Error if filter is using an non-existing key null key', () => { const validationObject = validateFilterKueryNode( - fromKueryExpression('foo.attributes.description: hello AND bye'), + esKuery.fromKueryExpression('foo.attributes.description: hello AND bye'), ['foo'], mockMappings ); diff --git a/src/core/server/saved_objects/service/lib/filter_utils.ts b/src/core/server/saved_objects/service/lib/filter_utils.ts index e331d3eff990f7..3cf499de541ee4 100644 --- a/src/core/server/saved_objects/service/lib/filter_utils.ts +++ b/src/core/server/saved_objects/service/lib/filter_utils.ts @@ -17,18 +17,18 @@ * under the License. */ -import { fromKueryExpression, KueryNode, nodeTypes } from '@kbn/es-query'; import { get, set } from 'lodash'; import { SavedObjectsErrorHelpers } from './errors'; import { IndexMapping } from '../../mappings'; +import { esKuery } from '../../../../../plugins/data/server'; export const validateConvertFilterToKueryNode = ( allowedTypes: string[], filter: string, indexMapping: IndexMapping -): KueryNode => { +): esKuery.KueryNode | undefined => { if (filter && filter.length > 0 && indexMapping) { - const filterKueryNode = fromKueryExpression(filter); + const filterKueryNode = esKuery.fromKueryExpression(filter); const validationFilterKuery = validateFilterKueryNode( filterKueryNode, @@ -54,7 +54,7 @@ export const validateConvertFilterToKueryNode = ( validationFilterKuery.forEach(item => { const path: string[] = item.astPath.length === 0 ? [] : item.astPath.split('.'); - const existingKueryNode: KueryNode = + const existingKueryNode: esKuery.KueryNode = path.length === 0 ? filterKueryNode : get(filterKueryNode, path); if (item.isSavedObjectAttr) { existingKueryNode.arguments[0].value = existingKueryNode.arguments[0].value.split('.')[1]; @@ -63,8 +63,8 @@ export const validateConvertFilterToKueryNode = ( set( filterKueryNode, path, - nodeTypes.function.buildNode('and', [ - nodeTypes.function.buildNode('is', 'type', itemType[0]), + esKuery.nodeTypes.function.buildNode('and', [ + esKuery.nodeTypes.function.buildNode('is', 'type', itemType[0]), existingKueryNode, ]) ); @@ -79,7 +79,6 @@ export const validateConvertFilterToKueryNode = ( }); return filterKueryNode; } - return null; }; interface ValidateFilterKueryNode { @@ -91,41 +90,44 @@ interface ValidateFilterKueryNode { } export const validateFilterKueryNode = ( - astFilter: KueryNode, + astFilter: esKuery.KueryNode, types: string[], indexMapping: IndexMapping, storeValue: boolean = false, path: string = 'arguments' ): ValidateFilterKueryNode[] => { - return astFilter.arguments.reduce((kueryNode: string[], ast: KueryNode, index: number) => { - if (ast.arguments) { - const myPath = `${path}.${index}`; - return [ - ...kueryNode, - ...validateFilterKueryNode( - ast, - types, - indexMapping, - ast.type === 'function' && ['is', 'range'].includes(ast.function), - `${myPath}.arguments` - ), - ]; - } - if (storeValue && index === 0) { - const splitPath = path.split('.'); - return [ - ...kueryNode, - { - astPath: splitPath.slice(0, splitPath.length - 1).join('.'), - error: hasFilterKeyError(ast.value, types, indexMapping), - isSavedObjectAttr: isSavedObjectAttr(ast.value, indexMapping), - key: ast.value, - type: getType(ast.value), - }, - ]; - } - return kueryNode; - }, []); + return astFilter.arguments.reduce( + (kueryNode: string[], ast: esKuery.KueryNode, index: number) => { + if (ast.arguments) { + const myPath = `${path}.${index}`; + return [ + ...kueryNode, + ...validateFilterKueryNode( + ast, + types, + indexMapping, + ast.type === 'function' && ['is', 'range'].includes(ast.function), + `${myPath}.arguments` + ), + ]; + } + if (storeValue && index === 0) { + const splitPath = path.split('.'); + return [ + ...kueryNode, + { + astPath: splitPath.slice(0, splitPath.length - 1).join('.'), + error: hasFilterKeyError(ast.value, types, indexMapping), + isSavedObjectAttr: isSavedObjectAttr(ast.value, indexMapping), + key: ast.value, + type: getType(ast.value), + }, + ]; + } + return kueryNode; + }, + [] + ); }; const getType = (key: string | undefined | null) => diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index 79a3e573ab98c9..3d81c2c2efd526 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -1289,8 +1289,7 @@ describe('SavedObjectsRepository', () => { type: 'foo', id: '1', }, - indexPattern: undefined, - kueryNode: null, + kueryNode: undefined, }; await savedObjectsRepository.find(relevantOpts); diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 51d4a8ad50ad63..e8f1fb16461c1d 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -448,11 +448,11 @@ export class SavedObjectsRepository { } let kueryNode; + try { - kueryNode = - filter && filter !== '' - ? validateConvertFilterToKueryNode(allowedTypes, filter, this._mappings) - : null; + if (filter) { + kueryNode = validateConvertFilterToKueryNode(allowedTypes, filter, this._mappings); + } } catch (e) { if (e.name === 'KQLSyntaxError') { throw SavedObjectsErrorHelpers.createBadRequestError('KQLSyntaxError: ' + e.message); diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts index bee35b899d83c5..cfeb258c2f03b4 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { toElasticsearchQuery, KueryNode } from '@kbn/es-query'; +import { esKuery } from '../../../../../../plugins/data/server'; import { getRootPropertiesObjects, IndexMapping } from '../../../mappings'; import { SavedObjectsSchema } from '../../../schema'; @@ -91,7 +91,7 @@ interface QueryParams { searchFields?: string[]; defaultSearchOperator?: string; hasReference?: HasReferenceQueryParams; - kueryNode?: KueryNode; + kueryNode?: esKuery.KueryNode; } /** @@ -111,7 +111,7 @@ export function getQueryParams({ const types = getTypes(mappings, type); const bool: any = { filter: [ - ...(kueryNode != null ? [toElasticsearchQuery(kueryNode)] : []), + ...(kueryNode != null ? [esKuery.toElasticsearchQuery(kueryNode)] : []), { bool: { must: hasReference diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts index 868ca51a76eab8..f2bbc3ef564a14 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts @@ -17,13 +17,13 @@ * under the License. */ -import { KueryNode } from '@kbn/es-query'; import Boom from 'boom'; import { IndexMapping } from '../../../mappings'; import { SavedObjectsSchema } from '../../../schema'; import { getQueryParams } from './query_params'; import { getSortingParams } from './sorting_params'; +import { esKuery } from '../../../../../../plugins/data/server'; interface GetSearchDslOptions { type: string | string[]; @@ -37,7 +37,7 @@ interface GetSearchDslOptions { type: string; id: string; }; - kueryNode?: KueryNode; + kueryNode?: esKuery.KueryNode; } export function getSearchDsl( diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index d6cfa543975659..3bbcb85fea9e54 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1577,6 +1577,12 @@ export class ScopedClusterClient implements IScopedClusterClient { callAsInternalUser(endpoint: string, clientParams?: Record, options?: CallAPIOptions): Promise; } +// @public +export interface SessionCookieValidationResult { + isValid: boolean; + path?: string; +} + // @public export interface SessionStorage { clear(): void; @@ -1589,7 +1595,7 @@ export interface SessionStorageCookieOptions { encryptionKey: string; isSecure: boolean; name: string; - validate: (sessionValue: T) => boolean | Promise; + validate: (sessionValue: T | T[]) => SessionCookieValidationResult; } // @public diff --git a/src/legacy/core_plugins/console/index.ts b/src/legacy/core_plugins/console/index.ts index caef3ff6f99f38..c4e6a77b7d859a 100644 --- a/src/legacy/core_plugins/console/index.ts +++ b/src/legacy/core_plugins/console/index.ts @@ -141,7 +141,7 @@ export default function(kibana: any) { server.route( createProxyRoute({ - baseUrl: head(legacyEsConfig.hosts), + hosts: legacyEsConfig.hosts, pathFilters: proxyPathFilters, getConfigForReq(req: any, uri: any) { const filteredHeaders = filterHeaders( diff --git a/src/legacy/core_plugins/console/server/__tests__/proxy_route/body.js b/src/legacy/core_plugins/console/server/__tests__/proxy_route/body.js index c9ad09cb017c4a..a7fd8df1b10f47 100644 --- a/src/legacy/core_plugins/console/server/__tests__/proxy_route/body.js +++ b/src/legacy/core_plugins/console/server/__tests__/proxy_route/body.js @@ -36,7 +36,7 @@ describe('Console Proxy Route', () => { const server = new Server(); server.route( createProxyRoute({ - baseUrl: 'http://localhost:9200', + hosts: ['http://localhost:9200'], }) ); diff --git a/src/legacy/core_plugins/console/server/__tests__/proxy_route/headers.js b/src/legacy/core_plugins/console/server/__tests__/proxy_route/headers.js index 2e78201f9990e4..347b8dae80e294 100644 --- a/src/legacy/core_plugins/console/server/__tests__/proxy_route/headers.js +++ b/src/legacy/core_plugins/console/server/__tests__/proxy_route/headers.js @@ -40,7 +40,7 @@ describe('Console Proxy Route', () => { const server = new Server(); server.route( createProxyRoute({ - baseUrl: 'http://localhost:9200', + hosts: ['http://localhost:9200'], }) ); diff --git a/src/legacy/core_plugins/console/server/__tests__/proxy_route/params.js b/src/legacy/core_plugins/console/server/__tests__/proxy_route/params.js index aa7b764f84fc78..2cf09f96e7b729 100644 --- a/src/legacy/core_plugins/console/server/__tests__/proxy_route/params.js +++ b/src/legacy/core_plugins/console/server/__tests__/proxy_route/params.js @@ -72,7 +72,7 @@ describe('Console Proxy Route', () => { const { server } = setup(); server.route( createProxyRoute({ - baseUrl: 'http://localhost:9200', + hosts: ['http://localhost:9200'], pathFilters: [/^\/foo\//, /^\/bar\//], }) ); @@ -91,7 +91,7 @@ describe('Console Proxy Route', () => { const { server } = setup(); server.route( createProxyRoute({ - baseUrl: 'http://localhost:9200', + hosts: ['http://localhost:9200'], pathFilters: [/^\/foo\//, /^\/bar\//], }) ); @@ -113,7 +113,7 @@ describe('Console Proxy Route', () => { const getConfigForReq = sinon.stub().returns({}); - server.route(createProxyRoute({ baseUrl: 'http://localhost:9200', getConfigForReq })); + server.route(createProxyRoute({ hosts: ['http://localhost:9200'], getConfigForReq })); await server.inject({ method: 'POST', url: '/api/console/proxy?method=HEAD&path=/index/id', @@ -142,7 +142,7 @@ describe('Console Proxy Route', () => { server.route( createProxyRoute({ - baseUrl: 'http://localhost:9200', + hosts: ['http://localhost:9200'], getConfigForReq: () => ({ timeout, agent, @@ -166,19 +166,5 @@ describe('Console Proxy Route', () => { expect(opts.headers).to.have.property('baz', 'bop'); }); }); - - describe('baseUrl', () => { - describe('default', () => { - it('ensures that the path starts with a /'); - }); - describe('url ends with a slash', () => { - it('combines clean with paths that start with a slash'); - it(`combines clean with paths that don't start with a slash`); - }); - describe(`url doesn't end with a slash`, () => { - it('combines clean with paths that start with a slash'); - it(`combines clean with paths that don't start with a slash`); - }); - }); }); }); diff --git a/src/legacy/core_plugins/console/server/__tests__/proxy_route/query_string.js b/src/legacy/core_plugins/console/server/__tests__/proxy_route/query_string.js index f20adb897be658..6b98702131d917 100644 --- a/src/legacy/core_plugins/console/server/__tests__/proxy_route/query_string.js +++ b/src/legacy/core_plugins/console/server/__tests__/proxy_route/query_string.js @@ -38,7 +38,7 @@ describe('Console Proxy Route', () => { const server = new Server(); server.route( createProxyRoute({ - baseUrl: 'http://localhost:9200', + hosts: ['http://localhost:9200'], }) ); diff --git a/src/legacy/core_plugins/console/server/proxy_route.js b/src/legacy/core_plugins/console/server/proxy_route.ts similarity index 65% rename from src/legacy/core_plugins/console/server/proxy_route.js rename to src/legacy/core_plugins/console/server/proxy_route.ts index 856128f3d4c031..f67c97443ba07d 100644 --- a/src/legacy/core_plugins/console/server/proxy_route.js +++ b/src/legacy/core_plugins/console/server/proxy_route.ts @@ -18,12 +18,13 @@ */ import Joi from 'joi'; +import * as url from 'url'; +import { IncomingMessage } from 'http'; import Boom from 'boom'; import { trimLeft, trimRight } from 'lodash'; import { sendRequest } from './request'; -import * as url from 'url'; -function toURL(base, path) { +function toURL(base: string, path: string) { const urlResult = new url.URL(`${trimRight(base, '/')}/${trimLeft(path, '/')}`); // Appending pretty here to have Elasticsearch do the JSON formatting, as doing // in JS can lead to data loss (7.0 will get munged into 7, thus losing indication of @@ -34,11 +35,11 @@ function toURL(base, path) { return urlResult; } -function getProxyHeaders(req) { +function getProxyHeaders(req: any) { const headers = Object.create(null); // Scope this proto-unsafe functionality to where it is being used. - function extendCommaList(obj, property, value) { + function extendCommaList(obj: Record, property: string, value: any) { obj[property] = (obj[property] ? obj[property] + ',' : '') + value; } @@ -58,9 +59,13 @@ function getProxyHeaders(req) { } export const createProxyRoute = ({ - baseUrl = '/', + hosts, pathFilters = [/.*/], getConfigForReq = () => ({}), +}: { + hosts: string[]; + pathFilters: RegExp[]; + getConfigForReq: (...args: any[]) => any; }) => ({ path: '/api/console/proxy', method: 'POST', @@ -84,7 +89,7 @@ export const createProxyRoute = ({ }, pre: [ - function filterPath(req) { + function filterPath(req: any) { const { path } = req.query; if (pathFilters.some(re => re.test(path))) { @@ -92,55 +97,74 @@ export const createProxyRoute = ({ } const err = Boom.forbidden(); - err.output.payload = `Error connecting to '${path}':\n\nUnable to send requests to that path.`; + err.output.payload = `Error connecting to '${path}':\n\nUnable to send requests to that path.` as any; err.output.headers['content-type'] = 'text/plain'; throw err; }, ], - handler: async (req, h) => { + handler: async (req: any, h: any) => { const { payload, query } = req; const { path, method } = query; - const uri = toURL(baseUrl, path); - - // Because this can technically be provided by a settings-defined proxy config, we need to - // preserve these property names to maintain BWC. - const { timeout, agent, headers, rejectUnauthorized } = getConfigForReq(req, uri.toString()); - - const requestHeaders = { - ...headers, - ...getProxyHeaders(req), - }; - - const esIncomingMessage = await sendRequest({ - method, - headers: requestHeaders, - uri, - timeout, - payload, - rejectUnauthorized, - agent, - }); + + let esIncomingMessage: IncomingMessage; + + for (let idx = 0; idx < hosts.length; ++idx) { + const host = hosts[idx]; + try { + const uri = toURL(host, path); + + // Because this can technically be provided by a settings-defined proxy config, we need to + // preserve these property names to maintain BWC. + const { timeout, agent, headers, rejectUnauthorized } = getConfigForReq( + req, + uri.toString() + ); + + const requestHeaders = { + ...headers, + ...getProxyHeaders(req), + }; + + esIncomingMessage = await sendRequest({ + method, + headers: requestHeaders, + uri, + timeout, + payload, + rejectUnauthorized, + agent, + }); + + break; + } catch (e) { + if (e.code !== 'ECONNREFUSED') { + throw Boom.boomify(e); + } + if (idx === hosts.length - 1) { + throw Boom.badGateway('Could not reach any configured nodes.'); + } + // Otherwise, try the next host... + } + } const { statusCode, statusMessage, - headers: responseHeaders, - } = esIncomingMessage; - - const { warning } = responseHeaders; + headers: { warning }, + } = esIncomingMessage!; if (method.toUpperCase() !== 'HEAD') { return h - .response(esIncomingMessage) + .response(esIncomingMessage!) .code(statusCode) - .header('warning', warning); + .header('warning', warning!); } else { return h .response(`${statusCode} - ${statusMessage}`) .code(statusCode) .type('text/plain') - .header('warning', warning); + .header('warning', warning!); } }, }, diff --git a/src/legacy/core_plugins/console/server/request.test.ts b/src/legacy/core_plugins/console/server/request.test.ts index d5504c0f3a3c26..2cbde5b3b39b85 100644 --- a/src/legacy/core_plugins/console/server/request.test.ts +++ b/src/legacy/core_plugins/console/server/request.test.ts @@ -24,7 +24,7 @@ import { fail } from 'assert'; describe(`Console's send request`, () => { let sandbox: sinon.SinonSandbox; - let stub: sinon.SinonStub, ClientRequest>; + let stub: sinon.SinonStub, ClientRequest>; let fakeRequest: http.ClientRequest; beforeEach(() => { @@ -52,7 +52,7 @@ describe(`Console's send request`, () => { method: 'get', payload: null as any, timeout: 0, // immediately timeout - uri: new URL('http://noone.nowhere.com'), + uri: new URL('http://noone.nowhere.none'), }); fail('Should not reach here!'); } catch (e) { diff --git a/src/legacy/core_plugins/console/server/request.ts b/src/legacy/core_plugins/console/server/request.ts index 0082f3591a1323..0f6b78b484adf7 100644 --- a/src/legacy/core_plugins/console/server/request.ts +++ b/src/legacy/core_plugins/console/server/request.ts @@ -89,7 +89,7 @@ export const sendRequest = ({ } }); - const onError = () => reject(); + const onError = (e: Error) => reject(e); req.once('error', onError); const timeoutPromise = new Promise((timeoutResolve, timeoutReject) => { @@ -103,5 +103,5 @@ export const sendRequest = ({ }, timeout); }); - return Promise.race([reqPromise, timeoutPromise]); + return Promise.race([reqPromise, timeoutPromise]); }; diff --git a/src/legacy/core_plugins/data/public/index.scss b/src/legacy/core_plugins/data/public/index.scss index 913141666c7b9a..94f02fe2d60495 100644 --- a/src/legacy/core_plugins/data/public/index.scss +++ b/src/legacy/core_plugins/data/public/index.scss @@ -4,4 +4,6 @@ @import 'src/plugins/data/public/ui/filter_bar/index'; +@import 'src/plugins/data/public/ui/typeahead/index'; + @import './search/search_bar/index'; diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 13491877790619..01f67a63ca9bec 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -37,7 +37,7 @@ export { IndexPatterns, StaticIndexPattern, } from './index_patterns'; -export { QueryBarInput } from './query'; +export { QueryStringInput } from './query'; export { SearchBar, SearchBarProps, SavedQueryAttributes, SavedQuery } from './search'; /** @public static code */ diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar_input.test.tsx.snap b/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_string_input.test.tsx.snap similarity index 99% rename from src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar_input.test.tsx.snap rename to src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_string_input.test.tsx.snap index 5dc8702411783d..6f155de95d6ebb 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_bar_input.test.tsx.snap +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/query_string_input.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`QueryBarInput Should disable autoFocus on EuiFieldText when disableAutoFocus prop is true 1`] = ` +exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableAutoFocus prop is true 1`] = ` - - + @@ -1114,7 +1114,7 @@ exports[`QueryBarInput Should disable autoFocus on EuiFieldText when disableAuto `; -exports[`QueryBarInput Should pass the query language to the language switcher 1`] = ` +exports[`QueryStringInput Should pass the query language to the language switcher 1`] = ` - - + @@ -2225,7 +2225,7 @@ exports[`QueryBarInput Should pass the query language to the language switcher 1 `; -exports[`QueryBarInput Should render the given query 1`] = ` +exports[`QueryStringInput Should render the given query 1`] = ` - - + diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/_index.scss b/src/legacy/core_plugins/data/public/query/query_bar/components/_index.scss index e17c416c135469..1d955920b8e132 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/_index.scss +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/_index.scss @@ -1,2 +1 @@ @import './query_bar'; -@import './typeahead/index'; diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx index ae08083f82af3e..ea01347e388654 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.test.tsx @@ -17,12 +17,16 @@ * under the License. */ -import { mockPersistedLogFactory } from './query_bar_input.test.mocks'; +import { mockPersistedLogFactory } from './query_string_input.test.mocks'; import React from 'react'; import { mount } from 'enzyme'; import { QueryBarTopRow } from './query_bar_top_row'; -import { IndexPattern } from '../../../index'; + +/* eslint-disable @kbn/eslint/no-restricted-paths */ + +import { stubIndexPatternWithFields } from '../../../../../../../plugins/data/public/stubs'; +/* eslint-enable @kbn/eslint/no-restricted-paths */ import { coreMock } from '../../../../../../../core/public/mocks'; import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; @@ -85,21 +89,6 @@ const createMockStorage = () => ({ clear: jest.fn(), }); -const mockIndexPattern = { - id: '1234', - title: 'logstash-*', - fields: [ - { - name: 'response', - type: 'number', - esTypes: ['integer'], - aggregatable: true, - filterable: true, - searchable: true, - }, - ], -} as IndexPattern; - function wrapQueryBarTopRowInContext(testProps: any) { const defaultOptions = { screenTitle: 'Another Screen', @@ -124,7 +113,7 @@ function wrapQueryBarTopRowInContext(testProps: any) { } describe('QueryBarTopRowTopRow', () => { - const QUERY_INPUT_SELECTOR = 'QueryBarInputUI'; + const QUERY_INPUT_SELECTOR = 'QueryStringInputUI'; const TIMEPICKER_SELECTOR = 'EuiSuperDatePicker'; const TIMEPICKER_DURATION = '[data-shared-timefilter-duration]'; @@ -138,7 +127,7 @@ describe('QueryBarTopRowTopRow', () => { query: kqlQuery, screenTitle: 'Another Screen', isDirty: false, - indexPatterns: [mockIndexPattern], + indexPatterns: [stubIndexPatternWithFields], timeHistory: mockTimeHistory, }) ); @@ -152,7 +141,7 @@ describe('QueryBarTopRowTopRow', () => { wrapQueryBarTopRowInContext({ query: kqlQuery, screenTitle: 'Another Screen', - indexPatterns: [mockIndexPattern], + indexPatterns: [stubIndexPatternWithFields], timeHistory: mockTimeHistory, disableAutoFocus: true, isDirty: false, @@ -225,7 +214,7 @@ describe('QueryBarTopRowTopRow', () => { const component = mount( wrapQueryBarTopRowInContext({ query: kqlQuery, - indexPatterns: [mockIndexPattern], + indexPatterns: [stubIndexPatternWithFields], isDirty: false, screenTitle: 'Another Screen', showDatePicker: false, @@ -245,7 +234,7 @@ describe('QueryBarTopRowTopRow', () => { query: kqlQuery, isDirty: false, screenTitle: 'Another Screen', - indexPatterns: [mockIndexPattern], + indexPatterns: [stubIndexPatternWithFields], showQueryInput: false, showDatePicker: false, timeHistory: mockTimeHistory, diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx index 1bf8ac086d3411..824e8cf1e2a7c0 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_top_row.tsx @@ -18,11 +18,8 @@ */ import dateMath from '@elastic/datemath'; -import { doesKueryExpressionHaveLuceneSyntaxError } from '@kbn/es-query'; - import classNames from 'classnames'; import React, { useState } from 'react'; - import { EuiButton, EuiFlexGroup, @@ -37,16 +34,16 @@ import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import { Toast } from 'src/core/public'; import { IDataPluginServices, + IIndexPattern, TimeRange, TimeHistoryContract, Query, PersistedLog, getQueryLog, + esKuery, } from '../../../../../../../plugins/data/public'; import { useKibana, toMountPoint } from '../../../../../../../plugins/kibana_react/public'; - -import { IndexPattern } from '../../../index_patterns'; -import { QueryBarInput } from './query_bar_input'; +import { QueryStringInput } from './query_string_input'; interface Props { query?: Query; @@ -56,7 +53,7 @@ interface Props { dataTestSubj?: string; disableAutoFocus?: boolean; screenTitle?: string; - indexPatterns?: Array; + indexPatterns?: Array; intl: InjectedIntl; isLoading?: boolean; prepend?: React.ReactNode; @@ -181,7 +178,7 @@ function QueryBarTopRowUI(props: Props) { if (!shouldRenderQueryInput()) return; return ( - ({ PersistedLog: mockPersistedLogFactory, diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_string_input.test.tsx similarity index 80% rename from src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.tsx rename to src/legacy/core_plugins/data/public/query/query_bar/components/query_string_input.test.tsx index 3edb689ca2bfeb..3512604b362611 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.test.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_string_input.test.tsx @@ -21,15 +21,19 @@ import { mockFetchIndexPatterns, mockPersistedLog, mockPersistedLogFactory, -} from './query_bar_input.test.mocks'; +} from './query_string_input.test.mocks'; import { EuiFieldText } from '@elastic/eui'; import React from 'react'; import { QueryLanguageSwitcher } from './language_switcher'; -import { QueryBarInput, QueryBarInputUI } from './query_bar_input'; +import { QueryStringInput, QueryStringInputUI } from './query_string_input'; import { coreMock } from '../../../../../../../core/public/mocks'; const startMock = coreMock.createStart(); -import { IndexPattern } from '../../../index'; +/* eslint-disable @kbn/eslint/no-restricted-paths */ + +import { stubIndexPatternWithFields } from '../../../../../../../plugins/data/public/stubs'; +/* eslint-enable @kbn/eslint/no-restricted-paths */ + import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; import { I18nProvider } from '@kbn/i18n/react'; import { mount } from 'enzyme'; @@ -65,22 +69,7 @@ const createMockStorage = () => ({ clear: jest.fn(), }); -const mockIndexPattern = { - id: '1234', - title: 'logstash-*', - fields: [ - { - name: 'response', - type: 'number', - esTypes: ['integer'], - aggregatable: true, - filterable: true, - searchable: true, - }, - ], -} as IndexPattern; - -function wrapQueryBarInputInContext(testProps: any, storage?: any) { +function wrapQueryStringInputInContext(testProps: any, storage?: any) { const defaultOptions = { screenTitle: 'Another Screen', intl: null as any, @@ -95,23 +84,23 @@ function wrapQueryBarInputInContext(testProps: any, storage?: any) { return ( - + ); } -describe('QueryBarInput', () => { +describe('QueryStringInput', () => { beforeEach(() => { jest.clearAllMocks(); }); it('Should render the given query', () => { const component = mount( - wrapQueryBarInputInContext({ + wrapQueryStringInputInContext({ query: kqlQuery, onSubmit: noop, - indexPatterns: [mockIndexPattern], + indexPatterns: [stubIndexPatternWithFields], }) ); @@ -120,10 +109,10 @@ describe('QueryBarInput', () => { it('Should pass the query language to the language switcher', () => { const component = mount( - wrapQueryBarInputInContext({ + wrapQueryStringInputInContext({ query: luceneQuery, onSubmit: noop, - indexPatterns: [mockIndexPattern], + indexPatterns: [stubIndexPatternWithFields], }) ); @@ -132,10 +121,10 @@ describe('QueryBarInput', () => { it('Should disable autoFocus on EuiFieldText when disableAutoFocus prop is true', () => { const component = mount( - wrapQueryBarInputInContext({ + wrapQueryStringInputInContext({ query: kqlQuery, onSubmit: noop, - indexPatterns: [mockIndexPattern], + indexPatterns: [stubIndexPatternWithFields], disableAutoFocus: true, }) ); @@ -147,10 +136,10 @@ describe('QueryBarInput', () => { mockPersistedLogFactory.mockClear(); mount( - wrapQueryBarInputInContext({ + wrapQueryStringInputInContext({ query: kqlQuery, onSubmit: noop, - indexPatterns: [mockIndexPattern], + indexPatterns: [stubIndexPatternWithFields], disableAutoFocus: true, appName: 'discover', }) @@ -162,11 +151,11 @@ describe('QueryBarInput', () => { const mockStorage = createMockStorage(); const mockCallback = jest.fn(); const component = mount( - wrapQueryBarInputInContext( + wrapQueryStringInputInContext( { query: kqlQuery, onSubmit: mockCallback, - indexPatterns: [mockIndexPattern], + indexPatterns: [stubIndexPatternWithFields], disableAutoFocus: true, appName: 'discover', }, @@ -186,15 +175,15 @@ describe('QueryBarInput', () => { const mockCallback = jest.fn(); const component = mount( - wrapQueryBarInputInContext({ + wrapQueryStringInputInContext({ query: kqlQuery, onSubmit: mockCallback, - indexPatterns: [mockIndexPattern], + indexPatterns: [stubIndexPatternWithFields], disableAutoFocus: true, }) ); - const instance = component.find('QueryBarInputUI').instance() as QueryBarInputUI; + const instance = component.find('QueryStringInputUI').instance() as QueryStringInputUI; const input = instance.inputRef; const inputWrapper = component.find(EuiFieldText).find('input'); inputWrapper.simulate('keyDown', { target: input, keyCode: 13, key: 'Enter', metaKey: true }); @@ -205,16 +194,16 @@ describe('QueryBarInput', () => { it('Should use PersistedLog for recent search suggestions', async () => { const component = mount( - wrapQueryBarInputInContext({ + wrapQueryStringInputInContext({ query: kqlQuery, onSubmit: noop, - indexPatterns: [mockIndexPattern], + indexPatterns: [stubIndexPatternWithFields], disableAutoFocus: true, persistedLog: mockPersistedLog, }) ); - const instance = component.find('QueryBarInputUI').instance() as QueryBarInputUI; + const instance = component.find('QueryStringInputUI').instance() as QueryStringInputUI; const input = instance.inputRef; const inputWrapper = component.find(EuiFieldText).find('input'); inputWrapper.simulate('keyDown', { target: input, keyCode: 13, key: 'Enter', metaKey: true }); @@ -229,7 +218,7 @@ describe('QueryBarInput', () => { it('Should accept index pattern strings and fetch the full object', () => { mockFetchIndexPatterns.mockClear(); mount( - wrapQueryBarInputInContext({ + wrapQueryStringInputInContext({ query: kqlQuery, onSubmit: noop, indexPatterns: ['logstash-*'], diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx b/src/legacy/core_plugins/data/public/query/query_bar/components/query_string_input.tsx similarity index 97% rename from src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx rename to src/legacy/core_plugins/data/public/query/query_bar/components/query_string_input.tsx index dce245e0ccb242..37519551ac5ad6 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/query_bar_input.tsx +++ b/src/legacy/core_plugins/data/public/query/query_bar/components/query_string_input.tsx @@ -38,7 +38,9 @@ import { AutocompleteSuggestion, AutocompleteSuggestionType, IDataPluginServices, + IIndexPattern, PersistedLog, + SuggestionsComponent, toUser, fromUser, matchPairs, @@ -50,15 +52,13 @@ import { KibanaReactContextValue, toMountPoint, } from '../../../../../../../plugins/kibana_react/public'; -import { IndexPattern, StaticIndexPattern } from '../../../index_patterns'; import { QueryLanguageSwitcher } from './language_switcher'; -import { SuggestionsComponent } from './typeahead/suggestions_component'; import { fetchIndexPatterns } from './fetch_index_patterns'; interface Props { kibana: KibanaReactContextValue; intl: InjectedIntl; - indexPatterns: Array; + indexPatterns: Array; query: Query; disableAutoFocus?: boolean; screenTitle?: string; @@ -79,7 +79,7 @@ interface State { suggestionLimit: number; selectionStart: number | null; selectionEnd: number | null; - indexPatterns: StaticIndexPattern[]; + indexPatterns: IIndexPattern[]; } const KEY_CODES = { @@ -96,7 +96,7 @@ const KEY_CODES = { const recentSearchType: AutocompleteSuggestionType = 'recentSearch'; -export class QueryBarInputUI extends Component { +export class QueryStringInputUI extends Component { public state: State = { isSuggestionsVisible: false, index: null, @@ -123,13 +123,13 @@ export class QueryBarInputUI extends Component { ) as string[]; const objectPatterns = this.props.indexPatterns.filter( indexPattern => typeof indexPattern !== 'string' - ) as IndexPattern[]; + ) as IIndexPattern[]; const objectPatternsFromStrings = (await fetchIndexPatterns( this.services.savedObjects!.client, stringPatterns, this.services.uiSettings! - )) as IndexPattern[]; + )) as IIndexPattern[]; this.setState({ indexPatterns: [...objectPatterns, ...objectPatternsFromStrings], @@ -589,4 +589,4 @@ export class QueryBarInputUI extends Component { } } -export const QueryBarInput = injectI18n(withKibana(QueryBarInputUI)); +export const QueryStringInput = injectI18n(withKibana(QueryStringInputUI)); diff --git a/src/legacy/core_plugins/data/public/query/query_bar/index.ts b/src/legacy/core_plugins/data/public/query/query_bar/index.ts index f0ad0707c699a6..47b0ca5eae1bf2 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/index.ts +++ b/src/legacy/core_plugins/data/public/query/query_bar/index.ts @@ -18,4 +18,4 @@ */ export { QueryBarTopRow } from './components/query_bar_top_row'; -export { QueryBarInput } from './components/query_bar_input'; +export { QueryStringInput } from './components/query_string_input'; diff --git a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts index 7165de026920d0..61b9b7bf83c03b 100644 --- a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts @@ -42,7 +42,7 @@ import { } from '../../../../../ui/public/filter_manager/query_filter'; import { buildTabularInspectorData } from '../../../../../ui/public/inspector/build_tabular_inspector_data'; -import { calculateObjectHash } from '../../../../../ui/public/vis/lib/calculate_object_hash'; +import { calculateObjectHash } from '../../../../visualizations/public'; import { getTime } from '../../../../../ui/public/timefilter'; // @ts-ignore import { tabifyAggResponse } from '../../../../../ui/public/agg_response/tabify/tabify'; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.test.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.test.tsx index 0ca9482fefa301..da7008b579eb78 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.test.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.test.tsx @@ -43,7 +43,7 @@ jest.mock('../../../../../../../plugins/data/public', () => { jest.mock('../../../../../data/public', () => { return { - QueryBarInput: () =>
, + QueryStringInput: () =>
, }; }); diff --git a/src/legacy/core_plugins/input_control_vis/public/register_vis.js b/src/legacy/core_plugins/input_control_vis/public/register_vis.js index 731cf2dac9dd2e..12e7291fea7a10 100644 --- a/src/legacy/core_plugins/input_control_vis/public/register_vis.js +++ b/src/legacy/core_plugins/input_control_vis/public/register_vis.js @@ -17,65 +17,58 @@ * under the License. */ -import { visFactory } from 'ui/vis/vis_factory'; import { VisController } from './vis_controller'; import { ControlsTab } from './components/editor/controls_tab'; import { OptionsTab } from './components/editor/options_tab'; -import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; -import { Status } from 'ui/vis/update_status'; import { i18n } from '@kbn/i18n'; import { setup as visualizations } from '../../visualizations/public/np_ready/public/legacy'; +import { Status, defaultFeedbackMessage } from '../../visualizations/public'; -function InputControlVisProvider() { - // return the visType object, which kibana will use to display and configure new Vis object of this type. - return visFactory.createBaseVisualization({ - name: 'input_control_vis', - title: i18n.translate('inputControl.register.controlsTitle', { - defaultMessage: 'Controls' - }), - icon: 'visControls', - description: i18n.translate('inputControl.register.controlsDescription', { - defaultMessage: 'Create interactive controls for easy dashboard manipulation.' - }), - stage: 'experimental', - requiresUpdateStatus: [Status.PARAMS, Status.TIME], - feedbackMessage: defaultFeedbackMessage, - visualization: VisController, - visConfig: { - defaults: { - controls: [], - updateFiltersOnChange: false, - useTimeFilter: false, - pinFilters: false, - }, - }, - editor: 'default', - editorConfig: { - optionTabs: [ - { - name: 'controls', - title: i18n.translate('inputControl.register.tabs.controlsTitle', { - defaultMessage: 'Controls' - }), - editor: ControlsTab - }, - { - name: 'options', - title: i18n.translate('inputControl.register.tabs.optionsTitle', { - defaultMessage: 'Options' - }), - editor: OptionsTab - } - ] +export const inputControlVisDefinition = { + name: 'input_control_vis', + title: i18n.translate('inputControl.register.controlsTitle', { + defaultMessage: 'Controls' + }), + icon: 'visControls', + description: i18n.translate('inputControl.register.controlsDescription', { + defaultMessage: 'Create interactive controls for easy dashboard manipulation.' + }), + stage: 'experimental', + requiresUpdateStatus: [Status.PARAMS, Status.TIME], + feedbackMessage: defaultFeedbackMessage, + visualization: VisController, + visConfig: { + defaults: { + controls: [], + updateFiltersOnChange: false, + useTimeFilter: false, + pinFilters: false, }, - requestHandler: 'none', - responseHandler: 'none', - }); -} + }, + editor: 'default', + editorConfig: { + optionTabs: [ + { + name: 'controls', + title: i18n.translate('inputControl.register.tabs.controlsTitle', { + defaultMessage: 'Controls' + }), + editor: ControlsTab + }, + { + name: 'options', + title: i18n.translate('inputControl.register.tabs.optionsTitle', { + defaultMessage: 'Options' + }), + editor: OptionsTab + } + ] + }, + requestHandler: 'none', + responseHandler: 'none', +}; // register the provider with the visTypes registry -visualizations.types.registerVisualization(InputControlVisProvider); +visualizations.types.createBaseVisualization(inputControlVisDefinition); -// export the provider so that the visType can be required with Private() -export default InputControlVisProvider; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/area.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/area.js index ca9115b729da81..a03e8affe319bd 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/area.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/area.js @@ -17,7 +17,6 @@ * under the License. */ -import { visFactory } from 'ui/vis/vis_factory'; import { i18n } from '@kbn/i18n'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { AggGroupNames } from 'ui/vis/editors/default'; @@ -37,143 +36,140 @@ import { getAreaOptionTabs, countLabel } from './utils/common_config'; import { palettes } from '@elastic/eui/lib/services'; import { vislibVisController } from './controller'; -export default function PointSeriesVisType() { - - return visFactory.createBaseVisualization({ - name: 'area', - title: i18n.translate('kbnVislibVisTypes.area.areaTitle', { defaultMessage: 'Area' }), - icon: 'visArea', - description: i18n.translate( - 'kbnVislibVisTypes.area.areaDescription', { defaultMessage: 'Emphasize the quantity beneath a line chart' }), - visualization: vislibVisController, - visConfig: { - defaults: { - type: 'area', - grid: { - categoryLines: false, - }, - categoryAxes: [ - { - id: 'CategoryAxis-1', - type: AxisTypes.CATEGORY, - position: Positions.BOTTOM, - show: true, - style: {}, - scale: { - type: ScaleTypes.LINEAR, - }, - labels: { - show: true, - filter: true, - truncate: 100 - }, - title: {} - } - ], - valueAxes: [ - { - id: 'ValueAxis-1', - name: 'LeftAxis-1', - type: AxisTypes.VALUE, - position: Positions.LEFT, - show: true, - style: {}, - scale: { - type: ScaleTypes.LINEAR, - mode: AxisModes.NORMAL, - }, - labels: { - show: true, - rotate: Rotates.HORIZONTAL, - filter: false, - truncate: 100 - }, - title: { - text: countLabel - } - } - ], - seriesParams: [ - { - show: true, - type: ChartTypes.AREA, - mode: ChartModes.STACKED, - data: { - label: countLabel, - id: '1' - }, - drawLinesBetweenPoints: true, - lineWidth: 2, - showCircles: true, - interpolate: InterpolationModes.LINEAR, - valueAxis: 'ValueAxis-1', - } - ], - addTooltip: true, - addLegend: true, - legendPosition: Positions.RIGHT, - times: [], - addTimeMarker: false, - thresholdLine: { - show: false, - value: 10, - width: 1, - style: ThresholdLineStyles.FULL, - color: palettes.euiPaletteColorBlind.colors[9] - }, - labels: {} +export const areaDefinition = { + name: 'area', + title: i18n.translate('kbnVislibVisTypes.area.areaTitle', { defaultMessage: 'Area' }), + icon: 'visArea', + description: i18n.translate( + 'kbnVislibVisTypes.area.areaDescription', { defaultMessage: 'Emphasize the quantity beneath a line chart' }), + visualization: vislibVisController, + visConfig: { + defaults: { + type: 'area', + grid: { + categoryLines: false, }, - }, - events: { - brush: { disabled: false }, - }, - editorConfig: { - collections: getConfigCollections(), - optionTabs: getAreaOptionTabs(), - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('kbnVislibVisTypes.area.metricsTitle', { defaultMessage: 'Y-axis' }), - aggFilter: ['!geo_centroid', '!geo_bounds'], - min: 1, - defaults: [ - { schema: 'metric', type: 'count' } - ] - }, - { - group: AggGroupNames.Metrics, - name: 'radius', - title: i18n.translate('kbnVislibVisTypes.area.radiusTitle', { defaultMessage: 'Dot size' }), - min: 0, - max: 1, - aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'] - }, + categoryAxes: [ { - group: AggGroupNames.Buckets, - name: 'segment', - title: i18n.translate('kbnVislibVisTypes.area.segmentTitle', { defaultMessage: 'X-axis' }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] - }, + id: 'CategoryAxis-1', + type: AxisTypes.CATEGORY, + position: Positions.BOTTOM, + show: true, + style: {}, + scale: { + type: ScaleTypes.LINEAR, + }, + labels: { + show: true, + filter: true, + truncate: 100 + }, + title: {} + } + ], + valueAxes: [ { - group: AggGroupNames.Buckets, - name: 'group', - title: i18n.translate('kbnVislibVisTypes.area.groupTitle', { defaultMessage: 'Split series' }), - min: 0, - max: 3, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] - }, + id: 'ValueAxis-1', + name: 'LeftAxis-1', + type: AxisTypes.VALUE, + position: Positions.LEFT, + show: true, + style: {}, + scale: { + type: ScaleTypes.LINEAR, + mode: AxisModes.NORMAL, + }, + labels: { + show: true, + rotate: Rotates.HORIZONTAL, + filter: false, + truncate: 100 + }, + title: { + text: countLabel + } + } + ], + seriesParams: [ { - group: AggGroupNames.Buckets, - name: 'split', - title: i18n.translate('kbnVislibVisTypes.area.splitTitle', { defaultMessage: 'Split chart' }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + show: true, + type: ChartTypes.AREA, + mode: ChartModes.STACKED, + data: { + label: countLabel, + id: '1' + }, + drawLinesBetweenPoints: true, + lineWidth: 2, + showCircles: true, + interpolate: InterpolationModes.LINEAR, + valueAxis: 'ValueAxis-1', } - ]) - } - }); -} + ], + addTooltip: true, + addLegend: true, + legendPosition: Positions.RIGHT, + times: [], + addTimeMarker: false, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: ThresholdLineStyles.FULL, + color: palettes.euiPaletteColorBlind.colors[9] + }, + labels: {} + }, + }, + events: { + brush: { disabled: false }, + }, + editorConfig: { + collections: getConfigCollections(), + optionTabs: getAreaOptionTabs(), + schemas: new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: i18n.translate('kbnVislibVisTypes.area.metricsTitle', { defaultMessage: 'Y-axis' }), + aggFilter: ['!geo_centroid', '!geo_bounds'], + min: 1, + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: AggGroupNames.Metrics, + name: 'radius', + title: i18n.translate('kbnVislibVisTypes.area.radiusTitle', { defaultMessage: 'Dot size' }), + min: 0, + max: 1, + aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'] + }, + { + group: AggGroupNames.Buckets, + name: 'segment', + title: i18n.translate('kbnVislibVisTypes.area.segmentTitle', { defaultMessage: 'X-axis' }), + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + }, + { + group: AggGroupNames.Buckets, + name: 'group', + title: i18n.translate('kbnVislibVisTypes.area.groupTitle', { defaultMessage: 'Split series' }), + min: 0, + max: 3, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + }, + { + group: AggGroupNames.Buckets, + name: 'split', + title: i18n.translate('kbnVislibVisTypes.area.splitTitle', { defaultMessage: 'Split chart' }), + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + } + ]) + } +}; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.js index 75907618eb859d..6d0d997604e013 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/gauge.js @@ -18,7 +18,6 @@ */ import { i18n } from '@kbn/i18n'; -import { visFactory } from 'ui/vis/vis_factory'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { AggGroupNames } from 'ui/vis/editors/default'; import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; @@ -26,87 +25,85 @@ import { GaugeOptions } from './components/options'; import { getGaugeCollections, Alignments, ColorModes, GaugeTypes } from './utils/collections'; import { vislibVisController } from './controller'; -export default function GaugeVisType() { - return visFactory.createBaseVisualization({ - name: 'gauge', - title: i18n.translate('kbnVislibVisTypes.gauge.gaugeTitle', { defaultMessage: 'Gauge' }), - icon: 'visGauge', - description: i18n.translate('kbnVislibVisTypes.gauge.gaugeDescription', { - defaultMessage: 'Gauges indicate the status of a metric. Use it to show how a metric\'s value relates to reference threshold values.' - }), - visConfig: { - defaults: { - type: 'gauge', - addTooltip: true, - addLegend: true, - isDisplayWarning: false, - gauge: { - alignment: Alignments.AUTOMATIC, - extendRange: true, - percentageMode: false, - gaugeType: GaugeTypes.ARC, - gaugeStyle: 'Full', - backStyle: 'Full', - orientation: 'vertical', - colorSchema: ColorSchemas.GreenToRed, - gaugeColorMode: ColorModes.LABELS, - colorsRange: [ - { from: 0, to: 50 }, - { from: 50, to: 75 }, - { from: 75, to: 100 } - ], - invertColors: false, - labels: { - show: true, - color: 'black' - }, - scale: { - show: true, - labels: false, - color: 'rgba(105,112,125,0.2)', - }, - type: 'meter', - style: { - bgWidth: 0.9, - width: 0.9, - mask: false, - bgMask: false, - maskBars: 50, - bgFill: 'rgba(105,112,125,0.2)', - bgColor: true, - subText: '', - fontSize: 60, - } - } - }, - }, - visualization: vislibVisController, - editorConfig: { - collections: getGaugeCollections(), - optionsTemplate: GaugeOptions, - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('kbnVislibVisTypes.gauge.metricTitle', { defaultMessage: 'Metric' }), - min: 1, - aggFilter: [ - '!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks', - '!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum', '!geo_bounds'], - defaults: [ - { schema: 'metric', type: 'count' } - ] +export const gaugeDefinition = { + name: 'gauge', + title: i18n.translate('kbnVislibVisTypes.gauge.gaugeTitle', { defaultMessage: 'Gauge' }), + icon: 'visGauge', + description: i18n.translate('kbnVislibVisTypes.gauge.gaugeDescription', { + defaultMessage: 'Gauges indicate the status of a metric. Use it to show how a metric\'s value relates to reference threshold values.' + }), + visConfig: { + defaults: { + type: 'gauge', + addTooltip: true, + addLegend: true, + isDisplayWarning: false, + gauge: { + alignment: Alignments.AUTOMATIC, + extendRange: true, + percentageMode: false, + gaugeType: GaugeTypes.ARC, + gaugeStyle: 'Full', + backStyle: 'Full', + orientation: 'vertical', + colorSchema: ColorSchemas.GreenToRed, + gaugeColorMode: ColorModes.LABELS, + colorsRange: [ + { from: 0, to: 50 }, + { from: 50, to: 75 }, + { from: 75, to: 100 } + ], + invertColors: false, + labels: { + show: true, + color: 'black' }, - { - group: AggGroupNames.Buckets, - name: 'group', - title: i18n.translate('kbnVislibVisTypes.gauge.groupTitle', { defaultMessage: 'Split group' }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + scale: { + show: true, + labels: false, + color: 'rgba(105,112,125,0.2)', + }, + type: 'meter', + style: { + bgWidth: 0.9, + width: 0.9, + mask: false, + bgMask: false, + maskBars: 50, + bgFill: 'rgba(105,112,125,0.2)', + bgColor: true, + subText: '', + fontSize: 60, } - ]) + } }, - useCustomNoDataScreen: true - }); -} + }, + visualization: vislibVisController, + editorConfig: { + collections: getGaugeCollections(), + optionsTemplate: GaugeOptions, + schemas: new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: i18n.translate('kbnVislibVisTypes.gauge.metricTitle', { defaultMessage: 'Metric' }), + min: 1, + aggFilter: [ + '!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks', + '!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum', '!geo_bounds'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: AggGroupNames.Buckets, + name: 'group', + title: i18n.translate('kbnVislibVisTypes.gauge.groupTitle', { defaultMessage: 'Split group' }), + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + } + ]) + }, + useCustomNoDataScreen: true +}; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/goal.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/goal.js index 3a6b9f873aa87c..dedd2e38858768 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/goal.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/goal.js @@ -24,86 +24,82 @@ import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; import { GaugeOptions } from './components/options'; import { getGaugeCollections, GaugeTypes, ColorModes } from './utils/collections'; import { vislibVisController } from './controller'; -import { visFactory } from '../../../ui/public/vis/vis_factory'; -export default function GoalVisType() { - - return visFactory.createBaseVisualization({ - name: 'goal', - title: i18n.translate('kbnVislibVisTypes.goal.goalTitle', { defaultMessage: 'Goal' }), - icon: 'visGoal', - description: i18n.translate('kbnVislibVisTypes.goal.goalDescription', { - defaultMessage: 'A goal chart indicates how close you are to your final goal.' - }), - visualization: vislibVisController, - visConfig: { - defaults: { - addTooltip: true, - addLegend: false, - isDisplayWarning: false, - type: 'gauge', - gauge: { - verticalSplit: false, - autoExtend: false, - percentageMode: true, - gaugeType: GaugeTypes.ARC, - gaugeStyle: 'Full', - backStyle: 'Full', - orientation: 'vertical', - useRanges: false, - colorSchema: ColorSchemas.GreenToRed, - gaugeColorMode: ColorModes.NONE, - colorsRange: [ - { from: 0, to: 10000 } - ], - invertColors: false, - labels: { - show: true, - color: 'black' - }, - scale: { - show: false, - labels: false, - color: 'rgba(105,112,125,0.2)', - width: 2 - }, - type: 'meter', - style: { - bgFill: 'rgba(105,112,125,0.2)', - bgColor: false, - labelColor: false, - subText: '', - fontSize: 60, - } - } - }, - }, - editorConfig: { - collections: getGaugeCollections(), - optionsTemplate: GaugeOptions, - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('kbnVislibVisTypes.goal.metricTitle', { defaultMessage: 'Metric' }), - min: 1, - aggFilter: [ - '!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks', - '!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum', '!geo_bounds'], - defaults: [ - { schema: 'metric', type: 'count' } - ] +export const goalDefinition = { + name: 'goal', + title: i18n.translate('kbnVislibVisTypes.goal.goalTitle', { defaultMessage: 'Goal' }), + icon: 'visGoal', + description: i18n.translate('kbnVislibVisTypes.goal.goalDescription', { + defaultMessage: 'A goal chart indicates how close you are to your final goal.' + }), + visualization: vislibVisController, + visConfig: { + defaults: { + addTooltip: true, + addLegend: false, + isDisplayWarning: false, + type: 'gauge', + gauge: { + verticalSplit: false, + autoExtend: false, + percentageMode: true, + gaugeType: GaugeTypes.ARC, + gaugeStyle: 'Full', + backStyle: 'Full', + orientation: 'vertical', + useRanges: false, + colorSchema: ColorSchemas.GreenToRed, + gaugeColorMode: ColorModes.NONE, + colorsRange: [ + { from: 0, to: 10000 } + ], + invertColors: false, + labels: { + show: true, + color: 'black' + }, + scale: { + show: false, + labels: false, + color: 'rgba(105,112,125,0.2)', + width: 2 }, - { - group: AggGroupNames.Buckets, - name: 'group', - title: i18n.translate('kbnVislibVisTypes.goal.groupTitle', { defaultMessage: 'Split group' }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + type: 'meter', + style: { + bgFill: 'rgba(105,112,125,0.2)', + bgColor: false, + labelColor: false, + subText: '', + fontSize: 60, } - ]) + } }, - useCustomNoDataScreen: true - }); -} + }, + editorConfig: { + collections: getGaugeCollections(), + optionsTemplate: GaugeOptions, + schemas: new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: i18n.translate('kbnVislibVisTypes.goal.metricTitle', { defaultMessage: 'Metric' }), + min: 1, + aggFilter: [ + '!std_dev', '!geo_centroid', '!percentiles', '!percentile_ranks', + '!derivative', '!serial_diff', '!moving_avg', '!cumulative_sum', '!geo_bounds'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: AggGroupNames.Buckets, + name: 'group', + title: i18n.translate('kbnVislibVisTypes.goal.groupTitle', { defaultMessage: 'Split group' }), + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + } + ]) + }, + useCustomNoDataScreen: true +}; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.js index 207f80996b5a79..e3212037ecf2fe 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/heatmap.js @@ -17,7 +17,6 @@ * under the License. */ -import { visFactory } from '../../../ui/public/vis/vis_factory'; import { i18n } from '@kbn/i18n'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { AggGroupNames } from 'ui/vis/editors/default'; @@ -26,89 +25,86 @@ import { AxisTypes, getHeatmapCollections, Positions, ScaleTypes } from './utils import { HeatmapOptions } from './components/options'; import { vislibVisController } from './controller'; -export default function HeatmapVisType() { - - return visFactory.createBaseVisualization({ - name: 'heatmap', - title: i18n.translate('kbnVislibVisTypes.heatmap.heatmapTitle', { defaultMessage: 'Heat Map' }), - icon: 'visHeatmap', - description: i18n.translate('kbnVislibVisTypes.heatmap.heatmapDescription', { defaultMessage: 'Shade cells within a matrix' }), - visualization: vislibVisController, - visConfig: { - defaults: { - type: 'heatmap', - addTooltip: true, - addLegend: true, - enableHover: false, - legendPosition: Positions.RIGHT, - times: [], - colorsNumber: 4, - colorSchema: ColorSchemas.Greens, - setColorRange: false, - colorsRange: [], - invertColors: false, - percentageMode: false, - valueAxes: [{ - show: false, - id: 'ValueAxis-1', - type: AxisTypes.VALUE, - scale: { - type: ScaleTypes.LINEAR, - defaultYExtents: false, - }, - labels: { - show: false, - rotate: 0, - overwriteColor: false, - color: 'black', - } - }] - }, - }, - events: { - brush: { disabled: false }, - }, - editorConfig: { - collections: getHeatmapCollections(), - optionsTemplate: HeatmapOptions, - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('kbnVislibVisTypes.heatmap.metricTitle', { defaultMessage: 'Value' }), - min: 1, - max: 1, - aggFilter: ['count', 'avg', 'median', 'sum', 'min', 'max', 'cardinality', 'std_dev', 'top_hits'], - defaults: [ - { schema: 'metric', type: 'count' } - ] - }, - { - group: AggGroupNames.Buckets, - name: 'segment', - title: i18n.translate('kbnVislibVisTypes.heatmap.segmentTitle', { defaultMessage: 'X-axis' }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] +export const heatmapDefinition = { + name: 'heatmap', + title: i18n.translate('kbnVislibVisTypes.heatmap.heatmapTitle', { defaultMessage: 'Heat Map' }), + icon: 'visHeatmap', + description: i18n.translate('kbnVislibVisTypes.heatmap.heatmapDescription', { defaultMessage: 'Shade cells within a matrix' }), + visualization: vislibVisController, + visConfig: { + defaults: { + type: 'heatmap', + addTooltip: true, + addLegend: true, + enableHover: false, + legendPosition: Positions.RIGHT, + times: [], + colorsNumber: 4, + colorSchema: ColorSchemas.Greens, + setColorRange: false, + colorsRange: [], + invertColors: false, + percentageMode: false, + valueAxes: [{ + show: false, + id: 'ValueAxis-1', + type: AxisTypes.VALUE, + scale: { + type: ScaleTypes.LINEAR, + defaultYExtents: false, }, - { - group: AggGroupNames.Buckets, - name: 'group', - title: i18n.translate('kbnVislibVisTypes.heatmap.groupTitle', { defaultMessage: 'Y-axis' }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] - }, - { - group: AggGroupNames.Buckets, - name: 'split', - title: i18n.translate('kbnVislibVisTypes.heatmap.splitTitle', { defaultMessage: 'Split chart' }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + labels: { + show: false, + rotate: 0, + overwriteColor: false, + color: 'black', } - ]) - } + }] + }, + }, + events: { + brush: { disabled: false }, + }, + editorConfig: { + collections: getHeatmapCollections(), + optionsTemplate: HeatmapOptions, + schemas: new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: i18n.translate('kbnVislibVisTypes.heatmap.metricTitle', { defaultMessage: 'Value' }), + min: 1, + max: 1, + aggFilter: ['count', 'avg', 'median', 'sum', 'min', 'max', 'cardinality', 'std_dev', 'top_hits'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: AggGroupNames.Buckets, + name: 'segment', + title: i18n.translate('kbnVislibVisTypes.heatmap.segmentTitle', { defaultMessage: 'X-axis' }), + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + }, + { + group: AggGroupNames.Buckets, + name: 'group', + title: i18n.translate('kbnVislibVisTypes.heatmap.groupTitle', { defaultMessage: 'Y-axis' }), + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + }, + { + group: AggGroupNames.Buckets, + name: 'split', + title: i18n.translate('kbnVislibVisTypes.heatmap.splitTitle', { defaultMessage: 'Split chart' }), + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + } + ]) + } - }); -} +}; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/histogram.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/histogram.js index 87e690fa6457e3..15ede19e21c22b 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/histogram.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/histogram.js @@ -17,7 +17,6 @@ * under the License. */ -import { visFactory } from '../../../ui/public/vis/vis_factory'; import { i18n } from '@kbn/i18n'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { AggGroupNames } from 'ui/vis/editors/default'; @@ -36,146 +35,143 @@ import { getAreaOptionTabs, countLabel } from './utils/common_config'; import { palettes } from '@elastic/eui/lib/services'; import { vislibVisController } from './controller'; -export default function PointSeriesVisType() { - - return visFactory.createBaseVisualization({ - name: 'histogram', - title: i18n.translate('kbnVislibVisTypes.histogram.histogramTitle', { defaultMessage: 'Vertical Bar' }), - icon: 'visBarVertical', - description: i18n.translate('kbnVislibVisTypes.histogram.histogramDescription', - { defaultMessage: 'Assign a continuous variable to each axis' } - ), - visualization: vislibVisController, - visConfig: { - defaults: { - type: 'histogram', - grid: { - categoryLines: false, - }, - categoryAxes: [ - { - id: 'CategoryAxis-1', - type: AxisTypes.CATEGORY, - position: Positions.BOTTOM, - show: true, - style: {}, - scale: { - type: ScaleTypes.LINEAR, - }, - labels: { - show: true, - filter: true, - truncate: 100 - }, - title: {} - } - ], - valueAxes: [ - { - id: 'ValueAxis-1', - name: 'LeftAxis-1', - type: AxisTypes.VALUE, - position: Positions.LEFT, +export const histogramDefinition = { + name: 'histogram', + title: i18n.translate('kbnVislibVisTypes.histogram.histogramTitle', { defaultMessage: 'Vertical Bar' }), + icon: 'visBarVertical', + description: i18n.translate('kbnVislibVisTypes.histogram.histogramDescription', + { defaultMessage: 'Assign a continuous variable to each axis' } + ), + visualization: vislibVisController, + visConfig: { + defaults: { + type: 'histogram', + grid: { + categoryLines: false, + }, + categoryAxes: [ + { + id: 'CategoryAxis-1', + type: AxisTypes.CATEGORY, + position: Positions.BOTTOM, + show: true, + style: {}, + scale: { + type: ScaleTypes.LINEAR, + }, + labels: { show: true, - style: {}, - scale: { - type: ScaleTypes.LINEAR, - mode: AxisModes.NORMAL, - }, - labels: { - show: true, - rotate: Rotates.HORIZONTAL, - filter: false, - truncate: 100 - }, - title: { - text: countLabel, - } - } - ], - seriesParams: [ - { + filter: true, + truncate: 100 + }, + title: {} + } + ], + valueAxes: [ + { + id: 'ValueAxis-1', + name: 'LeftAxis-1', + type: AxisTypes.VALUE, + position: Positions.LEFT, + show: true, + style: {}, + scale: { + type: ScaleTypes.LINEAR, + mode: AxisModes.NORMAL, + }, + labels: { show: true, - type: ChartTypes.HISTOGRAM, - mode: ChartModes.STACKED, - data: { - label: countLabel, - id: '1' - }, - valueAxis: 'ValueAxis-1', - drawLinesBetweenPoints: true, - lineWidth: 2, - showCircles: true + rotate: Rotates.HORIZONTAL, + filter: false, + truncate: 100 + }, + title: { + text: countLabel, } - ], - addTooltip: true, - addLegend: true, - legendPosition: Positions.RIGHT, - times: [], - addTimeMarker: false, - labels: { - show: false, - }, - thresholdLine: { - show: false, - value: 10, - width: 1, - style: ThresholdLineStyles.FULL, - color: palettes.euiPaletteColorBlind.colors[9] } - }, - }, - events: { - brush: { disabled: false }, - }, - editorConfig: { - collections: getConfigCollections(), - optionTabs: getAreaOptionTabs(), - schemas: new Schemas([ + ], + seriesParams: [ { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('kbnVislibVisTypes.histogram.metricTitle', { defaultMessage: 'Y-axis' }), - min: 1, - aggFilter: ['!geo_centroid', '!geo_bounds'], - defaults: [ - { schema: 'metric', type: 'count' } - ] - }, - { - group: AggGroupNames.Metrics, - name: 'radius', - title: i18n.translate('kbnVislibVisTypes.histogram.radiusTitle', { defaultMessage: 'Dot size' }), - min: 0, - max: 1, - aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'] - }, - { - group: AggGroupNames.Buckets, - name: 'segment', - title: i18n.translate('kbnVislibVisTypes.histogram.segmentTitle', { defaultMessage: 'X-axis' }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] - }, - { - group: AggGroupNames.Buckets, - name: 'group', - title: i18n.translate('kbnVislibVisTypes.histogram.groupTitle', { defaultMessage: 'Split series' }), - min: 0, - max: 3, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] - }, - { - group: AggGroupNames.Buckets, - name: 'split', - title: i18n.translate('kbnVislibVisTypes.histogram.splitTitle', { defaultMessage: 'Split chart' }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + show: true, + type: ChartTypes.HISTOGRAM, + mode: ChartModes.STACKED, + data: { + label: countLabel, + id: '1' + }, + valueAxis: 'ValueAxis-1', + drawLinesBetweenPoints: true, + lineWidth: 2, + showCircles: true } - ]) - } + ], + addTooltip: true, + addLegend: true, + legendPosition: Positions.RIGHT, + times: [], + addTimeMarker: false, + labels: { + show: false, + }, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: ThresholdLineStyles.FULL, + color: palettes.euiPaletteColorBlind.colors[9] + } + }, + }, + events: { + brush: { disabled: false }, + }, + editorConfig: { + collections: getConfigCollections(), + optionTabs: getAreaOptionTabs(), + schemas: new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: i18n.translate('kbnVislibVisTypes.histogram.metricTitle', { defaultMessage: 'Y-axis' }), + min: 1, + aggFilter: ['!geo_centroid', '!geo_bounds'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: AggGroupNames.Metrics, + name: 'radius', + title: i18n.translate('kbnVislibVisTypes.histogram.radiusTitle', { defaultMessage: 'Dot size' }), + min: 0, + max: 1, + aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'] + }, + { + group: AggGroupNames.Buckets, + name: 'segment', + title: i18n.translate('kbnVislibVisTypes.histogram.segmentTitle', { defaultMessage: 'X-axis' }), + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + }, + { + group: AggGroupNames.Buckets, + name: 'group', + title: i18n.translate('kbnVislibVisTypes.histogram.groupTitle', { defaultMessage: 'Split series' }), + min: 0, + max: 3, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + }, + { + group: AggGroupNames.Buckets, + name: 'split', + title: i18n.translate('kbnVislibVisTypes.histogram.splitTitle', { defaultMessage: 'Split chart' }), + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + } + ]) + } - }); -} +}; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js index 2f8107580f0f78..0369e8d8c27b5b 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js @@ -17,7 +17,6 @@ * under the License. */ -import { visFactory } from '../../../ui/public/vis/vis_factory'; import { i18n } from '@kbn/i18n'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { AggGroupNames } from 'ui/vis/editors/default'; @@ -36,144 +35,141 @@ import { getAreaOptionTabs, countLabel } from './utils/common_config'; import { palettes } from '@elastic/eui/lib/services'; import { vislibVisController } from './controller'; -export default function PointSeriesVisType() { - - return visFactory.createBaseVisualization({ - name: 'horizontal_bar', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.horizontalBarTitle', { defaultMessage: 'Horizontal Bar' }), - icon: 'visBarHorizontal', - description: i18n.translate('kbnVislibVisTypes.horizontalBar.horizontalBarDescription', - { defaultMessage: 'Assign a continuous variable to each axis' } - ), - visualization: vislibVisController, - visConfig: { - defaults: { - type: 'histogram', - grid: { - categoryLines: false, - }, - categoryAxes: [ - { - id: 'CategoryAxis-1', - type: AxisTypes.CATEGORY, - position: Positions.LEFT, - show: true, - style: { - }, - scale: { - type: ScaleTypes.LINEAR, - }, - labels: { - show: true, - rotate: Rotates.HORIZONTAL, - filter: false, - truncate: 200 - }, - title: {} - } - ], - valueAxes: [ - { - id: 'ValueAxis-1', - name: 'LeftAxis-1', - type: AxisTypes.VALUE, - position: Positions.BOTTOM, +export const horizontalBarDefinition = { + name: 'horizontal_bar', + title: i18n.translate('kbnVislibVisTypes.horizontalBar.horizontalBarTitle', { defaultMessage: 'Horizontal Bar' }), + icon: 'visBarHorizontal', + description: i18n.translate('kbnVislibVisTypes.horizontalBar.horizontalBarDescription', + { defaultMessage: 'Assign a continuous variable to each axis' } + ), + visualization: vislibVisController, + visConfig: { + defaults: { + type: 'histogram', + grid: { + categoryLines: false, + }, + categoryAxes: [ + { + id: 'CategoryAxis-1', + type: AxisTypes.CATEGORY, + position: Positions.LEFT, + show: true, + style: { + }, + scale: { + type: ScaleTypes.LINEAR, + }, + labels: { show: true, - style: { - }, - scale: { - type: ScaleTypes.LINEAR, - mode: AxisModes.NORMAL, - }, - labels: { - show: true, - rotate: Rotates.ANGLED, - filter: true, - truncate: 100 - }, - title: { - text: countLabel, - } - } - ], - seriesParams: [{ + rotate: Rotates.HORIZONTAL, + filter: false, + truncate: 200 + }, + title: {} + } + ], + valueAxes: [ + { + id: 'ValueAxis-1', + name: 'LeftAxis-1', + type: AxisTypes.VALUE, + position: Positions.BOTTOM, show: true, - type: ChartTypes.HISTOGRAM, - mode: ChartModes.NORMAL, - data: { - label: countLabel, - id: '1' + style: { }, - valueAxis: 'ValueAxis-1', - drawLinesBetweenPoints: true, - lineWidth: 2, - showCircles: true - }], - addTooltip: true, - addLegend: true, - legendPosition: Positions.RIGHT, - times: [], - addTimeMarker: false, - labels: {}, - thresholdLine: { - show: false, - value: 10, - width: 1, - style: ThresholdLineStyles.FULL, - color: palettes.euiPaletteColorBlind.colors[9] + scale: { + type: ScaleTypes.LINEAR, + mode: AxisModes.NORMAL, + }, + labels: { + show: true, + rotate: Rotates.ANGLED, + filter: true, + truncate: 100 + }, + title: { + text: countLabel, + } + } + ], + seriesParams: [{ + show: true, + type: ChartTypes.HISTOGRAM, + mode: ChartModes.NORMAL, + data: { + label: countLabel, + id: '1' }, + valueAxis: 'ValueAxis-1', + drawLinesBetweenPoints: true, + lineWidth: 2, + showCircles: true + }], + addTooltip: true, + addLegend: true, + legendPosition: Positions.RIGHT, + times: [], + addTimeMarker: false, + labels: {}, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: ThresholdLineStyles.FULL, + color: palettes.euiPaletteColorBlind.colors[9] }, }, - events: { - brush: { disabled: false }, - }, - editorConfig: { - collections: getConfigCollections(), - optionTabs: getAreaOptionTabs(), - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.metricTitle', { defaultMessage: 'Y-axis' }), - min: 1, - aggFilter: ['!geo_centroid', '!geo_bounds'], - defaults: [ - { schema: 'metric', type: 'count' } - ] - }, - { - group: AggGroupNames.Metrics, - name: 'radius', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.radiusTitle', { defaultMessage: 'Dot size' }), - min: 0, - max: 1, - aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'] - }, - { - group: AggGroupNames.Buckets, - name: 'segment', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.segmentTitle', { defaultMessage: 'X-axis' }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] - }, - { - group: AggGroupNames.Buckets, - name: 'group', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.groupTitle', { defaultMessage: 'Split series' }), - min: 0, - max: 3, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] - }, - { - group: AggGroupNames.Buckets, - name: 'split', - title: i18n.translate('kbnVislibVisTypes.horizontalBar.splitTitle', { defaultMessage: 'Split chart' }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] - } - ]) - } - }); -} + }, + events: { + brush: { disabled: false }, + }, + editorConfig: { + collections: getConfigCollections(), + optionTabs: getAreaOptionTabs(), + schemas: new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: i18n.translate('kbnVislibVisTypes.horizontalBar.metricTitle', { defaultMessage: 'Y-axis' }), + min: 1, + aggFilter: ['!geo_centroid', '!geo_bounds'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: AggGroupNames.Metrics, + name: 'radius', + title: i18n.translate('kbnVislibVisTypes.horizontalBar.radiusTitle', { defaultMessage: 'Dot size' }), + min: 0, + max: 1, + aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality'] + }, + { + group: AggGroupNames.Buckets, + name: 'segment', + title: i18n.translate('kbnVislibVisTypes.horizontalBar.segmentTitle', { defaultMessage: 'X-axis' }), + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + }, + { + group: AggGroupNames.Buckets, + name: 'group', + title: i18n.translate('kbnVislibVisTypes.horizontalBar.groupTitle', { defaultMessage: 'Split series' }), + min: 0, + max: 3, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + }, + { + group: AggGroupNames.Buckets, + name: 'split', + title: i18n.translate('kbnVislibVisTypes.horizontalBar.splitTitle', { defaultMessage: 'Split chart' }), + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + } + ]) + } +}; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js index fe2cca3b800641..c82073ff582b8c 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/kbn_vislib_vis_types.js @@ -19,20 +19,20 @@ import { setup as visualizations } from '../../visualizations/public/np_ready/public/legacy'; -import histogramVisTypeProvider from './histogram'; -import lineVisTypeProvider from './line'; -import pieVisTypeProvider from './pie'; -import areaVisTypeProvider from './area'; -import heatmapVisTypeProvider from './heatmap'; -import horizontalBarVisTypeProvider from './horizontal_bar'; -import gaugeVisTypeProvider from './gauge'; -import goalVisTypeProvider from './goal'; +import { histogramDefinition } from './histogram'; +import { lineDefinition } from './line'; +import { pieDefinition } from './pie'; +import { areaDefinition } from './area'; +import { heatmapDefinition } from './heatmap'; +import { horizontalBarDefinition } from './horizontal_bar'; +import { gaugeDefinition } from './gauge'; +import { goalDefinition } from './goal'; -visualizations.types.registerVisualization(histogramVisTypeProvider); -visualizations.types.registerVisualization(lineVisTypeProvider); -visualizations.types.registerVisualization(pieVisTypeProvider); -visualizations.types.registerVisualization(areaVisTypeProvider); -visualizations.types.registerVisualization(heatmapVisTypeProvider); -visualizations.types.registerVisualization(horizontalBarVisTypeProvider); -visualizations.types.registerVisualization(gaugeVisTypeProvider); -visualizations.types.registerVisualization(goalVisTypeProvider); +visualizations.types.createBaseVisualization(histogramDefinition); +visualizations.types.createBaseVisualization(lineDefinition); +visualizations.types.createBaseVisualization(pieDefinition); +visualizations.types.createBaseVisualization(areaDefinition); +visualizations.types.createBaseVisualization(heatmapDefinition); +visualizations.types.createBaseVisualization(horizontalBarDefinition); +visualizations.types.createBaseVisualization(gaugeDefinition); +visualizations.types.createBaseVisualization(goalDefinition); diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/line.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/line.js index fedbf485414514..74c7e2fa2af89d 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/line.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/line.js @@ -17,7 +17,6 @@ * under the License. */ -import { visFactory } from '../../../ui/public/vis/vis_factory'; import { i18n } from '@kbn/i18n'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { AggGroupNames } from 'ui/vis/editors/default'; @@ -37,142 +36,139 @@ import { palettes } from '@elastic/eui/lib/services'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; import { vislibVisController } from './controller'; -export default function PointSeriesVisType() { - - return visFactory.createBaseVisualization({ - name: 'line', - title: i18n.translate('kbnVislibVisTypes.line.lineTitle', { defaultMessage: 'Line' }), - icon: 'visLine', - description: i18n.translate('kbnVislibVisTypes.line.lineDescription', { defaultMessage: 'Emphasize trends' }), - visualization: vislibVisController, - visConfig: { - defaults: { - type: 'line', - grid: { - categoryLines: false, - }, - categoryAxes: [ - { - id: 'CategoryAxis-1', - type: AxisTypes.CATEGORY, - position: Positions.BOTTOM, - show: true, - style: {}, - scale: { - type: ScaleTypes.LINEAR, - }, - labels: { - show: true, - filter: true, - truncate: 100 - }, - title: {} - } - ], - valueAxes: [ - { - id: 'ValueAxis-1', - name: 'LeftAxis-1', - type: AxisTypes.VALUE, - position: Positions.LEFT, +export const lineDefinition = { + name: 'line', + title: i18n.translate('kbnVislibVisTypes.line.lineTitle', { defaultMessage: 'Line' }), + icon: 'visLine', + description: i18n.translate('kbnVislibVisTypes.line.lineDescription', { defaultMessage: 'Emphasize trends' }), + visualization: vislibVisController, + visConfig: { + defaults: { + type: 'line', + grid: { + categoryLines: false, + }, + categoryAxes: [ + { + id: 'CategoryAxis-1', + type: AxisTypes.CATEGORY, + position: Positions.BOTTOM, + show: true, + style: {}, + scale: { + type: ScaleTypes.LINEAR, + }, + labels: { show: true, - style: {}, - scale: { - type: ScaleTypes.LINEAR, - mode: AxisModes.NORMAL, - }, - labels: { - show: true, - rotate: Rotates.HORIZONTAL, - filter: false, - truncate: 100 - }, - title: { - text: countLabel, - } - } - ], - seriesParams: [ - { + filter: true, + truncate: 100 + }, + title: {} + } + ], + valueAxes: [ + { + id: 'ValueAxis-1', + name: 'LeftAxis-1', + type: AxisTypes.VALUE, + position: Positions.LEFT, + show: true, + style: {}, + scale: { + type: ScaleTypes.LINEAR, + mode: AxisModes.NORMAL, + }, + labels: { show: true, - type: ChartTypes.LINE, - mode: ChartModes.NORMAL, - data: { - label: countLabel, - id: '1' - }, - valueAxis: 'ValueAxis-1', - drawLinesBetweenPoints: true, - lineWidth: 2, - interpolate: InterpolationModes.LINEAR, - showCircles: true + rotate: Rotates.HORIZONTAL, + filter: false, + truncate: 100 + }, + title: { + text: countLabel, } - ], - addTooltip: true, - addLegend: true, - legendPosition: Positions.RIGHT, - times: [], - addTimeMarker: false, - labels: {}, - thresholdLine: { - show: false, - value: 10, - width: 1, - style: ThresholdLineStyles.FULL, - color: palettes.euiPaletteColorBlind.colors[9] } - }, - }, - events: { - brush: { disabled: false }, - }, - editorConfig: { - collections: getConfigCollections(), - optionTabs: getAreaOptionTabs(), - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('kbnVislibVisTypes.line.metricTitle', { defaultMessage: 'Y-axis' }), - min: 1, - aggFilter: ['!geo_centroid', '!geo_bounds'], - defaults: [ - { schema: 'metric', type: 'count' } - ] - }, - { - group: AggGroupNames.Metrics, - name: 'radius', - title: i18n.translate('kbnVislibVisTypes.line.radiusTitle', { defaultMessage: 'Dot size' }), - min: 0, - max: 1, - aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits'] - }, - { - group: AggGroupNames.Buckets, - name: 'segment', - title: i18n.translate('kbnVislibVisTypes.line.segmentTitle', { defaultMessage: 'X-axis' }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] - }, - { - group: AggGroupNames.Buckets, - name: 'group', - title: i18n.translate('kbnVislibVisTypes.line.groupTitle', { defaultMessage: 'Split series' }), - min: 0, - max: 3, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] - }, + ], + seriesParams: [ { - group: AggGroupNames.Buckets, - name: 'split', - title: i18n.translate('kbnVislibVisTypes.line.splitTitle', { defaultMessage: 'Split chart' }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + show: true, + type: ChartTypes.LINE, + mode: ChartModes.NORMAL, + data: { + label: countLabel, + id: '1' + }, + valueAxis: 'ValueAxis-1', + drawLinesBetweenPoints: true, + lineWidth: 2, + interpolate: InterpolationModes.LINEAR, + showCircles: true } - ]) - } - }); -} + ], + addTooltip: true, + addLegend: true, + legendPosition: Positions.RIGHT, + times: [], + addTimeMarker: false, + labels: {}, + thresholdLine: { + show: false, + value: 10, + width: 1, + style: ThresholdLineStyles.FULL, + color: palettes.euiPaletteColorBlind.colors[9] + } + }, + }, + events: { + brush: { disabled: false }, + }, + editorConfig: { + collections: getConfigCollections(), + optionTabs: getAreaOptionTabs(), + schemas: new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: i18n.translate('kbnVislibVisTypes.line.metricTitle', { defaultMessage: 'Y-axis' }), + min: 1, + aggFilter: ['!geo_centroid', '!geo_bounds'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: AggGroupNames.Metrics, + name: 'radius', + title: i18n.translate('kbnVislibVisTypes.line.radiusTitle', { defaultMessage: 'Dot size' }), + min: 0, + max: 1, + aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits'] + }, + { + group: AggGroupNames.Buckets, + name: 'segment', + title: i18n.translate('kbnVislibVisTypes.line.segmentTitle', { defaultMessage: 'X-axis' }), + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + }, + { + group: AggGroupNames.Buckets, + name: 'group', + title: i18n.translate('kbnVislibVisTypes.line.groupTitle', { defaultMessage: 'Split series' }), + min: 0, + max: 3, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + }, + { + group: AggGroupNames.Buckets, + name: 'split', + title: i18n.translate('kbnVislibVisTypes.line.splitTitle', { defaultMessage: 'Split chart' }), + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + } + ]) + } +}; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.js index 8a374f21dcb09b..691f2e6349f721 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/pie.js @@ -17,7 +17,6 @@ * under the License. */ -import { visFactory } from '../../../ui/public/vis/vis_factory'; import { i18n } from '@kbn/i18n'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { AggGroupNames } from 'ui/vis/editors/default'; @@ -25,66 +24,63 @@ import { PieOptions } from './components/options'; import { getPositions, Positions } from './utils/collections'; import { vislibVisController } from './controller'; -export default function HistogramVisType() { - - return visFactory.createBaseVisualization({ - name: 'pie', - title: i18n.translate('kbnVislibVisTypes.pie.pieTitle', { defaultMessage: 'Pie' }), - icon: 'visPie', - description: i18n.translate('kbnVislibVisTypes.pie.pieDescription', { defaultMessage: 'Compare parts of a whole' }), - visualization: vislibVisController, - visConfig: { - defaults: { - type: 'pie', - addTooltip: true, - addLegend: true, - legendPosition: Positions.RIGHT, - isDonut: true, - labels: { - show: false, - values: true, - last_level: true, - truncate: 100 - } - }, +export const pieDefinition = { + name: 'pie', + title: i18n.translate('kbnVislibVisTypes.pie.pieTitle', { defaultMessage: 'Pie' }), + icon: 'visPie', + description: i18n.translate('kbnVislibVisTypes.pie.pieDescription', { defaultMessage: 'Compare parts of a whole' }), + visualization: vislibVisController, + visConfig: { + defaults: { + type: 'pie', + addTooltip: true, + addLegend: true, + legendPosition: Positions.RIGHT, + isDonut: true, + labels: { + show: false, + values: true, + last_level: true, + truncate: 100 + } }, - editorConfig: { - collections: { - legendPositions: getPositions() - }, - optionsTemplate: PieOptions, - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('kbnVislibVisTypes.pie.metricTitle', { defaultMessage: 'Slice size' }), - min: 1, - max: 1, - aggFilter: ['sum', 'count', 'cardinality', 'top_hits'], - defaults: [ - { schema: 'metric', type: 'count' } - ] - }, - { - group: AggGroupNames.Buckets, - name: 'segment', - title: i18n.translate('kbnVislibVisTypes.pie.segmentTitle', { defaultMessage: 'Split slices' }), - min: 0, - max: Infinity, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] - }, - { - group: AggGroupNames.Buckets, - name: 'split', - title: i18n.translate('kbnVislibVisTypes.pie.splitTitle', { defaultMessage: 'Split chart' }), - mustBeFirst: true, - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] - } - ]) + }, + editorConfig: { + collections: { + legendPositions: getPositions() }, - hierarchicalData: true, - responseHandler: 'vislib_slices', - }); -} + optionsTemplate: PieOptions, + schemas: new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: i18n.translate('kbnVislibVisTypes.pie.metricTitle', { defaultMessage: 'Slice size' }), + min: 1, + max: 1, + aggFilter: ['sum', 'count', 'cardinality', 'top_hits'], + defaults: [ + { schema: 'metric', type: 'count' } + ] + }, + { + group: AggGroupNames.Buckets, + name: 'segment', + title: i18n.translate('kbnVislibVisTypes.pie.segmentTitle', { defaultMessage: 'Split slices' }), + min: 0, + max: Infinity, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + }, + { + group: AggGroupNames.Buckets, + name: 'split', + title: i18n.translate('kbnVislibVisTypes.pie.splitTitle', { defaultMessage: 'Split chart' }), + mustBeFirst: true, + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'] + } + ]) + }, + hierarchicalData: true, + responseHandler: 'vislib_slices', +}; diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index dcb7d7998ff1a4..91364071579abb 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -324,6 +324,7 @@ export default function (kibana) { }, init: async function (server) { + const { usageCollection } = server.newPlatform.setup.plugins; // uuid await manageUuid(server); // routes @@ -338,8 +339,8 @@ export default function (kibana) { registerKqlTelemetryApi(server); registerFieldFormats(server); registerTutorials(server); - makeKQLUsageCollector(server); - registerCspCollector(server); + makeKQLUsageCollector(usageCollection, server); + registerCspCollector(usageCollection, server); server.expose('systemApi', systemApi); server.injectUiAppVars('kibana', () => injectVars(server)); }, diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index fc5f34fab75649..5a10e02ba8131f 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -81,7 +81,7 @@ export function getServices() { // EXPORT legacy static dependencies export { angular }; -export { buildVislibDimensions } from 'ui/visualize/loader/pipeline_helpers/build_pipeline'; +export { buildVislibDimensions } from '../../../visualizations/public'; // @ts-ignore export { callAfterBindingsWorkaround } from 'ui/compat'; export { diff --git a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js index aec80b8d13551a..ae4b4d1c779df1 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js +++ b/src/legacy/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js @@ -27,7 +27,7 @@ import { Vis } from 'ui/vis'; import { uiModules } from 'ui/modules'; -import { updateOldState } from 'ui/vis/vis_update_state'; +import { updateOldState } from '../../../../visualizations/public'; import { VisualizeConstants } from '../visualize_constants'; import { createLegacyClass } from 'ui/utils/legacy_class'; import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx index 245b4270c6aea1..fcc612ab49bd26 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx @@ -34,7 +34,7 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; -import { memoizeLast } from 'ui/utils/memoize'; +import { memoizeLast } from '../../../../../visualizations/public/np_ready/public/legacy/memoize'; import { VisType } from '../../kibana_services'; import { VisTypeAlias } from '../../../../../visualizations/public'; import { NewVisHelp } from './new_vis_help'; 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 3ff39c1a4eb8c4..9890aaf187a130 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,6 +19,7 @@ import { Server } from 'hapi'; import { createCSPRuleString, DEFAULT_CSP_RULES } from '../../../../../server/csp'; +import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; export function createCspCollector(server: Server) { return { @@ -42,8 +43,7 @@ export function createCspCollector(server: Server) { }; } -export function registerCspCollector(server: Server): void { - const { collectorSet } = server.usage; - const collector = collectorSet.makeUsageCollector(createCspCollector(server)); - collectorSet.register(collector); +export function registerCspCollector(usageCollection: UsageCollectionSetup, server: Server): void { + const collector = usageCollection.makeUsageCollector(createCspCollector(server)); + usageCollection.registerCollector(collector); } diff --git a/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/make_kql_usage_collector.js b/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/make_kql_usage_collector.js index 19fb64b7ecc745..6d751a9e9ff45b 100644 --- a/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/make_kql_usage_collector.js +++ b/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/make_kql_usage_collector.js @@ -19,14 +19,14 @@ import { fetchProvider } from './fetch'; -export function makeKQLUsageCollector(server) { +export function makeKQLUsageCollector(usageCollection, server) { const index = server.config().get('kibana.index'); const fetch = fetchProvider(index); - const kqlUsageCollector = server.usage.collectorSet.makeUsageCollector({ + const kqlUsageCollector = usageCollection.makeUsageCollector({ type: 'kql', fetch, isReady: () => true, }); - server.usage.collectorSet.register(kqlUsageCollector); + usageCollection.registerCollector(kqlUsageCollector); } diff --git a/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/make_kql_usage_collector.test.js b/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/make_kql_usage_collector.test.js index 24f336043d0d13..7737a0fbc2a713 100644 --- a/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/make_kql_usage_collector.test.js +++ b/src/legacy/core_plugins/kibana/server/lib/kql_usage_collector/make_kql_usage_collector.test.js @@ -20,29 +20,30 @@ import { makeKQLUsageCollector } from './make_kql_usage_collector'; describe('makeKQLUsageCollector', () => { - let server; let makeUsageCollectorStub; let registerStub; + let usageCollection; beforeEach(() => { makeUsageCollectorStub = jest.fn(); registerStub = jest.fn(); + usageCollection = { + makeUsageCollector: makeUsageCollectorStub, + registerCollector: registerStub, + }; server = { - usage: { - collectorSet: { makeUsageCollector: makeUsageCollectorStub, register: registerStub }, - }, config: () => ({ get: () => '.kibana' }) }; }); - it('should call collectorSet.register', () => { - makeKQLUsageCollector(server); + it('should call registerCollector', () => { + makeKQLUsageCollector(usageCollection, server); expect(registerStub).toHaveBeenCalledTimes(1); }); it('should call makeUsageCollector with type = kql', () => { - makeKQLUsageCollector(server); + makeKQLUsageCollector(usageCollection, server); expect(makeUsageCollectorStub).toHaveBeenCalledTimes(1); expect(makeUsageCollectorStub.mock.calls[0][0].type).toBe('kql'); }); diff --git a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js index b57fbd637f0b72..7571c616093ba1 100644 --- a/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js +++ b/src/legacy/core_plugins/region_map/public/__tests__/region_map_visualization.js @@ -109,7 +109,7 @@ describe('RegionMapsVisualizationTests', function () { if(!visRegComplete) { visRegComplete = true; - visualizationsSetup.types.registerVisualization(() => createRegionMapTypeDefinition(dependencies)); + visualizationsSetup.types.createBaseVisualization(createRegionMapTypeDefinition(dependencies)); } RegionMapsVisualization = createRegionMapVisualization(dependencies); diff --git a/src/legacy/core_plugins/region_map/public/plugin.ts b/src/legacy/core_plugins/region_map/public/plugin.ts index aaaf4c866c521b..a41d638986ae5d 100644 --- a/src/legacy/core_plugins/region_map/public/plugin.ts +++ b/src/legacy/core_plugins/region_map/public/plugin.ts @@ -70,7 +70,7 @@ export class RegionMapPlugin implements Plugin, void> { expressions.registerFunction(createRegionMapFn); - visualizations.types.registerVisualization(() => + visualizations.types.createBaseVisualization( createRegionMapTypeDefinition(visualizationDependencies) ); } diff --git a/src/legacy/core_plugins/region_map/public/region_map_type.js b/src/legacy/core_plugins/region_map/public/region_map_type.js index 3a28277f9f4c7d..03e0de728ca858 100644 --- a/src/legacy/core_plugins/region_map/public/region_map_type.js +++ b/src/legacy/core_plugins/region_map/public/region_map_type.js @@ -22,11 +22,9 @@ import { Schemas } from 'ui/vis/editors/default/schemas'; import { colorSchemas } from 'ui/vislib/components/color/truncated_colormaps'; import { mapToLayerWithId } from './util'; import { createRegionMapVisualization } from './region_map_visualization'; -import { Status } from 'ui/vis/update_status'; +import { Status } from '../../visualizations/public'; import { RegionMapOptions } from './components/region_map_options'; -import { visFactory } from '../../visualizations/public'; - // TODO: reference to TILE_MAP plugin should be removed import { ORIGIN } from '../../tile_map/common/origin'; @@ -34,7 +32,7 @@ export function createRegionMapTypeDefinition(dependencies) { const { uiSettings, regionmapsConfig, serviceSettings } = dependencies; const visualization = createRegionMapVisualization(dependencies); - return visFactory.createBaseVisualization({ + return { name: 'region_map', title: i18n.translate('regionMap.mapVis.regionMapTitle', { defaultMessage: 'Region Map' }), description: i18n.translate('regionMap.mapVis.regionMapDescription', { @@ -155,5 +153,5 @@ provided base maps, or add your own. Darker colors represent higher values.', return savedVis; }, - }); + }; } diff --git a/src/legacy/core_plugins/telemetry/README.md b/src/legacy/core_plugins/telemetry/README.md new file mode 100644 index 00000000000000..830c08f8e8bedf --- /dev/null +++ b/src/legacy/core_plugins/telemetry/README.md @@ -0,0 +1,9 @@ +# Kibana Telemetry Service + +Telemetry allows Kibana features to have usage tracked in the wild. The general term "telemetry" refers to multiple things: + +1. Integrating with the telemetry service to express how to collect usage data (Collecting). +2. Sending a payload of usage data up to Elastic's telemetry cluster. +3. Viewing usage data in the Kibana instance of the telemetry cluster (Viewing). + +This plugin is responsible for sending usage data to the telemetry cluster. For collecting usage data, use diff --git a/src/legacy/core_plugins/telemetry/index.ts b/src/legacy/core_plugins/telemetry/index.ts index 5ae0d5f127eeda..9f850fc0fe7192 100644 --- a/src/legacy/core_plugins/telemetry/index.ts +++ b/src/legacy/core_plugins/telemetry/index.ts @@ -27,14 +27,7 @@ import { i18n } from '@kbn/i18n'; import mappings from './mappings.json'; import { CONFIG_TELEMETRY, getConfigTelemetryDesc } from './common/constants'; import { getXpackConfigWithDeprecated } from './common/get_xpack_config_with_deprecated'; -import { telemetryPlugin, replaceTelemetryInjectedVars, FetcherTask } from './server'; - -import { - createLocalizationUsageCollector, - createTelemetryUsageCollector, - createUiMetricUsageCollector, - createTelemetryPluginUsageCollector, -} from './server/collectors'; +import { telemetryPlugin, replaceTelemetryInjectedVars, FetcherTask, PluginsSetup } from './server'; const ENDPOINT_VERSION = 'v2'; @@ -123,6 +116,7 @@ const telemetry = (kibana: any) => { fetcherTask.start(); }, init(server: Server) { + const { usageCollection } = server.newPlatform.setup.plugins; const initializerContext = { env: { packageInfo: { @@ -149,12 +143,11 @@ const telemetry = (kibana: any) => { log: server.log, } as any) as CoreSetup; - telemetryPlugin(initializerContext).setup(coreSetup); - // register collectors - server.usage.collectorSet.register(createTelemetryPluginUsageCollector(server)); - server.usage.collectorSet.register(createLocalizationUsageCollector(server)); - server.usage.collectorSet.register(createTelemetryUsageCollector(server)); - server.usage.collectorSet.register(createUiMetricUsageCollector(server)); + const pluginsSetup: PluginsSetup = { + usageCollection, + }; + + telemetryPlugin(initializerContext).setup(coreSetup, pluginsSetup, server); }, }); }; diff --git a/src/legacy/core_plugins/telemetry/server/collection_manager.ts b/src/legacy/core_plugins/telemetry/server/collection_manager.ts index 799d9f4ee9c8b6..933c249cd72793 100644 --- a/src/legacy/core_plugins/telemetry/server/collection_manager.ts +++ b/src/legacy/core_plugins/telemetry/server/collection_manager.ts @@ -19,6 +19,7 @@ import { encryptTelemetry } from './collectors'; import { CallCluster } from '../../elasticsearch'; +import { UsageCollectionSetup } from '../../../../plugins/usage_collection/server'; export type EncryptedStatsGetterConfig = { unencrypted: false } & { server: any; @@ -37,6 +38,7 @@ export interface ClusterDetails { } export interface StatsCollectionConfig { + usageCollection: UsageCollectionSetup; callCluster: CallCluster; server: any; start: string; @@ -112,7 +114,8 @@ export class TelemetryCollectionManager { ? (...args: any[]) => callWithRequest(config.req, ...args) : callWithInternalUser; - return { server, callCluster, start, end }; + const { usageCollection } = server.newPlatform.setup.plugins; + return { server, callCluster, start, end, usageCollection }; }; private getOptInStatsForCollection = async ( diff --git a/src/legacy/core_plugins/telemetry/server/collectors/index.ts b/src/legacy/core_plugins/telemetry/server/collectors/index.ts index f963ecec0477cf..2f2a53278117be 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/index.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/index.ts @@ -18,7 +18,7 @@ */ export { encryptTelemetry } from './encryption'; -export { createTelemetryUsageCollector } from './usage'; -export { createUiMetricUsageCollector } from './ui_metric'; -export { createLocalizationUsageCollector } from './localization'; -export { createTelemetryPluginUsageCollector } from './telemetry_plugin'; +export { registerTelemetryUsageCollector } from './usage'; +export { registerUiMetricUsageCollector } from './ui_metric'; +export { registerLocalizationUsageCollector } from './localization'; +export { registerTelemetryPluginUsageCollector } from './telemetry_plugin'; diff --git a/src/legacy/core_plugins/telemetry/server/collectors/localization/index.ts b/src/legacy/core_plugins/telemetry/server/collectors/localization/index.ts index 3b289752ce39fc..71026b026263f9 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/localization/index.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/localization/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { createLocalizationUsageCollector } from './telemetry_localization_collector'; +export { registerLocalizationUsageCollector } from './telemetry_localization_collector'; diff --git a/src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.ts b/src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.ts index 74c93931096b2e..191565187be149 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/localization/telemetry_localization_collector.ts @@ -21,6 +21,7 @@ import { i18nLoader } from '@kbn/i18n'; import { size } from 'lodash'; import { getIntegrityHashes, Integrities } from './file_integrity'; import { KIBANA_LOCALIZATION_STATS_TYPE } from '../../../common/constants'; +import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; export interface UsageStats { locale: string; integrities: Integrities; @@ -51,15 +52,15 @@ export function createCollectorFetch(server: any) { }; } -/* - * @param {Object} server - * @return {Object} kibana usage stats type collection object - */ -export function createLocalizationUsageCollector(server: any) { - const { collectorSet } = server.usage; - return collectorSet.makeUsageCollector({ +export function registerLocalizationUsageCollector( + usageCollection: UsageCollectionSetup, + server: any +) { + const collector = usageCollection.makeUsageCollector({ type: KIBANA_LOCALIZATION_STATS_TYPE, isReady: () => true, fetch: createCollectorFetch(server), }); + + usageCollection.registerCollector(collector); } diff --git a/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/index.ts b/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/index.ts index e96c47741f79c9..631a37e674c4ee 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/index.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { createTelemetryPluginUsageCollector } from './telemetry_plugin_collector'; +export { registerTelemetryPluginUsageCollector } from './telemetry_plugin_collector'; diff --git a/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts b/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts index a172ba7dc69559..5e25538cbad808 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/telemetry_plugin/telemetry_plugin_collector.ts @@ -20,6 +20,8 @@ import { TELEMETRY_STATS_TYPE } from '../../../common/constants'; import { getTelemetrySavedObject, TelemetrySavedObject } from '../../telemetry_repository'; import { getTelemetryOptIn, getTelemetrySendUsageFrom } from '../../telemetry_config'; +import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; + export interface TelemetryUsageStats { opt_in_status?: boolean | null; usage_fetcher?: 'browser' | 'server'; @@ -61,15 +63,15 @@ export function createCollectorFetch(server: any) { }; } -/* - * @param {Object} server - * @return {Object} kibana usage stats type collection object - */ -export function createTelemetryPluginUsageCollector(server: any) { - const { collectorSet } = server.usage; - return collectorSet.makeUsageCollector({ +export function registerTelemetryPluginUsageCollector( + usageCollection: UsageCollectionSetup, + server: any +) { + const collector = usageCollection.makeUsageCollector({ type: TELEMETRY_STATS_TYPE, isReady: () => true, fetch: createCollectorFetch(server), }); + + usageCollection.registerCollector(collector); } diff --git a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.ts b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.ts index e1ac7a1f5af122..013db526211e15 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { createUiMetricUsageCollector } from './telemetry_ui_metric_collector'; +export { registerUiMetricUsageCollector } from './telemetry_ui_metric_collector'; diff --git a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts index fa3159669c33ca..73157abce86292 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/ui_metric/telemetry_ui_metric_collector.ts @@ -18,10 +18,10 @@ */ import { UI_METRIC_USAGE_TYPE } from '../../../common/constants'; +import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; -export function createUiMetricUsageCollector(server: any) { - const { collectorSet } = server.usage; - return collectorSet.makeUsageCollector({ +export function registerUiMetricUsageCollector(usageCollection: UsageCollectionSetup, server: any) { + const collector = usageCollection.makeUsageCollector({ type: UI_METRIC_USAGE_TYPE, fetch: async () => { const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects; @@ -55,4 +55,6 @@ export function createUiMetricUsageCollector(server: any) { }, isReady: () => true, }); + + usageCollection.registerCollector(collector); } diff --git a/src/legacy/core_plugins/telemetry/server/collectors/usage/index.ts b/src/legacy/core_plugins/telemetry/server/collectors/usage/index.ts index a1b3d5a7b1982b..3ef9eed3c12651 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/usage/index.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/usage/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { createTelemetryUsageCollector } from './telemetry_usage_collector'; +export { registerTelemetryUsageCollector } from './telemetry_usage_collector'; diff --git a/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts b/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts index 3806dfc77120f9..2b2e946198e0a8 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.test.ts @@ -25,20 +25,15 @@ import { createTelemetryUsageCollector, isFileReadable, readTelemetryFile, - KibanaHapiServer, MAX_FILE_SIZE, } from './telemetry_usage_collector'; -const getMockServer = (): KibanaHapiServer => - ({ - usage: { - collectorSet: { makeUsageCollector: jest.fn().mockImplementationOnce((arg: object) => arg) }, - }, - } as KibanaHapiServer & Server); +const mockUsageCollector = () => ({ + makeUsageCollector: jest.fn().mockImplementationOnce((arg: object) => arg), +}); -const serverWithConfig = (configPath: string): KibanaHapiServer & Server => { +const serverWithConfig = (configPath: string): Server => { return { - ...getMockServer(), config: () => ({ get: (key: string) => { if (key !== 'telemetry.config' && key !== 'xpack.xpack_main.telemetry.config') { @@ -48,7 +43,7 @@ const serverWithConfig = (configPath: string): KibanaHapiServer & Server => { return configPath; }, }), - } as KibanaHapiServer & Server; + } as Server; }; describe('telemetry_usage_collector', () => { @@ -130,14 +125,15 @@ describe('telemetry_usage_collector', () => { }); describe('createTelemetryUsageCollector', () => { - test('calls `collectorSet.makeUsageCollector`', async () => { + test('calls `makeUsageCollector`', async () => { // note: it uses the file's path to get the directory, then looks for 'telemetry.yml' // exclusively, which is indirectly tested by passing it the wrong "file" in the same // dir - const server: KibanaHapiServer & Server = serverWithConfig(tempFiles.unreadable); + const server: Server = serverWithConfig(tempFiles.unreadable); // the `makeUsageCollector` is mocked above to return the argument passed to it - const collectorOptions = createTelemetryUsageCollector(server); + const usageCollector = mockUsageCollector() as any; + const collectorOptions = createTelemetryUsageCollector(usageCollector, server); expect(collectorOptions.type).toBe('static_telemetry'); expect(await collectorOptions.fetch()).toEqual(expectedObject); diff --git a/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts b/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts index c9274536411934..99090cb2fb7ef2 100644 --- a/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts +++ b/src/legacy/core_plugins/telemetry/server/collectors/usage/telemetry_usage_collector.ts @@ -25,20 +25,13 @@ import { dirname, join } from 'path'; // look for telemetry.yml in the same places we expect kibana.yml import { ensureDeepObject } from './ensure_deep_object'; import { getXpackConfigWithDeprecated } from '../../../common/get_xpack_config_with_deprecated'; +import { UsageCollectionSetup } from '../../../../../../plugins/usage_collection/server'; /** * The maximum file size before we ignore it (note: this limit is arbitrary). */ export const MAX_FILE_SIZE = 10 * 1024; // 10 KB -export interface KibanaHapiServer extends Server { - usage: { - collectorSet: { - makeUsageCollector: (collector: object) => any; - }; - }; -} - /** * Determine if the supplied `path` is readable. * @@ -83,19 +76,11 @@ export async function readTelemetryFile(path: string): Promise true, fetch: async () => { @@ -106,3 +91,11 @@ export function createTelemetryUsageCollector(server: KibanaHapiServer) { }, }); } + +export function registerTelemetryUsageCollector( + usageCollection: UsageCollectionSetup, + server: Server +) { + const collector = createTelemetryUsageCollector(usageCollection, server); + usageCollection.registerCollector(collector); +} diff --git a/src/legacy/core_plugins/telemetry/server/index.ts b/src/legacy/core_plugins/telemetry/server/index.ts index 02752ca773488e..6c62d03adf25ce 100644 --- a/src/legacy/core_plugins/telemetry/server/index.ts +++ b/src/legacy/core_plugins/telemetry/server/index.ts @@ -24,7 +24,7 @@ import * as constants from '../common/constants'; export { FetcherTask } from './fetcher'; export { replaceTelemetryInjectedVars } from './telemetry_config'; export { telemetryCollectionManager } from './collection_manager'; - +export { PluginsSetup } from './plugin'; export const telemetryPlugin = (initializerContext: PluginInitializerContext) => new TelemetryPlugin(initializerContext); export { constants }; diff --git a/src/legacy/core_plugins/telemetry/server/plugin.ts b/src/legacy/core_plugins/telemetry/server/plugin.ts index f2628090c08afb..06a974f473498e 100644 --- a/src/legacy/core_plugins/telemetry/server/plugin.ts +++ b/src/legacy/core_plugins/telemetry/server/plugin.ts @@ -18,8 +18,20 @@ */ import { CoreSetup, PluginInitializerContext } from 'src/core/server'; +import { Server } from 'hapi'; import { registerRoutes } from './routes'; import { registerCollection } from './telemetry_collection'; +import { UsageCollectionSetup } from '../../../../plugins/usage_collection/server'; +import { + registerUiMetricUsageCollector, + registerTelemetryUsageCollector, + registerLocalizationUsageCollector, + registerTelemetryPluginUsageCollector, +} from './collectors'; + +export interface PluginsSetup { + usageCollection: UsageCollectionSetup; +} export class TelemetryPlugin { private readonly currentKibanaVersion: string; @@ -28,9 +40,15 @@ export class TelemetryPlugin { this.currentKibanaVersion = initializerContext.env.packageInfo.version; } - public setup(core: CoreSetup) { + public setup(core: CoreSetup, { usageCollection }: PluginsSetup, server: Server) { const currentKibanaVersion = this.currentKibanaVersion; + registerCollection(); registerRoutes({ core, currentKibanaVersion }); + + registerTelemetryPluginUsageCollector(usageCollection, server); + registerLocalizationUsageCollector(usageCollection, server); + registerTelemetryUsageCollector(usageCollection, server); + registerUiMetricUsageCollector(usageCollection, server); } } diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js b/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js index 4cbdf18df4a744..140204ac5ab490 100644 --- a/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/__tests__/get_local_stats.js @@ -29,7 +29,12 @@ import { handleLocalStats, } from '../get_local_stats'; -const getMockServer = (getCluster = sinon.stub(), kibanaUsage = {}) => ({ +const mockUsageCollection = (kibanaUsage = {}) => ({ + bulkFetch: () => kibanaUsage, + toObject: data => data, +}); + +const getMockServer = (getCluster = sinon.stub()) => ({ log(tags, message) { console.log({ tags, message }); }, @@ -43,7 +48,6 @@ const getMockServer = (getCluster = sinon.stub(), kibanaUsage = {}) => ({ } }; }, - usage: { collectorSet: { bulkFetch: () => kibanaUsage, toObject: data => data } }, plugins: { elasticsearch: { getCluster }, }, @@ -155,15 +159,16 @@ describe('get_local_stats', () => { describe.skip('getLocalStats', () => { it('returns expected object without xpack data when X-Pack fails to respond', async () => { const callClusterUsageFailed = sinon.stub(); - + const usageCollection = mockUsageCollection(); mockGetLocalStats( callClusterUsageFailed, Promise.resolve(clusterInfo), Promise.resolve(clusterStats), ); - const result = await getLocalStats({ + const result = await getLocalStats([], { server: getMockServer(), callCluster: callClusterUsageFailed, + usageCollection, }); expect(result.cluster_uuid).to.eql(combinedStatsResult.cluster_uuid); expect(result.cluster_name).to.eql(combinedStatsResult.cluster_name); @@ -178,15 +183,16 @@ describe('get_local_stats', () => { it('returns expected object with xpack and kibana data', async () => { const callCluster = sinon.stub(); - + const usageCollection = mockUsageCollection(kibana); mockGetLocalStats( callCluster, Promise.resolve(clusterInfo), Promise.resolve(clusterStats), ); - const result = await getLocalStats({ - server: getMockServer(callCluster, kibana), + const result = await getLocalStats([], { + server: getMockServer(callCluster), + usageCollection, callCluster, }); diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_kibana.js b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_kibana.js index 051ef370fcde51..236dd046148f6d 100644 --- a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_kibana.js +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_kibana.js @@ -47,12 +47,7 @@ export function handleKibanaStats(server, response) { }; } -/* - * Check user privileges for read access to monitoring - * Pass callWithInternalUser to bulkFetchUsage - */ -export async function getKibana(server, callWithInternalUser) { - const { collectorSet } = server.usage; - const usage = await collectorSet.bulkFetch(callWithInternalUser); - return collectorSet.toObject(usage); +export async function getKibana(usageCollection, callWithInternalUser) { + const usage = await usageCollection.bulkFetch(callWithInternalUser); + return usageCollection.toObject(usage); } diff --git a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.ts b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.ts index e11c6b1277d5bf..a4ea2eb534226e 100644 --- a/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.ts +++ b/src/legacy/core_plugins/telemetry/server/telemetry_collection/get_local_stats.ts @@ -55,13 +55,14 @@ export function handleLocalStats(server: any, clusterInfo: any, clusterStats: an * @return {Promise} The object containing the current Elasticsearch cluster's telemetry. */ export const getLocalStats: StatsGetter = async (clustersDetails, config) => { - const { server, callCluster } = config; + const { server, callCluster, usageCollection } = config; + return await Promise.all( clustersDetails.map(async clustersDetail => { const [clusterInfo, clusterStats, kibana] = await Promise.all([ getClusterInfo(callCluster), // cluster info getClusterStats(callCluster), // cluster stats (not to be confused with cluster _state_) - getKibana(server, callCluster), + getKibana(usageCollection, callCluster), ]); return handleLocalStats(server, clusterInfo, clusterStats, kibana); }) diff --git a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js index 0e3c4fdd9d3557..57469625ea4a6a 100644 --- a/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js +++ b/src/legacy/core_plugins/tile_map/public/__tests__/coordinate_maps_visualization.js @@ -86,7 +86,7 @@ describe('CoordinateMapsVisualizationTest', function () { if(!visRegComplete) { visRegComplete = true; - visualizationsSetup.types.registerVisualization(() => createTileMapTypeDefinition(dependencies)); + visualizationsSetup.types.createBaseVisualization(createTileMapTypeDefinition(dependencies)); } diff --git a/src/legacy/core_plugins/tile_map/public/plugin.ts b/src/legacy/core_plugins/tile_map/public/plugin.ts index 602f6c266b5f6a..14a348f6240026 100644 --- a/src/legacy/core_plugins/tile_map/public/plugin.ts +++ b/src/legacy/core_plugins/tile_map/public/plugin.ts @@ -64,7 +64,7 @@ export class TileMapPlugin implements Plugin, void> { expressions.registerFunction(() => createTileMapFn(visualizationDependencies)); - visualizations.types.registerVisualization(() => + visualizations.types.createBaseVisualization( createTileMapTypeDefinition(visualizationDependencies) ); } diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_type.js b/src/legacy/core_plugins/tile_map/public/tile_map_type.js index 243b4c2bf7765a..a976fb5c77ef0c 100644 --- a/src/legacy/core_plugins/tile_map/public/tile_map_type.js +++ b/src/legacy/core_plugins/tile_map/public/tile_map_type.js @@ -22,12 +22,11 @@ import { i18n } from '@kbn/i18n'; import { supports } from 'ui/utils/supports'; import { Schemas } from 'ui/vis/editors/default/schemas'; -import { Status } from 'ui/vis/update_status'; import { colorSchemas } from 'ui/vislib/components/color/truncated_colormaps'; import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson'; import { createTileMapVisualization } from './tile_map_visualization'; -import { visFactory } from '../../visualizations/public'; +import { Status } from '../../visualizations/public'; import { TileMapOptions } from './components/tile_map_options'; import { MapTypes } from './map_types'; @@ -35,7 +34,7 @@ export function createTileMapTypeDefinition(dependencies) { const CoordinateMapsVisualization = createTileMapVisualization(dependencies); const { uiSettings, serviceSettings } = dependencies; - return visFactory.createBaseVisualization({ + return { name: 'tile_map', title: i18n.translate('tileMap.vis.mapTitle', { defaultMessage: 'Coordinate Map', @@ -160,5 +159,5 @@ export function createTileMapTypeDefinition(dependencies) { } return savedVis; }, - }); + }; } diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index 6291948f750775..b0123cd34b49e3 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -76,7 +76,7 @@ export class TimelionPlugin implements Plugin, void> { this.registerPanels(dependencies); expressions.registerFunction(() => getTimelionVisualizationConfig(dependencies)); - visualizations.types.registerVisualization(() => getTimelionVisualization(dependencies)); + visualizations.types.createBaseVisualization(getTimelionVisualization(dependencies)); } private registerPanels(dependencies: TimelionVisualizationDependencies) { diff --git a/src/legacy/core_plugins/timelion/public/vis/index.ts b/src/legacy/core_plugins/timelion/public/vis/index.ts index a586fd71e6cf89..7b82553a24e5b1 100644 --- a/src/legacy/core_plugins/timelion/public/vis/index.ts +++ b/src/legacy/core_plugins/timelion/public/vis/index.ts @@ -20,7 +20,6 @@ import { i18n } from '@kbn/i18n'; // @ts-ignore import { DefaultEditorSize } from 'ui/vis/editor_size'; -import { visFactory } from '../../../visualizations/public'; import { getTimelionRequestHandler } from './timelion_request_handler'; import visConfigTemplate from './timelion_vis.html'; import editorConfigTemplate from './timelion_vis_params.html'; @@ -35,7 +34,7 @@ export function getTimelionVisualization(dependencies: TimelionVisualizationDepe // return the visType object, which kibana will use to display and configure new // Vis object of this type. - return visFactory.createBaseVisualization({ + return { name: TIMELION_VIS_NAME, title: 'Timelion', icon: 'visTimelion', @@ -61,5 +60,5 @@ export function getTimelionVisualization(dependencies: TimelionVisualizationDepe showQueryBar: false, showFilterBar: false, }, - }); + }; } diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts index 74111bf7948775..14cd3d0083e6ab 100644 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_request_handler.ts @@ -83,7 +83,7 @@ export function getTimelionRequestHandler(dependencies: TimelionVisualizationDep sheet: [expression], extended: { es: { - filter: esQuery.buildEsQuery(null, query, filters, esQueryConfigs), + filter: esQuery.buildEsQuery(undefined, query, filters, esQueryConfigs), }, }, time: { diff --git a/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts b/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts index 7b2f8f6c236b29..524bbeed1b5522 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts +++ b/src/legacy/core_plugins/vis_type_markdown/public/markdown_vis.ts @@ -19,13 +19,13 @@ import { i18n } from '@kbn/i18n'; -import { visFactory, DefaultEditorSize } from '../../visualizations/public'; +import { DefaultEditorSize } from '../../visualizations/public'; import { MarkdownVisWrapper } from './markdown_vis_controller'; import { MarkdownOptions } from './markdown_options'; import { SettingsOptions } from './settings_options'; -export const markdownVis = visFactory.createReactVisualization({ +export const markdownVisDefinition = { name: 'markdown', title: 'Markdown', isAccessible: true, @@ -67,4 +67,4 @@ export const markdownVis = visFactory.createReactVisualization({ }, requestHandler: 'none', responseHandler: 'none', -}); +}; diff --git a/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts b/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts index 85d8c27ed970d8..f1316647562020 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_markdown/public/plugin.ts @@ -21,7 +21,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../.. import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; import { VisualizationsSetup } from '../../visualizations/public'; -import { markdownVis } from './markdown_vis'; +import { markdownVisDefinition } from './markdown_vis'; import { createMarkdownVisFn } from './markdown_fn'; /** @internal */ @@ -39,7 +39,7 @@ export class MarkdownPlugin implements Plugin { } public setup(core: CoreSetup, { expressions, visualizations }: MarkdownPluginSetupDependencies) { - visualizations.types.registerVisualization(() => markdownVis); + visualizations.types.createReactVisualization(markdownVisDefinition); expressions.registerFunction(createMarkdownVisFn); } diff --git a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js b/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js index 384beb3764e2e0..de126087b36bec 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js +++ b/src/legacy/core_plugins/vis_type_metric/public/__tests__/metric_vis.js @@ -24,7 +24,7 @@ import expect from '@kbn/expect'; import { Vis } from 'ui/vis'; import LogstashIndexPatternStubProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { createMetricVisTypeDefinition } from '../metric_vis_type'; +import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; describe('metric_vis - createMetricVisTypeDefinition', () => { let setup = null; @@ -34,7 +34,7 @@ describe('metric_vis - createMetricVisTypeDefinition', () => { beforeEach( ngMock.inject(Private => { setup = () => { - const metricVisType = createMetricVisTypeDefinition(); + const metricVisType = visualizations.types.get('metric'); const indexPattern = Private(LogstashIndexPatternStubProvider); indexPattern.stubSetFieldFormat('ip', 'url', { diff --git a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts index a05df6f4d15643..ceab5dafe1f064 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/metric_vis_type.ts @@ -28,107 +28,104 @@ import { colorSchemas, ColorSchemas } from 'ui/vislib/components/color/colormaps // @ts-ignore import { MetricVisComponent } from './components/metric_vis_controller'; -import { visFactory } from '../../visualizations/public'; import { MetricVisOptions } from './components/metric_vis_options'; import { ColorModes } from '../../kbn_vislib_vis_types/public/utils/collections'; -export const createMetricVisTypeDefinition = () => { - return visFactory.createReactVisualization({ - name: 'metric', - title: i18n.translate('visTypeMetric.metricTitle', { defaultMessage: 'Metric' }), - icon: 'visMetric', - description: i18n.translate('visTypeMetric.metricDescription', { - defaultMessage: 'Display a calculation as a single number', - }), - visConfig: { - component: MetricVisComponent, - defaults: { - addTooltip: true, - addLegend: false, - type: 'metric', - metric: { - percentageMode: false, - useRanges: false, - colorSchema: ColorSchemas.GreenToRed, - metricColorMode: ColorModes.NONE, - colorsRange: [{ from: 0, to: 10000 }], - labels: { - show: true, - }, - invertColors: false, - style: { - bgFill: '#000', - bgColor: false, - labelColor: false, - subText: '', - fontSize: 60, - }, +export const metricVisDefinition = { + name: 'metric', + title: i18n.translate('visTypeMetric.metricTitle', { defaultMessage: 'Metric' }), + icon: 'visMetric', + description: i18n.translate('visTypeMetric.metricDescription', { + defaultMessage: 'Display a calculation as a single number', + }), + visConfig: { + component: MetricVisComponent, + defaults: { + addTooltip: true, + addLegend: false, + type: 'metric', + metric: { + percentageMode: false, + useRanges: false, + colorSchema: ColorSchemas.GreenToRed, + metricColorMode: ColorModes.NONE, + colorsRange: [{ from: 0, to: 10000 }], + labels: { + show: true, + }, + invertColors: false, + style: { + bgFill: '#000', + bgColor: false, + labelColor: false, + subText: '', + fontSize: 60, }, }, }, - editorConfig: { - collections: { - metricColorMode: [ - { - id: ColorModes.NONE, - label: i18n.translate('visTypeMetric.colorModes.noneOptionLabel', { - defaultMessage: 'None', - }), - }, - { - id: ColorModes.LABELS, - label: i18n.translate('visTypeMetric.colorModes.labelsOptionLabel', { - defaultMessage: 'Labels', - }), - }, - { - id: ColorModes.BACKGROUND, - label: i18n.translate('visTypeMetric.colorModes.backgroundOptionLabel', { - defaultMessage: 'Background', - }), - }, - ], - colorSchemas, - }, - optionsTemplate: MetricVisOptions, - schemas: new Schemas([ + }, + editorConfig: { + collections: { + metricColorMode: [ { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('visTypeMetric.schemas.metricTitle', { defaultMessage: 'Metric' }), - min: 1, - aggFilter: [ - '!std_dev', - '!geo_centroid', - '!derivative', - '!serial_diff', - '!moving_avg', - '!cumulative_sum', - '!geo_bounds', - ], - aggSettings: { - top_hits: { - allowStrings: true, - }, - }, - defaults: [ - { - type: 'count', - schema: 'metric', - }, - ], + id: ColorModes.NONE, + label: i18n.translate('visTypeMetric.colorModes.noneOptionLabel', { + defaultMessage: 'None', + }), + }, + { + id: ColorModes.LABELS, + label: i18n.translate('visTypeMetric.colorModes.labelsOptionLabel', { + defaultMessage: 'Labels', + }), }, { - group: AggGroupNames.Buckets, - name: 'group', - title: i18n.translate('visTypeMetric.schemas.splitGroupTitle', { - defaultMessage: 'Split group', + id: ColorModes.BACKGROUND, + label: i18n.translate('visTypeMetric.colorModes.backgroundOptionLabel', { + defaultMessage: 'Background', }), - min: 0, - max: 1, - aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], }, - ]), + ], + colorSchemas, }, - }); + optionsTemplate: MetricVisOptions, + schemas: new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: i18n.translate('visTypeMetric.schemas.metricTitle', { defaultMessage: 'Metric' }), + min: 1, + aggFilter: [ + '!std_dev', + '!geo_centroid', + '!derivative', + '!serial_diff', + '!moving_avg', + '!cumulative_sum', + '!geo_bounds', + ], + aggSettings: { + top_hits: { + allowStrings: true, + }, + }, + defaults: [ + { + type: 'count', + schema: 'metric', + }, + ], + }, + { + group: AggGroupNames.Buckets, + name: 'group', + title: i18n.translate('visTypeMetric.schemas.splitGroupTitle', { + defaultMessage: 'Split group', + }), + min: 0, + max: 1, + aggFilter: ['!geohash_grid', '!geotile_grid', '!filter'], + }, + ]), + }, }; diff --git a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts index e3e0ffaab21163..f5c152ce888c00 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/plugin.ts @@ -22,7 +22,7 @@ import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressio import { VisualizationsSetup } from '../../visualizations/public'; import { createMetricVisFn } from './metric_vis_fn'; -import { createMetricVisTypeDefinition } from './metric_vis_type'; +import { metricVisDefinition } from './metric_vis_type'; /** @internal */ export interface MetricVisPluginSetupDependencies { @@ -40,7 +40,7 @@ export class MetricVisPlugin implements Plugin { public setup(core: CoreSetup, { expressions, visualizations }: MetricVisPluginSetupDependencies) { expressions.registerFunction(createMetricVisFn); - visualizations.types.registerVisualization(createMetricVisTypeDefinition); + visualizations.types.createReactVisualization(metricVisDefinition); } public start(core: CoreStart) { diff --git a/src/legacy/core_plugins/vis_type_metric/public/types.ts b/src/legacy/core_plugins/vis_type_metric/public/types.ts index 54f1f36e19c996..ce0e78140a86a4 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/types.ts +++ b/src/legacy/core_plugins/vis_type_metric/public/types.ts @@ -19,7 +19,7 @@ import { ColorSchemas } from 'ui/vislib/components/color/colormaps'; import { RangeValues } from 'ui/vis/editors/default/controls/ranges'; -import { SchemaConfig } from 'ui/visualize/loader/pipeline_helpers/build_pipeline'; +import { SchemaConfig } from '../../visualizations/public'; import { ColorModes } from '../../kbn_vislib_vis_types/public/utils/collections'; import { Labels, Style } from '../../kbn_vislib_vis_types/public/types'; diff --git a/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js b/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js index 4153ce2da36a73..e22dd4caa6d011 100644 --- a/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js +++ b/src/legacy/core_plugins/vis_type_table/public/__tests__/table_vis_controller.js @@ -21,13 +21,12 @@ import $ from 'jquery'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy'; -import { Vis } from 'ui/vis'; -import { VisFactoryProvider } from 'ui/vis/vis_factory'; +import { Vis } from '../../../visualizations/public'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; import { AppStateProvider } from 'ui/state_management/app_state'; import { tabifyAggResponse } from 'ui/agg_response/tabify'; -import { createTableVisTypeDefinition } from '../table_vis_type'; +import { tableVisTypeDefinition } from '../table_vis_type'; import { setup as visualizationsSetup } from '../../../visualizations/public/np_ready/public/legacy'; describe('Table Vis - Controller', async function () { @@ -40,18 +39,10 @@ describe('Table Vis - Controller', async function () { let AppState; let tableAggResponse; let tabifiedResponse; - let legacyDependencies; - ngMock.inject(function ($injector) { - Private = $injector.get('Private'); - legacyDependencies = { - // eslint-disable-next-line new-cap - createAngularVisualization: VisFactoryProvider(Private).createAngularVisualization, - }; + ngMock.inject(function () { - visualizationsSetup.types.registerVisualization(() => - createTableVisTypeDefinition(legacyDependencies) - ); + visualizationsSetup.types.createBaseVisualization(tableVisTypeDefinition); }); beforeEach(ngMock.module('kibana', 'kibana/table_vis')); diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js index 13e8a4fd9535a1..2978856a3511d9 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/__tests__/agg_table.js @@ -25,12 +25,11 @@ import fixtures from 'fixtures/fake_hierarchical_data'; import sinon from 'sinon'; import { legacyResponseHandlerProvider } from 'ui/vis/response_handlers/legacy'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { Vis } from 'ui/vis'; +import { Vis } from '../../../../visualizations/public'; import { tabifyAggResponse } from 'ui/agg_response/tabify'; import { round } from 'lodash'; -import { VisFactoryProvider } from 'ui/vis/vis_factory'; -import { createTableVisTypeDefinition } from '../../table_vis_type'; +import { tableVisTypeDefinition } from '../../table_vis_type'; import { setup as visualizationsSetup } from '../../../../visualizations/public/np_ready/public/legacy'; describe('Table Vis - AggTable Directive', function () { @@ -39,7 +38,6 @@ describe('Table Vis - AggTable Directive', function () { let indexPattern; let settings; let tableAggResponse; - let legacyDependencies; const tabifiedData = {}; const init = () => { @@ -98,13 +96,8 @@ describe('Table Vis - AggTable Directive', function () { ); }; - ngMock.inject(function (Private) { - legacyDependencies = { - // eslint-disable-next-line new-cap - createAngularVisualization: VisFactoryProvider(Private).createAngularVisualization, - }; - - visualizationsSetup.types.registerVisualization(() => createTableVisTypeDefinition(legacyDependencies)); + ngMock.inject(function () { + visualizationsSetup.types.createBaseVisualization(tableVisTypeDefinition); }); beforeEach(ngMock.module('kibana')); diff --git a/src/legacy/core_plugins/vis_type_table/public/plugin.ts b/src/legacy/core_plugins/vis_type_table/public/plugin.ts index 28e812701c2d35..ce8d349d8dd7aa 100644 --- a/src/legacy/core_plugins/vis_type_table/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_table/public/plugin.ts @@ -24,7 +24,7 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../.. import { LegacyDependenciesPlugin } from './shim'; import { createTableVisFn } from './table_vis_fn'; -import { createTableVisTypeDefinition } from './table_vis_type'; +import { tableVisTypeDefinition } from './table_vis_type'; /** @internal */ export interface TablePluginSetupDependencies { @@ -48,7 +48,7 @@ export class TableVisPlugin implements Plugin, void> { __LEGACY.setup(); expressions.registerFunction(createTableVisFn); - visualizations.types.registerVisualization(createTableVisTypeDefinition); + visualizations.types.createBaseVisualization(tableVisTypeDefinition); } public start(core: CoreStart) { diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts index 1de72c0b33a4cd..7e8537a1fee54d 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_type.ts @@ -20,7 +20,6 @@ import { i18n } from '@kbn/i18n'; import { Vis } from 'ui/vis'; // @ts-ignore -import { visFactory } from 'ui/vis/vis_factory'; // @ts-ignore import { Schemas } from 'ui/vis/editors/default/schemas'; @@ -32,74 +31,72 @@ import { tableVisResponseHandler } from './table_vis_request_handler'; import tableVisTemplate from './table_vis.html'; import { TableOptions } from './components/table_vis_options'; -export const createTableVisTypeDefinition = () => { - return visFactory.createBaseVisualization({ - type: 'table', - name: 'table', - title: i18n.translate('visTypeTable.tableVisTitle', { - defaultMessage: 'Data Table', - }), - icon: 'visTable', - description: i18n.translate('visTypeTable.tableVisDescription', { - defaultMessage: 'Display values in a table', - }), - visualization: AngularVisController, - visConfig: { - defaults: { - perPage: 10, - showPartialRows: false, - showMetricsAtAllLevels: false, - sort: { - columnIndex: null, - direction: null, - }, - showTotal: false, - totalFunc: 'sum', - percentageCol: '', +export const tableVisTypeDefinition = { + type: 'table', + name: 'table', + title: i18n.translate('visTypeTable.tableVisTitle', { + defaultMessage: 'Data Table', + }), + icon: 'visTable', + description: i18n.translate('visTypeTable.tableVisDescription', { + defaultMessage: 'Display values in a table', + }), + visualization: AngularVisController, + visConfig: { + defaults: { + perPage: 10, + showPartialRows: false, + showMetricsAtAllLevels: false, + sort: { + columnIndex: null, + direction: null, }, - template: tableVisTemplate, + showTotal: false, + totalFunc: 'sum', + percentageCol: '', }, - editorConfig: { - optionsTemplate: TableOptions, - schemas: new Schemas([ - { - group: AggGroupNames.Metrics, - name: 'metric', - title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.metricTitle', { - defaultMessage: 'Metric', - }), - aggFilter: ['!geo_centroid', '!geo_bounds'], - aggSettings: { - top_hits: { - allowStrings: true, - }, + template: tableVisTemplate, + }, + editorConfig: { + optionsTemplate: TableOptions, + schemas: new Schemas([ + { + group: AggGroupNames.Metrics, + name: 'metric', + title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.metricTitle', { + defaultMessage: 'Metric', + }), + aggFilter: ['!geo_centroid', '!geo_bounds'], + aggSettings: { + top_hits: { + allowStrings: true, }, - min: 1, - defaults: [{ type: 'count', schema: 'metric' }], - }, - { - group: AggGroupNames.Buckets, - name: 'bucket', - title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.bucketTitle', { - defaultMessage: 'Split rows', - }), - aggFilter: ['!filter'], }, - { - group: AggGroupNames.Buckets, - name: 'split', - title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.splitTitle', { - defaultMessage: 'Split table', - }), - min: 0, - max: 1, - aggFilter: ['!filter'], - }, - ]), - }, - responseHandler: tableVisResponseHandler, - hierarchicalData: (vis: Vis) => { - return Boolean(vis.params.showPartialRows || vis.params.showMetricsAtAllLevels); - }, - }); + min: 1, + defaults: [{ type: 'count', schema: 'metric' }], + }, + { + group: AggGroupNames.Buckets, + name: 'bucket', + title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.bucketTitle', { + defaultMessage: 'Split rows', + }), + aggFilter: ['!filter'], + }, + { + group: AggGroupNames.Buckets, + name: 'split', + title: i18n.translate('visTypeTable.tableVisEditorConfig.schemas.splitTitle', { + defaultMessage: 'Split table', + }), + min: 0, + max: 1, + aggFilter: ['!filter'], + }, + ]), + }, + responseHandler: tableVisResponseHandler, + hierarchicalData: (vis: Vis) => { + return Boolean(vis.params.showPartialRows || vis.params.showMetricsAtAllLevels); + }, }; diff --git a/src/legacy/core_plugins/vis_type_table/public/types.ts b/src/legacy/core_plugins/vis_type_table/public/types.ts index 4a16bb72bd2e37..39023d1305cb63 100644 --- a/src/legacy/core_plugins/vis_type_table/public/types.ts +++ b/src/legacy/core_plugins/vis_type_table/public/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { SchemaConfig } from 'ui/visualize/loader/pipeline_helpers/build_pipeline'; +import { SchemaConfig } from '../../visualizations/public'; export enum AggTypes { SUM = 'sum', diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts index bcb210e9ed081a..865229ce0e4c18 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/plugin.ts @@ -22,7 +22,7 @@ import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressio import { VisualizationsSetup } from '../../visualizations/public'; import { createTagCloudFn } from './tag_cloud_fn'; -import { createTagCloudTypeDefinition } from './tag_cloud_type'; +import { tagcloudVisDefinition } from './tag_cloud_type'; /** @internal */ export interface TagCloudPluginSetupDependencies { @@ -40,7 +40,7 @@ export class TagCloudPlugin implements Plugin { public setup(core: CoreSetup, { expressions, visualizations }: TagCloudPluginSetupDependencies) { expressions.registerFunction(createTagCloudFn); - visualizations.types.registerVisualization(createTagCloudTypeDefinition); + visualizations.types.createBaseVisualization(tagcloudVisDefinition); } public start(core: CoreStart) { diff --git a/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts b/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts index 0b4d90522cc448..9c673b11225738 100644 --- a/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts +++ b/src/legacy/core_plugins/vis_type_tagcloud/public/tag_cloud_type.ts @@ -18,110 +18,107 @@ */ import { i18n } from '@kbn/i18n'; -import { Status } from 'ui/vis/update_status'; // @ts-ignore import { Schemas } from 'ui/vis/editors/default/schemas'; +import { Status } from '../../visualizations/public'; import { TagCloudOptions } from './components/tag_cloud_options'; -import { visFactory } from '../../visualizations/public'; // @ts-ignore import { TagCloudVisualization } from './components/tag_cloud_visualization'; -export const createTagCloudTypeDefinition = () => { - return visFactory.createBaseVisualization({ - name: 'tagcloud', - title: i18n.translate('visTypeTagCloud.vis.tagCloudTitle', { defaultMessage: 'Tag Cloud' }), - icon: 'visTagCloud', - description: i18n.translate('visTypeTagCloud.vis.tagCloudDescription', { - defaultMessage: 'A group of words, sized according to their importance', - }), - visConfig: { - defaults: { - scale: 'linear', - orientation: 'single', - minFontSize: 18, - maxFontSize: 72, - showLabel: true, - }, +export const tagcloudVisDefinition = { + name: 'tagcloud', + title: i18n.translate('visTypeTagCloud.vis.tagCloudTitle', { defaultMessage: 'Tag Cloud' }), + icon: 'visTagCloud', + description: i18n.translate('visTypeTagCloud.vis.tagCloudDescription', { + defaultMessage: 'A group of words, sized according to their importance', + }), + visConfig: { + defaults: { + scale: 'linear', + orientation: 'single', + minFontSize: 18, + maxFontSize: 72, + showLabel: true, }, - requiresUpdateStatus: [Status.PARAMS, Status.RESIZE, Status.DATA], - visualization: TagCloudVisualization, - editorConfig: { - collections: { - scales: [ - { - text: i18n.translate('visTypeTagCloud.vis.editorConfig.scales.linearText', { - defaultMessage: 'Linear', - }), - value: 'linear', - }, - { - text: i18n.translate('visTypeTagCloud.vis.editorConfig.scales.logText', { - defaultMessage: 'Log', - }), - value: 'log', - }, - { - text: i18n.translate('visTypeTagCloud.vis.editorConfig.scales.squareRootText', { - defaultMessage: 'Square root', - }), - value: 'square root', - }, - ], - orientations: [ - { - text: i18n.translate('visTypeTagCloud.vis.editorConfig.orientations.singleText', { - defaultMessage: 'Single', - }), - value: 'single', - }, - { - text: i18n.translate('visTypeTagCloud.vis.editorConfig.orientations.rightAngledText', { - defaultMessage: 'Right angled', - }), - value: 'right angled', - }, - { - text: i18n.translate('visTypeTagCloud.vis.editorConfig.orientations.multipleText', { - defaultMessage: 'Multiple', - }), - value: 'multiple', - }, - ], - }, - optionsTemplate: TagCloudOptions, - schemas: new Schemas([ + }, + requiresUpdateStatus: [Status.PARAMS, Status.RESIZE, Status.DATA], + visualization: TagCloudVisualization, + editorConfig: { + collections: { + scales: [ + { + text: i18n.translate('visTypeTagCloud.vis.editorConfig.scales.linearText', { + defaultMessage: 'Linear', + }), + value: 'linear', + }, + { + text: i18n.translate('visTypeTagCloud.vis.editorConfig.scales.logText', { + defaultMessage: 'Log', + }), + value: 'log', + }, { - group: 'metrics', - name: 'metric', - title: i18n.translate('visTypeTagCloud.vis.schemas.metricTitle', { - defaultMessage: 'Tag size', + text: i18n.translate('visTypeTagCloud.vis.editorConfig.scales.squareRootText', { + defaultMessage: 'Square root', }), - min: 1, - max: 1, - aggFilter: [ - '!std_dev', - '!percentiles', - '!percentile_ranks', - '!derivative', - '!geo_bounds', - '!geo_centroid', - ], - defaults: [{ schema: 'metric', type: 'count' }], + value: 'square root', }, + ], + orientations: [ { - group: 'buckets', - name: 'segment', - title: i18n.translate('visTypeTagCloud.vis.schemas.segmentTitle', { - defaultMessage: 'Tags', + text: i18n.translate('visTypeTagCloud.vis.editorConfig.orientations.singleText', { + defaultMessage: 'Single', }), - min: 1, - max: 1, - aggFilter: ['terms', 'significant_terms'], + value: 'single', }, - ]), + { + text: i18n.translate('visTypeTagCloud.vis.editorConfig.orientations.rightAngledText', { + defaultMessage: 'Right angled', + }), + value: 'right angled', + }, + { + text: i18n.translate('visTypeTagCloud.vis.editorConfig.orientations.multipleText', { + defaultMessage: 'Multiple', + }), + value: 'multiple', + }, + ], }, - useCustomNoDataScreen: true, - }); + optionsTemplate: TagCloudOptions, + schemas: new Schemas([ + { + group: 'metrics', + name: 'metric', + title: i18n.translate('visTypeTagCloud.vis.schemas.metricTitle', { + defaultMessage: 'Tag size', + }), + min: 1, + max: 1, + aggFilter: [ + '!std_dev', + '!percentiles', + '!percentile_ranks', + '!derivative', + '!geo_bounds', + '!geo_centroid', + ], + defaults: [{ schema: 'metric', type: 'count' }], + }, + { + group: 'buckets', + name: 'segment', + title: i18n.translate('visTypeTagCloud.vis.schemas.segmentTitle', { + defaultMessage: 'Tags', + }), + min: 1, + max: 1, + aggFilter: ['terms', 'significant_terms'], + }, + ]), + }, + useCustomNoDataScreen: true, }; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.test.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.test.js index 7b8a7061e8bb1d..9ec8184dbaebb6 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.test.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.test.js @@ -22,18 +22,12 @@ import { shallowWithIntl } from 'test_utils/enzyme_helpers'; jest.mock('plugins/data', () => { return { - QueryBarInput: () =>
, + QueryStringInput: () =>
, }; }); import { GaugePanelConfig } from './gauge'; -jest.mock('plugins/data', () => { - return { - QueryBar: () =>
, - }; -}); - describe('GaugePanelConfig', () => { it('call switch tab onChange={handleChange}', () => { const props = { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/query_bar_wrapper.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/query_bar_wrapper.js index 2eb9a7b03ac5fd..dc976beeca0d14 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/query_bar_wrapper.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/query_bar_wrapper.js @@ -19,10 +19,10 @@ import React, { useContext } from 'react'; import { CoreStartContext } from '../contexts/query_input_bar_context'; -import { QueryBarInput } from 'plugins/data'; +import { QueryStringInput } from 'plugins/data'; export function QueryBarWrapper(props) { const coreStartContext = useContext(CoreStartContext); - return ; + return ; } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js index 2b42c22ad7c435..1d42b773369334 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js @@ -22,7 +22,6 @@ import React, { Component } from 'react'; import * as Rx from 'rxjs'; import { share } from 'rxjs/operators'; import { isEqual, isEmpty, debounce } from 'lodash'; -import { fromKueryExpression } from '@kbn/es-query'; import { VisEditorVisualization } from './vis_editor_visualization'; import { Visualization } from './visualization'; import { VisPicker } from './vis_picker'; @@ -30,6 +29,7 @@ import { PanelConfig } from './panel_config'; import { createBrushHandler } from '../lib/create_brush_handler'; import { fetchFields } from '../lib/fetch_fields'; import { extractIndexPatterns } from '../../common/extract_index_patterns'; +import { esKuery } from '../../../../../plugins/data/public'; import { npStart } from 'ui/new_platform'; @@ -88,7 +88,7 @@ export class VisEditor extends Component { if (filterQuery && filterQuery.language === 'kuery') { try { const queryOptions = this.coreContext.uiSettings.get('query:allowLeadingWildcards'); - fromKueryExpression(filterQuery.query, { allowLeadingWildcards: queryOptions }); + esKuery.fromKueryExpression(filterQuery.query, { allowLeadingWildcards: queryOptions }); } catch (error) { return false; } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.test.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.test.js index edbeba5d176ae1..4efd5bb65451c4 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.test.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/gauge/series.test.js @@ -22,7 +22,7 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; jest.mock('plugins/data', () => { return { - QueryBarInput: () =>
, + QueryStringInput: () =>
, }; }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.test.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.test.js index cfcac5d4908a03..299e7c12f931ae 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.test.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/metric/series.test.js @@ -23,7 +23,7 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; jest.mock('plugins/data', () => { return { - QueryBarInput: () =>
, + QueryStringInput: () =>
, }; }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/editor_controller.js b/src/legacy/core_plugins/vis_type_timeseries/public/editor_controller.js index 464cec744eed74..4d029553145da6 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/editor_controller.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/editor_controller.js @@ -19,69 +19,68 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nContext } from 'ui/i18n'; import { fetchIndexPatternFields } from './lib/fetch_fields'; +import { getSavedObjectsClient, getUISettings, getI18n } from './services'; -export function createEditorController(config, savedObjectsClient) { - return class { - constructor(el, savedObj) { - this.el = el; +export class EditorController { + constructor(el, savedObj) { + this.el = el; - this.state = { - savedObj: savedObj, - vis: savedObj.vis, - isLoaded: false, - }; - } + this.state = { + savedObj: savedObj, + vis: savedObj.vis, + isLoaded: false, + }; + } - fetchDefaultIndexPattern = async () => { - const indexPattern = await savedObjectsClient.get( - 'index-pattern', - config.get('defaultIndex') - ); + fetchDefaultIndexPattern = async () => { + const indexPattern = await getSavedObjectsClient().client.get( + 'index-pattern', + getUISettings().get('defaultIndex') + ); - return indexPattern.attributes; - }; + return indexPattern.attributes; + }; - fetchDefaultParams = async () => { - const { title, timeFieldName } = await this.fetchDefaultIndexPattern(); + fetchDefaultParams = async () => { + const { title, timeFieldName } = await this.fetchDefaultIndexPattern(); - this.state.vis.params.default_index_pattern = title; - this.state.vis.params.default_timefield = timeFieldName; - this.state.vis.fields = await fetchIndexPatternFields(this.state.vis); + this.state.vis.params.default_index_pattern = title; + this.state.vis.params.default_timefield = timeFieldName; + this.state.vis.fields = await fetchIndexPatternFields(this.state.vis); - this.state.isLoaded = true; - }; + this.state.isLoaded = true; + }; - getComponent = () => { - return this.state.vis.type.editorConfig.component; - }; + getComponent = () => { + return this.state.vis.type.editorConfig.component; + }; - async render(params) { - const Component = this.getComponent(); + async render(params) { + const Component = this.getComponent(); + const I18nContext = getI18n().Context; - !this.state.isLoaded && (await this.fetchDefaultParams()); + !this.state.isLoaded && (await this.fetchDefaultParams()); - render( - - {}} - isEditorMode={true} - appState={params.appState} - /> - , - this.el - ); - } + render( + + {}} + isEditorMode={true} + appState={params.appState} + /> + , + this.el + ); + } - destroy() { - unmountComponentAtNode(this.el); - } - }; + destroy() { + unmountComponentAtNode(this.el); + } } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts index 12f14ea3cb8165..8740f84dab3b9e 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts @@ -20,12 +20,10 @@ import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; import { PersistedState } from 'ui/persisted_state'; -import chrome from 'ui/chrome'; - import { ExpressionFunction, KibanaContext, Render } from '../../../../plugins/expressions/public'; // @ts-ignore -import { createMetricsRequestHandler } from './request_handler'; +import { metricsRequestHandler } from './request_handler'; const name = 'tsvb'; type Context = KibanaContext | null; @@ -68,8 +66,6 @@ export const createMetricsFn = (): ExpressionFunction { - const uiSettings = chrome.getUiSettingsClient(); - const savedObjectsClient = chrome.getSavedObjectsClient(); - const EditorController = createEditorController(uiSettings, savedObjectsClient); - const metricsRequestHandler = createMetricsRequestHandler(uiSettings); - - return visFactory.createReactVisualization({ - name: 'metrics', - title: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsTitle', { defaultMessage: 'TSVB' }), - description: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsDescription', { - defaultMessage: 'Build time-series using a visual pipeline interface', - }), - icon: 'visVisualBuilder', - feedbackMessage: defaultFeedbackMessage, - visConfig: { - defaults: { - id: '61ca57f0-469d-11e7-af02-69e470af7417', - type: PANEL_TYPES.TIMESERIES, - series: [ - { - id: '61ca57f1-469d-11e7-af02-69e470af7417', - color: '#68BC00', - split_mode: 'everything', - metrics: [ - { - id: '61ca57f2-469d-11e7-af02-69e470af7417', - type: 'count', - }, - ], - separate_axis: 0, - axis_position: 'right', - formatter: 'number', - chart_type: 'line', - line_width: 1, - point_size: 1, - fill: 0.5, - stacked: 'none', - }, - ], - time_field: '', - index_pattern: '', - interval: '', - axis_position: 'left', - axis_formatter: 'number', - axis_scale: 'normal', - show_legend: 1, - show_grid: 1, - }, - component: require('./components/vis_editor').VisEditor, - }, - editor: EditorController, - editorConfig: { - component: require('./components/vis_editor').VisEditor, - }, - options: { - showQueryBar: false, - showFilterBar: false, - showIndexSelection: false, +export const metricsVisDefinition = { + name: 'metrics', + title: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsTitle', { defaultMessage: 'TSVB' }), + description: i18n.translate('visTypeTimeseries.kbnVisTypes.metricsDescription', { + defaultMessage: 'Build time-series using a visual pipeline interface', + }), + icon: 'visVisualBuilder', + feedbackMessage: defaultFeedbackMessage, + visConfig: { + defaults: { + id: '61ca57f0-469d-11e7-af02-69e470af7417', + type: PANEL_TYPES.TIMESERIES, + series: [ + { + id: '61ca57f1-469d-11e7-af02-69e470af7417', + color: '#68BC00', + split_mode: 'everything', + metrics: [ + { + id: '61ca57f2-469d-11e7-af02-69e470af7417', + type: 'count', + }, + ], + separate_axis: 0, + axis_position: 'right', + formatter: 'number', + chart_type: 'line', + line_width: 1, + point_size: 1, + fill: 0.5, + stacked: 'none', + }, + ], + time_field: '', + index_pattern: '', + interval: '', + axis_position: 'left', + axis_formatter: 'number', + axis_scale: 'normal', + show_legend: 1, + show_grid: 1, }, - requestHandler: metricsRequestHandler, - responseHandler: 'none', - }); + component: require('./components/vis_editor').VisEditor, + }, + editor: EditorController, + editorConfig: { + component: require('./components/vis_editor').VisEditor, + }, + options: { + showQueryBar: false, + showFilterBar: false, + showIndexSelection: false, + }, + requestHandler: metricsRequestHandler, + responseHandler: 'none', }; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts b/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts index 42ff653e9bfe91..75a65e131797d7 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/plugin.ts @@ -16,18 +16,30 @@ * specific language governing permissions and limitations * under the License. */ -import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../../../core/public'; +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + SavedObjectsClientContract, + UiSettingsClientContract, +} from '../../../../core/public'; import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; import { VisualizationsSetup } from '../../visualizations/public'; import { createMetricsFn } from './metrics_fn'; -import { createMetricsTypeDefinition } from './metrics_type'; +import { metricsVisDefinition } from './metrics_type'; +import { setSavedObjectsClient, setUISettings, setI18n } from './services'; /** @internal */ export interface MetricsPluginSetupDependencies { expressions: ReturnType; visualizations: VisualizationsSetup; } +export interface MetricsVisualizationDependencies { + uiSettings: UiSettingsClientContract; + savedObjectsClient: SavedObjectsClientContract; +} /** @internal */ export class MetricsPlugin implements Plugin, void> { @@ -42,10 +54,13 @@ export class MetricsPlugin implements Plugin, void> { { expressions, visualizations }: MetricsPluginSetupDependencies ) { expressions.registerFunction(createMetricsFn); - visualizations.types.registerVisualization(createMetricsTypeDefinition); + setUISettings(core.uiSettings); + visualizations.types.createReactVisualization(metricsVisDefinition); } public start(core: CoreStart) { // nothing to do here yet + setSavedObjectsClient(core.savedObjects); + setI18n(core.i18n); } } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js b/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js index 7bd400e8bed150..f4032af1838c1b 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/request_handler.js @@ -21,48 +21,47 @@ import { validateInterval } from './lib/validate_interval'; import { timezoneProvider } from 'ui/vis/lib/timezone'; import { timefilter } from 'ui/timefilter'; import { kfetch } from 'ui/kfetch'; +import { getUISettings } from './services'; -export const createMetricsRequestHandler = function (config) { +export const metricsRequestHandler = async ({ uiState, timeRange, filters, query, visParams }) => { + const config = getUISettings(); const timezone = timezoneProvider(config)(); + const uiStateObj = uiState.get(visParams.type, {}); + const parsedTimeRange = timefilter.calculateBounds(timeRange); + const scaledDataFormat = config.get('dateFormat:scaled'); + const dateFormat = config.get('dateFormat'); - return async ({ uiState, timeRange, filters, query, visParams }) => { - const uiStateObj = uiState.get(visParams.type, {}); - const parsedTimeRange = timefilter.calculateBounds(timeRange); - const scaledDataFormat = config.get('dateFormat:scaled'); - const dateFormat = config.get('dateFormat'); + if (visParams && visParams.id && !visParams.isModelInvalid) { + try { + const maxBuckets = config.get('metrics:max_buckets'); - if (visParams && visParams.id && !visParams.isModelInvalid) { - try { - const maxBuckets = config.get('metrics:max_buckets'); + validateInterval(parsedTimeRange, visParams, maxBuckets); - validateInterval(parsedTimeRange, visParams, maxBuckets); + const resp = await kfetch({ + pathname: '/api/metrics/vis/data', + method: 'POST', + body: JSON.stringify({ + timerange: { + timezone, + ...parsedTimeRange, + }, + query, + filters, + panels: [visParams], + state: uiStateObj, + }), + }); - const resp = await kfetch({ - pathname: '/api/metrics/vis/data', - method: 'POST', - body: JSON.stringify({ - timerange: { - timezone, - ...parsedTimeRange, - }, - query, - filters, - panels: [visParams], - state: uiStateObj, - }), - }); - - return { - dateFormat, - scaledDataFormat, - timezone, - ...resp, - }; - } catch (error) { - return Promise.reject(error); - } + return { + dateFormat, + scaledDataFormat, + timezone, + ...resp, + }; + } catch (error) { + return Promise.reject(error); } + } - return Promise.resolve({}); - }; + return Promise.resolve({}); }; diff --git a/src/legacy/ui/public/vis/vis_factory.js b/src/legacy/core_plugins/vis_type_timeseries/public/services.ts similarity index 62% rename from src/legacy/ui/public/vis/vis_factory.js rename to src/legacy/core_plugins/vis_type_timeseries/public/services.ts index 136122f097f383..dcc7de4098bddd 100644 --- a/src/legacy/ui/public/vis/vis_factory.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/services.ts @@ -17,19 +17,15 @@ * under the License. */ -import { BaseVisType, ReactVisType } from './vis_types'; +import { I18nStart, SavedObjectsStart, UiSettingsClientContract } from 'src/core/public'; +import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; -export const visFactory = { - createBaseVisualization: (config) => { - return new BaseVisType(config); - }, - createReactVisualization: (config) => { - return new ReactVisType(config); - }, -}; +export const [getUISettings, setUISettings] = createGetterSetter( + 'UISettings' +); -export const VisFactoryProvider = () => { - return { - ...visFactory, - }; -}; +export const [getSavedObjectsClient, setSavedObjectsClient] = createGetterSetter( + 'SavedObjectsClient' +); + +export const [getI18n, setI18n] = createGetterSetter('I18n'); diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js index 191f35d2e03ea3..6a1a5431f2e512 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js +++ b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_visualization.js @@ -67,9 +67,7 @@ describe('VegaVisualizations', () => { if(!visRegComplete) { visRegComplete = true; - visualizationsSetup.types.registerVisualization(() => - createVegaTypeDefinition(vegaVisualizationDependencies) - ); + visualizationsSetup.types.createBaseVisualization(createVegaTypeDefinition(vegaVisualizationDependencies)); } diff --git a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts index 67fbba7f161d34..9001164afe8207 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/plugin.ts @@ -61,7 +61,7 @@ export class VegaPlugin implements Plugin, void> { expressions.registerFunction(() => createVegaFn(visualizationDependencies)); - visualizations.types.registerVisualization(() => + visualizations.types.createBaseVisualization( createVegaTypeDefinition(visualizationDependencies) ); } diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts b/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts index 83ae31bf874001..26380bf2b9d94f 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_request_handler.ts @@ -49,7 +49,7 @@ export function createVegaRequestHandler({ timeCache.setTimeRange(timeRange); const esQueryConfigs = esQuery.getEsQueryConfig(uiSettings); - const filtersDsl = esQuery.buildEsQuery(null, query, filters, esQueryConfigs); + const filtersDsl = esQuery.buildEsQuery(undefined, query, filters, esQueryConfigs); const vp = new VegaParser(visParams.spec, searchCache, timeCache, filtersDsl, serviceSettings); return vp.parseAsync(); diff --git a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts index 0d5290ddbefc7b..9ab5f820cec317 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts +++ b/src/legacy/core_plugins/vis_type_vega/public/vega_type.ts @@ -18,13 +18,12 @@ */ import { i18n } from '@kbn/i18n'; -import { Status } from 'ui/vis/update_status'; // @ts-ignore import { DefaultEditorSize } from 'ui/vis/editor_size'; // @ts-ignore import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; +import { Status } from '../../visualizations/public'; -import { visFactory } from '../../visualizations/public'; import { VegaVisualizationDependencies } from './plugin'; import { VegaVisEditor } from './components'; @@ -38,7 +37,7 @@ export const createVegaTypeDefinition = (dependencies: VegaVisualizationDependen const requestHandler = createVegaRequestHandler(dependencies); const visualization = createVegaVisualization(dependencies); - return visFactory.createBaseVisualization({ + return { name: 'vega', title: 'Vega', description: i18n.translate('visTypeVega.type.vegaDescription', { @@ -63,5 +62,5 @@ export const createVegaTypeDefinition = (dependencies: VegaVisualizationDependen }, stage: 'experimental', feedbackMessage: defaultFeedbackMessage, - }); + }; }; diff --git a/src/legacy/core_plugins/visualizations/public/expressions/visualization_renderer.tsx b/src/legacy/core_plugins/visualizations/public/expressions/visualization_renderer.tsx index f15cdf23fe15b8..40648a137c1415 100644 --- a/src/legacy/core_plugins/visualizations/public/expressions/visualization_renderer.tsx +++ b/src/legacy/core_plugins/visualizations/public/expressions/visualization_renderer.tsx @@ -22,7 +22,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; // @ts-ignore import { Vis } from '../../../../ui/public/visualize/loader/vis'; -import { Visualization } from '../../../../ui/public/visualize/components'; +import { Visualization } from '../../../visualizations/public/np_ready/public/components'; export const visualization = () => ({ name: 'visualization', diff --git a/src/legacy/core_plugins/visualizations/public/index.ts b/src/legacy/core_plugins/visualizations/public/index.ts index ca79f547890f9c..f38c03c50c307f 100644 --- a/src/legacy/core_plugins/visualizations/public/index.ts +++ b/src/legacy/core_plugins/visualizations/public/index.ts @@ -26,23 +26,8 @@ // @ts-ignore Used only by tsvb, vega, input control vis export { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; // @ts-ignore -export { visFactory } from 'ui/vis/vis_factory'; -// @ts-ignore export { DefaultEditorSize } from 'ui/vis/editor_size'; -/** - * Legacy types which haven't been moved to this plugin yet, but - * should be eventually. - * - * @public - */ -import * as types from 'ui/vis/vis'; -export type Vis = types.Vis; -export type VisParams = types.VisParams; -export type VisState = types.VisState; -export { VisualizationController } from 'ui/vis/vis_types/vis_type'; -export { Status } from 'ui/vis/update_status'; - /** * Static np-ready code, re-exported here so consumers can import from * `src/legacy/core_plugins/visualizations/public` @@ -50,6 +35,3 @@ export { Status } from 'ui/vis/update_status'; * @public */ export * from './np_ready/public'; - -// for backwards compatibility with 7.3 -export { setup as visualizations } from './np_ready/public/legacy'; diff --git a/packages/kbn-es-query/babel.config.js b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts similarity index 56% rename from packages/kbn-es-query/babel.config.js rename to src/legacy/core_plugins/visualizations/public/legacy_imports.ts index 68783433fc711c..92d8ac2c7db3ab 100644 --- a/packages/kbn-es-query/babel.config.js +++ b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts @@ -17,19 +17,14 @@ * under the License. */ -// We can't use common Kibana presets here because of babel versions incompatibility -module.exports = { - env: { - public: { - presets: [ - '@kbn/babel-preset/webpack_preset' - ], - }, - server: { - presets: [ - '@kbn/babel-preset/node_preset' - ], - }, - }, - ignore: ['**/__tests__/**/*', '**/*.test.ts', '**/*.test.tsx'], -}; +export { PersistedState } from '../../../ui/public/persisted_state'; +export { SearchError } from '../../../ui/public/courier/search_strategy/search_error'; +export { AggConfig } from '../../../ui/public/agg_types/agg_config'; +export { AggConfigs } from '../../../ui/public/agg_types/agg_configs'; +export { + isDateHistogramBucketAggConfig, + setBounds, +} from '../../../ui/public/agg_types/buckets/date_histogram'; +export { createFormat } from '../../../ui/public/visualize/loader/pipeline_helpers/utilities'; +export { I18nContext } from '../../../ui/public/i18n'; +import '../../../ui/public/directives/bind'; diff --git a/packages/kbn-es-query/src/index.js b/src/legacy/core_plugins/visualizations/public/legacy_mocks.ts similarity index 90% rename from packages/kbn-es-query/src/index.js rename to src/legacy/core_plugins/visualizations/public/legacy_mocks.ts index 79e6903b186448..e6ca678db563d6 100644 --- a/packages/kbn-es-query/src/index.js +++ b/src/legacy/core_plugins/visualizations/public/legacy_mocks.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from './kuery'; +export { searchSourceMock } from '../../../ui/public/courier/search_source/mocks'; diff --git a/src/legacy/ui/public/visualize/components/__snapshots__/visualization_noresults.test.js.snap b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/__snapshots__/visualization_noresults.test.js.snap similarity index 100% rename from src/legacy/ui/public/visualize/components/__snapshots__/visualization_noresults.test.js.snap rename to src/legacy/core_plugins/visualizations/public/np_ready/public/components/__snapshots__/visualization_noresults.test.js.snap diff --git a/src/legacy/ui/public/visualize/components/__snapshots__/visualization_requesterror.test.js.snap b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/__snapshots__/visualization_requesterror.test.js.snap similarity index 100% rename from src/legacy/ui/public/visualize/components/__snapshots__/visualization_requesterror.test.js.snap rename to src/legacy/core_plugins/visualizations/public/np_ready/public/components/__snapshots__/visualization_requesterror.test.js.snap diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/components/_index.scss b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/_index.scss new file mode 100644 index 00000000000000..532e8106b023fd --- /dev/null +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/_index.scss @@ -0,0 +1 @@ +@import 'visualization'; diff --git a/src/legacy/ui/public/visualize/components/_visualization.scss b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/_visualization.scss similarity index 100% rename from src/legacy/ui/public/visualize/components/_visualization.scss rename to src/legacy/core_plugins/visualizations/public/np_ready/public/components/_visualization.scss diff --git a/src/legacy/ui/public/visualize/components/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/index.ts similarity index 100% rename from src/legacy/ui/public/visualize/components/index.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/components/index.ts diff --git a/src/legacy/ui/public/visualize/components/visualization.test.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.test.js similarity index 100% rename from src/legacy/ui/public/visualize/components/visualization.test.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.test.js diff --git a/src/legacy/ui/public/visualize/components/visualization.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.tsx similarity index 95% rename from src/legacy/ui/public/visualize/components/visualization.tsx rename to src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.tsx index 26c894f914910a..5a9a1830ebdf33 100644 --- a/src/legacy/ui/public/visualize/components/visualization.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization.tsx @@ -20,12 +20,12 @@ import { get } from 'lodash'; import React from 'react'; -import { PersistedState } from '../../persisted_state'; -import { memoizeLast } from '../../utils/memoize'; -import { Vis } from '../../vis'; +import { PersistedState } from '../../../legacy_imports'; +import { memoizeLast } from '../legacy/memoize'; import { VisualizationChart } from './visualization_chart'; import { VisualizationNoResults } from './visualization_noresults'; import { VisualizationRequestError } from './visualization_requesterror'; +import { Vis } from '..'; function shouldShowNoResultsMessage(vis: Vis, visData: any): boolean { const requiresSearch = get(vis, 'type.requiresSearch'); diff --git a/src/legacy/ui/public/visualize/components/visualization_chart.test.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_chart.test.js similarity index 100% rename from src/legacy/ui/public/visualize/components/visualization_chart.test.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_chart.test.js diff --git a/src/legacy/ui/public/visualize/components/visualization_chart.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_chart.tsx similarity index 95% rename from src/legacy/ui/public/visualize/components/visualization_chart.tsx rename to src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_chart.tsx index 8aec7adeaec9ad..95fd31049d2336 100644 --- a/src/legacy/ui/public/visualize/components/visualization_chart.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_chart.tsx @@ -21,10 +21,10 @@ import React from 'react'; import * as Rx from 'rxjs'; import { debounceTime, filter, share, switchMap } from 'rxjs/operators'; -import { PersistedState } from '../../persisted_state'; -import { ResizeChecker } from '../../../../../plugins/kibana_utils/public'; -import { Vis, VisualizationController } from '../../vis'; -import { getUpdateStatus } from '../../vis/update_status'; +import { PersistedState } from '../../../legacy_imports'; +import { Vis, VisualizationController } from '../vis'; +import { getUpdateStatus } from '../legacy/update_status'; +import { ResizeChecker } from '../../../../../../../plugins/kibana_utils/public'; interface VisualizationChartProps { onInit?: () => void; diff --git a/src/legacy/ui/public/visualize/components/visualization_noresults.test.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_noresults.test.js similarity index 100% rename from src/legacy/ui/public/visualize/components/visualization_noresults.test.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_noresults.test.js diff --git a/src/legacy/ui/public/visualize/components/visualization_noresults.tsx b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_noresults.tsx similarity index 90% rename from src/legacy/ui/public/visualize/components/visualization_noresults.tsx rename to src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_noresults.tsx index 8ba3f66ec4d861..5a964caa46b4bd 100644 --- a/src/legacy/ui/public/visualize/components/visualization_noresults.tsx +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/components/visualization_noresults.tsx @@ -19,7 +19,6 @@ import { EuiIcon, EuiSpacer, EuiText } from '@elastic/eui'; import React from 'react'; -import { dispatchRenderComplete } from '../../../../../plugins/kibana_utils/public'; interface VisualizationNoResultsProps { onInit?: () => void; @@ -58,8 +57,5 @@ export class VisualizationNoResults extends React.Component void; @@ -59,8 +58,5 @@ export class VisualizationRequestError extends React.Component ({ npStart: { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/index.ts index 480796c3771752..4558621dc6615a 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/index.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/index.ts @@ -17,4 +17,5 @@ * under the License. */ -export * from './filters_service'; +// @ts-ignore +export * from './vis_filters'; diff --git a/src/legacy/ui/public/vis/vis_filters/vis_filters.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/vis_filters.js similarity index 84% rename from src/legacy/ui/public/vis/vis_filters/vis_filters.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/filters/vis_filters.js index 18d633e1b5fb2f..9e72cb3402a5a4 100644 --- a/src/legacy/ui/public/vis/vis_filters/vis_filters.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/vis_filters.js @@ -17,10 +17,8 @@ * under the License. */ -import _ from 'lodash'; -import { pushFilterBarFilters } from '../push_filters'; import { onBrushEvent } from './brush_event'; -import { uniqFilters, esFilters } from '../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../../plugins/data/public'; /** * For terms aggregations on `__other__` buckets, this assembles a list of applicable filter @@ -104,20 +102,4 @@ const createFiltersFromEvent = (event) => { return filters; }; -const VisFiltersProvider = (getAppState, $timeout) => { - - const pushFilters = (filters, simulate) => { - const appState = getAppState(); - if (filters.length && !simulate) { - pushFilterBarFilters(appState, uniqFilters(filters)); - // to trigger angular digest cycle, we can get rid of this once we have either new filterManager or actions API - $timeout(_.noop, 0); - } - }; - - return { - pushFilters, - }; -}; - -export { VisFiltersProvider, createFilter, createFiltersFromEvent, onBrushEvent }; +export { createFilter, createFiltersFromEvent, onBrushEvent }; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts index ceb0ca5316354a..2e9d055858a483 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts @@ -43,4 +43,12 @@ export function plugin(initializerContext: PluginInitializerContext) { } /** @public static code */ -// TODO once items are moved from ui/vis into this service +export { Vis, VisParams, VisState } from './vis'; +export * from './filters'; + +export { Status } from './legacy/update_status'; +export { buildPipeline, buildVislibDimensions, SchemaConfig } from './legacy/build_pipeline'; + +// @ts-ignore +export { updateOldState } from './legacy/vis_update_state'; +export { calculateObjectHash } from './legacy/calculate_object_hash'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts index 16cec5d2d9e91c..1e86aa64d1fa85 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy.ts @@ -21,18 +21,11 @@ import { PluginInitializerContext } from 'src/core/public'; /* eslint-disable @kbn/eslint/no-restricted-paths */ import { npSetup, npStart } from 'ui/new_platform'; -// @ts-ignore -import { VisFiltersProvider, createFilter } from 'ui/vis/vis_filters'; /* eslint-enable @kbn/eslint/no-restricted-paths */ import { plugin } from '.'; const pluginInstance = plugin({} as PluginInitializerContext); -export const setup = pluginInstance.setup(npSetup.core, { - __LEGACY: { - VisFiltersProvider, - createFilter, - }, -}); +export const setup = pluginInstance.setup(npSetup.core); export const start = pluginInstance.start(npStart.core); diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/__snapshots__/build_pipeline.test.ts.snap b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap similarity index 100% rename from src/legacy/ui/public/visualize/loader/pipeline_helpers/__snapshots__/build_pipeline.test.ts.snap rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__snapshots__/build_pipeline.test.ts.snap diff --git a/src/legacy/ui/public/vis/__tests__/_vis.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/_vis.js similarity index 96% rename from src/legacy/ui/public/vis/__tests__/_vis.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/_vis.js index 1d5e2de6dafe37..bd8ce8381608ca 100644 --- a/src/legacy/ui/public/vis/__tests__/_vis.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/_vis.js @@ -20,9 +20,9 @@ import _ from 'lodash'; import ngMock from 'ng_mock'; import expect from '@kbn/expect'; -import { Vis } from '..'; +import { Vis } from '../..'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import { start as visualizations } from '../../../../core_plugins/visualizations/public/np_ready/public/legacy'; +import { start as visualizations } from '../../legacy'; describe('Vis Class', function () { let indexPattern; diff --git a/src/legacy/ui/public/vis/__tests__/vis_types/base_vis_type.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/base_vis_type.js similarity index 96% rename from src/legacy/ui/public/vis/__tests__/vis_types/base_vis_type.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/base_vis_type.js index d2e545bde82412..5e03b205e76e47 100644 --- a/src/legacy/ui/public/vis/__tests__/vis_types/base_vis_type.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/base_vis_type.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { BaseVisType } from '../../vis_types/base_vis_type'; +import { BaseVisType } from '../../../types/base_vis_type'; describe('Base Vis Type', function () { beforeEach(ngMock.module('kibana')); diff --git a/src/legacy/ui/public/vis/__tests__/vis_types/react_vis_type.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/react_vis_type.js similarity index 96% rename from src/legacy/ui/public/vis/__tests__/vis_types/react_vis_type.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/react_vis_type.js index b2478655d1cfea..bc16c6acbc20c8 100644 --- a/src/legacy/ui/public/vis/__tests__/vis_types/react_vis_type.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_types/react_vis_type.js @@ -19,7 +19,7 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { ReactVisType } from '../../vis_types/react_vis_type'; +import { ReactVisType } from '../../../types/react_vis_type'; describe('React Vis Type', function () { diff --git a/src/legacy/ui/public/vis/__tests__/vis_update_objs/gauge_objs.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_update_objs/gauge_objs.js similarity index 100% rename from src/legacy/ui/public/vis/__tests__/vis_update_objs/gauge_objs.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/__tests__/vis_update_objs/gauge_objs.js diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts similarity index 98% rename from src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts index 608a8b9ce8aa7f..e733bad2c01274 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.test.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts @@ -26,9 +26,9 @@ import { SchemaConfig, Schemas, } from './build_pipeline'; -import { Vis, VisState } from 'ui/vis'; -import { AggConfig } from 'ui/agg_types/agg_config'; -import { searchSourceMock } from '../../../courier/search_source/mocks'; +import { Vis, VisState } from '..'; +import { AggConfig } from '../../../legacy_imports'; +import { searchSourceMock } from '../../../legacy_mocks'; jest.mock('ui/new_platform'); jest.mock('ui/agg_types/buckets/date_histogram', () => ({ diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts similarity index 98% rename from src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts index ca9540b4d37370..0f9e9c11a9dbc2 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/build_pipeline.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts @@ -19,13 +19,17 @@ import { cloneDeep, get } from 'lodash'; // @ts-ignore -import { setBounds } from 'ui/agg_types'; -import { AggConfig, Vis, VisParams, VisState } from 'ui/vis'; -import { isDateHistogramBucketAggConfig } from 'ui/agg_types/buckets/date_histogram'; import moment from 'moment'; import { SerializedFieldFormat } from 'src/plugins/expressions/public'; -import { SearchSourceContract } from '../../../courier/types'; -import { createFormat } from './utilities'; +import { + AggConfig, + setBounds, + isDateHistogramBucketAggConfig, + createFormat, +} from '../../../legacy_imports'; +// eslint-disable-next-line +import { SearchSourceContract } from '../../../../../../ui/public/courier/search_source/search_source'; +import { Vis, VisParams, VisState } from '..'; interface SchemaConfigParams { precision?: number; diff --git a/src/legacy/ui/public/vis/lib/calculate_object_hash.d.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/calculate_object_hash.d.ts similarity index 100% rename from src/legacy/ui/public/vis/lib/calculate_object_hash.d.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/calculate_object_hash.d.ts diff --git a/src/legacy/ui/public/vis/lib/calculate_object_hash.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/calculate_object_hash.js similarity index 100% rename from src/legacy/ui/public/vis/lib/calculate_object_hash.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/calculate_object_hash.js diff --git a/src/legacy/ui/public/utils/memoize.test.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/memoize.test.ts similarity index 100% rename from src/legacy/ui/public/utils/memoize.test.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/memoize.test.ts diff --git a/src/legacy/ui/public/utils/memoize.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/memoize.ts similarity index 100% rename from src/legacy/ui/public/utils/memoize.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/memoize.ts diff --git a/src/legacy/ui/public/vis/update_status.test.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.test.js similarity index 100% rename from src/legacy/ui/public/vis/update_status.test.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.test.js diff --git a/src/legacy/ui/public/vis/update_status.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.ts similarity index 95% rename from src/legacy/ui/public/vis/update_status.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.ts index f5e162f50dcf6a..6d32a6df5f1ec6 100644 --- a/src/legacy/ui/public/vis/update_status.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/update_status.ts @@ -17,9 +17,9 @@ * under the License. */ -import { PersistedState } from '../persisted_state'; -import { calculateObjectHash } from './lib/calculate_object_hash'; -import { Vis } from './vis'; +import { PersistedState } from '../../../legacy_imports'; +import { calculateObjectHash } from './calculate_object_hash'; +import { Vis } from '../vis'; enum Status { AGGS = 'aggs', diff --git a/src/legacy/ui/public/vis/vis_update.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/vis_update.js similarity index 100% rename from src/legacy/ui/public/vis/vis_update.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/vis_update.js diff --git a/src/legacy/ui/public/vis/vis_update_state.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/vis_update_state.js similarity index 100% rename from src/legacy/ui/public/vis/vis_update_state.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/vis_update_state.js diff --git a/src/legacy/ui/public/vis/vis_update_state.test.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/vis_update_state.test.js similarity index 100% rename from src/legacy/ui/public/vis/vis_update_state.test.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/vis_update_state.test.js diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts index 5d7ab12a677cfd..88c5768a0b4e43 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/mocks.ts @@ -29,18 +29,10 @@ import { VisualizationsSetup, VisualizationsStart } from './'; import { VisualizationsPlugin } from './plugin'; import { coreMock } from '../../../../../../core/public/mocks'; -/* eslint-disable */ -// @ts-ignore -import { VisFiltersProvider, createFilter } from 'ui/vis/vis_filters'; -/* eslint-enable */ - const createSetupContract = (): VisualizationsSetup => ({ - filters: { - VisFiltersProvider: jest.fn(), - createFilter: jest.fn(), - }, types: { - registerVisualization: jest.fn(), + createBaseVisualization: jest.fn(), + createReactVisualization: jest.fn(), registerAlias: jest.fn(), hideTypes: jest.fn(), }, @@ -57,12 +49,7 @@ const createStartContract = (): VisualizationsStart => ({ const createInstance = async () => { const plugin = new VisualizationsPlugin({} as PluginInitializerContext); - const setup = plugin.setup(coreMock.createSetup(), { - __LEGACY: { - VisFiltersProvider, - createFilter, - }, - }); + const setup = plugin.setup(coreMock.createSetup()); const doStart = () => plugin.start(coreMock.createStart()); return { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts index 0e77c3ce883850..ccf6aaf152ea4b 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/plugin.ts @@ -17,21 +17,8 @@ * under the License. */ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; - -import { FiltersService, FiltersSetup } from './filters'; import { TypesService, TypesSetup, TypesStart } from './types'; - -/** - * Interface for any dependencies on other plugins' contracts. - * - * @internal - */ -interface VisualizationsPluginSetupDependencies { - __LEGACY: { - VisFiltersProvider: any; - createFilter: any; - }; -} +import { setUISettings, setTypes, setI18n } from './services'; /** * Interface for this plugin's returned setup/start contracts. @@ -39,7 +26,6 @@ interface VisualizationsPluginSetupDependencies { * @public */ export interface VisualizationsSetup { - filters: FiltersSetup; types: TypesSetup; } @@ -56,34 +42,28 @@ export interface VisualizationsStart { * * @internal */ -export class VisualizationsPlugin - implements - Plugin { - private readonly filters: FiltersService = new FiltersService(); +export class VisualizationsPlugin implements Plugin { private readonly types: TypesService = new TypesService(); constructor(initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup, { __LEGACY }: VisualizationsPluginSetupDependencies) { - const { VisFiltersProvider, createFilter } = __LEGACY; - + public setup(core: CoreSetup) { + setUISettings(core.uiSettings); return { - filters: this.filters.setup({ - VisFiltersProvider, - createFilter, - }), types: this.types.setup(), }; } public start(core: CoreStart) { + setI18n(core.i18n); + const types = this.types.start(); + setTypes(types); return { - types: this.types.start(), + types, }; } public stop() { - this.filters.stop(); this.types.stop(); } } diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/filters_service.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts similarity index 63% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/filters/filters_service.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts index 51709f365dbbdc..63afbca71a2800 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/filters_service.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts @@ -17,28 +17,14 @@ * under the License. */ -interface SetupDependecies { - VisFiltersProvider: any; - createFilter: any; -} +import { I18nStart, UiSettingsClientContract } from 'src/core/public'; +import { TypesStart } from './types'; +import { createGetterSetter } from '../../../../../../plugins/kibana_utils/public'; -/** - * Vis Filters Service - * - * @internal - */ -export class FiltersService { - public setup({ VisFiltersProvider, createFilter }: SetupDependecies) { - return { - VisFiltersProvider, - createFilter, - }; - } +export const [getUISettings, setUISettings] = createGetterSetter( + 'UISettings' +); - public stop() { - // nothing to do here yet - } -} +export const [getTypes, setTypes] = createGetterSetter('Types'); -/** @public */ -export type FiltersSetup = ReturnType; +export const [getI18n, setI18n] = createGetterSetter('I18n'); diff --git a/src/legacy/ui/public/vis/vis_types/base_vis_type.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js similarity index 97% rename from src/legacy/ui/public/vis/vis_types/base_vis_type.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js index 30d806bc305af4..2dc657ecde05b2 100644 --- a/src/legacy/ui/public/vis/vis_types/base_vis_type.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { createFiltersFromEvent, onBrushEvent } from '../vis_filters'; +import { createFiltersFromEvent, onBrushEvent } from '../filters'; export class BaseVisType { constructor(opts = {}) { diff --git a/src/legacy/ui/public/vis/vis_types/react_vis_type.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/react_vis_type.js similarity index 93% rename from src/legacy/ui/public/vis/vis_types/react_vis_type.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/types/react_vis_type.js index 29f809a65eddac..2566e25c17343b 100644 --- a/src/legacy/ui/public/vis/vis_types/react_vis_type.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/react_vis_type.js @@ -19,11 +19,9 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -import chrome from '../../chrome'; -import { I18nContext } from '../../i18n'; +import { getUISettings, getI18n } from '../services'; import { BaseVisType } from './base_vis_type'; - class ReactVisController { constructor(element, vis) { this.el = element; @@ -33,9 +31,11 @@ class ReactVisController { render(visData, visParams, updateStatus) { this.visData = visData; + const I18nContext = getI18n().Context; + return new Promise((resolve) => { const Component = this.vis.type.visConfig.component; - const config = chrome.getUiSettingsClient(); + const config = getUISettings(); render( = {}; private unregisteredHiddenTypes: string[] = []; + public setup() { - return { - registerVisualization: (registerFn: () => VisType) => { - const visDefinition = registerFn(); - if (this.unregisteredHiddenTypes.includes(visDefinition.name)) { - visDefinition.hidden = true; - } + const registerVisualization = (registerFn: () => VisType) => { + const visDefinition = registerFn(); + if (this.unregisteredHiddenTypes.includes(visDefinition.name)) { + visDefinition.hidden = true; + } - if (this.types[visDefinition.name]) { - throw new Error('type already exists!'); - } - this.types[visDefinition.name] = visDefinition; + if (this.types[visDefinition.name]) { + throw new Error('type already exists!'); + } + this.types[visDefinition.name] = visDefinition; + }; + return { + createBaseVisualization: (config: any) => { + const vis = new BaseVisType(config); + registerVisualization(() => vis); + }, + createReactVisualization: (config: any) => { + const vis = new ReactVisType(config); + registerVisualization(() => vis); }, registerAlias: visTypeAliasRegistry.add, hideTypes: (typeNames: string[]) => { diff --git a/src/legacy/ui/public/vis/vis.d.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.d.ts similarity index 76% rename from src/legacy/ui/public/vis/vis.d.ts rename to src/legacy/core_plugins/visualizations/public/np_ready/public/vis.d.ts index e16562641801ea..6e6a2174d6ad14 100644 --- a/src/legacy/ui/public/vis/vis.d.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.d.ts @@ -17,8 +17,9 @@ * under the License. */ -import { VisType } from './vis_types/vis_type'; -import { AggConfigs } from '../agg_types/agg_configs'; +import { VisType } from './types'; +import { AggConfigs } from '../../legacy_imports'; +import { Status } from './legacy/update_status'; export interface Vis { type: VisType; @@ -40,3 +41,10 @@ export interface VisState { params: VisParams; aggs: AggConfigs; } + +export declare class VisualizationController { + constructor(element: HTMLElement, vis: Vis); + public render(visData: any, visParams: any, update: { [key in Status]: boolean }): Promise; + public destroy(): void; + public isLoaded?(): Promise | void; +} diff --git a/src/legacy/ui/public/vis/vis.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js similarity index 91% rename from src/legacy/ui/public/vis/vis.js rename to src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js index 304289a5cfa073..558fff7d0076e9 100644 --- a/src/legacy/ui/public/vis/vis.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js @@ -29,16 +29,9 @@ import { EventEmitter } from 'events'; import _ from 'lodash'; -import '../render_complete/directive'; -import { AggConfigs } from '../agg_types/agg_configs'; -import { PersistedState } from '../persisted_state'; -import { updateVisualizationConfig } from './vis_update'; -import { SearchSource } from '../courier'; -import { start as visualizations } from '../../../core_plugins/visualizations/public/np_ready/public/legacy'; - -import '../directives/bind'; - -const visTypes = visualizations.types; +import { AggConfigs, PersistedState } from '../../legacy_imports'; +import { updateVisualizationConfig } from './legacy/vis_update'; +import { getTypes } from './services'; class Vis extends EventEmitter { constructor(indexPattern, visState) { @@ -50,6 +43,7 @@ class Vis extends EventEmitter { type: visState }; } + this.indexPattern = indexPattern; this._setUiState(new PersistedState()); this.setCurrentState(visState); @@ -60,7 +54,6 @@ class Vis extends EventEmitter { this.sessionState = {}; this.API = { - SearchSource: SearchSource, events: { filter: data => this.eventsSubject.next({ name: 'filterBucket', data }), brush: data => this.eventsSubject.next({ name: 'brush', data }), @@ -72,7 +65,7 @@ class Vis extends EventEmitter { this.title = state.title || ''; const type = state.type || this.type; if (_.isString(type)) { - this.type = visTypes.get(type); + this.type = getTypes().get(type); if (!this.type) { throw new Error(`Invalid type "${type}"`); } diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 9cc4e30d4252dd..6f2730476956ee 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -38,7 +38,7 @@ import { LegacyServiceSetupDeps, LegacyServiceStartDeps } from '../../core/serve import { SavedObjectsManagement } from '../../core/server/saved_objects/management'; import { ApmOssPlugin } from '../core_plugins/apm_oss'; import { CallClusterWithRequest, ElasticsearchPlugin } from '../core_plugins/elasticsearch'; - +import { UsageCollectionSetup } from '../../plugins/usage_collection/server'; import { CapabilitiesModifier } from './capabilities'; import { IndexPatternsServiceFactory } from './index_patterns'; import { Capabilities } from '../../core/public'; @@ -67,7 +67,6 @@ declare module 'hapi' { config: () => KibanaConfig; indexPatternsServiceFactory: IndexPatternsServiceFactory; savedObjects: SavedObjectsLegacyService; - usage: { collectorSet: any }; injectUiAppVars: (pluginName: string, getAppVars: () => { [key: string]: any }) => void; getHiddenUiAppById(appId: string): UiApp; registerCapabilitiesModifier: (provider: CapabilitiesModifier) => void; @@ -101,6 +100,11 @@ declare module 'hapi' { type KbnMixinFunc = (kbnServer: KbnServer, server: Server, config: any) => Promise | void; +export interface PluginsSetup { + usageCollection: UsageCollectionSetup; + [key: string]: object; +} + // eslint-disable-next-line import/no-default-export export default class KbnServer { public readonly newPlatform: { @@ -120,7 +124,7 @@ export default class KbnServer { }; setup: { core: CoreSetup; - plugins: Record; + plugins: PluginsSetup; }; start: { core: CoreSetup; diff --git a/src/legacy/server/kbn_server.js b/src/legacy/server/kbn_server.js index f7ed56b10c267a..e5f182c931d80a 100644 --- a/src/legacy/server/kbn_server.js +++ b/src/legacy/server/kbn_server.js @@ -28,7 +28,6 @@ import httpMixin from './http'; import { coreMixin } from './core'; import { loggingMixin } from './logging'; import warningsMixin from './warnings'; -import { usageMixin } from './usage'; import { statusMixin } from './status'; import pidMixin from './pid'; import { configDeprecationWarningsMixin } from './config/deprecation_warnings'; @@ -94,7 +93,6 @@ export default class KbnServer { loggingMixin, configDeprecationWarningsMixin, warningsMixin, - usageMixin, statusMixin, // writes pid file diff --git a/src/legacy/server/sample_data/usage/collector.ts b/src/legacy/server/sample_data/usage/collector.ts index 8561a6c3f10078..bcb5e7be2597a9 100644 --- a/src/legacy/server/sample_data/usage/collector.ts +++ b/src/legacy/server/sample_data/usage/collector.ts @@ -17,26 +17,25 @@ * under the License. */ -import * as Hapi from 'hapi'; +import { Server } from 'hapi'; import { fetchProvider } from './collector_fetch'; +import { UsageCollectionSetup } from '../../../../plugins/usage_collection/server'; -interface KbnServer extends Hapi.Server { - usage: any; -} - -export function makeSampleDataUsageCollector(server: KbnServer) { +export function makeSampleDataUsageCollector( + usageCollection: UsageCollectionSetup, + server: Server +) { let index: string; try { index = server.config().get('kibana.index'); } catch (err) { return; // kibana plugin is not enabled (test environment) } + const collector = usageCollection.makeUsageCollector({ + type: 'sample-data', + fetch: fetchProvider(index), + isReady: () => true, + }); - server.usage.collectorSet.register( - server.usage.collectorSet.makeUsageCollector({ - type: 'sample-data', - fetch: fetchProvider(index), - isReady: () => true, - }) - ); + usageCollection.registerCollector(collector); } diff --git a/src/legacy/server/status/collectors/get_ops_stats_collector.js b/src/legacy/server/status/collectors/get_ops_stats_collector.js index aded85384fd85c..116e588c5ade68 100644 --- a/src/legacy/server/status/collectors/get_ops_stats_collector.js +++ b/src/legacy/server/status/collectors/get_ops_stats_collector.js @@ -35,9 +35,8 @@ import { getKibanaInfoForStats } from '../lib'; * the metrics. * See PR comment in https://github.com/elastic/kibana/pull/20577/files#r202416647 */ -export function getOpsStatsCollector(server, kbnServer) { - const { collectorSet } = server.usage; - return collectorSet.makeStatsCollector({ +export function getOpsStatsCollector(usageCollection, server, kbnServer) { + return usageCollection.makeStatsCollector({ type: KIBANA_STATS_TYPE, fetch: () => { return { @@ -49,3 +48,10 @@ export function getOpsStatsCollector(server, kbnServer) { ignoreForInternalUploader: true, // Ignore this one from internal uploader. A different stats collector is used there. }); } + +export function registerOpsStatsCollector(usageCollection, server, kbnServer) { + if (usageCollection) { + const collector = getOpsStatsCollector(usageCollection, server, kbnServer); + usageCollection.registerCollector(collector); + } +} diff --git a/src/legacy/server/status/collectors/index.js b/src/legacy/server/status/collectors/index.js index 4310dff7359ef8..92d9e601bbb35e 100644 --- a/src/legacy/server/status/collectors/index.js +++ b/src/legacy/server/status/collectors/index.js @@ -17,4 +17,4 @@ * under the License. */ -export { getOpsStatsCollector } from './get_ops_stats_collector'; +export { registerOpsStatsCollector } from './get_ops_stats_collector'; diff --git a/src/legacy/server/status/index.js b/src/legacy/server/status/index.js index dda20878605e59..ba2f835599bc98 100644 --- a/src/legacy/server/status/index.js +++ b/src/legacy/server/status/index.js @@ -20,17 +20,15 @@ import ServerStatus from './server_status'; import { Metrics } from './lib/metrics'; import { registerStatusPage, registerStatusApi, registerStatsApi } from './routes'; -import { getOpsStatsCollector } from './collectors'; +import { registerOpsStatsCollector } from './collectors'; import Oppsy from 'oppsy'; import { cloneDeep } from 'lodash'; import { getOSInfo } from './lib/get_os_info'; export function statusMixin(kbnServer, server, config) { kbnServer.status = new ServerStatus(kbnServer.server); - - const statsCollector = getOpsStatsCollector(server, kbnServer); - const { collectorSet } = server.usage; - collectorSet.register(statsCollector); + const { usageCollection } = server.newPlatform.setup.plugins; + registerOpsStatsCollector(usageCollection, server, kbnServer); const metrics = new Metrics(config, server); @@ -57,7 +55,7 @@ export function statusMixin(kbnServer, server, config) { // init routes registerStatusPage(kbnServer, server, config); registerStatusApi(kbnServer, server, config); - registerStatsApi(kbnServer, server, config); + registerStatsApi(usageCollection, server, config); // expore shared functionality server.decorate('server', 'getOSInfo', getOSInfo); diff --git a/src/legacy/server/status/routes/api/register_stats.js b/src/legacy/server/status/routes/api/register_stats.js index 91272ead1d2c12..366d36860731c0 100644 --- a/src/legacy/server/status/routes/api/register_stats.js +++ b/src/legacy/server/status/routes/api/register_stats.js @@ -29,7 +29,7 @@ const STATS_NOT_READY_MESSAGE = i18n.translate('server.stats.notReadyMessage', { /* * API for Kibana meta info and accumulated operations stats - * Including ?extended in the query string fetches Elasticsearch cluster_uuid and server.usage.collectorSet data + * Including ?extended in the query string fetches Elasticsearch cluster_uuid and usageCollection data * - Requests to set isExtended = true * GET /api/stats?extended=true * GET /api/stats?extended @@ -37,9 +37,8 @@ const STATS_NOT_READY_MESSAGE = i18n.translate('server.stats.notReadyMessage', { * - Any other value causes a statusCode 400 response (Bad Request) * Including ?exclude_usage in the query string excludes the usage stats from the response. Same value semantics as ?extended */ -export function registerStatsApi(kbnServer, server, config) { +export function registerStatsApi(usageCollection, server, config) { const wrapAuth = wrapAuthConfig(config.get('status.allowAnonymous')); - const { collectorSet } = server.usage; const getClusterUuid = async callCluster => { const { cluster_uuid: uuid } = await callCluster('info', { filterPath: 'cluster_uuid', }); @@ -47,8 +46,8 @@ export function registerStatsApi(kbnServer, server, config) { }; const getUsage = async callCluster => { - const usage = await collectorSet.bulkFetchUsage(callCluster); - return collectorSet.toObject(usage); + const usage = await usageCollection.bulkFetchUsage(callCluster); + return usageCollection.toObject(usage); }; server.route( @@ -74,7 +73,7 @@ export function registerStatsApi(kbnServer, server, config) { if (isExtended) { const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('admin'); const callCluster = (...args) => callWithRequest(req, ...args); - const collectorsReady = await collectorSet.areAllCollectorsReady(); + const collectorsReady = await usageCollection.areAllCollectorsReady(); if (shouldGetUsage && !collectorsReady) { return boom.serverUnavailable(STATS_NOT_READY_MESSAGE); @@ -126,7 +125,7 @@ export function registerStatsApi(kbnServer, server, config) { }; } else { - extended = collectorSet.toApiFieldNames({ + extended = usageCollection.toApiFieldNames({ usage: modifiedUsage, clusterUuid }); @@ -139,12 +138,12 @@ export function registerStatsApi(kbnServer, server, config) { /* kibana_stats gets singled out from the collector set as it is used * for health-checking Kibana and fetch does not rely on fetching data * from ES */ - const kibanaStatsCollector = collectorSet.getCollectorByType(KIBANA_STATS_TYPE); + const kibanaStatsCollector = usageCollection.getCollectorByType(KIBANA_STATS_TYPE); if (!await kibanaStatsCollector.isReady()) { return boom.serverUnavailable(STATS_NOT_READY_MESSAGE); } let kibanaStats = await kibanaStatsCollector.fetch(); - kibanaStats = collectorSet.toApiFieldNames(kibanaStats); + kibanaStats = usageCollection.toApiFieldNames(kibanaStats); return { ...kibanaStats, diff --git a/src/legacy/server/usage/README.md b/src/legacy/server/usage/README.md deleted file mode 100644 index 5c4bcc05bbc383..00000000000000 --- a/src/legacy/server/usage/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# Kibana Telemetry Service - -Telemetry allows Kibana features to have usage tracked in the wild. The general term "telemetry" refers to multiple things: - -1. Integrating with the telemetry service to express how to collect usage data (Collecting). -2. Sending a payload of usage data up to Elastic's telemetry cluster, once per browser per day (Sending). -3. Viewing usage data in the Kibana instance of the telemetry cluster (Viewing). - -You, the feature or plugin developer, mainly need to worry about the first meaning: collecting. To integrate with the telemetry services for usage collection of your feature, there are 2 steps: - -1. Create a usage collector using a factory function -2. Register the usage collector with the Telemetry service - -NOTE: To a lesser extent, there's also a need to update the telemetry payload of Kibana stats and telemetry cluster field mappings to include your fields. This part is typically handled not by you, the developer, but different maintainers of the telemetry cluster. Usually, this step just means talk to the Platform team and have them approve your data model or added fields. - -## Creating and Registering Usage Collector - -A usage collector object is an instance of a class called `UsageCollector`. A factory function on `server.usage.collectorSet` object allows you to create an instance of this class. 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. - -Example: - -```js -// create usage collector -const myCollector = server.usage.collectorSet.makeUsageCollector({ - type: MY_USAGE_TYPE, - fetch: async callCluster => { - - // query ES and get some data - // summarize the data into a model - // return the modeled object that includes whatever you want to track - - return { - my_objects: { - total: SOME_NUMBER - } - }; - }, -}); - -// register usage collector -server.usage.collectorSet.register(myCollector); -``` - -Some background: The `callCluster` that gets passed to the `fetch` method is created in a way that's a bit tricky, to support multiple contexts the `fetch` method could be called. Your `fetch` method could get called as a result of an HTTP API request: in this case, the `callCluster` function wraps `callWithRequest`, and the request headers are expected to have read privilege on the entire `.kibana` index. The use case for this is stats pulled from a Kibana Metricbeat module, where the Beat calls Kibana's stats API in Kibana to invoke collection. - -The fetch method also might be called through an internal background task on the Kibana server, which currently lives in the `kibana_monitoring` module of the X-Pack Monitoring plugin, that polls for data and uploads it to Elasticsearch through a bulk API exposed by the Monitoring plugin for Elasticsearch. In this case, the `callCluster` method will be the internal system user and will have read privilege over the entire `.kibana` index. - -Note: there will be many cases where you won't need to use the `callCluster` function that gets passed in to your `fetch` method at all. Your feature might have an accumulating value in server memory, or read something from the OS. - - -Typically, a plugin will create the collector object and register it with the Telemetry service from the `init` method of the plugin definition, or a helper module called from `init`. - -## 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. - -As of the time of this writing (pre-6.5.0) there are a few unpleasant realities with this module. Today, this module has to be aware of all the features that have integrated with it, which it does from hard-coding. It does this because at the time of creation, the payload implemented a designed model where X-Pack plugin info went together regardless if it was ES-specific or Kibana-specific. In hindsight, all the Kibana data could just be put together, X-Pack or not, which it could do in a generic way. This is a known problem and a solution will be implemented in an upcoming refactoring phase, as this would break the contract for model of data sent in the payload. - -The second reality is that new fields added to the telemetry payload currently mean that telemetry cluster field mappings have to be updated, so they can be searched and aggregated in Kibana visualizations. This is also a short-term obligation. In the next refactoring phase, collectors will need to use a proscribed data model that eliminates maintenance of mappings in the telemetry cluster. - -## Testing - -There are a few ways you can test that your usage collector is working properly. - -1. The `/api/stats?extended=true` HTTP API in Kibana (added in 6.4.0) will call the fetch methods of all the registered collectors, and add them to a stats object you can see in a browser or in curl. To test that your usage collector has been registered correctly and that it has the model of data you expected it to have, call that HTTP API manually and you should see a key in the `usage` object of the response named after your usage collector's `type` field. This method tests the Metricbeat scenario described above where `callCluster` wraps `callWithRequest`. -2. There is a dev script in x-pack that will give a sample of a payload of data that gets sent up to the telemetry cluster for the sending phase of telemetry. Collected data comes from: - - The `.monitoring-*` indices, when Monitoring is enabled. Monitoring enhances the sent payload of telemetry by producing usage data potentially of multiple clusters that exist in the monitoring data. Monitoring data is time-based, and the time frame of collection is the last 15 minutes. - - Live-pulled from ES API endpoints. This will get just real-time stats without context of historical data. ✳ - - The dev script in x-pack can be run on the command-line with: - ``` - cd x-pack - node scripts/api_debug.js telemetry --host=http://localhost:5601 - ``` - Where `http://localhost:5601` is a Kibana server running in dev mode. If needed, authentication and basePath info can be provided in the command as well. - - Automatic inclusion of all the stats fetched by collectors is added in https://github.com/elastic/kibana/pull/22336 / 6.5.0 -3. In Dev mode, Kibana will send telemetry data to a staging telemetry cluster. Assuming you have access to the staging cluster, you can log in and check the latest documents for your new fields. -4. If you catch the network traffic coming from your browser when a telemetry payload is sent, you can examine the request payload body to see the data. This can be tricky as telemetry payloads are sent only once per day per browser. Use incognito mode or clear your localStorage data to force a telemetry payload. - -✳ At the time of this writing, there is an open issue that in the sending phase, Kibana usage collectors are not "live-pulled" from Kibana API endpoints if Monitoring is disabled. The implementation on this depends on a new secure way to live-pull the data from the end-user's browser, as it would not be appropriate to supply only partial data if the logged-in user only has partial access to `.kibana`. - -## FAQ - -1. **Can telemetry track UI interactions, such as button click?** - Brief answer: no. Telemetry collection happens on the server-side so the usage data will only include information that the server-side is aware of. There is no generic way to do this today, but UI-interaction KPIs can be tracked with a custom server endpoint that gets called for tracking when the UI event happens. -2. **Does the telemetry service have a hook that I can call whenever some event happens in my feature?** - Brief answer: no. Telemetry collection is a fetch model, not a push model. Telemetry fetches info from your collector. -3. **How should I design my data model?** - Keep it simple, and keep it to a model that Kibana will be able to understand. In short, that means don't rely on nested fields (arrays with objects). Flat arrays, such as arrays of strings are fine. -4. **Can the telemetry payload include dynamic fields?** - Yes. When you talk to the Platform team about new fields being added, point out specifically which properties will have dynamic inner fields. -5. **If I accumulate an event counter in server memory, which my fetch method returns, won't it reset when the Kibana server restarts?** - Yes, but that is not a major concern. A visualization on such info might be a date histogram that gets events-per-second or something, which would be impacted by server restarts, so we'll have to offset the beginning of the time range when we detect that the latest metric is smaller than the earliest metric. That would be a pretty custom visualization, but perhaps future Kibana enhancements will be able to support that. diff --git a/src/legacy/server/usage/classes/collector_set.js b/src/legacy/server/usage/classes/collector_set.js deleted file mode 100644 index 5a86992f0af719..00000000000000 --- a/src/legacy/server/usage/classes/collector_set.js +++ /dev/null @@ -1,206 +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 { snakeCase } from 'lodash'; -import { getCollectorLogger } from '../lib'; -import { Collector } from './collector'; -import { UsageCollector } from './usage_collector'; - -let _waitingForAllCollectorsTimestamp = null; - -/* - * A collector object has types registered into it with the register(type) - * function. Each type that gets registered defines how to fetch its own data - * and optionally, how to combine it into a unified payload for bulk upload. - */ -export class CollectorSet { - /* - * @param {Object} server - server object - * @param {Array} collectors to initialize, usually as a result of filtering another CollectorSet instance - */ - constructor(server, collectors = [], config = null) { - this._log = getCollectorLogger(server); - this._collectors = collectors; - - /* - * Helper Factory methods - * Define as instance properties to allow enclosing the server object - */ - this.makeStatsCollector = options => new Collector(server, options); - this.makeUsageCollector = options => new UsageCollector(server, options); - this._makeCollectorSetFromArray = collectorsArray => new CollectorSet(server, collectorsArray, config); - - this._maximumWaitTimeForAllCollectorsInS = config ? config.get('stats.maximumWaitTimeForAllCollectorsInS') : 60; - } - - /* - * @param collector {Collector} collector object - */ - register(collector) { - // check instanceof - if (!(collector instanceof Collector)) { - throw new Error('CollectorSet can only have Collector instances registered'); - } - - this._collectors.push(collector); - - if (collector.init) { - this._log.debug(`Initializing ${collector.type} collector`); - collector.init(); - } - } - - getCollectorByType(type) { - return this._collectors.find(c => c.type === type); - } - - // isUsageCollector(x: UsageCollector | any): x is UsageCollector { - isUsageCollector(x) { - return x instanceof UsageCollector; - } - - async areAllCollectorsReady(collectorSet = this) { - if (!(collectorSet instanceof CollectorSet)) { - throw new Error(`areAllCollectorsReady method given bad collectorSet parameter: ` + typeof collectorSet); - } - - const collectorTypesNotReady = []; - let allReady = true; - await collectorSet.asyncEach(async collector => { - if (!await collector.isReady()) { - allReady = false; - collectorTypesNotReady.push(collector.type); - } - }); - - if (!allReady && this._maximumWaitTimeForAllCollectorsInS >= 0) { - const nowTimestamp = +new Date(); - _waitingForAllCollectorsTimestamp = _waitingForAllCollectorsTimestamp || nowTimestamp; - const timeWaitedInMS = nowTimestamp - _waitingForAllCollectorsTimestamp; - const timeLeftInMS = (this._maximumWaitTimeForAllCollectorsInS * 1000) - timeWaitedInMS; - if (timeLeftInMS <= 0) { - this._log.debug(`All collectors are not ready (waiting for ${collectorTypesNotReady.join(',')}) ` - + `but we have waited the required ` - + `${this._maximumWaitTimeForAllCollectorsInS}s and will return data from all collectors that are ready.`); - return true; - } else { - this._log.debug(`All collectors are not ready. Waiting for ${timeLeftInMS}ms longer.`); - } - } else { - _waitingForAllCollectorsTimestamp = null; - } - - return allReady; - } - - /* - * Call a bunch of fetch methods and then do them in bulk - * @param {CollectorSet} collectorSet - a set of collectors to fetch. Default to all registered collectors - */ - async bulkFetch(callCluster, collectorSet = this) { - if (!(collectorSet instanceof CollectorSet)) { - throw new Error(`bulkFetch method given bad collectorSet parameter: ` + typeof collectorSet); - } - - const responses = []; - await collectorSet.asyncEach(async collector => { - this._log.debug(`Fetching data from ${collector.type} collector`); - try { - responses.push({ - type: collector.type, - result: await collector.fetchInternal(callCluster) - }); - } - catch (err) { - this._log.warn(err); - this._log.warn(`Unable to fetch data from ${collector.type} collector`); - } - }); - return responses; - } - - /* - * @return {new CollectorSet} - */ - getFilteredCollectorSet(filter) { - const filtered = this._collectors.filter(filter); - return this._makeCollectorSetFromArray(filtered); - } - - async bulkFetchUsage(callCluster) { - const usageCollectors = this.getFilteredCollectorSet(c => c instanceof UsageCollector); - return this.bulkFetch(callCluster, usageCollectors); - } - - // convert an array of fetched stats results into key/object - toObject(statsData) { - if (!statsData) return {}; - return statsData.reduce((accumulatedStats, { type, result }) => { - return { - ...accumulatedStats, - [type]: result, - }; - }, {}); - } - - // rename fields to use api conventions - toApiFieldNames(apiData) { - const getValueOrRecurse = value => { - if (value == null || typeof value !== 'object') { - return value; - } else { - return this.toApiFieldNames(value); // recurse - } - }; - - // handle array and return early, or return a reduced object - - if (Array.isArray(apiData)) { - return apiData.map(getValueOrRecurse); - } - - return Object.keys(apiData).reduce((accum, field) => { - const value = apiData[field]; - let newName = field; - newName = snakeCase(newName); - newName = newName.replace(/^(1|5|15)_m/, '$1m'); // os.load.15m, os.load.5m, os.load.1m - newName = newName.replace('_in_bytes', '_bytes'); - newName = newName.replace('_in_millis', '_ms'); - - return { - ...accum, - [newName]: getValueOrRecurse(value), - }; - }, {}); - } - - map(mapFn) { - return this._collectors.map(mapFn); - } - - some(someFn) { - return this._collectors.some(someFn); - } - - async asyncEach(eachFn) { - for (const collector of this._collectors) { - await eachFn(collector); - } - } -} diff --git a/src/legacy/server/usage/index.js b/src/legacy/server/usage/index.js deleted file mode 100644 index 2a02070a55f953..00000000000000 --- a/src/legacy/server/usage/index.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { CollectorSet } from './classes'; - -export function usageMixin(kbnServer, server, config) { - const collectorSet = new CollectorSet(server, undefined, config); - - /* - * expose the collector set object on the server - * provides factory methods for feature owners to create their own collector objects - * use collectorSet.register(collector) to register your feature's collector object(s) - */ - server.decorate('server', 'usage', { collectorSet }); -} diff --git a/src/legacy/server/usage/lib/get_collector_logger.js b/src/legacy/server/usage/lib/get_collector_logger.js deleted file mode 100644 index 023bf6bf635a88..00000000000000 --- a/src/legacy/server/usage/lib/get_collector_logger.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -const LOGGING_TAGS = ['stats-collection']; -/* - * @param {Object} server - * @return {Object} helpful logger object - */ -export function getCollectorLogger(server) { - return { - debug: message => server.log(['debug', ...LOGGING_TAGS], message), - info: message => server.log(['info', ...LOGGING_TAGS], message), - warn: message => server.log(['warning', ...LOGGING_TAGS], message) - }; -} diff --git a/src/legacy/server/usage/lib/index.js b/src/legacy/server/usage/lib/index.js deleted file mode 100644 index 7db3cd45065038..00000000000000 --- a/src/legacy/server/usage/lib/index.js +++ /dev/null @@ -1,20 +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 { getCollectorLogger } from './get_collector_logger'; diff --git a/src/legacy/ui/public/agg_types/agg_configs.ts b/src/legacy/ui/public/agg_types/agg_configs.ts index 2f6951891f84d0..b4ea0ec8bc465c 100644 --- a/src/legacy/ui/public/agg_types/agg_configs.ts +++ b/src/legacy/ui/public/agg_types/agg_configs.ts @@ -28,13 +28,14 @@ import _ from 'lodash'; import { TimeRange } from 'src/plugins/data/public'; -import { Schemas } from '../visualize/loader/pipeline_helpers/build_pipeline'; import { Schema } from '../vis/editors/default/schemas'; import { AggConfig, AggConfigOptions } from './agg_config'; import { AggGroupNames } from '../vis/editors/default/agg_groups'; import { IndexPattern } from '../../../core_plugins/data/public'; import { SearchSourceContract, FetchOptions } from '../courier/types'; +type Schemas = Record; + function removeParentAggs(obj: any) { for (const prop in obj) { if (prop === 'parentAggs') delete obj[prop]; diff --git a/src/legacy/ui/public/agg_types/buckets/date_histogram.ts b/src/legacy/ui/public/agg_types/buckets/date_histogram.ts index 03e358af5f1f06..6a87b2e88ac4cc 100644 --- a/src/legacy/ui/public/agg_types/buckets/date_histogram.ts +++ b/src/legacy/ui/public/agg_types/buckets/date_histogram.ts @@ -21,7 +21,7 @@ import _ from 'lodash'; import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; import { BUCKET_TYPES } from 'ui/agg_types/buckets/bucket_agg_types'; -import chrome from '../../chrome'; +import { npStart } from 'ui/new_platform'; import { BucketAggParam, BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { createFilterDateHistogram } from './create_filter/date_histogram'; import { intervalOptions } from './_interval_options'; @@ -39,7 +39,6 @@ import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; // @ts-ignore import { TimeBuckets } from '../../time_buckets'; -const config = chrome.getUiSettingsClient(); const detectedTimezone = moment.tz.guess(); const tzOffset = moment().format('Z'); @@ -224,6 +223,7 @@ export const dateHistogramBucketAgg = new BucketAggType config.get(...args); +const getConfig = (...args) => npStart.core.uiSettings.get(...args); function isValidMoment(m) { return m && ('isValid' in m) && m.isValid(); @@ -238,14 +235,14 @@ TimeBuckets.prototype.getInterval = function (useNormalizedEsInterval = true) { function readInterval() { const interval = self._i; if (moment.isDuration(interval)) return interval; - return calcAutoIntervalNear(config.get('histogram:barTarget'), Number(duration)); + return calcAutoIntervalNear(getConfig('histogram:barTarget'), Number(duration)); } // check to see if the interval should be scaled, and scale it if so function maybeScaleInterval(interval) { if (!self.hasBounds()) return interval; - const maxLength = config.get('histogram:maxBars'); + const maxLength = getConfig('histogram:maxBars'); const approxLen = duration / interval; let scaled; @@ -299,7 +296,7 @@ TimeBuckets.prototype.getInterval = function (useNormalizedEsInterval = true) { */ TimeBuckets.prototype.getScaledDateFormat = function () { const interval = this.getInterval(); - const rules = config.get('dateFormat:scaled'); + const rules = getConfig('dateFormat:scaled'); for (let i = rules.length - 1; i >= 0; i--) { const rule = rules[i]; @@ -308,7 +305,7 @@ TimeBuckets.prototype.getScaledDateFormat = function () { } } - return config.get('dateFormat'); + return getConfig('dateFormat'); }; TimeBuckets.prototype.getScaledDateFormatter = function () { diff --git a/src/legacy/ui/public/vis/__tests__/index.js b/src/legacy/ui/public/vis/__tests__/index.js index 93a0bf026ae5d1..46074f2c5197b7 100644 --- a/src/legacy/ui/public/vis/__tests__/index.js +++ b/src/legacy/ui/public/vis/__tests__/index.js @@ -19,6 +19,3 @@ import './_agg_config'; import './_agg_configs'; -import './_vis'; -describe('Vis Component', function () { -}); diff --git a/src/legacy/ui/public/vis/editors/default/controls/filter.tsx b/src/legacy/ui/public/vis/editors/default/controls/filter.tsx index e847a95ead4780..664a0b3e02a008 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/filter.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/filter.tsx @@ -20,7 +20,7 @@ import React, { useState } from 'react'; import { EuiForm, EuiButtonIcon, EuiFieldText, EuiFormRow, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { QueryBarInput } from 'plugins/data'; +import { QueryStringInput } from 'plugins/data'; import { Query } from 'src/plugins/data/public'; import { AggConfig } from '../../..'; import { npStart } from '../../../../new_platform'; @@ -100,7 +100,7 @@ function FilterRow({ ...npStart.core, }} > - onChangeValue(id, query, customLabel)} diff --git a/src/legacy/ui/public/vis/index.d.ts b/src/legacy/ui/public/vis/index.d.ts index 791ce2563e0f17..85798549691a55 100644 --- a/src/legacy/ui/public/vis/index.d.ts +++ b/src/legacy/ui/public/vis/index.d.ts @@ -18,5 +18,4 @@ */ export { AggConfig } from '../agg_types/agg_config'; -export { Vis, VisParams, VisState } from './vis'; -export { VisualizationController, VisType } from './vis_types/vis_type'; +export { Vis, VisParams, VisState, VisType } from '../../../core_plugins/visualizations/public'; diff --git a/src/legacy/ui/public/vis/index.js b/src/legacy/ui/public/vis/index.js index 05cd030f7d1006..aaee86c3789845 100644 --- a/src/legacy/ui/public/vis/index.js +++ b/src/legacy/ui/public/vis/index.js @@ -17,4 +17,4 @@ * under the License. */ -export { Vis } from './vis'; +export { Vis } from '../../../core_plugins/visualizations/public/np_ready/public/vis'; diff --git a/src/legacy/ui/public/vis/push_filters.js b/src/legacy/ui/public/vis/push_filters.js deleted file mode 100644 index 771de14f9446d5..00000000000000 --- a/src/legacy/ui/public/vis/push_filters.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -// TODO: should it be here or in vis filters (only place where it's used). -// $newFilters is not defined by filter_bar as well. -export function pushFilterBarFilters($state, filters) { - if (!_.isObject($state)) throw new Error('pushFilters requires a state object'); - $state.$newFilters = filters; -} diff --git a/src/legacy/ui/public/vis/vis_filters/index.js b/src/legacy/ui/public/vis/vis_filters/index.js deleted file mode 100644 index 1236e88a528038..00000000000000 --- a/src/legacy/ui/public/vis/vis_filters/index.js +++ /dev/null @@ -1,20 +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 { VisFiltersProvider, createFilter, createFiltersFromEvent, onBrushEvent } from './vis_filters'; diff --git a/src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js b/src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js index afb3fea15a4307..4ad579e1e45f9a 100644 --- a/src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js +++ b/src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js @@ -21,7 +21,7 @@ import $ from 'jquery'; import _ from 'lodash'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { Vis } from '../../vis'; +import { Vis } from '../../../../../core_plugins/visualizations/public/np_ready/public/vis'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; describe('visualize_legend directive', function () { diff --git a/src/legacy/ui/public/vis/vis_types/index.js b/src/legacy/ui/public/vis/vis_types/index.js index 9c4ae82d58e6f8..113aa903df52fc 100644 --- a/src/legacy/ui/public/vis/vis_types/index.js +++ b/src/legacy/ui/public/vis/vis_types/index.js @@ -17,7 +17,7 @@ * under the License. */ -import { BaseVisType } from './base_vis_type'; -import { ReactVisType } from './react_vis_type'; +import { BaseVisType } from '../../../../core_plugins/visualizations/public/np_ready/public/types/base_vis_type'; +import { ReactVisType } from '../../../../core_plugins/visualizations/public/np_ready/public/types/react_vis_type'; export { BaseVisType, ReactVisType }; diff --git a/src/legacy/ui/public/vis/vis_types/vis_type.ts b/src/legacy/ui/public/vis/vis_types/vis_type.ts deleted file mode 100644 index 9d06409fda622a..00000000000000 --- a/src/legacy/ui/public/vis/vis_types/vis_type.ts +++ /dev/null @@ -1,29 +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 { Status } from '../update_status'; -import { Vis } from '..'; -export { VisType } from '../../../../core_plugins/visualizations/public'; - -export declare class VisualizationController { - constructor(element: HTMLElement, vis: Vis); - public render(visData: any, visParams: any, update: { [key in Status]: boolean }): Promise; - public destroy(): void; - public isLoaded?(): Promise | void; -} diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.js b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.js index ce94c3a5f68abb..3d054b8f8a2fbd 100644 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.js +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.js @@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n'; import html from './vislib_vis_legend.html'; import { Data } from '../../vislib/lib/data'; import { uiModules } from '../../modules'; -import { createFiltersFromEvent } from '../vis_filters'; +import { createFiltersFromEvent } from '../../../../core_plugins/visualizations/public'; import { htmlIdGenerator, keyCodes } from '@elastic/eui'; import { getTableAggs } from '../../visualize/loader/pipeline_helpers/utilities'; diff --git a/src/legacy/ui/public/visualize/_index.scss b/src/legacy/ui/public/visualize/_index.scss index 192091fb04e3c9..c528c1e37b4124 100644 --- a/src/legacy/ui/public/visualize/_index.scss +++ b/src/legacy/ui/public/visualize/_index.scss @@ -1 +1 @@ -@import './components/index'; +@import '../../../core_plugins/visualizations/public/np_ready/public/components/index'; diff --git a/src/legacy/ui/public/visualize/components/_index.scss b/src/legacy/ui/public/visualize/components/_index.scss deleted file mode 100644 index 99c357b53952f6..00000000000000 --- a/src/legacy/ui/public/visualize/components/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './visualization'; diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/index.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/index.ts index a1292c59ac61d1..f19940726ef2d6 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/index.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { buildPipeline } from './build_pipeline'; +export { buildPipeline } from '../../../../../core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline'; diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts index f49e0f08e87324..377e2cd97b72e8 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts @@ -26,7 +26,6 @@ import { SerializedFieldFormat } from 'src/plugins/expressions/public'; import { IFieldFormatId, FieldFormat } from '../../../../../../plugins/data/public'; import { tabifyGetColumns } from '../../../agg_response/tabify/_get_columns'; -import chrome from '../../../chrome'; import { dateRange } from '../../../utils/date_range'; import { ipRange } from '../../../utils/ip_range'; import { DateRangeKey } from '../../../agg_types/buckets/date_range'; @@ -146,7 +145,7 @@ export const getFormat: FormatFactory = mapping => { const parsedUrl = { origin: window.location.origin, pathname: window.location.pathname, - basePath: chrome.getBasePath(), + basePath: npStart.core.http.basePath, }; // @ts-ignore return format.convert(val, undefined, undefined, parsedUrl); @@ -163,7 +162,7 @@ export const getFormat: FormatFactory = mapping => { const parsedUrl = { origin: window.location.origin, pathname: window.location.pathname, - basePath: chrome.getBasePath(), + basePath: npStart.core.http.basePath, }; // @ts-ignore return format.convert(val, type, undefined, parsedUrl); diff --git a/src/plugins/data/common/es_query/es_query/build_es_query.test.ts b/src/plugins/data/common/es_query/es_query/build_es_query.test.ts index 3db23051b6ced9..405754ffcb572d 100644 --- a/src/plugins/data/common/es_query/es_query/build_es_query.test.ts +++ b/src/plugins/data/common/es_query/es_query/build_es_query.test.ts @@ -18,7 +18,7 @@ */ import { buildEsQuery } from './build_es_query'; -import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { fromKueryExpression, toElasticsearchQuery } from '../kuery'; import { luceneStringToDsl } from './lucene_string_to_dsl'; import { decorateQuery } from './decorate_query'; import { IIndexPattern } from '../../index_patterns'; diff --git a/src/plugins/data/common/es_query/es_query/build_es_query.ts b/src/plugins/data/common/es_query/es_query/build_es_query.ts index b7544967936603..e4f5f1f9e216c8 100644 --- a/src/plugins/data/common/es_query/es_query/build_es_query.ts +++ b/src/plugins/data/common/es_query/es_query/build_es_query.ts @@ -41,7 +41,7 @@ export interface EsQueryConfig { * config contains dateformat:tz */ export function buildEsQuery( - indexPattern: IIndexPattern | null, + indexPattern: IIndexPattern | undefined, queries: Query | Query[], filters: Filter | Filter[], config: EsQueryConfig = { diff --git a/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts b/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts index 6e03c665290ae9..669c5a62af7263 100644 --- a/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts +++ b/src/plugins/data/common/es_query/es_query/filter_matches_index.test.ts @@ -31,9 +31,8 @@ describe('filterMatchesIndex', () => { it('should return true if no index pattern is passed', () => { const filter = { meta: { index: 'foo', key: 'bar' } } as Filter; - const indexPattern = null; - expect(filterMatchesIndex(filter, indexPattern)).toBe(true); + expect(filterMatchesIndex(filter, undefined)).toBe(true); }); it('should return true if the filter key matches a field name', () => { diff --git a/src/plugins/data/common/es_query/es_query/filter_matches_index.ts b/src/plugins/data/common/es_query/es_query/filter_matches_index.ts index 9b68f5088c4479..a9cd3d8b7ba268 100644 --- a/src/plugins/data/common/es_query/es_query/filter_matches_index.ts +++ b/src/plugins/data/common/es_query/es_query/filter_matches_index.ts @@ -25,7 +25,7 @@ import { Filter } from '../filters'; * this to check if `filter.meta.index` matches `indexPattern.id` instead, but that's a breaking * change. */ -export function filterMatchesIndex(filter: Filter, indexPattern: IIndexPattern | null) { +export function filterMatchesIndex(filter: Filter, indexPattern?: IIndexPattern | null) { if (!filter.meta?.key || !indexPattern) { return true; } diff --git a/src/plugins/data/common/es_query/es_query/from_filters.ts b/src/plugins/data/common/es_query/es_query/from_filters.ts index 1e0957d8165908..e33040485bf47d 100644 --- a/src/plugins/data/common/es_query/es_query/from_filters.ts +++ b/src/plugins/data/common/es_query/es_query/from_filters.ts @@ -54,7 +54,7 @@ const translateToQuery = (filter: Filter) => { export const buildQueryFromFilters = ( filters: Filter[] = [], - indexPattern: IIndexPattern | null, + indexPattern: IIndexPattern | undefined, ignoreFilterIfFieldNotInIndex: boolean = false ) => { filters = filters.filter(filter => filter && !isFilterDisabled(filter)); diff --git a/src/plugins/data/common/es_query/es_query/from_kuery.test.ts b/src/plugins/data/common/es_query/es_query/from_kuery.test.ts index 000815b51f6208..4574cd5ffd0cbd 100644 --- a/src/plugins/data/common/es_query/es_query/from_kuery.test.ts +++ b/src/plugins/data/common/es_query/es_query/from_kuery.test.ts @@ -18,7 +18,7 @@ */ import { buildQueryFromKuery } from './from_kuery'; -import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { fromKueryExpression, toElasticsearchQuery } from '../kuery'; import { IIndexPattern } from '../../index_patterns'; import { fields } from '../../index_patterns/mocks'; import { Query } from '../../query/types'; @@ -30,7 +30,7 @@ describe('build query', () => { describe('buildQueryFromKuery', () => { test('should return the parameters of an Elasticsearch bool query', () => { - const result = buildQueryFromKuery(null, [], true); + const result = buildQueryFromKuery(undefined, [], true); const expected = { must: [], filter: [], diff --git a/src/plugins/data/common/es_query/es_query/from_kuery.ts b/src/plugins/data/common/es_query/es_query/from_kuery.ts index f91c3d97b95b43..f4ec0fe0b34c5e 100644 --- a/src/plugins/data/common/es_query/es_query/from_kuery.ts +++ b/src/plugins/data/common/es_query/es_query/from_kuery.ts @@ -17,12 +17,12 @@ * under the License. */ -import { fromKueryExpression, toElasticsearchQuery, nodeTypes, KueryNode } from '@kbn/es-query'; +import { fromKueryExpression, toElasticsearchQuery, nodeTypes, KueryNode } from '../kuery'; import { IIndexPattern } from '../../index_patterns'; import { Query } from '../../query/types'; export function buildQueryFromKuery( - indexPattern: IIndexPattern | null, + indexPattern: IIndexPattern | undefined, queries: Query[] = [], allowLeadingWildcards: boolean = false, dateFormatTZ?: string @@ -35,22 +35,20 @@ export function buildQueryFromKuery( } function buildQuery( - indexPattern: IIndexPattern | null, + indexPattern: IIndexPattern | undefined, queryASTs: KueryNode[], config: Record = {} ) { - const compoundQueryAST: KueryNode = nodeTypes.function.buildNode('and', queryASTs); - const kueryQuery: Record = toElasticsearchQuery( - compoundQueryAST, - indexPattern, - config - ); + const compoundQueryAST = nodeTypes.function.buildNode('and', queryASTs); + const kueryQuery = toElasticsearchQuery(compoundQueryAST, indexPattern, config); - return { - must: [], - filter: [], - should: [], - must_not: [], - ...kueryQuery.bool, - }; + return Object.assign( + { + must: [], + filter: [], + should: [], + must_not: [], + }, + kueryQuery.bool + ); } diff --git a/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts b/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts index 4617ee1a1c43d3..e01240da87543b 100644 --- a/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts +++ b/src/plugins/data/common/es_query/es_query/migrate_filter.test.ts @@ -40,7 +40,7 @@ describe('migrateFilter', function() { } as unknown) as PhraseFilter; it('should migrate match filters of type phrase', function() { - const migratedFilter = migrateFilter(oldMatchPhraseFilter, null); + const migratedFilter = migrateFilter(oldMatchPhraseFilter, undefined); expect(isEqual(migratedFilter, newMatchPhraseFilter)).toBe(true); }); @@ -48,7 +48,7 @@ describe('migrateFilter', function() { it('should not modify the original filter', function() { const oldMatchPhraseFilterCopy = clone(oldMatchPhraseFilter, true); - migrateFilter(oldMatchPhraseFilter, null); + migrateFilter(oldMatchPhraseFilter, undefined); expect(isEqual(oldMatchPhraseFilter, oldMatchPhraseFilterCopy)).toBe(true); }); @@ -57,7 +57,7 @@ describe('migrateFilter', function() { const originalFilter = { match_all: {}, } as MatchAllFilter; - const migratedFilter = migrateFilter(originalFilter, null); + const migratedFilter = migrateFilter(originalFilter, undefined); expect(migratedFilter).toBe(originalFilter); expect(isEqual(migratedFilter, originalFilter)).toBe(true); diff --git a/src/plugins/data/common/es_query/es_query/migrate_filter.ts b/src/plugins/data/common/es_query/es_query/migrate_filter.ts index 258ab9e7031319..fdc40768ebe41c 100644 --- a/src/plugins/data/common/es_query/es_query/migrate_filter.ts +++ b/src/plugins/data/common/es_query/es_query/migrate_filter.ts @@ -43,7 +43,7 @@ function isMatchPhraseFilter(filter: any): filter is DeprecatedMatchPhraseFilter return Boolean(fieldName && get(filter, ['match', fieldName, 'type']) === 'phrase'); } -export function migrateFilter(filter: Filter, indexPattern: IIndexPattern | null) { +export function migrateFilter(filter: Filter, indexPattern?: IIndexPattern) { if (isMatchPhraseFilter(filter)) { const fieldName = Object.keys(filter.match)[0]; const params: Record = get(filter, ['match', fieldName]); diff --git a/src/plugins/data/common/es_query/index.ts b/src/plugins/data/common/es_query/index.ts index 56eb45c4b1dcaf..937fe09903b6bc 100644 --- a/src/plugins/data/common/es_query/index.ts +++ b/src/plugins/data/common/es_query/index.ts @@ -18,6 +18,7 @@ */ import * as esQuery from './es_query'; import * as esFilters from './filters'; +import * as esKuery from './kuery'; import * as utils from './utils'; -export { esFilters, esQuery, utils }; +export { esFilters, esQuery, utils, esKuery }; diff --git a/packages/kbn-es-query/src/kuery/ast/kuery.js b/src/plugins/data/common/es_query/kuery/ast/_generated_/kuery.js similarity index 100% rename from packages/kbn-es-query/src/kuery/ast/kuery.js rename to src/plugins/data/common/es_query/kuery/ast/_generated_/kuery.js diff --git a/src/plugins/data/common/es_query/kuery/ast/ast.test.ts b/src/plugins/data/common/es_query/kuery/ast/ast.test.ts new file mode 100644 index 00000000000000..e441420760475c --- /dev/null +++ b/src/plugins/data/common/es_query/kuery/ast/ast.test.ts @@ -0,0 +1,421 @@ +/* + * 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 { + fromKueryExpression, + fromLiteralExpression, + toElasticsearchQuery, + doesKueryExpressionHaveLuceneSyntaxError, +} from './ast'; +import { nodeTypes } from '../node_types'; +import { fields } from '../../../index_patterns/mocks'; +import { IIndexPattern } from '../../../index_patterns'; +import { KueryNode } from '../types'; + +describe('kuery AST API', () => { + let indexPattern: IIndexPattern; + + beforeEach(() => { + indexPattern = ({ + fields, + } as unknown) as IIndexPattern; + }); + + describe('fromKueryExpression', () => { + test('should return a match all "is" function for whitespace', () => { + const expected = nodeTypes.function.buildNode('is', '*', '*'); + const actual = fromKueryExpression(' '); + expect(actual).toEqual(expected); + }); + + test('should return an "is" function with a null field for single literals', () => { + const expected = nodeTypes.function.buildNode('is', null, 'foo'); + const actual = fromKueryExpression('foo'); + expect(actual).toEqual(expected); + }); + + test('should ignore extraneous whitespace at the beginning and end of the query', () => { + const expected = nodeTypes.function.buildNode('is', null, 'foo'); + const actual = fromKueryExpression(' foo '); + expect(actual).toEqual(expected); + }); + + test('should not split on whitespace', () => { + const expected = nodeTypes.function.buildNode('is', null, 'foo bar'); + const actual = fromKueryExpression('foo bar'); + expect(actual).toEqual(expected); + }); + + test('should support "and" as a binary operator', () => { + const expected = nodeTypes.function.buildNode('and', [ + nodeTypes.function.buildNode('is', null, 'foo'), + nodeTypes.function.buildNode('is', null, 'bar'), + ]); + const actual = fromKueryExpression('foo and bar'); + expect(actual).toEqual(expected); + }); + + test('should support "or" as a binary operator', () => { + const expected = nodeTypes.function.buildNode('or', [ + nodeTypes.function.buildNode('is', null, 'foo'), + nodeTypes.function.buildNode('is', null, 'bar'), + ]); + const actual = fromKueryExpression('foo or bar'); + expect(actual).toEqual(expected); + }); + + test('should support negation of queries with a "not" prefix', () => { + const expected = nodeTypes.function.buildNode( + 'not', + nodeTypes.function.buildNode('or', [ + nodeTypes.function.buildNode('is', null, 'foo'), + nodeTypes.function.buildNode('is', null, 'bar'), + ]) + ); + const actual = fromKueryExpression('not (foo or bar)'); + expect(actual).toEqual(expected); + }); + + test('"and" should have a higher precedence than "or"', () => { + const expected = nodeTypes.function.buildNode('or', [ + nodeTypes.function.buildNode('is', null, 'foo'), + nodeTypes.function.buildNode('or', [ + nodeTypes.function.buildNode('and', [ + nodeTypes.function.buildNode('is', null, 'bar'), + nodeTypes.function.buildNode('is', null, 'baz'), + ]), + nodeTypes.function.buildNode('is', null, 'qux'), + ]), + ]); + const actual = fromKueryExpression('foo or bar and baz or qux'); + expect(actual).toEqual(expected); + }); + + test('should support grouping to override default precedence', () => { + const expected = nodeTypes.function.buildNode('and', [ + nodeTypes.function.buildNode('or', [ + nodeTypes.function.buildNode('is', null, 'foo'), + nodeTypes.function.buildNode('is', null, 'bar'), + ]), + nodeTypes.function.buildNode('is', null, 'baz'), + ]); + const actual = fromKueryExpression('(foo or bar) and baz'); + expect(actual).toEqual(expected); + }); + + test('should support matching against specific fields', () => { + const expected = nodeTypes.function.buildNode('is', 'foo', 'bar'); + const actual = fromKueryExpression('foo:bar'); + expect(actual).toEqual(expected); + }); + + test('should also not split on whitespace when matching specific fields', () => { + const expected = nodeTypes.function.buildNode('is', 'foo', 'bar baz'); + const actual = fromKueryExpression('foo:bar baz'); + expect(actual).toEqual(expected); + }); + + test('should treat quoted values as phrases', () => { + const expected = nodeTypes.function.buildNode('is', 'foo', 'bar baz', true); + const actual = fromKueryExpression('foo:"bar baz"'); + expect(actual).toEqual(expected); + }); + + test('should support a shorthand for matching multiple values against a single field', () => { + const expected = nodeTypes.function.buildNode('or', [ + nodeTypes.function.buildNode('is', 'foo', 'bar'), + nodeTypes.function.buildNode('is', 'foo', 'baz'), + ]); + const actual = fromKueryExpression('foo:(bar or baz)'); + expect(actual).toEqual(expected); + }); + + test('should support "and" and "not" operators and grouping in the shorthand as well', () => { + const expected = nodeTypes.function.buildNode('and', [ + nodeTypes.function.buildNode('or', [ + nodeTypes.function.buildNode('is', 'foo', 'bar'), + nodeTypes.function.buildNode('is', 'foo', 'baz'), + ]), + nodeTypes.function.buildNode('not', nodeTypes.function.buildNode('is', 'foo', 'qux')), + ]); + const actual = fromKueryExpression('foo:((bar or baz) and not qux)'); + expect(actual).toEqual(expected); + }); + + test('should support exclusive range operators', () => { + const expected = nodeTypes.function.buildNode('and', [ + nodeTypes.function.buildNode('range', 'bytes', { + gt: 1000, + }), + nodeTypes.function.buildNode('range', 'bytes', { + lt: 8000, + }), + ]); + const actual = fromKueryExpression('bytes > 1000 and bytes < 8000'); + expect(actual).toEqual(expected); + }); + + test('should support inclusive range operators', () => { + const expected = nodeTypes.function.buildNode('and', [ + nodeTypes.function.buildNode('range', 'bytes', { + gte: 1000, + }), + nodeTypes.function.buildNode('range', 'bytes', { + lte: 8000, + }), + ]); + const actual = fromKueryExpression('bytes >= 1000 and bytes <= 8000'); + expect(actual).toEqual(expected); + }); + + test('should support wildcards in field names', () => { + const expected = nodeTypes.function.buildNode('is', 'machine*', 'osx'); + const actual = fromKueryExpression('machine*:osx'); + expect(actual).toEqual(expected); + }); + + test('should support wildcards in values', () => { + const expected = nodeTypes.function.buildNode('is', 'foo', 'ba*'); + const actual = fromKueryExpression('foo:ba*'); + expect(actual).toEqual(expected); + }); + + test('should create an exists "is" query when a field is given and "*" is the value', () => { + const expected = nodeTypes.function.buildNode('is', 'foo', '*'); + const actual = fromKueryExpression('foo:*'); + expect(actual).toEqual(expected); + }); + + test('should support nested queries indicated by curly braces', () => { + const expected = nodeTypes.function.buildNode( + 'nested', + 'nestedField', + nodeTypes.function.buildNode('is', 'childOfNested', 'foo') + ); + const actual = fromKueryExpression('nestedField:{ childOfNested: foo }'); + expect(actual).toEqual(expected); + }); + + test('should support nested subqueries and subqueries inside nested queries', () => { + const expected = nodeTypes.function.buildNode('and', [ + nodeTypes.function.buildNode('is', 'response', '200'), + nodeTypes.function.buildNode( + 'nested', + 'nestedField', + nodeTypes.function.buildNode('or', [ + nodeTypes.function.buildNode('is', 'childOfNested', 'foo'), + nodeTypes.function.buildNode('is', 'childOfNested', 'bar'), + ]) + ), + ]); + const actual = fromKueryExpression( + 'response:200 and nestedField:{ childOfNested:foo or childOfNested:bar }' + ); + expect(actual).toEqual(expected); + }); + + test('should support nested sub-queries inside paren groups', () => { + const expected = nodeTypes.function.buildNode('and', [ + nodeTypes.function.buildNode('is', 'response', '200'), + nodeTypes.function.buildNode('or', [ + nodeTypes.function.buildNode( + 'nested', + 'nestedField', + nodeTypes.function.buildNode('is', 'childOfNested', 'foo') + ), + nodeTypes.function.buildNode( + 'nested', + 'nestedField', + nodeTypes.function.buildNode('is', 'childOfNested', 'bar') + ), + ]), + ]); + const actual = fromKueryExpression( + 'response:200 and ( nestedField:{ childOfNested:foo } or nestedField:{ childOfNested:bar } )' + ); + expect(actual).toEqual(expected); + }); + + test('should support nested groups inside other nested groups', () => { + const expected = nodeTypes.function.buildNode( + 'nested', + 'nestedField', + nodeTypes.function.buildNode( + 'nested', + 'nestedChild', + nodeTypes.function.buildNode('is', 'doublyNestedChild', 'foo') + ) + ); + const actual = fromKueryExpression('nestedField:{ nestedChild:{ doublyNestedChild:foo } }'); + expect(actual).toEqual(expected); + }); + }); + + describe('fromLiteralExpression', () => { + test('should create literal nodes for unquoted values with correct primitive types', () => { + const stringLiteral = nodeTypes.literal.buildNode('foo'); + const booleanFalseLiteral = nodeTypes.literal.buildNode(false); + const booleanTrueLiteral = nodeTypes.literal.buildNode(true); + const numberLiteral = nodeTypes.literal.buildNode(42); + + expect(fromLiteralExpression('foo')).toEqual(stringLiteral); + expect(fromLiteralExpression('true')).toEqual(booleanTrueLiteral); + expect(fromLiteralExpression('false')).toEqual(booleanFalseLiteral); + expect(fromLiteralExpression('42')).toEqual(numberLiteral); + }); + + test('should allow escaping of special characters with a backslash', () => { + const expected = nodeTypes.literal.buildNode('\\():<>"*'); + // yo dawg + const actual = fromLiteralExpression('\\\\\\(\\)\\:\\<\\>\\"\\*'); + expect(actual).toEqual(expected); + }); + + test('should support double quoted strings that do not need escapes except for quotes', () => { + const expected = nodeTypes.literal.buildNode('\\():<>"*'); + const actual = fromLiteralExpression('"\\():<>\\"*"'); + expect(actual).toEqual(expected); + }); + + test('should support escaped backslashes inside quoted strings', () => { + const expected = nodeTypes.literal.buildNode('\\'); + const actual = fromLiteralExpression('"\\\\"'); + expect(actual).toEqual(expected); + }); + + test('should detect wildcards and build wildcard AST nodes', () => { + const expected = nodeTypes.wildcard.buildNode('foo*bar'); + const actual = fromLiteralExpression('foo*bar'); + expect(actual).toEqual(expected); + }); + }); + + describe('toElasticsearchQuery', () => { + test("should return the given node type's ES query representation", () => { + const node = nodeTypes.function.buildNode('exists', 'response'); + const expected = nodeTypes.function.toElasticsearchQuery(node, indexPattern); + const result = toElasticsearchQuery(node, indexPattern); + expect(result).toEqual(expected); + }); + + test('should return an empty "and" function for undefined nodes and unknown node types', () => { + const expected = nodeTypes.function.toElasticsearchQuery( + nodeTypes.function.buildNode('and', []), + indexPattern + ); + + expect(toElasticsearchQuery((null as unknown) as KueryNode, undefined)).toEqual(expected); + + const noTypeNode = nodeTypes.function.buildNode('exists', 'foo'); + delete noTypeNode.type; + expect(toElasticsearchQuery(noTypeNode)).toEqual(expected); + + const unknownTypeNode = nodeTypes.function.buildNode('exists', 'foo'); + + // @ts-ignore + unknownTypeNode.type = 'notValid'; + expect(toElasticsearchQuery(unknownTypeNode)).toEqual(expected); + }); + + test("should return the given node type's ES query representation including a time zone parameter when one is provided", () => { + const config = { dateFormatTZ: 'America/Phoenix' }; + const node = nodeTypes.function.buildNode('is', '@timestamp', '"2018-04-03T19:04:17"'); + const expected = nodeTypes.function.toElasticsearchQuery(node, indexPattern, config); + const result = toElasticsearchQuery(node, indexPattern, config); + expect(result).toEqual(expected); + }); + }); + + describe('doesKueryExpressionHaveLuceneSyntaxError', () => { + test('should return true for Lucene ranges', () => { + const result = doesKueryExpressionHaveLuceneSyntaxError('bar: [1 TO 10]'); + expect(result).toEqual(true); + }); + + test('should return false for KQL ranges', () => { + const result = doesKueryExpressionHaveLuceneSyntaxError('bar < 1'); + expect(result).toEqual(false); + }); + + test('should return true for Lucene exists', () => { + const result = doesKueryExpressionHaveLuceneSyntaxError('_exists_: bar'); + expect(result).toEqual(true); + }); + + test('should return false for KQL exists', () => { + const result = doesKueryExpressionHaveLuceneSyntaxError('bar:*'); + expect(result).toEqual(false); + }); + + test('should return true for Lucene wildcards', () => { + const result = doesKueryExpressionHaveLuceneSyntaxError('bar: ba?'); + expect(result).toEqual(true); + }); + + test('should return false for KQL wildcards', () => { + const result = doesKueryExpressionHaveLuceneSyntaxError('bar: ba*'); + expect(result).toEqual(false); + }); + + test('should return true for Lucene regex', () => { + const result = doesKueryExpressionHaveLuceneSyntaxError('bar: /ba.*/'); + expect(result).toEqual(true); + }); + + test('should return true for Lucene fuzziness', () => { + const result = doesKueryExpressionHaveLuceneSyntaxError('bar: ba~'); + expect(result).toEqual(true); + }); + + test('should return true for Lucene proximity', () => { + const result = doesKueryExpressionHaveLuceneSyntaxError('bar: "ba"~2'); + expect(result).toEqual(true); + }); + + test('should return true for Lucene boosting', () => { + const result = doesKueryExpressionHaveLuceneSyntaxError('bar: ba^2'); + expect(result).toEqual(true); + }); + + test('should return true for Lucene + operator', () => { + const result = doesKueryExpressionHaveLuceneSyntaxError('+foo: bar'); + expect(result).toEqual(true); + }); + + test('should return true for Lucene - operators', () => { + const result = doesKueryExpressionHaveLuceneSyntaxError('-foo: bar'); + expect(result).toEqual(true); + }); + + test('should return true for Lucene && operators', () => { + const result = doesKueryExpressionHaveLuceneSyntaxError('foo: bar && baz: qux'); + expect(result).toEqual(true); + }); + + test('should return true for Lucene || operators', () => { + const result = doesKueryExpressionHaveLuceneSyntaxError('foo: bar || baz: qux'); + expect(result).toEqual(true); + }); + + test('should return true for mixed KQL/Lucene queries', () => { + const result = doesKueryExpressionHaveLuceneSyntaxError('foo: bar and (baz: qux || bag)'); + expect(result).toEqual(true); + }); + }); +}); diff --git a/packages/kbn-es-query/src/kuery/ast/ast.js b/src/plugins/data/common/es_query/kuery/ast/ast.ts similarity index 53% rename from packages/kbn-es-query/src/kuery/ast/ast.js rename to src/plugins/data/common/es_query/kuery/ast/ast.ts index 1688995d46f80e..253f4326179725 100644 --- a/packages/kbn-es-query/src/kuery/ast/ast.js +++ b/src/plugins/data/common/es_query/kuery/ast/ast.ts @@ -17,21 +17,44 @@ * under the License. */ -import _ from 'lodash'; import { nodeTypes } from '../node_types/index'; -import { parse as parseKuery } from './kuery'; -import { KQLSyntaxError } from '../errors'; +import { KQLSyntaxError } from '../kuery_syntax_error'; +import { KueryNode, JsonObject, DslQuery, KueryParseOptions } from '../types'; +import { IIndexPattern } from '../../../index_patterns/types'; -export function fromLiteralExpression(expression, parseOptions) { - parseOptions = { - ...parseOptions, - startRule: 'Literal', - }; +// @ts-ignore +import { parse as parseKuery } from './_generated_/kuery'; - return fromExpression(expression, parseOptions, parseKuery); -} +const fromExpression = ( + expression: string | DslQuery, + parseOptions: Partial = {}, + parse: Function = parseKuery +): KueryNode => { + if (typeof expression === 'undefined') { + throw new Error('expression must be a string, got undefined instead'); + } + + return parse(expression, { ...parseOptions, helpers: { nodeTypes } }); +}; + +export const fromLiteralExpression = ( + expression: string | DslQuery, + parseOptions: Partial = {} +): KueryNode => { + return fromExpression( + expression, + { + ...parseOptions, + startRule: 'Literal', + }, + parseKuery + ); +}; -export function fromKueryExpression(expression, parseOptions) { +export const fromKueryExpression = ( + expression: string | DslQuery, + parseOptions: Partial = {} +): KueryNode => { try { return fromExpression(expression, parseOptions, parseKuery); } catch (error) { @@ -41,20 +64,18 @@ export function fromKueryExpression(expression, parseOptions) { throw error; } } -} +}; -function fromExpression(expression, parseOptions = {}, parse = parseKuery) { - if (_.isUndefined(expression)) { - throw new Error('expression must be a string, got undefined instead'); +export const doesKueryExpressionHaveLuceneSyntaxError = ( + expression: string | DslQuery +): boolean => { + try { + fromExpression(expression, { errorOnLuceneSyntax: true }, parseKuery); + return false; + } catch (e) { + return e.message.startsWith('Lucene'); } - - parseOptions = { - ...parseOptions, - helpers: { nodeTypes }, - }; - - return parse(expression, parseOptions); -} +}; /** * @params {String} indexPattern @@ -63,19 +84,17 @@ function fromExpression(expression, parseOptions = {}, parse = parseKuery) { * IndexPattern isn't required, but if you pass one in, we can be more intelligent * about how we craft the queries (e.g. scripted fields) */ -export function toElasticsearchQuery(node, indexPattern, config = {}, context = {}) { +export const toElasticsearchQuery = ( + node: KueryNode, + indexPattern?: IIndexPattern, + config?: Record, + context?: Record +): JsonObject => { if (!node || !node.type || !nodeTypes[node.type]) { - return toElasticsearchQuery(nodeTypes.function.buildNode('and', [])); + return toElasticsearchQuery(nodeTypes.function.buildNode('and', []), indexPattern); } - return nodeTypes[node.type].toElasticsearchQuery(node, indexPattern, config, context); -} + const nodeType = (nodeTypes[node.type] as unknown) as any; -export function doesKueryExpressionHaveLuceneSyntaxError(expression) { - try { - fromExpression(expression, { errorOnLuceneSyntax: true }, parseKuery); - return false; - } catch (e) { - return (e.message.startsWith('Lucene')); - } -} + return nodeType.toElasticsearchQuery(node, indexPattern, config, context); +}; diff --git a/packages/kbn-es-query/src/kuery/ast/index.js b/src/plugins/data/common/es_query/kuery/ast/index.ts similarity index 100% rename from packages/kbn-es-query/src/kuery/ast/index.js rename to src/plugins/data/common/es_query/kuery/ast/index.ts diff --git a/packages/kbn-es-query/src/kuery/ast/kuery.peg b/src/plugins/data/common/es_query/kuery/ast/kuery.peg similarity index 100% rename from packages/kbn-es-query/src/kuery/ast/kuery.peg rename to src/plugins/data/common/es_query/kuery/ast/kuery.peg diff --git a/packages/kbn-es-query/src/kuery/functions/and.js b/src/plugins/data/common/es_query/kuery/functions/and.js similarity index 100% rename from packages/kbn-es-query/src/kuery/functions/and.js rename to src/plugins/data/common/es_query/kuery/functions/and.js diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/and.js b/src/plugins/data/common/es_query/kuery/functions/and.test.ts similarity index 50% rename from packages/kbn-es-query/src/kuery/functions/__tests__/and.js rename to src/plugins/data/common/es_query/kuery/functions/and.test.ts index 07289a878e8c1e..133e691b27dbad 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/and.js +++ b/src/plugins/data/common/es_query/kuery/functions/and.test.ts @@ -17,43 +17,53 @@ * under the License. */ -import expect from '@kbn/expect'; -import * as and from '../and'; -import { nodeTypes } from '../../node_types'; -import * as ast from '../../ast'; -import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; +import { nodeTypes } from '../node_types'; +import { fields } from '../../../index_patterns/mocks'; +import { IIndexPattern } from '../../../index_patterns'; +import * as ast from '../ast'; -let indexPattern; +// @ts-ignore +import * as and from './and'; const childNode1 = nodeTypes.function.buildNode('is', 'machine.os', 'osx'); const childNode2 = nodeTypes.function.buildNode('is', 'extension', 'jpg'); -describe('kuery functions', function () { - describe('and', function () { +describe('kuery functions', () => { + describe('and', () => { + let indexPattern: IIndexPattern; beforeEach(() => { - indexPattern = indexPatternResponse; + indexPattern = ({ + fields, + } as unknown) as IIndexPattern; }); - describe('buildNodeParams', function () { - - it('arguments should contain the unmodified child nodes', function () { + describe('buildNodeParams', () => { + test('arguments should contain the unmodified child nodes', () => { const result = and.buildNodeParams([childNode1, childNode2]); - const { arguments: [ actualChildNode1, actualChildNode2 ] } = result; - expect(actualChildNode1).to.be(childNode1); - expect(actualChildNode2).to.be(childNode2); + const { + arguments: [actualChildNode1, actualChildNode2], + } = result; + + expect(actualChildNode1).toBe(childNode1); + expect(actualChildNode2).toBe(childNode2); }); }); - describe('toElasticsearchQuery', function () { - - it('should wrap subqueries in an ES bool query\'s filter clause', function () { + describe('toElasticsearchQuery', () => { + test("should wrap subqueries in an ES bool query's filter clause", () => { const node = nodeTypes.function.buildNode('and', [childNode1, childNode2]); const result = and.toElasticsearchQuery(node, indexPattern); - expect(result).to.only.have.keys('bool'); - expect(result.bool).to.only.have.keys('filter'); - expect(result.bool.filter).to.eql( - [childNode1, childNode2].map((childNode) => ast.toElasticsearchQuery(childNode, indexPattern)) + + expect(result).toHaveProperty('bool'); + expect(Object.keys(result).length).toBe(1); + expect(result.bool).toHaveProperty('filter'); + expect(Object.keys(result.bool).length).toBe(1); + + expect(result.bool.filter).toEqual( + [childNode1, childNode2].map(childNode => + ast.toElasticsearchQuery(childNode, indexPattern) + ) ); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/exists.js b/src/plugins/data/common/es_query/kuery/functions/exists.js similarity index 100% rename from packages/kbn-es-query/src/kuery/functions/exists.js rename to src/plugins/data/common/es_query/kuery/functions/exists.js diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/exists.js b/src/plugins/data/common/es_query/kuery/functions/exists.test.ts similarity index 51% rename from packages/kbn-es-query/src/kuery/functions/__tests__/exists.js rename to src/plugins/data/common/es_query/kuery/functions/exists.test.ts index ee4cfab94e614d..8443436cf4cfb1 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/exists.js +++ b/src/plugins/data/common/es_query/kuery/functions/exists.test.ts @@ -17,67 +17,73 @@ * under the License. */ -import expect from '@kbn/expect'; -import * as exists from '../exists'; -import { nodeTypes } from '../../node_types'; -import _ from 'lodash'; -import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; +import { nodeTypes } from '../node_types'; +import { fields } from '../../../index_patterns/mocks'; +import { IIndexPattern } from '../../../index_patterns'; +// @ts-ignore +import * as exists from './exists'; -let indexPattern; - -describe('kuery functions', function () { - describe('exists', function () { +describe('kuery functions', () => { + describe('exists', () => { + let indexPattern: IIndexPattern; beforeEach(() => { - indexPattern = indexPatternResponse; + indexPattern = ({ + fields, + } as unknown) as IIndexPattern; }); - describe('buildNodeParams', function () { - it('should return a single "arguments" param', function () { + describe('buildNodeParams', () => { + test('should return a single "arguments" param', () => { const result = exists.buildNodeParams('response'); - expect(result).to.only.have.key('arguments'); + + expect(result).toHaveProperty('arguments'); + expect(Object.keys(result).length).toBe(1); }); - it('arguments should contain the provided fieldName as a literal', function () { - const { arguments: [ arg ] } = exists.buildNodeParams('response'); - expect(arg).to.have.property('type', 'literal'); - expect(arg).to.have.property('value', 'response'); + test('arguments should contain the provided fieldName as a literal', () => { + const { + arguments: [arg], + } = exists.buildNodeParams('response'); + + expect(arg).toHaveProperty('type', 'literal'); + expect(arg).toHaveProperty('value', 'response'); }); }); - describe('toElasticsearchQuery', function () { - it('should return an ES exists query', function () { + describe('toElasticsearchQuery', () => { + test('should return an ES exists query', () => { const expected = { - exists: { field: 'response' } + exists: { field: 'response' }, }; - const existsNode = nodeTypes.function.buildNode('exists', 'response'); const result = exists.toElasticsearchQuery(existsNode, indexPattern); - expect(_.isEqual(expected, result)).to.be(true); + + expect(expected).toEqual(result); }); - it('should return an ES exists query without an index pattern', function () { + test('should return an ES exists query without an index pattern', () => { const expected = { - exists: { field: 'response' } + exists: { field: 'response' }, }; - const existsNode = nodeTypes.function.buildNode('exists', 'response'); const result = exists.toElasticsearchQuery(existsNode); - expect(_.isEqual(expected, result)).to.be(true); + + expect(expected).toEqual(result); }); - it('should throw an error for scripted fields', function () { + test('should throw an error for scripted fields', () => { const existsNode = nodeTypes.function.buildNode('exists', 'script string'); - expect(exists.toElasticsearchQuery) - .withArgs(existsNode, indexPattern).to.throwException(/Exists query does not support scripted fields/); + expect(() => exists.toElasticsearchQuery(existsNode, indexPattern)).toThrowError( + /Exists query does not support scripted fields/ + ); }); - it('should use a provided nested context to create a full field name', function () { + test('should use a provided nested context to create a full field name', () => { const expected = { - exists: { field: 'nestedField.response' } + exists: { field: 'nestedField.response' }, }; - const existsNode = nodeTypes.function.buildNode('exists', 'response'); const result = exists.toElasticsearchQuery( existsNode, @@ -85,7 +91,8 @@ describe('kuery functions', function () { {}, { nested: { path: 'nestedField' } } ); - expect(_.isEqual(expected, result)).to.be(true); + + expect(expected).toEqual(result); }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/geo_bounding_box.js b/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.js similarity index 100% rename from packages/kbn-es-query/src/kuery/functions/geo_bounding_box.js rename to src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.js diff --git a/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.test.ts b/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.test.ts new file mode 100644 index 00000000000000..cf287ff2c437ac --- /dev/null +++ b/src/plugins/data/common/es_query/kuery/functions/geo_bounding_box.test.ts @@ -0,0 +1,133 @@ +/* + * 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 { get } from 'lodash'; +import { nodeTypes } from '../node_types'; +import { fields } from '../../../index_patterns/mocks'; +import { IIndexPattern } from '../../../index_patterns'; + +// @ts-ignore +import * as geoBoundingBox from './geo_bounding_box'; + +const params = { + bottomRight: { + lat: 50.73, + lon: -135.35, + }, + topLeft: { + lat: 73.12, + lon: -174.37, + }, +}; + +describe('kuery functions', () => { + describe('geoBoundingBox', () => { + let indexPattern: IIndexPattern; + + beforeEach(() => { + indexPattern = ({ + fields, + } as unknown) as IIndexPattern; + }); + + describe('buildNodeParams', () => { + test('should return an "arguments" param', () => { + const result = geoBoundingBox.buildNodeParams('geo', params); + + expect(result).toHaveProperty('arguments'); + expect(Object.keys(result).length).toBe(1); + }); + + test('arguments should contain the provided fieldName as a literal', () => { + const result = geoBoundingBox.buildNodeParams('geo', params); + const { + arguments: [fieldName], + } = result; + + expect(fieldName).toHaveProperty('type', 'literal'); + expect(fieldName).toHaveProperty('value', 'geo'); + }); + + test('arguments should contain the provided params as named arguments with "lat, lon" string values', () => { + const result = geoBoundingBox.buildNodeParams('geo', params); + const { + arguments: [, ...args], + } = result; + + args.map((param: any) => { + expect(param).toHaveProperty('type', 'namedArg'); + expect(['bottomRight', 'topLeft'].includes(param.name)).toBe(true); + expect(param.value.type).toBe('literal'); + + const { lat, lon } = get(params, param.name); + + expect(param.value.value).toBe(`${lat}, ${lon}`); + }); + }); + }); + + describe('toElasticsearchQuery', () => { + test('should return an ES geo_bounding_box query representing the given node', () => { + const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params); + const result = geoBoundingBox.toElasticsearchQuery(node, indexPattern); + + expect(result).toHaveProperty('geo_bounding_box'); + expect(result.geo_bounding_box.geo).toHaveProperty('top_left', '73.12, -174.37'); + expect(result.geo_bounding_box.geo).toHaveProperty('bottom_right', '50.73, -135.35'); + }); + + test('should return an ES geo_bounding_box query without an index pattern', () => { + const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params); + const result = geoBoundingBox.toElasticsearchQuery(node); + + expect(result).toHaveProperty('geo_bounding_box'); + expect(result.geo_bounding_box.geo).toHaveProperty('top_left', '73.12, -174.37'); + expect(result.geo_bounding_box.geo).toHaveProperty('bottom_right', '50.73, -135.35'); + }); + + test('should use the ignore_unmapped parameter', () => { + const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params); + const result = geoBoundingBox.toElasticsearchQuery(node, indexPattern); + + expect(result.geo_bounding_box.ignore_unmapped).toBe(true); + }); + + test('should throw an error for scripted fields', () => { + const node = nodeTypes.function.buildNode('geoBoundingBox', 'script number', params); + + expect(() => geoBoundingBox.toElasticsearchQuery(node, indexPattern)).toThrowError( + /Geo bounding box query does not support scripted fields/ + ); + }); + + test('should use a provided nested context to create a full field name', () => { + const node = nodeTypes.function.buildNode('geoBoundingBox', 'geo', params); + const result = geoBoundingBox.toElasticsearchQuery( + node, + indexPattern, + {}, + { nested: { path: 'nestedField' } } + ); + + expect(result).toHaveProperty('geo_bounding_box'); + expect(result.geo_bounding_box['nestedField.geo']).toBeDefined(); + }); + }); + }); +}); diff --git a/packages/kbn-es-query/src/kuery/functions/geo_polygon.js b/src/plugins/data/common/es_query/kuery/functions/geo_polygon.js similarity index 100% rename from packages/kbn-es-query/src/kuery/functions/geo_polygon.js rename to src/plugins/data/common/es_query/kuery/functions/geo_polygon.js diff --git a/src/plugins/data/common/es_query/kuery/functions/geo_polygon.test.ts b/src/plugins/data/common/es_query/kuery/functions/geo_polygon.test.ts new file mode 100644 index 00000000000000..84500cb4ade7e6 --- /dev/null +++ b/src/plugins/data/common/es_query/kuery/functions/geo_polygon.test.ts @@ -0,0 +1,143 @@ +/* + * 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 { nodeTypes } from '../node_types'; +import { fields } from '../../../index_patterns/mocks'; +import { IIndexPattern } from '../../../index_patterns'; + +// @ts-ignore +import * as geoPolygon from './geo_polygon'; + +const points = [ + { + lat: 69.77, + lon: -171.56, + }, + { + lat: 50.06, + lon: -169.1, + }, + { + lat: 69.16, + lon: -125.85, + }, +]; + +describe('kuery functions', () => { + describe('geoPolygon', () => { + let indexPattern: IIndexPattern; + + beforeEach(() => { + indexPattern = ({ + fields, + } as unknown) as IIndexPattern; + }); + + describe('buildNodeParams', () => { + test('should return an "arguments" param', () => { + const result = geoPolygon.buildNodeParams('geo', points); + + expect(result).toHaveProperty('arguments'); + expect(Object.keys(result).length).toBe(1); + }); + + test('arguments should contain the provided fieldName as a literal', () => { + const result = geoPolygon.buildNodeParams('geo', points); + const { + arguments: [fieldName], + } = result; + + expect(fieldName).toHaveProperty('type', 'literal'); + expect(fieldName).toHaveProperty('value', 'geo'); + }); + + test('arguments should contain the provided points literal "lat, lon" string values', () => { + const result = geoPolygon.buildNodeParams('geo', points); + const { + arguments: [, ...args], + } = result; + + args.forEach((param: any, index: number) => { + const expectedPoint = points[index]; + const expectedLatLon = `${expectedPoint.lat}, ${expectedPoint.lon}`; + + expect(param).toHaveProperty('type', 'literal'); + expect(param.value).toBe(expectedLatLon); + }); + }); + }); + + describe('toElasticsearchQuery', () => { + test('should return an ES geo_polygon query representing the given node', () => { + const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points); + const result = geoPolygon.toElasticsearchQuery(node, indexPattern); + + expect(result).toHaveProperty('geo_polygon'); + expect(result.geo_polygon.geo).toHaveProperty('points'); + + result.geo_polygon.geo.points.forEach((point: any, index: number) => { + const expectedLatLon = `${points[index].lat}, ${points[index].lon}`; + + expect(point).toBe(expectedLatLon); + }); + }); + + test('should return an ES geo_polygon query without an index pattern', () => { + const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points); + const result = geoPolygon.toElasticsearchQuery(node); + + expect(result).toHaveProperty('geo_polygon'); + expect(result.geo_polygon.geo).toHaveProperty('points'); + + result.geo_polygon.geo.points.forEach((point: any, index: number) => { + const expectedLatLon = `${points[index].lat}, ${points[index].lon}`; + + expect(point).toBe(expectedLatLon); + }); + }); + + test('should use the ignore_unmapped parameter', () => { + const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points); + const result = geoPolygon.toElasticsearchQuery(node, indexPattern); + + expect(result.geo_polygon.ignore_unmapped).toBe(true); + }); + + test('should throw an error for scripted fields', () => { + const node = nodeTypes.function.buildNode('geoPolygon', 'script number', points); + expect(() => geoPolygon.toElasticsearchQuery(node, indexPattern)).toThrowError( + /Geo polygon query does not support scripted fields/ + ); + }); + + test('should use a provided nested context to create a full field name', () => { + const node = nodeTypes.function.buildNode('geoPolygon', 'geo', points); + const result = geoPolygon.toElasticsearchQuery( + node, + indexPattern, + {}, + { nested: { path: 'nestedField' } } + ); + + expect(result).toHaveProperty('geo_polygon'); + expect(result.geo_polygon['nestedField.geo']).toBeDefined(); + }); + }); + }); +}); diff --git a/packages/kbn-es-query/src/kuery/functions/index.js b/src/plugins/data/common/es_query/kuery/functions/index.js similarity index 100% rename from packages/kbn-es-query/src/kuery/functions/index.js rename to src/plugins/data/common/es_query/kuery/functions/index.js diff --git a/packages/kbn-es-query/src/kuery/functions/is.js b/src/plugins/data/common/es_query/kuery/functions/is.js similarity index 95% rename from packages/kbn-es-query/src/kuery/functions/is.js rename to src/plugins/data/common/es_query/kuery/functions/is.js index 63ade9e8793a7b..4f2f298c4707d1 100644 --- a/packages/kbn-es-query/src/kuery/functions/is.js +++ b/src/plugins/data/common/es_query/kuery/functions/is.js @@ -17,20 +17,22 @@ * under the License. */ -import _ from 'lodash'; -import * as ast from '../ast'; -import * as literal from '../node_types/literal'; -import * as wildcard from '../node_types/wildcard'; -import { getPhraseScript } from '../../utils/filters'; +import { get, isUndefined } from 'lodash'; +import { getPhraseScript } from '../../filters'; import { getFields } from './utils/get_fields'; import { getTimeZoneFromSettings } from '../../utils/get_time_zone_from_settings'; import { getFullFieldNameNode } from './utils/get_full_field_name_node'; +import * as ast from '../ast'; + +import * as literal from '../node_types/literal'; +import * as wildcard from '../node_types/wildcard'; + export function buildNodeParams(fieldName, value, isPhrase = false) { - if (_.isUndefined(fieldName)) { + if (isUndefined(fieldName)) { throw new Error('fieldName is a required argument'); } - if (_.isUndefined(value)) { + if (isUndefined(value)) { throw new Error('value is a required argument'); } const fieldNode = typeof fieldName === 'string' ? ast.fromLiteralExpression(fieldName) : literal.buildNode(fieldName); @@ -45,7 +47,7 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}, con const { arguments: [fieldNameArg, valueArg, isPhraseArg] } = node; const fullFieldNameArg = getFullFieldNameNode(fieldNameArg, indexPattern, context.nested ? context.nested.path : undefined); const fieldName = ast.toElasticsearchQuery(fullFieldNameArg); - const value = !_.isUndefined(valueArg) ? ast.toElasticsearchQuery(valueArg) : valueArg; + const value = !isUndefined(valueArg) ? ast.toElasticsearchQuery(valueArg) : valueArg; const type = isPhraseArg.value ? 'phrase' : 'best_fields'; if (fullFieldNameArg.value === null) { if (valueArg.type === 'wildcard') { @@ -94,7 +96,7 @@ export function toElasticsearchQuery(node, indexPattern = null, config = {}, con // users handle this themselves so we automatically add nested queries in this scenario. if ( !(fullFieldNameArg.type === 'wildcard') - || !_.get(field, 'subType.nested') + || !get(field, 'subType.nested') || context.nested ) { return query; diff --git a/src/plugins/data/common/es_query/kuery/functions/is.test.ts b/src/plugins/data/common/es_query/kuery/functions/is.test.ts new file mode 100644 index 00000000000000..df147bad54a34b --- /dev/null +++ b/src/plugins/data/common/es_query/kuery/functions/is.test.ts @@ -0,0 +1,305 @@ +/* + * 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 { nodeTypes } from '../node_types'; +import { fields } from '../../../index_patterns/mocks'; + +// @ts-ignore +import * as is from './is'; +import { IIndexPattern } from '../../../index_patterns'; + +describe('kuery functions', () => { + describe('is', () => { + let indexPattern: IIndexPattern; + + beforeEach(() => { + indexPattern = ({ + fields, + } as unknown) as IIndexPattern; + }); + + describe('buildNodeParams', () => { + test('fieldName and value should be required arguments', () => { + expect(() => is.buildNodeParams()).toThrowError(/fieldName is a required argument/); + expect(() => is.buildNodeParams('foo')).toThrowError(/value is a required argument/); + }); + + test('arguments should contain the provided fieldName and value as literals', () => { + const { + arguments: [fieldName, value], + } = is.buildNodeParams('response', 200); + + expect(fieldName).toHaveProperty('type', 'literal'); + expect(fieldName).toHaveProperty('value', 'response'); + expect(value).toHaveProperty('type', 'literal'); + expect(value).toHaveProperty('value', 200); + }); + + test('should detect wildcards in the provided arguments', () => { + const { + arguments: [fieldName, value], + } = is.buildNodeParams('machine*', 'win*'); + + expect(fieldName).toHaveProperty('type', 'wildcard'); + expect(value).toHaveProperty('type', 'wildcard'); + }); + + test('should default to a non-phrase query', () => { + const { + arguments: [, , isPhrase], + } = is.buildNodeParams('response', 200); + expect(isPhrase.value).toBe(false); + }); + + test('should allow specification of a phrase query', () => { + const { + arguments: [, , isPhrase], + } = is.buildNodeParams('response', 200, true); + expect(isPhrase.value).toBe(true); + }); + }); + + describe('toElasticsearchQuery', () => { + test('should return an ES match_all query when fieldName and value are both "*"', () => { + const expected = { + match_all: {}, + }; + const node = nodeTypes.function.buildNode('is', '*', '*'); + const result = is.toElasticsearchQuery(node, indexPattern); + + expect(result).toEqual(expected); + }); + + test('should return an ES multi_match query using default_field when fieldName is null', () => { + const expected = { + multi_match: { + query: 200, + type: 'best_fields', + lenient: true, + }, + }; + const node = nodeTypes.function.buildNode('is', null, 200); + const result = is.toElasticsearchQuery(node, indexPattern); + + expect(result).toEqual(expected); + }); + + test('should return an ES query_string query using default_field when fieldName is null and value contains a wildcard', () => { + const expected = { + query_string: { + query: 'jpg*', + }, + }; + const node = nodeTypes.function.buildNode('is', null, 'jpg*'); + const result = is.toElasticsearchQuery(node, indexPattern); + + expect(result).toEqual(expected); + }); + + test('should return an ES bool query with a sub-query for each field when fieldName is "*"', () => { + const node = nodeTypes.function.buildNode('is', '*', 200); + const result = is.toElasticsearchQuery(node, indexPattern); + + expect(result).toHaveProperty('bool'); + expect(result.bool.should.length).toBe(indexPattern.fields.length); + }); + + test('should return an ES exists query when value is "*"', () => { + const expected = { + bool: { + should: [{ exists: { field: 'extension' } }], + minimum_should_match: 1, + }, + }; + const node = nodeTypes.function.buildNode('is', 'extension', '*'); + const result = is.toElasticsearchQuery(node, indexPattern); + + expect(result).toEqual(expected); + }); + + test('should return an ES match query when a concrete fieldName and value are provided', () => { + const expected = { + bool: { + should: [{ match: { extension: 'jpg' } }], + minimum_should_match: 1, + }, + }; + const node = nodeTypes.function.buildNode('is', 'extension', 'jpg'); + const result = is.toElasticsearchQuery(node, indexPattern); + + expect(result).toEqual(expected); + }); + + test('should return an ES match query when a concrete fieldName and value are provided without an index pattern', () => { + const expected = { + bool: { + should: [{ match: { extension: 'jpg' } }], + minimum_should_match: 1, + }, + }; + const node = nodeTypes.function.buildNode('is', 'extension', 'jpg'); + const result = is.toElasticsearchQuery(node); + + expect(result).toEqual(expected); + }); + + test('should support creation of phrase queries', () => { + const expected = { + bool: { + should: [{ match_phrase: { extension: 'jpg' } }], + minimum_should_match: 1, + }, + }; + const node = nodeTypes.function.buildNode('is', 'extension', 'jpg', true); + const result = is.toElasticsearchQuery(node, indexPattern); + + expect(result).toEqual(expected); + }); + + test('should create a query_string query for wildcard values', () => { + const expected = { + bool: { + should: [ + { + query_string: { + fields: ['extension'], + query: 'jpg*', + }, + }, + ], + minimum_should_match: 1, + }, + }; + const node = nodeTypes.function.buildNode('is', 'extension', 'jpg*'); + const result = is.toElasticsearchQuery(node, indexPattern); + + expect(result).toEqual(expected); + }); + + test('should support scripted fields', () => { + const node = nodeTypes.function.buildNode('is', 'script string', 'foo'); + const result = is.toElasticsearchQuery(node, indexPattern); + + expect(result.bool.should[0]).toHaveProperty('script'); + }); + + test('should support date fields without a dateFormat provided', () => { + const expected = { + bool: { + should: [ + { + range: { + '@timestamp': { + gte: '2018-04-03T19:04:17', + lte: '2018-04-03T19:04:17', + }, + }, + }, + ], + minimum_should_match: 1, + }, + }; + const node = nodeTypes.function.buildNode('is', '@timestamp', '"2018-04-03T19:04:17"'); + const result = is.toElasticsearchQuery(node, indexPattern); + + expect(result).toEqual(expected); + }); + + test('should support date fields with a dateFormat provided', () => { + const config = { dateFormatTZ: 'America/Phoenix' }; + const expected = { + bool: { + should: [ + { + range: { + '@timestamp': { + gte: '2018-04-03T19:04:17', + lte: '2018-04-03T19:04:17', + time_zone: 'America/Phoenix', + }, + }, + }, + ], + minimum_should_match: 1, + }, + }; + const node = nodeTypes.function.buildNode('is', '@timestamp', '"2018-04-03T19:04:17"'); + const result = is.toElasticsearchQuery(node, indexPattern, config); + + expect(result).toEqual(expected); + }); + + test('should use a provided nested context to create a full field name', () => { + const expected = { + bool: { + should: [{ match: { 'nestedField.extension': 'jpg' } }], + minimum_should_match: 1, + }, + }; + const node = nodeTypes.function.buildNode('is', 'extension', 'jpg'); + const result = is.toElasticsearchQuery( + node, + indexPattern, + {}, + { nested: { path: 'nestedField' } } + ); + + expect(result).toEqual(expected); + }); + + test('should support wildcard field names', () => { + const expected = { + bool: { + should: [{ match: { extension: 'jpg' } }], + minimum_should_match: 1, + }, + }; + const node = nodeTypes.function.buildNode('is', 'ext*', 'jpg'); + const result = is.toElasticsearchQuery(node, indexPattern); + + expect(result).toEqual(expected); + }); + + test('should automatically add a nested query when a wildcard field name covers a nested field', () => { + const expected = { + bool: { + should: [ + { + nested: { + path: 'nestedField.nestedChild', + query: { + match: { + 'nestedField.nestedChild.doublyNestedChild': 'foo', + }, + }, + score_mode: 'none', + }, + }, + ], + minimum_should_match: 1, + }, + }; + const node = nodeTypes.function.buildNode('is', '*doublyNested*', 'foo'); + const result = is.toElasticsearchQuery(node, indexPattern); + + expect(result).toEqual(expected); + }); + }); + }); +}); diff --git a/packages/kbn-es-query/src/kuery/functions/nested.js b/src/plugins/data/common/es_query/kuery/functions/nested.js similarity index 100% rename from packages/kbn-es-query/src/kuery/functions/nested.js rename to src/plugins/data/common/es_query/kuery/functions/nested.js diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/nested.js b/src/plugins/data/common/es_query/kuery/functions/nested.test.ts similarity index 54% rename from packages/kbn-es-query/src/kuery/functions/__tests__/nested.js rename to src/plugins/data/common/es_query/kuery/functions/nested.test.ts index 5ba73e485ddf1d..945a36d304a057 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/nested.js +++ b/src/plugins/data/common/es_query/kuery/functions/nested.test.ts @@ -17,52 +17,60 @@ * under the License. */ -import expect from '@kbn/expect'; -import * as nested from '../nested'; -import { nodeTypes } from '../../node_types'; -import * as ast from '../../ast'; -import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; +import { nodeTypes } from '../node_types'; +import { fields } from '../../../index_patterns/mocks'; +import { IIndexPattern } from '../../../index_patterns'; -let indexPattern; +import * as ast from '../ast'; + +// @ts-ignore +import * as nested from './nested'; const childNode = nodeTypes.function.buildNode('is', 'child', 'foo'); -describe('kuery functions', function () { - describe('nested', function () { +describe('kuery functions', () => { + describe('nested', () => { + let indexPattern: IIndexPattern; beforeEach(() => { - indexPattern = indexPatternResponse; + indexPattern = ({ + fields, + } as unknown) as IIndexPattern; }); - describe('buildNodeParams', function () { - - it('arguments should contain the unmodified child nodes', function () { + describe('buildNodeParams', () => { + test('arguments should contain the unmodified child nodes', () => { const result = nested.buildNodeParams('nestedField', childNode); - const { arguments: [ resultPath, resultChildNode ] } = result; - expect(ast.toElasticsearchQuery(resultPath)).to.be('nestedField'); - expect(resultChildNode).to.be(childNode); + const { + arguments: [resultPath, resultChildNode], + } = result; + + expect(ast.toElasticsearchQuery(resultPath)).toBe('nestedField'); + expect(resultChildNode).toBe(childNode); }); }); - describe('toElasticsearchQuery', function () { - - it('should wrap subqueries in an ES nested query', function () { + describe('toElasticsearchQuery', () => { + test('should wrap subqueries in an ES nested query', () => { const node = nodeTypes.function.buildNode('nested', 'nestedField', childNode); const result = nested.toElasticsearchQuery(node, indexPattern); - expect(result).to.only.have.keys('nested'); - expect(result.nested.path).to.be('nestedField'); - expect(result.nested.score_mode).to.be('none'); + + expect(result).toHaveProperty('nested'); + expect(Object.keys(result).length).toBe(1); + + expect(result.nested.path).toBe('nestedField'); + expect(result.nested.score_mode).toBe('none'); }); - it('should pass the nested path to subqueries so the full field name can be used', function () { + test('should pass the nested path to subqueries so the full field name can be used', () => { const node = nodeTypes.function.buildNode('nested', 'nestedField', childNode); const result = nested.toElasticsearchQuery(node, indexPattern); const expectedSubQuery = ast.toElasticsearchQuery( nodeTypes.function.buildNode('is', 'nestedField.child', 'foo') ); - expect(result.nested.query).to.eql(expectedSubQuery); - }); + expect(result.nested.query).toEqual(expectedSubQuery); + }); }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/not.js b/src/plugins/data/common/es_query/kuery/functions/not.js similarity index 100% rename from packages/kbn-es-query/src/kuery/functions/not.js rename to src/plugins/data/common/es_query/kuery/functions/not.js diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/not.js b/src/plugins/data/common/es_query/kuery/functions/not.test.ts similarity index 50% rename from packages/kbn-es-query/src/kuery/functions/__tests__/not.js rename to src/plugins/data/common/es_query/kuery/functions/not.test.ts index 7a2d7fa39c1528..01c1976b939ea0 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/not.js +++ b/src/plugins/data/common/es_query/kuery/functions/not.test.ts @@ -17,44 +17,50 @@ * under the License. */ -import expect from '@kbn/expect'; -import * as not from '../not'; -import { nodeTypes } from '../../node_types'; -import * as ast from '../../ast'; -import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; +import { nodeTypes } from '../node_types'; +import { fields } from '../../../index_patterns/mocks'; +import { IIndexPattern } from '../../../index_patterns'; -let indexPattern; +import * as ast from '../ast'; -const childNode = nodeTypes.function.buildNode('is', 'extension', 'jpg'); +// @ts-ignore +import * as not from './not'; -describe('kuery functions', function () { +const childNode = nodeTypes.function.buildNode('is', 'extension', 'jpg'); - describe('not', function () { +describe('kuery functions', () => { + describe('not', () => { + let indexPattern: IIndexPattern; beforeEach(() => { - indexPattern = indexPatternResponse; + indexPattern = ({ + fields, + } as unknown) as IIndexPattern; }); - describe('buildNodeParams', function () { + describe('buildNodeParams', () => { + test('arguments should contain the unmodified child node', () => { + const { + arguments: [actualChild], + } = not.buildNodeParams(childNode); - it('arguments should contain the unmodified child node', function () { - const { arguments: [ actualChild ] } = not.buildNodeParams(childNode); - expect(actualChild).to.be(childNode); + expect(actualChild).toBe(childNode); }); - - }); - describe('toElasticsearchQuery', function () { - - it('should wrap a subquery in an ES bool query\'s must_not clause', function () { + describe('toElasticsearchQuery', () => { + test("should wrap a subquery in an ES bool query's must_not clause", () => { const node = nodeTypes.function.buildNode('not', childNode); const result = not.toElasticsearchQuery(node, indexPattern); - expect(result).to.only.have.keys('bool'); - expect(result.bool).to.only.have.keys('must_not'); - expect(result.bool.must_not).to.eql(ast.toElasticsearchQuery(childNode, indexPattern)); - }); + expect(result).toHaveProperty('bool'); + expect(Object.keys(result).length).toBe(1); + + expect(result.bool).toHaveProperty('must_not'); + expect(Object.keys(result.bool).length).toBe(1); + + expect(result.bool.must_not).toEqual(ast.toElasticsearchQuery(childNode, indexPattern)); + }); }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/or.js b/src/plugins/data/common/es_query/kuery/functions/or.js similarity index 100% rename from packages/kbn-es-query/src/kuery/functions/or.js rename to src/plugins/data/common/es_query/kuery/functions/or.js diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/or.js b/src/plugins/data/common/es_query/kuery/functions/or.test.ts similarity index 52% rename from packages/kbn-es-query/src/kuery/functions/__tests__/or.js rename to src/plugins/data/common/es_query/kuery/functions/or.test.ts index f24f24b98e7fbd..a6590546e5fc5c 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/or.js +++ b/src/plugins/data/common/es_query/kuery/functions/or.test.ts @@ -17,56 +17,61 @@ * under the License. */ -import expect from '@kbn/expect'; -import * as or from '../or'; -import { nodeTypes } from '../../node_types'; -import * as ast from '../../ast'; -import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; +import { nodeTypes } from '../node_types'; +import { fields } from '../../../index_patterns/mocks'; +import { IIndexPattern } from '../../../index_patterns'; -let indexPattern; +import * as ast from '../ast'; + +// @ts-ignore +import * as or from './or'; const childNode1 = nodeTypes.function.buildNode('is', 'machine.os', 'osx'); const childNode2 = nodeTypes.function.buildNode('is', 'extension', 'jpg'); -describe('kuery functions', function () { - - describe('or', function () { +describe('kuery functions', () => { + describe('or', () => { + let indexPattern: IIndexPattern; beforeEach(() => { - indexPattern = indexPatternResponse; + indexPattern = ({ + fields, + } as unknown) as IIndexPattern; }); - - describe('buildNodeParams', function () { - - it('arguments should contain the unmodified child nodes', function () { + describe('buildNodeParams', () => { + test('arguments should contain the unmodified child nodes', () => { const result = or.buildNodeParams([childNode1, childNode2]); - const { arguments: [ actualChildNode1, actualChildNode2 ] } = result; - expect(actualChildNode1).to.be(childNode1); - expect(actualChildNode2).to.be(childNode2); - }); + const { + arguments: [actualChildNode1, actualChildNode2], + } = result; + expect(actualChildNode1).toBe(childNode1); + expect(actualChildNode2).toBe(childNode2); + }); }); - describe('toElasticsearchQuery', function () { - - it('should wrap subqueries in an ES bool query\'s should clause', function () { + describe('toElasticsearchQuery', () => { + test("should wrap subqueries in an ES bool query's should clause", () => { const node = nodeTypes.function.buildNode('or', [childNode1, childNode2]); const result = or.toElasticsearchQuery(node, indexPattern); - expect(result).to.only.have.keys('bool'); - expect(result.bool).to.have.keys('should'); - expect(result.bool.should).to.eql( - [childNode1, childNode2].map((childNode) => ast.toElasticsearchQuery(childNode, indexPattern)) + + expect(result).toHaveProperty('bool'); + expect(Object.keys(result).length).toBe(1); + expect(result.bool).toHaveProperty('should'); + expect(result.bool.should).toEqual( + [childNode1, childNode2].map(childNode => + ast.toElasticsearchQuery(childNode, indexPattern) + ) ); }); - it('should require one of the clauses to match', function () { + test('should require one of the clauses to match', () => { const node = nodeTypes.function.buildNode('or', [childNode1, childNode2]); const result = or.toElasticsearchQuery(node, indexPattern); - expect(result.bool).to.have.property('minimum_should_match', 1); - }); + expect(result.bool).toHaveProperty('minimum_should_match', 1); + }); }); - }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/range.js b/src/plugins/data/common/es_query/kuery/functions/range.js similarity index 98% rename from packages/kbn-es-query/src/kuery/functions/range.js rename to src/plugins/data/common/es_query/kuery/functions/range.js index f7719998ad5240..80181cfc003f1c 100644 --- a/packages/kbn-es-query/src/kuery/functions/range.js +++ b/src/plugins/data/common/es_query/kuery/functions/range.js @@ -20,7 +20,7 @@ import _ from 'lodash'; import { nodeTypes } from '../node_types'; import * as ast from '../ast'; -import { getRangeScript } from '../../utils/filters'; +import { getRangeScript } from '../../filters'; import { getFields } from './utils/get_fields'; import { getTimeZoneFromSettings } from '../../utils/get_time_zone_from_settings'; import { getFullFieldNameNode } from './utils/get_full_field_name_node'; diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/range.js b/src/plugins/data/common/es_query/kuery/functions/range.test.ts similarity index 54% rename from packages/kbn-es-query/src/kuery/functions/__tests__/range.js rename to src/plugins/data/common/es_query/kuery/functions/range.test.ts index 2361e8bb667691..ed8e40830df021 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/range.js +++ b/src/plugins/data/common/es_query/kuery/functions/range.test.ts @@ -17,53 +17,57 @@ * under the License. */ -import expect from '@kbn/expect'; -import * as range from '../range'; -import { nodeTypes } from '../../node_types'; -import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; +import { get } from 'lodash'; +import { nodeTypes } from '../node_types'; +import { fields } from '../../../index_patterns/mocks'; +import { IIndexPattern } from '../../../index_patterns'; +import { RangeFilterParams } from '../../filters'; -let indexPattern; - -describe('kuery functions', function () { - - describe('range', function () { +// @ts-ignore +import * as range from './range'; +describe('kuery functions', () => { + describe('range', () => { + let indexPattern: IIndexPattern; beforeEach(() => { - indexPattern = indexPatternResponse; + indexPattern = ({ + fields, + } as unknown) as IIndexPattern; }); - describe('buildNodeParams', function () { - - it('arguments should contain the provided fieldName as a literal', function () { + describe('buildNodeParams', () => { + test('arguments should contain the provided fieldName as a literal', () => { const result = range.buildNodeParams('bytes', { gt: 1000, lt: 8000 }); - const { arguments: [fieldName] } = result; + const { + arguments: [fieldName], + } = result; - expect(fieldName).to.have.property('type', 'literal'); - expect(fieldName).to.have.property('value', 'bytes'); + expect(fieldName).toHaveProperty('type', 'literal'); + expect(fieldName).toHaveProperty('value', 'bytes'); }); - it('arguments should contain the provided params as named arguments', function () { - const givenParams = { gt: 1000, lt: 8000, format: 'epoch_millis' }; + test('arguments should contain the provided params as named arguments', () => { + const givenParams: RangeFilterParams = { gt: 1000, lt: 8000, format: 'epoch_millis' }; const result = range.buildNodeParams('bytes', givenParams); - const { arguments: [, ...params] } = result; + const { + arguments: [, ...params], + } = result; - expect(params).to.be.an('array'); - expect(params).to.not.be.empty(); + expect(Array.isArray(params)).toBeTruthy(); + expect(params.length).toBeGreaterThan(1); - params.map((param) => { - expect(param).to.have.property('type', 'namedArg'); - expect(['gt', 'lt', 'format'].includes(param.name)).to.be(true); - expect(param.value.type).to.be('literal'); - expect(param.value.value).to.be(givenParams[param.name]); + params.map((param: any) => { + expect(param).toHaveProperty('type', 'namedArg'); + expect(['gt', 'lt', 'format'].includes(param.name)).toBe(true); + expect(param.value.type).toBe('literal'); + expect(param.value.value).toBe(get(givenParams, param.name)); }); }); - }); - describe('toElasticsearchQuery', function () { - - it('should return an ES range query for the node\'s field and params', function () { + describe('toElasticsearchQuery', () => { + test("should return an ES range query for the node's field and params", () => { const expected = { bool: { should: [ @@ -71,21 +75,21 @@ describe('kuery functions', function () { range: { bytes: { gt: 1000, - lt: 8000 - } - } - } + lt: 8000, + }, + }, + }, ], - minimum_should_match: 1 - } + minimum_should_match: 1, + }, }; - const node = nodeTypes.function.buildNode('range', 'bytes', { gt: 1000, lt: 8000 }); const result = range.toElasticsearchQuery(node, indexPattern); - expect(result).to.eql(expected); + + expect(result).toEqual(expected); }); - it('should return an ES range query without an index pattern', function () { + test('should return an ES range query without an index pattern', () => { const expected = { bool: { should: [ @@ -93,21 +97,22 @@ describe('kuery functions', function () { range: { bytes: { gt: 1000, - lt: 8000 - } - } - } + lt: 8000, + }, + }, + }, ], - minimum_should_match: 1 - } + minimum_should_match: 1, + }, }; const node = nodeTypes.function.buildNode('range', 'bytes', { gt: 1000, lt: 8000 }); const result = range.toElasticsearchQuery(node); - expect(result).to.eql(expected); + + expect(result).toEqual(expected); }); - it('should support wildcard field names', function () { + test('should support wildcard field names', () => { const expected = { bool: { should: [ @@ -115,27 +120,29 @@ describe('kuery functions', function () { range: { bytes: { gt: 1000, - lt: 8000 - } - } - } + lt: 8000, + }, + }, + }, ], - minimum_should_match: 1 - } + minimum_should_match: 1, + }, }; const node = nodeTypes.function.buildNode('range', 'byt*', { gt: 1000, lt: 8000 }); const result = range.toElasticsearchQuery(node, indexPattern); - expect(result).to.eql(expected); + + expect(result).toEqual(expected); }); - it('should support scripted fields', function () { + test('should support scripted fields', () => { const node = nodeTypes.function.buildNode('range', 'script number', { gt: 1000, lt: 8000 }); const result = range.toElasticsearchQuery(node, indexPattern); - expect(result.bool.should[0]).to.have.key('script'); + + expect(result.bool.should[0]).toHaveProperty('script'); }); - it('should support date fields without a dateFormat provided', function () { + test('should support date fields without a dateFormat provided', () => { const expected = { bool: { should: [ @@ -144,20 +151,23 @@ describe('kuery functions', function () { '@timestamp': { gt: '2018-01-03T19:04:17', lt: '2018-04-03T19:04:17', - } - } - } + }, + }, + }, ], - minimum_should_match: 1 - } + minimum_should_match: 1, + }, }; - - const node = nodeTypes.function.buildNode('range', '@timestamp', { gt: '2018-01-03T19:04:17', lt: '2018-04-03T19:04:17' }); + const node = nodeTypes.function.buildNode('range', '@timestamp', { + gt: '2018-01-03T19:04:17', + lt: '2018-04-03T19:04:17', + }); const result = range.toElasticsearchQuery(node, indexPattern); - expect(result).to.eql(expected); + + expect(result).toEqual(expected); }); - it('should support date fields with a dateFormat provided', function () { + test('should support date fields with a dateFormat provided', () => { const config = { dateFormatTZ: 'America/Phoenix' }; const expected = { bool: { @@ -168,20 +178,23 @@ describe('kuery functions', function () { gt: '2018-01-03T19:04:17', lt: '2018-04-03T19:04:17', time_zone: 'America/Phoenix', - } - } - } + }, + }, + }, ], - minimum_should_match: 1 - } + minimum_should_match: 1, + }, }; - - const node = nodeTypes.function.buildNode('range', '@timestamp', { gt: '2018-01-03T19:04:17', lt: '2018-04-03T19:04:17' }); + const node = nodeTypes.function.buildNode('range', '@timestamp', { + gt: '2018-01-03T19:04:17', + lt: '2018-04-03T19:04:17', + }); const result = range.toElasticsearchQuery(node, indexPattern, config); - expect(result).to.eql(expected); + + expect(result).toEqual(expected); }); - it('should use a provided nested context to create a full field name', function () { + test('should use a provided nested context to create a full field name', () => { const expected = { bool: { should: [ @@ -189,15 +202,14 @@ describe('kuery functions', function () { range: { 'nestedField.bytes': { gt: 1000, - lt: 8000 - } - } - } + lt: 8000, + }, + }, + }, ], - minimum_should_match: 1 - } + minimum_should_match: 1, + }, }; - const node = nodeTypes.function.buildNode('range', 'bytes', { gt: 1000, lt: 8000 }); const result = range.toElasticsearchQuery( node, @@ -205,10 +217,11 @@ describe('kuery functions', function () { {}, { nested: { path: 'nestedField' } } ); - expect(result).to.eql(expected); + + expect(result).toEqual(expected); }); - it('should automatically add a nested query when a wildcard field name covers a nested field', function () { + test('should automatically add a nested query when a wildcard field name covers a nested field', () => { const expected = { bool: { should: [ @@ -219,21 +232,24 @@ describe('kuery functions', function () { range: { 'nestedField.nestedChild.doublyNestedChild': { gt: 1000, - lt: 8000 - } - } + lt: 8000, + }, + }, }, - score_mode: 'none' - } - } + score_mode: 'none', + }, + }, ], - minimum_should_match: 1 - } + minimum_should_match: 1, + }, }; - - const node = nodeTypes.function.buildNode('range', '*doublyNested*', { gt: 1000, lt: 8000 }); + const node = nodeTypes.function.buildNode('range', '*doublyNested*', { + gt: 1000, + lt: 8000, + }); const result = range.toElasticsearchQuery(node, indexPattern); - expect(result).to.eql(expected); + + expect(result).toEqual(expected); }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/utils/get_fields.js b/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.js similarity index 100% rename from packages/kbn-es-query/src/kuery/functions/utils/get_fields.js rename to src/plugins/data/common/es_query/kuery/functions/utils/get_fields.js diff --git a/packages/kbn-es-query/src/kuery/functions/__tests__/utils/get_fields.js b/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.test.ts similarity index 52% rename from packages/kbn-es-query/src/kuery/functions/__tests__/utils/get_fields.js rename to src/plugins/data/common/es_query/kuery/functions/utils/get_fields.test.ts index 7718479130a8af..d48f0943082c92 100644 --- a/packages/kbn-es-query/src/kuery/functions/__tests__/utils/get_fields.js +++ b/src/plugins/data/common/es_query/kuery/functions/utils/get_fields.test.ts @@ -17,39 +17,41 @@ * under the License. */ -import { getFields } from '../../utils/get_fields'; -import expect from '@kbn/expect'; -import indexPatternResponse from '../../../../__fixtures__/index_pattern_response.json'; +import { fields } from '../../../../index_patterns/mocks'; -import { nodeTypes } from '../../..'; +import { nodeTypes } from '../../index'; +import { IIndexPattern, IFieldType } from '../../../../index_patterns'; -let indexPattern; - -describe('getFields', function () { +// @ts-ignore +import { getFields } from './get_fields'; +describe('getFields', () => { + let indexPattern: IIndexPattern; beforeEach(() => { - indexPattern = indexPatternResponse; + indexPattern = ({ + fields, + } as unknown) as IIndexPattern; }); - describe('field names without a wildcard', function () { - - it('should return an empty array if the field does not exist in the index pattern', function () { + describe('field names without a wildcard', () => { + test('should return an empty array if the field does not exist in the index pattern', () => { const fieldNameNode = nodeTypes.literal.buildNode('nonExistentField'); - const expected = []; const actual = getFields(fieldNameNode, indexPattern); - expect(actual).to.eql(expected); + + expect(actual).toEqual([]); }); - it('should return the single matching field in an array', function () { + test('should return the single matching field in an array', () => { const fieldNameNode = nodeTypes.literal.buildNode('extension'); const results = getFields(fieldNameNode, indexPattern); - expect(results).to.be.an('array'); - expect(results).to.have.length(1); - expect(results[0].name).to.be('extension'); + + expect(results).toHaveLength(1); + expect(Array.isArray(results)).toBeTruthy(); + expect(results[0].name).toBe('extension'); }); - it('should not match a wildcard in a literal node', function () { + test('should not match a wildcard in a literal node', () => { const indexPatternWithWildField = { title: 'wildIndex', fields: [ @@ -61,37 +63,32 @@ describe('getFields', function () { const fieldNameNode = nodeTypes.literal.buildNode('foo*'); const results = getFields(fieldNameNode, indexPatternWithWildField); - expect(results).to.be.an('array'); - expect(results).to.have.length(1); - expect(results[0].name).to.be('foo*'); - // ensure the wildcard is not actually being parsed - const expected = []; + expect(results).toHaveLength(1); + expect(Array.isArray(results)).toBeTruthy(); + expect(results[0].name).toBe('foo*'); + const actual = getFields(nodeTypes.literal.buildNode('fo*'), indexPatternWithWildField); - expect(actual).to.eql(expected); + expect(actual).toEqual([]); }); }); - describe('field name patterns with a wildcard', function () { - - it('should return an empty array if it does not match any fields in the index pattern', function () { + describe('field name patterns with a wildcard', () => { + test('should return an empty array if test does not match any fields in the index pattern', () => { const fieldNameNode = nodeTypes.wildcard.buildNode('nonExistent*'); - const expected = []; const actual = getFields(fieldNameNode, indexPattern); - expect(actual).to.eql(expected); + + expect(actual).toEqual([]); }); - it('should return all fields that match the pattern in an array', function () { + test('should return all fields that match the pattern in an array', () => { const fieldNameNode = nodeTypes.wildcard.buildNode('machine*'); const results = getFields(fieldNameNode, indexPattern); - expect(results).to.be.an('array'); - expect(results).to.have.length(2); - expect(results.find((field) => { - return field.name === 'machine.os'; - })).to.be.ok(); - expect(results.find((field) => { - return field.name === 'machine.os.raw'; - })).to.be.ok(); + + expect(Array.isArray(results)).toBeTruthy(); + expect(results).toHaveLength(2); + expect(results.find((field: IFieldType) => field.name === 'machine.os')).toBeDefined(); + expect(results.find((field: IFieldType) => field.name === 'machine.os.raw')).toBeDefined(); }); }); }); diff --git a/packages/kbn-es-query/src/kuery/functions/utils/get_full_field_name_node.js b/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.js similarity index 100% rename from packages/kbn-es-query/src/kuery/functions/utils/get_full_field_name_node.js rename to src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.js diff --git a/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.test.ts b/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.test.ts new file mode 100644 index 00000000000000..e138e22b76ad30 --- /dev/null +++ b/src/plugins/data/common/es_query/kuery/functions/utils/get_full_field_name_node.test.ts @@ -0,0 +1,87 @@ +/* + * 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 { nodeTypes } from '../../node_types'; +import { fields } from '../../../../index_patterns/mocks'; +import { IIndexPattern } from '../../../../index_patterns'; + +// @ts-ignore +import { getFullFieldNameNode } from './get_full_field_name_node'; + +describe('getFullFieldNameNode', function() { + let indexPattern: IIndexPattern; + + beforeEach(() => { + indexPattern = ({ + fields, + } as unknown) as IIndexPattern; + }); + + test('should return unchanged name node if no nested path is passed in', () => { + const nameNode = nodeTypes.literal.buildNode('notNested'); + const result = getFullFieldNameNode(nameNode, indexPattern); + + expect(result).toEqual(nameNode); + }); + + test('should add the nested path if test is valid according to the index pattern', () => { + const nameNode = nodeTypes.literal.buildNode('child'); + const result = getFullFieldNameNode(nameNode, indexPattern, 'nestedField'); + + expect(result).toEqual(nodeTypes.literal.buildNode('nestedField.child')); + }); + + test('should throw an error if a path is provided for a non-nested field', () => { + const nameNode = nodeTypes.literal.buildNode('os'); + expect(() => getFullFieldNameNode(nameNode, indexPattern, 'machine')).toThrowError( + /machine.os is not a nested field but is in nested group "machine" in the KQL expression/ + ); + }); + + test('should throw an error if a nested field is not passed with a path', () => { + const nameNode = nodeTypes.literal.buildNode('nestedField.child'); + + expect(() => getFullFieldNameNode(nameNode, indexPattern)).toThrowError( + /nestedField.child is a nested field, but is not in a nested group in the KQL expression./ + ); + }); + + test('should throw an error if a nested field is passed with the wrong path', () => { + const nameNode = nodeTypes.literal.buildNode('nestedChild.doublyNestedChild'); + + expect(() => getFullFieldNameNode(nameNode, indexPattern, 'nestedField')).toThrowError( + /Nested field nestedField.nestedChild.doublyNestedChild is being queried with the incorrect nested path. The correct path is nestedField.nestedChild/ + ); + }); + + test('should skip error checking for wildcard names', () => { + const nameNode = nodeTypes.wildcard.buildNode('nested*'); + const result = getFullFieldNameNode(nameNode, indexPattern); + + expect(result).toEqual(nameNode); + }); + + test('should skip error checking if no index pattern is passed in', () => { + const nameNode = nodeTypes.literal.buildNode('os'); + expect(() => getFullFieldNameNode(nameNode, null, 'machine')).not.toThrowError(); + + const result = getFullFieldNameNode(nameNode, null, 'machine'); + expect(result).toEqual(nodeTypes.literal.buildNode('machine.os')); + }); +}); diff --git a/packages/kbn-es-query/src/kuery/index.js b/src/plugins/data/common/es_query/kuery/index.ts similarity index 91% rename from packages/kbn-es-query/src/kuery/index.js rename to src/plugins/data/common/es_query/kuery/index.ts index e0cacada7f274e..4184dea62ef2c4 100644 --- a/packages/kbn-es-query/src/kuery/index.js +++ b/src/plugins/data/common/es_query/kuery/index.ts @@ -17,6 +17,8 @@ * under the License. */ -export * from './ast'; +export { KQLSyntaxError } from './kuery_syntax_error'; export { nodeTypes } from './node_types'; -export * from './errors'; +export * from './ast'; + +export * from './types'; diff --git a/packages/kbn-es-query/src/kuery/errors/index.test.js b/src/plugins/data/common/es_query/kuery/kuery_syntax_error.test.ts similarity index 66% rename from packages/kbn-es-query/src/kuery/errors/index.test.js rename to src/plugins/data/common/es_query/kuery/kuery_syntax_error.test.ts index d8040e464b696b..cfe2f86e813cad 100644 --- a/packages/kbn-es-query/src/kuery/errors/index.test.js +++ b/src/plugins/data/common/es_query/kuery/kuery_syntax_error.test.ts @@ -17,89 +17,92 @@ * under the License. */ -import { fromKueryExpression } from '../ast'; - +import { fromKueryExpression } from './ast'; describe('kql syntax errors', () => { - it('should throw an error for a field query missing a value', () => { expect(() => { fromKueryExpression('response:'); - }).toThrow('Expected "(", "{", value, whitespace but end of input found.\n' + - 'response:\n' + - '---------^'); + }).toThrow( + 'Expected "(", "{", value, whitespace but end of input found.\n' + + 'response:\n' + + '---------^' + ); }); it('should throw an error for an OR query missing a right side sub-query', () => { expect(() => { fromKueryExpression('response:200 or '); - }).toThrow('Expected "(", NOT, field name, value but end of input found.\n' + - 'response:200 or \n' + - '----------------^'); + }).toThrow( + 'Expected "(", NOT, field name, value but end of input found.\n' + + 'response:200 or \n' + + '----------------^' + ); }); it('should throw an error for an OR list of values missing a right side sub-query', () => { expect(() => { fromKueryExpression('response:(200 or )'); - }).toThrow('Expected "(", NOT, value but ")" found.\n' + - 'response:(200 or )\n' + - '-----------------^'); + }).toThrow( + 'Expected "(", NOT, value but ")" found.\n' + 'response:(200 or )\n' + '-----------------^' + ); }); it('should throw an error for a NOT query missing a sub-query', () => { expect(() => { fromKueryExpression('response:200 and not '); - }).toThrow('Expected "(", field name, value but end of input found.\n' + - 'response:200 and not \n' + - '---------------------^'); + }).toThrow( + 'Expected "(", field name, value but end of input found.\n' + + 'response:200 and not \n' + + '---------------------^' + ); }); it('should throw an error for a NOT list missing a sub-query', () => { expect(() => { fromKueryExpression('response:(200 and not )'); - }).toThrow('Expected "(", value but ")" found.\n' + - 'response:(200 and not )\n' + - '----------------------^'); + }).toThrow( + 'Expected "(", value but ")" found.\n' + + 'response:(200 and not )\n' + + '----------------------^' + ); }); it('should throw an error for unbalanced quotes', () => { expect(() => { fromKueryExpression('foo:"ba '); - }).toThrow('Expected "(", "{", value, whitespace but """ found.\n' + - 'foo:"ba \n' + - '----^'); + }).toThrow('Expected "(", "{", value, whitespace but """ found.\n' + 'foo:"ba \n' + '----^'); }); it('should throw an error for unescaped quotes in a quoted string', () => { expect(() => { fromKueryExpression('foo:"ba "r"'); - }).toThrow('Expected AND, OR, end of input, whitespace but "r" found.\n' + - 'foo:"ba "r"\n' + - '---------^'); + }).toThrow( + 'Expected AND, OR, end of input, whitespace but "r" found.\n' + 'foo:"ba "r"\n' + '---------^' + ); }); it('should throw an error for unescaped special characters in literals', () => { expect(() => { fromKueryExpression('foo:ba:r'); - }).toThrow('Expected AND, OR, end of input, whitespace but ":" found.\n' + - 'foo:ba:r\n' + - '------^'); + }).toThrow( + 'Expected AND, OR, end of input, whitespace but ":" found.\n' + 'foo:ba:r\n' + '------^' + ); }); it('should throw an error for range queries missing a value', () => { expect(() => { fromKueryExpression('foo > '); - }).toThrow('Expected literal, whitespace but end of input found.\n' + - 'foo > \n' + - '------^'); + }).toThrow('Expected literal, whitespace but end of input found.\n' + 'foo > \n' + '------^'); }); it('should throw an error for range queries missing a field', () => { expect(() => { fromKueryExpression('< 1000'); - }).toThrow('Expected "(", NOT, end of input, field name, value, whitespace but "<" found.\n' + - '< 1000\n' + - '^'); + }).toThrow( + 'Expected "(", NOT, end of input, field name, value, whitespace but "<" found.\n' + + '< 1000\n' + + '^' + ); }); - }); diff --git a/packages/kbn-es-query/src/kuery/errors/index.js b/src/plugins/data/common/es_query/kuery/kuery_syntax_error.ts similarity index 55% rename from packages/kbn-es-query/src/kuery/errors/index.js rename to src/plugins/data/common/es_query/kuery/kuery_syntax_error.ts index 82e1aee7b775a1..7c90119fcc1bc3 100644 --- a/packages/kbn-es-query/src/kuery/errors/index.js +++ b/src/plugins/data/common/es_query/kuery/kuery_syntax_error.ts @@ -20,35 +20,46 @@ import { repeat } from 'lodash'; import { i18n } from '@kbn/i18n'; -const endOfInputText = i18n.translate('kbnESQuery.kql.errors.endOfInputText', { +const endOfInputText = i18n.translate('data.common.esQuery.kql.errors.endOfInputText', { defaultMessage: 'end of input', }); -export class KQLSyntaxError extends Error { +const grammarRuleTranslations: Record = { + fieldName: i18n.translate('data.common.esQuery.kql.errors.fieldNameText', { + defaultMessage: 'field name', + }), + value: i18n.translate('data.common.esQuery.kql.errors.valueText', { + defaultMessage: 'value', + }), + literal: i18n.translate('data.common.esQuery.kql.errors.literalText', { + defaultMessage: 'literal', + }), + whitespace: i18n.translate('data.common.esQuery.kql.errors.whitespaceText', { + defaultMessage: 'whitespace', + }), +}; + +interface KQLSyntaxErrorData extends Error { + found: string; + expected: KQLSyntaxErrorExpected[]; + location: any; +} - constructor(error, expression) { - const grammarRuleTranslations = { - fieldName: i18n.translate('kbnESQuery.kql.errors.fieldNameText', { - defaultMessage: 'field name', - }), - value: i18n.translate('kbnESQuery.kql.errors.valueText', { - defaultMessage: 'value', - }), - literal: i18n.translate('kbnESQuery.kql.errors.literalText', { - defaultMessage: 'literal', - }), - whitespace: i18n.translate('kbnESQuery.kql.errors.whitespaceText', { - defaultMessage: 'whitespace', - }), - }; +interface KQLSyntaxErrorExpected { + description: string; +} + +export class KQLSyntaxError extends Error { + shortMessage: string; - const translatedExpectations = error.expected.map((expected) => { + constructor(error: KQLSyntaxErrorData, expression: any) { + const translatedExpectations = error.expected.map(expected => { return grammarRuleTranslations[expected.description] || expected.description; }); const translatedExpectationText = translatedExpectations.join(', '); - const message = i18n.translate('kbnESQuery.kql.errors.syntaxError', { + const message = i18n.translate('data.common.esQuery.kql.errors.syntaxError', { defaultMessage: 'Expected {expectedList} but {foundInput} found.', values: { expectedList: translatedExpectationText, @@ -56,11 +67,9 @@ export class KQLSyntaxError extends Error { }, }); - const fullMessage = [ - message, - expression, - repeat('-', error.location.start.offset) + '^', - ].join('\n'); + const fullMessage = [message, expression, repeat('-', error.location.start.offset) + '^'].join( + '\n' + ); super(fullMessage); this.name = 'KQLSyntaxError'; diff --git a/packages/kbn-es-query/src/kuery/node_types/function.js b/src/plugins/data/common/es_query/kuery/node_types/function.js similarity index 100% rename from packages/kbn-es-query/src/kuery/node_types/function.js rename to src/plugins/data/common/es_query/kuery/node_types/function.js diff --git a/src/plugins/data/common/es_query/kuery/node_types/function.test.ts b/src/plugins/data/common/es_query/kuery/node_types/function.test.ts new file mode 100644 index 00000000000000..ca9798eb6e74f5 --- /dev/null +++ b/src/plugins/data/common/es_query/kuery/node_types/function.test.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 { fields } from '../../../index_patterns/mocks'; + +import { nodeTypes } from './index'; +import { IIndexPattern } from '../../../index_patterns'; + +// @ts-ignore +import { buildNode, buildNodeWithArgumentNodes, toElasticsearchQuery } from './function'; +// @ts-ignore +import { toElasticsearchQuery as isFunctionToElasticsearchQuery } from '../functions/is'; + +describe('kuery node types', () => { + describe('function', () => { + let indexPattern: IIndexPattern; + + beforeEach(() => { + indexPattern = ({ + fields, + } as unknown) as IIndexPattern; + }); + + describe('buildNode', () => { + test('should return a node representing the given kuery function', () => { + const result = buildNode('is', 'extension', 'jpg'); + + expect(result).toHaveProperty('type', 'function'); + expect(result).toHaveProperty('function', 'is'); + expect(result).toHaveProperty('arguments'); + }); + }); + + describe('buildNodeWithArgumentNodes', () => { + test('should return a function node with the given argument list untouched', () => { + const fieldNameLiteral = nodeTypes.literal.buildNode('extension'); + const valueLiteral = nodeTypes.literal.buildNode('jpg'); + const argumentNodes = [fieldNameLiteral, valueLiteral]; + const result = buildNodeWithArgumentNodes('is', argumentNodes); + + expect(result).toHaveProperty('type', 'function'); + expect(result).toHaveProperty('function', 'is'); + expect(result).toHaveProperty('arguments'); + expect(result.arguments).toBe(argumentNodes); + expect(result.arguments).toEqual(argumentNodes); + }); + }); + + describe('toElasticsearchQuery', () => { + test("should return the given function type's ES query representation", () => { + const node = buildNode('is', 'extension', 'jpg'); + const expected = isFunctionToElasticsearchQuery(node, indexPattern); + const result = toElasticsearchQuery(node, indexPattern); + + expect(expected).toEqual(result); + }); + }); + }); +}); diff --git a/packages/kbn-es-query/src/kuery/node_types/index.d.ts b/src/plugins/data/common/es_query/kuery/node_types/index.d.ts similarity index 72% rename from packages/kbn-es-query/src/kuery/node_types/index.d.ts rename to src/plugins/data/common/es_query/kuery/node_types/index.d.ts index daf8032f9fe0ef..720d64e11a0f89 100644 --- a/packages/kbn-es-query/src/kuery/node_types/index.d.ts +++ b/src/plugins/data/common/es_query/kuery/node_types/index.d.ts @@ -21,7 +21,8 @@ * WARNING: these typings are incomplete */ -import { JsonObject, JsonValue } from '..'; +import { IIndexPattern } from '../../../index_patterns'; +import { KueryNode, JsonValue } from '..'; type FunctionName = | 'is' @@ -34,6 +35,17 @@ type FunctionName = | 'geoPolygon' | 'nested'; +interface FunctionType { + buildNode: (functionName: FunctionName, ...args: any[]) => FunctionTypeBuildNode; + buildNodeWithArgumentNodes: (functionName: FunctionName, ...args: any[]) => FunctionTypeBuildNode; + toElasticsearchQuery: ( + node: any, + indexPattern?: IIndexPattern, + config?: Record, + context?: Record + ) => JsonValue; +} + interface FunctionTypeBuildNode { type: 'function'; function: FunctionName; @@ -41,32 +53,40 @@ interface FunctionTypeBuildNode { arguments: any[]; } -interface FunctionType { - buildNode: (functionName: FunctionName, ...args: any[]) => FunctionTypeBuildNode; - buildNodeWithArgumentNodes: (functionName: FunctionName, ...args: any[]) => FunctionTypeBuildNode; - toElasticsearchQuery: (node: any, indexPattern: any, config: JsonObject) => JsonValue; -} - interface LiteralType { - buildNode: ( - value: null | boolean | number | string - ) => { type: 'literal'; value: null | boolean | number | string }; + buildNode: (value: null | boolean | number | string) => LiteralTypeBuildNode; toElasticsearchQuery: (node: any) => null | boolean | number | string; } +interface LiteralTypeBuildNode { + type: 'literal'; + value: null | boolean | number | string; +} + interface NamedArgType { - buildNode: (name: string, value: any) => { type: 'namedArg'; name: string; value: any }; + buildNode: (name: string, value: any) => NamedArgTypeBuildNode; toElasticsearchQuery: (node: any) => string; } +interface NamedArgTypeBuildNode { + type: 'namedArg'; + name: string; + value: any; +} + interface WildcardType { - buildNode: (value: string) => { type: 'wildcard'; value: string }; + buildNode: (value: string) => WildcardTypeBuildNode; test: (node: any, string: string) => boolean; toElasticsearchQuery: (node: any) => string; toQueryStringQuery: (node: any) => string; hasLeadingWildcard: (node: any) => boolean; } +interface WildcardTypeBuildNode { + type: 'wildcard'; + value: string; +} + interface NodeTypes { function: FunctionType; literal: LiteralType; diff --git a/packages/kbn-es-query/src/kuery/node_types/index.js b/src/plugins/data/common/es_query/kuery/node_types/index.js similarity index 100% rename from packages/kbn-es-query/src/kuery/node_types/index.js rename to src/plugins/data/common/es_query/kuery/node_types/index.js diff --git a/packages/kbn-es-query/src/kuery/node_types/literal.js b/src/plugins/data/common/es_query/kuery/node_types/literal.js similarity index 100% rename from packages/kbn-es-query/src/kuery/node_types/literal.js rename to src/plugins/data/common/es_query/kuery/node_types/literal.js diff --git a/packages/kbn-es-query/src/kuery/node_types/__tests__/literal.js b/src/plugins/data/common/es_query/kuery/node_types/literal.test.ts similarity index 54% rename from packages/kbn-es-query/src/kuery/node_types/__tests__/literal.js rename to src/plugins/data/common/es_query/kuery/node_types/literal.test.ts index 25fe2bcc45a453..60fe2d6d1013c6 100644 --- a/packages/kbn-es-query/src/kuery/node_types/__tests__/literal.js +++ b/src/plugins/data/common/es_query/kuery/node_types/literal.test.ts @@ -17,34 +17,27 @@ * under the License. */ -import expect from '@kbn/expect'; -import * as literal from '../literal'; +// @ts-ignore +import { buildNode, toElasticsearchQuery } from './literal'; -describe('kuery node types', function () { +describe('kuery node types', () => { + describe('literal', () => { + describe('buildNode', () => { + test('should return a node representing the given value', () => { + const result = buildNode('foo'); - describe('literal', function () { - - describe('buildNode', function () { - - it('should return a node representing the given value', function () { - const result = literal.buildNode('foo'); - expect(result).to.have.property('type', 'literal'); - expect(result).to.have.property('value', 'foo'); + expect(result).toHaveProperty('type', 'literal'); + expect(result).toHaveProperty('value', 'foo'); }); - }); - describe('toElasticsearchQuery', function () { + describe('toElasticsearchQuery', () => { + test('should return the literal value represented by the given node', () => { + const node = buildNode('foo'); + const result = toElasticsearchQuery(node); - it('should return the literal value represented by the given node', function () { - const node = literal.buildNode('foo'); - const result = literal.toElasticsearchQuery(node); - expect(result).to.be('foo'); + expect(result).toBe('foo'); }); - }); - - }); - }); diff --git a/packages/kbn-es-query/src/kuery/node_types/named_arg.js b/src/plugins/data/common/es_query/kuery/node_types/named_arg.js similarity index 100% rename from packages/kbn-es-query/src/kuery/node_types/named_arg.js rename to src/plugins/data/common/es_query/kuery/node_types/named_arg.js diff --git a/src/plugins/data/common/es_query/kuery/node_types/named_arg.test.ts b/src/plugins/data/common/es_query/kuery/node_types/named_arg.test.ts new file mode 100644 index 00000000000000..36c40d28e55c22 --- /dev/null +++ b/src/plugins/data/common/es_query/kuery/node_types/named_arg.test.ts @@ -0,0 +1,57 @@ +/* + * 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 { nodeTypes } from './index'; + +// @ts-ignore +import { buildNode, toElasticsearchQuery } from './named_arg'; + +describe('kuery node types', () => { + describe('named arg', () => { + describe('buildNode', () => { + test('should return a node representing a named argument with the given value', () => { + const result = buildNode('fieldName', 'foo'); + expect(result).toHaveProperty('type', 'namedArg'); + expect(result).toHaveProperty('name', 'fieldName'); + expect(result).toHaveProperty('value'); + + const literalValue = result.value; + expect(literalValue).toHaveProperty('type', 'literal'); + expect(literalValue).toHaveProperty('value', 'foo'); + }); + + test('should support literal nodes as values', () => { + const value = nodeTypes.literal.buildNode('foo'); + const result = buildNode('fieldName', value); + + expect(result.value).toBe(value); + expect(result.value).toEqual(value); + }); + }); + + describe('toElasticsearchQuery', () => { + test('should return the argument value represented by the given node', () => { + const node = buildNode('fieldName', 'foo'); + const result = toElasticsearchQuery(node); + + expect(result).toBe('foo'); + }); + }); + }); +}); diff --git a/packages/kbn-es-query/src/kuery/node_types/wildcard.js b/src/plugins/data/common/es_query/kuery/node_types/wildcard.js similarity index 100% rename from packages/kbn-es-query/src/kuery/node_types/wildcard.js rename to src/plugins/data/common/es_query/kuery/node_types/wildcard.js diff --git a/src/plugins/data/common/es_query/kuery/node_types/wildcard.test.ts b/src/plugins/data/common/es_query/kuery/node_types/wildcard.test.ts new file mode 100644 index 00000000000000..7e221d96b49e91 --- /dev/null +++ b/src/plugins/data/common/es_query/kuery/node_types/wildcard.test.ts @@ -0,0 +1,110 @@ +/* + * 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 { + buildNode, + wildcardSymbol, + hasLeadingWildcard, + toElasticsearchQuery, + test as testNode, + toQueryStringQuery, + // @ts-ignore +} from './wildcard'; + +describe('kuery node types', () => { + describe('wildcard', () => { + describe('buildNode', () => { + test('should accept a string argument representing a wildcard string', () => { + const wildcardValue = `foo${wildcardSymbol}bar`; + const result = buildNode(wildcardValue); + + expect(result).toHaveProperty('type', 'wildcard'); + expect(result).toHaveProperty('value', wildcardValue); + }); + + test('should accept and parse a wildcard string', () => { + const result = buildNode('foo*bar'); + + expect(result).toHaveProperty('type', 'wildcard'); + expect(result.value).toBe(`foo${wildcardSymbol}bar`); + }); + }); + + describe('toElasticsearchQuery', () => { + test('should return the string representation of the wildcard literal', () => { + const node = buildNode('foo*bar'); + const result = toElasticsearchQuery(node); + + expect(result).toBe('foo*bar'); + }); + }); + + describe('toQueryStringQuery', () => { + test('should return the string representation of the wildcard literal', () => { + const node = buildNode('foo*bar'); + const result = toQueryStringQuery(node); + + expect(result).toBe('foo*bar'); + }); + + test('should escape query_string query special characters other than wildcard', () => { + const node = buildNode('+foo*bar'); + const result = toQueryStringQuery(node); + + expect(result).toBe('\\+foo*bar'); + }); + }); + + describe('test', () => { + test('should return a boolean indicating whether the string matches the given wildcard node', () => { + const node = buildNode('foo*bar'); + + expect(testNode(node, 'foobar')).toBe(true); + expect(testNode(node, 'foobazbar')).toBe(true); + expect(testNode(node, 'foobar')).toBe(true); + expect(testNode(node, 'fooqux')).toBe(false); + expect(testNode(node, 'bazbar')).toBe(false); + }); + + test('should return a true even when the string has newlines or tabs', () => { + const node = buildNode('foo*bar'); + + expect(testNode(node, 'foo\nbar')).toBe(true); + expect(testNode(node, 'foo\tbar')).toBe(true); + }); + }); + + describe('hasLeadingWildcard', () => { + test('should determine whether a wildcard node contains a leading wildcard', () => { + const node = buildNode('foo*bar'); + expect(hasLeadingWildcard(node)).toBe(false); + + const leadingWildcardNode = buildNode('*foobar'); + expect(hasLeadingWildcard(leadingWildcardNode)).toBe(true); + }); + + // Lone wildcards become exists queries, so we aren't worried about their performance + test('should not consider a lone wildcard to be a leading wildcard', () => { + const leadingWildcardNode = buildNode('*'); + + expect(hasLeadingWildcard(leadingWildcardNode)).toBe(false); + }); + }); + }); +}); diff --git a/packages/kbn-es-query/src/kuery/index.d.ts b/src/plugins/data/common/es_query/kuery/types.ts similarity index 73% rename from packages/kbn-es-query/src/kuery/index.d.ts rename to src/plugins/data/common/es_query/kuery/types.ts index b01a8914f68ef3..86cb7e08a767c1 100644 --- a/packages/kbn-es-query/src/kuery/index.d.ts +++ b/src/plugins/data/common/es_query/kuery/types.ts @@ -17,14 +17,29 @@ * under the License. */ -export * from './ast'; +import { NodeTypes } from './node_types'; + +export interface KueryNode { + type: keyof NodeTypes; + [key: string]: any; +} + +export type DslQuery = any; + +export interface KueryParseOptions { + helpers: { + [key: string]: any; + }; + startRule: string; + allowLeadingWildcards: boolean; + errorOnLuceneSyntax: boolean; +} + export { nodeTypes } from './node_types'; +export type JsonArray = JsonValue[]; export type JsonValue = null | boolean | number | string | JsonObject | JsonArray; export interface JsonObject { [key: string]: JsonValue; } - -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface JsonArray extends Array {} diff --git a/src/plugins/data/common/field_formats/converters/custom.ts b/src/plugins/data/common/field_formats/converters/custom.ts index 687870306c8731..1c17e231cace83 100644 --- a/src/plugins/data/common/field_formats/converters/custom.ts +++ b/src/plugins/data/common/field_formats/converters/custom.ts @@ -17,10 +17,10 @@ * under the License. */ -import { FieldFormat } from '../field_format'; +import { FieldFormat, IFieldFormatType } from '../field_format'; import { TextContextTypeConvert, FIELD_FORMAT_IDS } from '../types'; -export const createCustomFieldFormat = (convert: TextContextTypeConvert) => +export const createCustomFieldFormat = (convert: TextContextTypeConvert): IFieldFormatType => class CustomFieldFormat extends FieldFormat { static id = FIELD_FORMAT_IDS.CUSTOM; diff --git a/src/plugins/data/common/field_formats/field_format.ts b/src/plugins/data/common/field_formats/field_format.ts index 6b5f665c6e20e5..dd445a33f21c5f 100644 --- a/src/plugins/data/common/field_formats/field_format.ts +++ b/src/plugins/data/common/field_formats/field_format.ts @@ -73,7 +73,7 @@ export abstract class FieldFormat { */ public type: any = this.constructor; - private readonly _params: any; + protected readonly _params: any; protected getConfig: Function | undefined; constructor(_params: any = {}, getConfig?: Function) { diff --git a/src/plugins/data/public/index_patterns/index_pattern.stub.ts b/src/plugins/data/public/index_patterns/index_pattern.stub.ts index 3d5151752a0806..4f8108575aa15f 100644 --- a/src/plugins/data/public/index_patterns/index_pattern.stub.ts +++ b/src/plugins/data/public/index_patterns/index_pattern.stub.ts @@ -26,3 +26,18 @@ export const stubIndexPattern: IIndexPattern = { title: 'logstash-*', timeFieldName: '@timestamp', }; + +export const stubIndexPatternWithFields: IIndexPattern = { + id: '1234', + title: 'logstash-*', + fields: [ + { + name: 'response', + type: 'number', + esTypes: ['integer'], + aggregatable: true, + filterable: true, + searchable: true, + }, + ], +}; diff --git a/src/plugins/data/public/query/timefilter/time_history.ts b/src/plugins/data/public/query/timefilter/time_history.ts index 4dabbb557e9db2..fe73fd85b164d9 100644 --- a/src/plugins/data/public/query/timefilter/time_history.ts +++ b/src/plugins/data/public/query/timefilter/time_history.ts @@ -37,7 +37,7 @@ export class TimeHistory { } add(time: TimeRange) { - if (!time) { + if (!time || !time.from || !time.to) { return; } diff --git a/src/plugins/data/public/stubs.ts b/src/plugins/data/public/stubs.ts index 01e68288bd6556..d2519716dd83e6 100644 --- a/src/plugins/data/public/stubs.ts +++ b/src/plugins/data/public/stubs.ts @@ -17,6 +17,6 @@ * under the License. */ -export { stubIndexPattern } from './index_patterns/index_pattern.stub'; +export { stubIndexPattern, stubIndexPatternWithFields } from './index_patterns/index_pattern.stub'; export { stubFields } from './index_patterns/field.stub'; export * from '../common/es_query/filters/stubs'; diff --git a/src/plugins/data/public/ui/index.ts b/src/plugins/data/public/ui/index.ts index cb7c92b00ea3ad..607f690d41c67b 100644 --- a/src/plugins/data/public/ui/index.ts +++ b/src/plugins/data/public/ui/index.ts @@ -17,6 +17,7 @@ * under the License. */ +export { SuggestionsComponent } from './typeahead/suggestions_component'; export { IndexPatternSelect } from './index_pattern_select'; export { FilterBar } from './filter_bar'; export { applyFiltersPopover } from './apply_filters'; diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/__snapshots__/suggestion_component.test.tsx.snap b/src/plugins/data/public/ui/typeahead/__snapshots__/suggestion_component.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/__snapshots__/suggestion_component.test.tsx.snap rename to src/plugins/data/public/ui/typeahead/__snapshots__/suggestion_component.test.tsx.snap diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/__snapshots__/suggestions_component.test.tsx.snap b/src/plugins/data/public/ui/typeahead/__snapshots__/suggestions_component.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/__snapshots__/suggestions_component.test.tsx.snap rename to src/plugins/data/public/ui/typeahead/__snapshots__/suggestions_component.test.tsx.snap diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/_index.scss b/src/plugins/data/public/ui/typeahead/_index.scss similarity index 100% rename from src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/_index.scss rename to src/plugins/data/public/ui/typeahead/_index.scss diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/_suggestion.scss b/src/plugins/data/public/ui/typeahead/_suggestion.scss similarity index 100% rename from src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/_suggestion.scss rename to src/plugins/data/public/ui/typeahead/_suggestion.scss diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/suggestion_component.test.tsx b/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx similarity index 97% rename from src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/suggestion_component.test.tsx rename to src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx index dc7ebfc7b37ea8..591176bf133faf 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/suggestion_component.test.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestion_component.test.tsx @@ -19,7 +19,7 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import { AutocompleteSuggestion } from '../../../../../../../../plugins/data/public'; +import { AutocompleteSuggestion } from '../..'; import { SuggestionComponent } from './suggestion_component'; const noop = () => { diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/suggestion_component.tsx b/src/plugins/data/public/ui/typeahead/suggestion_component.tsx similarity index 96% rename from src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/suggestion_component.tsx rename to src/plugins/data/public/ui/typeahead/suggestion_component.tsx index 27e3eb1eebd1bd..fd29de4573ff07 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/suggestion_component.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestion_component.tsx @@ -20,7 +20,7 @@ import { EuiIcon } from '@elastic/eui'; import classNames from 'classnames'; import React, { FunctionComponent } from 'react'; -import { AutocompleteSuggestion } from '../../../../../../../../plugins/data/public'; +import { AutocompleteSuggestion } from '../..'; function getEuiIconType(type: string) { switch (type) { diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/suggestions_component.test.tsx b/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx similarity index 97% rename from src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/suggestions_component.test.tsx rename to src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx index ea360fc8fd72e8..7fb2fdf25104ac 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/suggestions_component.test.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestions_component.test.tsx @@ -19,7 +19,7 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; -import { AutocompleteSuggestion } from '../../../../../../../../plugins/data/public'; +import { AutocompleteSuggestion } from '../..'; import { SuggestionComponent } from './suggestion_component'; import { SuggestionsComponent } from './suggestions_component'; diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/suggestions_component.tsx b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx similarity index 97% rename from src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/suggestions_component.tsx rename to src/plugins/data/public/ui/typeahead/suggestions_component.tsx index 32860e7cb390be..e4cccbcde4fb8b 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/typeahead/suggestions_component.tsx +++ b/src/plugins/data/public/ui/typeahead/suggestions_component.tsx @@ -19,7 +19,7 @@ import { isEmpty } from 'lodash'; import React, { Component } from 'react'; -import { AutocompleteSuggestion } from '../../../../../../../../plugins/data/public'; +import { AutocompleteSuggestion } from '../..'; import { SuggestionComponent } from './suggestion_component'; interface Props { diff --git a/src/plugins/expressions/public/execute.test.ts b/src/plugins/expressions/public/execute.test.ts index b60c4aed89fcf2..6700ec38df9404 100644 --- a/src/plugins/expressions/public/execute.test.ts +++ b/src/plugins/expressions/public/execute.test.ts @@ -29,6 +29,13 @@ jest.mock('./services', () => ({ }, }; }, + getNotifications: jest.fn(() => { + return { + toasts: { + addError: jest.fn(() => {}), + }, + }; + }), })); describe('execute helper function', () => { diff --git a/src/plugins/expressions/public/expression_renderer.test.tsx b/src/plugins/expressions/public/expression_renderer.test.tsx index 26db8753e64035..217618bc3a1775 100644 --- a/src/plugins/expressions/public/expression_renderer.test.tsx +++ b/src/plugins/expressions/public/expression_renderer.test.tsx @@ -18,12 +18,14 @@ */ import React from 'react'; +import { act } from 'react-dom/test-utils'; import { Subject } from 'rxjs'; import { share } from 'rxjs/operators'; import { ExpressionRendererImplementation } from './expression_renderer'; import { ExpressionLoader } from './loader'; import { mount } from 'enzyme'; import { EuiProgress } from '@elastic/eui'; +import { RenderErrorHandlerFnType } from './types'; jest.mock('./loader', () => { return { @@ -54,60 +56,38 @@ describe('ExpressionRenderer', () => { const instance = mount(); - loadingSubject.next(); + act(() => { + loadingSubject.next(); + }); + instance.update(); expect(instance.find(EuiProgress)).toHaveLength(1); - renderSubject.next(1); + act(() => { + renderSubject.next(1); + }); instance.update(); expect(instance.find(EuiProgress)).toHaveLength(0); instance.setProps({ expression: 'something new' }); - loadingSubject.next(); + act(() => { + loadingSubject.next(); + }); instance.update(); expect(instance.find(EuiProgress)).toHaveLength(1); - - renderSubject.next(1); - instance.update(); - - expect(instance.find(EuiProgress)).toHaveLength(0); - }); - - it('should display an error message when the expression fails', () => { - const dataSubject = new Subject(); - const data$ = dataSubject.asObservable().pipe(share()); - const renderSubject = new Subject(); - const render$ = renderSubject.asObservable().pipe(share()); - const loadingSubject = new Subject(); - const loading$ = loadingSubject.asObservable().pipe(share()); - - (ExpressionLoader as jest.Mock).mockImplementation(() => { - return { - render$, - data$, - loading$, - update: jest.fn(), - }; - }); - - const instance = mount(); - - dataSubject.next('good data'); - renderSubject.next({ - type: 'error', - error: { message: 'render error' }, + act(() => { + renderSubject.next(1); }); instance.update(); expect(instance.find(EuiProgress)).toHaveLength(0); - expect(instance.find('[data-test-subj="expression-renderer-error"]')).toHaveLength(1); }); - it('should display a custom error message if the user provides one', () => { + it('should display a custom error message if the user provides one and then remove it after successful render', () => { const dataSubject = new Subject(); const data$ = dataSubject.asObservable().pipe(share()); const renderSubject = new Subject(); @@ -115,7 +95,10 @@ describe('ExpressionRenderer', () => { const loadingSubject = new Subject(); const loading$ = loadingSubject.asObservable().pipe(share()); - (ExpressionLoader as jest.Mock).mockImplementation(() => { + let onRenderError: RenderErrorHandlerFnType; + (ExpressionLoader as jest.Mock).mockImplementation((...args) => { + const params = args[2]; + onRenderError = params.onRenderError; return { render$, data$, @@ -124,18 +107,32 @@ describe('ExpressionRenderer', () => { }; }); - const renderErrorFn = jest.fn().mockReturnValue(null); - const instance = mount( - +
{message}
} + /> ); - renderSubject.next({ - type: 'error', - error: { message: 'render error' }, + act(() => { + onRenderError!(instance.getDOMNode(), new Error('render error'), { + done: () => { + renderSubject.next(1); + }, + } as any); }); + instance.update(); + expect(instance.find(EuiProgress)).toHaveLength(0); + expect(instance.find('[data-test-subj="custom-error"]')).toHaveLength(1); + expect(instance.find('[data-test-subj="custom-error"]').contains('render error')).toBeTruthy(); - expect(renderErrorFn).toHaveBeenCalledWith('render error'); + act(() => { + loadingSubject.next(); + renderSubject.next(2); + }); + instance.update(); + expect(instance.find(EuiProgress)).toHaveLength(0); + expect(instance.find('[data-test-subj="custom-error"]')).toHaveLength(0); }); }); diff --git a/src/plugins/expressions/public/expression_renderer.tsx b/src/plugins/expressions/public/expression_renderer.tsx index b4f0a509c81b61..3989f4ed7d6982 100644 --- a/src/plugins/expressions/public/expression_renderer.tsx +++ b/src/plugins/expressions/public/expression_renderer.tsx @@ -17,12 +17,15 @@ * under the License. */ -import { useRef, useEffect, useState } from 'react'; +import { useRef, useEffect, useState, useLayoutEffect } from 'react'; import React from 'react'; import classNames from 'classnames'; +import { Subscription } from 'rxjs'; +import { filter } from 'rxjs/operators'; import { EuiLoadingChart, EuiProgress } from '@elastic/eui'; import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { IExpressionLoaderParams } from './types'; +import { useShallowCompareEffect } from '../../kibana_react/public'; +import { IExpressionLoaderParams, IInterpreterRenderHandlers, RenderError } from './types'; import { ExpressionAST } from '../common/types'; import { ExpressionLoader } from './loader'; @@ -39,7 +42,7 @@ export interface ExpressionRendererProps extends IExpressionLoaderParams { interface State { isEmpty: boolean; isLoading: boolean; - error: null | { message: string }; + error: null | RenderError; } export type ExpressionRenderer = React.FC; @@ -53,73 +56,94 @@ const defaultState: State = { export const ExpressionRendererImplementation = ({ className, dataAttrs, - expression, - renderError, padding, - ...options + renderError, + expression, + ...expressionLoaderOptions }: ExpressionRendererProps) => { const mountpoint: React.MutableRefObject = useRef(null); - const handlerRef: React.MutableRefObject = useRef(null); const [state, setState] = useState({ ...defaultState }); + const hasCustomRenderErrorHandler = !!renderError; + const expressionLoaderRef: React.MutableRefObject = useRef(null); + // flag to skip next render$ notification, + // because of just handled error + const hasHandledErrorRef = useRef(false); - // Re-fetch data automatically when the inputs change - /* eslint-disable react-hooks/exhaustive-deps */ - useEffect(() => { - if (handlerRef.current) { - handlerRef.current.update(expression, options); - } - }, [ - expression, - options.searchContext, - options.context, - options.variables, - options.disableCaching, - ]); - /* eslint-enable react-hooks/exhaustive-deps */ + // will call done() in LayoutEffect when done with rendering custom error state + const errorRenderHandlerRef: React.MutableRefObject = useRef( + null + ); - // Initialize the loader only once + /* eslint-disable react-hooks/exhaustive-deps */ + // OK to ignore react-hooks/exhaustive-deps because options update is handled by calling .update() useEffect(() => { - if (mountpoint.current && !handlerRef.current) { - handlerRef.current = new ExpressionLoader(mountpoint.current, expression, options); + const subs: Subscription[] = []; + expressionLoaderRef.current = new ExpressionLoader(mountpoint.current!, expression, { + ...expressionLoaderOptions, + // react component wrapper provides different + // error handling api which is easier to work with from react + // if custom renderError is not provided then we fallback to default error handling from ExpressionLoader + onRenderError: hasCustomRenderErrorHandler + ? (domNode, error, handlers) => { + errorRenderHandlerRef.current = handlers; + setState(() => ({ + ...defaultState, + isEmpty: false, + error, + })); - handlerRef.current.loading$.subscribe(() => { - if (!handlerRef.current) { - return; - } + if (expressionLoaderOptions.onRenderError) { + expressionLoaderOptions.onRenderError(domNode, error, handlers); + } + } + : expressionLoaderOptions.onRenderError, + }); + subs.push( + expressionLoaderRef.current.loading$.subscribe(() => { + hasHandledErrorRef.current = false; setState(prevState => ({ ...prevState, isLoading: true })); - }); - handlerRef.current.render$.subscribe(item => { - if (!handlerRef.current) { - return; - } - if (typeof item !== 'number') { + }), + expressionLoaderRef.current.render$ + .pipe(filter(() => !hasHandledErrorRef.current)) + .subscribe(item => { setState(() => ({ ...defaultState, isEmpty: false, - error: item.error, })); - } else { - setState(() => ({ - ...defaultState, - isEmpty: false, - })); - } - }); - } - /* eslint-disable */ - // TODO: Replace mountpoint.current by something else. - }, [mountpoint.current]); - /* eslint-enable */ + }) + ); - useEffect(() => { - // We only want a clean up to run when the entire component is unloaded, not on every render - return function cleanup() { - if (handlerRef.current) { - handlerRef.current.destroy(); - handlerRef.current = null; + return () => { + subs.forEach(s => s.unsubscribe()); + if (expressionLoaderRef.current) { + expressionLoaderRef.current.destroy(); + expressionLoaderRef.current = null; } + + errorRenderHandlerRef.current = null; }; - }, []); + }, [hasCustomRenderErrorHandler]); + + // Re-fetch data automatically when the inputs change + useShallowCompareEffect( + () => { + if (expressionLoaderRef.current) { + expressionLoaderRef.current.update(expression, expressionLoaderOptions); + } + }, + // when expression is changed by reference and when any other loaderOption is changed by reference + [{ expression, ...expressionLoaderOptions }] + ); + + /* eslint-enable react-hooks/exhaustive-deps */ + // call expression loader's done() handler when finished rendering custom error state + useLayoutEffect(() => { + if (state.error && errorRenderHandlerRef.current) { + hasHandledErrorRef.current = true; + errorRenderHandlerRef.current.done(); + errorRenderHandlerRef.current = null; + } + }, [state.error]); const classes = classNames('expExpressionRenderer', { 'expExpressionRenderer-isEmpty': state.isEmpty, @@ -135,15 +159,9 @@ export const ExpressionRendererImplementation = ({ return (
- {state.isEmpty ? : null} - {state.isLoading ? : null} - {!state.isLoading && state.error ? ( - renderError ? ( - renderError(state.error.message) - ) : ( -
{state.error.message}
- ) - ) : null} + {state.isEmpty && } + {state.isLoading && } + {!state.isLoading && state.error && renderError && renderError(state.error.message)}
{ getRenderersRegistry: () => ({ get: (id: string) => renderers[id], }), + getNotifications: jest.fn(() => { + return { + toasts: { + addError: jest.fn(() => {}), + }, + }; + }), }; }); @@ -97,20 +104,14 @@ describe('ExpressionLoader', () => { expect(response).toEqual({ type: 'render', as: 'test' }); }); - it('emits on loading$ when starting to load', async () => { + it('emits on loading$ on initial load and on updates', async () => { const expressionLoader = new ExpressionLoader(element, expressionString, {}); - let loadingPromise = expressionLoader.loading$.pipe(first()).toPromise(); + const loadingPromise = expressionLoader.loading$.pipe(toArray()).toPromise(); expressionLoader.update('test'); - let response = await loadingPromise; - expect(response).toBeUndefined(); - loadingPromise = expressionLoader.loading$.pipe(first()).toPromise(); expressionLoader.update(''); - response = await loadingPromise; - expect(response).toBeUndefined(); - loadingPromise = expressionLoader.loading$.pipe(first()).toPromise(); expressionLoader.update(); - response = await loadingPromise; - expect(response).toBeUndefined(); + expressionLoader.destroy(); + expect(await loadingPromise).toHaveLength(4); }); it('emits on render$ when rendering is done', async () => { diff --git a/src/plugins/expressions/public/loader.ts b/src/plugins/expressions/public/loader.ts index 200249b60c773c..0342713f7627b1 100644 --- a/src/plugins/expressions/public/loader.ts +++ b/src/plugins/expressions/public/loader.ts @@ -17,8 +17,8 @@ * under the License. */ -import { Observable, Subject } from 'rxjs'; -import { share } from 'rxjs/operators'; +import { BehaviorSubject, Observable, Subject } from 'rxjs'; +import { filter, map } from 'rxjs/operators'; import { Adapters, InspectorSession } from '../../inspector/public'; import { ExpressionDataHandler } from './execute'; import { ExpressionRenderHandler } from './render'; @@ -36,7 +36,7 @@ export class ExpressionLoader { private dataHandler: ExpressionDataHandler | undefined; private renderHandler: ExpressionRenderHandler; private dataSubject: Subject; - private loadingSubject: Subject; + private loadingSubject: Subject; private data: Data; private params: IExpressionLoaderParams = {}; @@ -46,12 +46,20 @@ export class ExpressionLoader { params?: IExpressionLoaderParams ) { this.dataSubject = new Subject(); - this.data$ = this.dataSubject.asObservable().pipe(share()); - - this.loadingSubject = new Subject(); - this.loading$ = this.loadingSubject.asObservable().pipe(share()); - - this.renderHandler = new ExpressionRenderHandler(element); + this.data$ = this.dataSubject.asObservable(); + + this.loadingSubject = new BehaviorSubject(false); + // loading is a "hot" observable, + // as loading$ could emit straight away in the constructor + // and we want to notify subscribers about it, but all subscriptions will happen later + this.loading$ = this.loadingSubject.asObservable().pipe( + filter(_ => _ === true), + map(() => void 0) + ); + + this.renderHandler = new ExpressionRenderHandler(element, { + onRenderError: params && params.onRenderError, + }); this.render$ = this.renderHandler.render$; this.update$ = this.renderHandler.update$; this.events$ = this.renderHandler.events$; @@ -64,9 +72,14 @@ export class ExpressionLoader { this.render(data); }); + this.render$.subscribe(() => { + this.loadingSubject.next(false); + }); + this.setParams(params); if (expression) { + this.loadingSubject.next(true); this.loadData(expression, this.params); } } @@ -120,7 +133,7 @@ export class ExpressionLoader { update(expression?: string | ExpressionAST, params?: IExpressionLoaderParams): void { this.setParams(params); - this.loadingSubject.next(); + this.loadingSubject.next(true); if (expression) { this.loadData(expression, this.params); } else if (this.data) { diff --git a/src/plugins/expressions/public/plugin.ts b/src/plugins/expressions/public/plugin.ts index 3a28256d571626..7471326cdd749e 100644 --- a/src/plugins/expressions/public/plugin.ts +++ b/src/plugins/expressions/public/plugin.ts @@ -21,7 +21,13 @@ import { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from '../../.. import { ExpressionInterpretWithHandlers, ExpressionExecutor } from './types'; import { FunctionsRegistry, RenderFunctionsRegistry, TypesRegistry } from './registries'; import { Setup as InspectorSetup, Start as InspectorStart } from '../../inspector/public'; -import { setCoreStart, setInspector, setInterpreter, setRenderersRegistry } from './services'; +import { + setCoreStart, + setInspector, + setInterpreter, + setRenderersRegistry, + setNotifications, +} from './services'; import { clog as clogFunction } from './functions/clog'; import { font as fontFunction } from './functions/font'; import { kibana as kibanaFunction } from './functions/kibana'; @@ -158,6 +164,7 @@ export class ExpressionsPublicPlugin public start(core: CoreStart, { inspector }: ExpressionsStartDeps): ExpressionsStart { setCoreStart(core); setInspector(inspector); + setNotifications(core.notifications); return { execute, diff --git a/src/plugins/expressions/public/render.test.ts b/src/plugins/expressions/public/render.test.ts index 6b5acc8405fd27..56eb43a9bd1338 100644 --- a/src/plugins/expressions/public/render.test.ts +++ b/src/plugins/expressions/public/render.test.ts @@ -17,14 +17,18 @@ * under the License. */ -import { render, ExpressionRenderHandler } from './render'; +import { ExpressionRenderHandler, render } from './render'; import { Observable } from 'rxjs'; -import { IInterpreterRenderHandlers } from './types'; +import { IInterpreterRenderHandlers, RenderError } from './types'; import { getRenderersRegistry } from './services'; -import { first } from 'rxjs/operators'; +import { first, take, toArray } from 'rxjs/operators'; const element: HTMLElement = {} as HTMLElement; - +const mockNotificationService = { + toasts: { + addError: jest.fn(() => {}), + }, +}; jest.mock('./services', () => { const renderers: Record = { test: { @@ -38,9 +42,24 @@ jest.mock('./services', () => { getRenderersRegistry: jest.fn(() => ({ get: jest.fn((id: string) => renderers[id]), })), + getNotifications: jest.fn(() => { + return mockNotificationService; + }), }; }); +const mockMockErrorRenderFunction = jest.fn( + (el: HTMLElement, error: RenderError, handlers: IInterpreterRenderHandlers) => handlers.done() +); +// extracts data from mockMockErrorRenderFunction call to assert in tests +const getHandledError = () => { + try { + return mockMockErrorRenderFunction.mock.calls[0][1]; + } catch (e) { + return null; + } +}; + describe('render helper function', () => { it('returns ExpressionRenderHandler instance', () => { const response = render(element, {}); @@ -62,40 +81,33 @@ describe('ExpressionRenderHandler', () => { }); describe('render()', () => { - it('sends an observable error and keeps it open if invalid data is provided', async () => { + beforeEach(() => { + mockMockErrorRenderFunction.mockClear(); + mockNotificationService.toasts.addError.mockClear(); + }); + + it('in case of error render$ should emit when error renderer is finished', async () => { const expressionRenderHandler = new ExpressionRenderHandler(element); - const promise1 = expressionRenderHandler.render$.pipe(first()).toPromise(); expressionRenderHandler.render(false); - await expect(promise1).resolves.toEqual({ - type: 'error', - error: { - message: 'invalid data provided to the expression renderer', - }, - }); + const promise1 = expressionRenderHandler.render$.pipe(first()).toPromise(); + await expect(promise1).resolves.toEqual(1); - const promise2 = expressionRenderHandler.render$.pipe(first()).toPromise(); expressionRenderHandler.render(false); - await expect(promise2).resolves.toEqual({ - type: 'error', - error: { - message: 'invalid data provided to the expression renderer', - }, - }); + const promise2 = expressionRenderHandler.render$.pipe(first()).toPromise(); + await expect(promise2).resolves.toEqual(2); }); - it('sends an observable error if renderer does not exist', async () => { - const expressionRenderHandler = new ExpressionRenderHandler(element); - const promise = expressionRenderHandler.render$.pipe(first()).toPromise(); - expressionRenderHandler.render({ type: 'render', as: 'something' }); - await expect(promise).resolves.toEqual({ - type: 'error', - error: { - message: `invalid renderer id 'something'`, - }, + it('should use custom error handler if provided', async () => { + const expressionRenderHandler = new ExpressionRenderHandler(element, { + onRenderError: mockMockErrorRenderFunction, }); + await expressionRenderHandler.render(false); + expect(getHandledError()!.message).toEqual( + `invalid data provided to the expression renderer` + ); }); - it('sends an observable error if the rendering function throws', async () => { + it('should throw error if the rendering function throws', async () => { (getRenderersRegistry as jest.Mock).mockReturnValueOnce({ get: () => true }); (getRenderersRegistry as jest.Mock).mockReturnValueOnce({ get: () => ({ @@ -105,15 +117,11 @@ describe('ExpressionRenderHandler', () => { }), }); - const expressionRenderHandler = new ExpressionRenderHandler(element); - const promise = expressionRenderHandler.render$.pipe(first()).toPromise(); - expressionRenderHandler.render({ type: 'render', as: 'something' }); - await expect(promise).resolves.toEqual({ - type: 'error', - error: { - message: 'renderer error', - }, + const expressionRenderHandler = new ExpressionRenderHandler(element, { + onRenderError: mockMockErrorRenderFunction, }); + await expressionRenderHandler.render({ type: 'render', as: 'something' }); + expect(getHandledError()!.message).toEqual('renderer error'); }); it('sends a next observable once rendering is complete', () => { @@ -129,18 +137,56 @@ describe('ExpressionRenderHandler', () => { }); }); + it('default renderer should use notification service', async () => { + const expressionRenderHandler = new ExpressionRenderHandler(element); + const promise1 = expressionRenderHandler.render$.pipe(first()).toPromise(); + expressionRenderHandler.render(false); + await expect(promise1).resolves.toEqual(1); + expect(mockNotificationService.toasts.addError).toBeCalledWith( + expect.objectContaining({ + message: 'invalid data provided to the expression renderer', + }), + { + title: 'Error in visualisation', + toastMessage: 'invalid data provided to the expression renderer', + } + ); + }); + // in case render$ subscription happen after render() got called // we still want to be notified about sync render$ updates it("doesn't swallow sync render errors", async () => { + const expressionRenderHandler1 = new ExpressionRenderHandler(element, { + onRenderError: mockMockErrorRenderFunction, + }); + expressionRenderHandler1.render(false); + const renderPromiseAfterRender = expressionRenderHandler1.render$.pipe(first()).toPromise(); + await expect(renderPromiseAfterRender).resolves.toEqual(1); + expect(getHandledError()!.message).toEqual( + 'invalid data provided to the expression renderer' + ); + + mockMockErrorRenderFunction.mockClear(); + + const expressionRenderHandler2 = new ExpressionRenderHandler(element, { + onRenderError: mockMockErrorRenderFunction, + }); + const renderPromiseBeforeRender = expressionRenderHandler2.render$.pipe(first()).toPromise(); + expressionRenderHandler2.render(false); + await expect(renderPromiseBeforeRender).resolves.toEqual(1); + expect(getHandledError()!.message).toEqual( + 'invalid data provided to the expression renderer' + ); + }); + + // it is expected side effect of using BehaviorSubject for render$, + // that observables will emit previous result if subscription happens after render + it('should emit previous render and error results', async () => { const expressionRenderHandler = new ExpressionRenderHandler(element); expressionRenderHandler.render(false); - const promise = expressionRenderHandler.render$.pipe(first()).toPromise(); - await expect(promise).resolves.toEqual({ - type: 'error', - error: { - message: 'invalid data provided to the expression renderer', - }, - }); + const renderPromise = expressionRenderHandler.render$.pipe(take(2), toArray()).toPromise(); + expressionRenderHandler.render(false); + await expect(renderPromise).resolves.toEqual([1, 2]); }); }); }); diff --git a/src/plugins/expressions/public/render.ts b/src/plugins/expressions/public/render.ts index 3c7008806e779c..62bde12490fbe4 100644 --- a/src/plugins/expressions/public/render.ts +++ b/src/plugins/expressions/public/render.ts @@ -17,48 +17,58 @@ * under the License. */ -import { Observable } from 'rxjs'; import * as Rx from 'rxjs'; -import { filter, share } from 'rxjs/operators'; -import { event, RenderId, Data, IInterpreterRenderHandlers } from './types'; +import { Observable } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { + Data, + event, + IInterpreterRenderHandlers, + RenderError, + RenderErrorHandlerFnType, + RenderId, +} from './types'; import { getRenderersRegistry } from './services'; - -interface RenderError { - type: 'error'; - error: { type?: string; message: string }; -} +import { renderErrorHandler as defaultRenderErrorHandler } from './render_error_handler'; export type IExpressionRendererExtraHandlers = Record; -export type RenderResult = RenderId | RenderError; +export interface ExpressionRenderHandlerParams { + onRenderError: RenderErrorHandlerFnType; +} export class ExpressionRenderHandler { - render$: Observable; + render$: Observable; update$: Observable; events$: Observable; private element: HTMLElement; private destroyFn?: any; private renderCount: number = 0; - private renderSubject: Rx.BehaviorSubject; + private renderSubject: Rx.BehaviorSubject; private eventsSubject: Rx.Subject; private updateSubject: Rx.Subject; private handlers: IInterpreterRenderHandlers; + private onRenderError: RenderErrorHandlerFnType; - constructor(element: HTMLElement) { + constructor( + element: HTMLElement, + { onRenderError }: Partial = {} + ) { this.element = element; this.eventsSubject = new Rx.Subject(); - this.events$ = this.eventsSubject.asObservable().pipe(share()); + this.events$ = this.eventsSubject.asObservable(); + + this.onRenderError = onRenderError || defaultRenderErrorHandler; - this.renderSubject = new Rx.BehaviorSubject(null as RenderResult | null); - this.render$ = this.renderSubject.asObservable().pipe( - share(), - filter(_ => _ !== null) - ) as Observable; + this.renderSubject = new Rx.BehaviorSubject(null as RenderId | null); + this.render$ = this.renderSubject.asObservable().pipe(filter(_ => _ !== null)) as Observable< + RenderId + >; this.updateSubject = new Rx.Subject(); - this.update$ = this.updateSubject.asObservable().pipe(share()); + this.update$ = this.updateSubject.asObservable(); this.handlers = { onDestroy: (fn: any) => { @@ -82,33 +92,21 @@ export class ExpressionRenderHandler { render = async (data: Data, extraHandlers: IExpressionRendererExtraHandlers = {}) => { if (!data || typeof data !== 'object') { - this.renderSubject.next({ - type: 'error', - error: { - message: 'invalid data provided to the expression renderer', - }, - }); - return; + return this.handleRenderError(new Error('invalid data provided to the expression renderer')); } if (data.type !== 'render' || !data.as) { if (data.type === 'error') { - this.renderSubject.next(data); + return this.handleRenderError(data.error); } else { - this.renderSubject.next({ - type: 'error', - error: { message: 'invalid data provided to the expression renderer' }, - }); + return this.handleRenderError( + new Error('invalid data provided to the expression renderer') + ); } - return; } if (!getRenderersRegistry().get(data.as)) { - this.renderSubject.next({ - type: 'error', - error: { message: `invalid renderer id '${data.as}'` }, - }); - return; + return this.handleRenderError(new Error(`invalid renderer id '${data.as}'`)); } try { @@ -117,10 +115,7 @@ export class ExpressionRenderHandler { .get(data.as)! .render(this.element, data.value, { ...this.handlers, ...extraHandlers }); } catch (e) { - this.renderSubject.next({ - type: 'error', - error: { type: e.type, message: e.message }, - }); + return this.handleRenderError(e); } }; @@ -136,10 +131,18 @@ export class ExpressionRenderHandler { getElement = () => { return this.element; }; + + handleRenderError = (error: RenderError) => { + this.onRenderError(this.element, error, this.handlers); + }; } -export function render(element: HTMLElement, data: Data): ExpressionRenderHandler { - const handler = new ExpressionRenderHandler(element); +export function render( + element: HTMLElement, + data: Data, + options?: Partial +): ExpressionRenderHandler { + const handler = new ExpressionRenderHandler(element, options); handler.render(data); return handler; } diff --git a/packages/kbn-es-query/src/utils/__tests__/get_time_zone_from_settings.js b/src/plugins/expressions/public/render_error_handler.ts similarity index 59% rename from packages/kbn-es-query/src/utils/__tests__/get_time_zone_from_settings.js rename to src/plugins/expressions/public/render_error_handler.ts index 6deaccadfdb76c..4d6bee1e375e0e 100644 --- a/packages/kbn-es-query/src/utils/__tests__/get_time_zone_from_settings.js +++ b/src/plugins/expressions/public/render_error_handler.ts @@ -17,20 +17,20 @@ * under the License. */ -import expect from '@kbn/expect'; -import { getTimeZoneFromSettings } from '../get_time_zone_from_settings'; +import { i18n } from '@kbn/i18n'; +import { RenderErrorHandlerFnType, IInterpreterRenderHandlers, RenderError } from './types'; +import { getNotifications } from './services'; -describe('get timezone from settings', function () { - - it('should return the config timezone if the time zone is set', function () { - const result = getTimeZoneFromSettings('America/Chicago'); - expect(result).to.eql('America/Chicago'); - }); - - it('should return the system timezone if the time zone is set to "Browser"', function () { - const result = getTimeZoneFromSettings('Browser'); - expect(result).to.not.equal('Browser'); +export const renderErrorHandler: RenderErrorHandlerFnType = ( + element: HTMLElement, + error: RenderError, + handlers: IInterpreterRenderHandlers +) => { + getNotifications().toasts.addError(error, { + title: i18n.translate('expressions.defaultErrorRenderer.errorTitle', { + defaultMessage: 'Error in visualisation', + }), + toastMessage: error.message, }); - -}); - + handlers.done(); +}; diff --git a/src/plugins/expressions/public/services.ts b/src/plugins/expressions/public/services.ts index a1a42aa85e670f..75ec4826ea45aa 100644 --- a/src/plugins/expressions/public/services.ts +++ b/src/plugins/expressions/public/services.ts @@ -17,6 +17,7 @@ * under the License. */ +import { NotificationsStart } from 'kibana/public'; import { createKibanaUtilsCore, createGetterSetter } from '../../kibana_utils/public'; import { ExpressionInterpreter } from './types'; import { Start as IInspector } from '../../inspector/public'; @@ -29,6 +30,9 @@ export const [getInspector, setInspector] = createGetterSetter('Insp export const [getInterpreter, setInterpreter] = createGetterSetter( 'Interpreter' ); +export const [getNotifications, setNotifications] = createGetterSetter( + 'Notifications' +); export const [getRenderersRegistry, setRenderersRegistry] = createGetterSetter< ExpressionsSetup['__LEGACY']['renderers'] diff --git a/src/plugins/expressions/public/types/index.ts b/src/plugins/expressions/public/types/index.ts index d86e042bca15c0..66a3da48dbee9c 100644 --- a/src/plugins/expressions/public/types/index.ts +++ b/src/plugins/expressions/public/types/index.ts @@ -20,6 +20,7 @@ import { ExpressionInterpret } from '../interpreter_provider'; import { TimeRange, Query, esFilters } from '../../../data/public'; import { Adapters } from '../../../inspector/public'; +import { ExpressionRenderDefinition } from '../registries'; export type ExpressionInterpretWithHandlers = ( ast: Parameters[0], @@ -58,6 +59,7 @@ export interface IExpressionLoaderParams { customRenderers?: []; extraHandlers?: Record; inspectorAdapters?: Adapters; + onRenderError?: RenderErrorHandlerFnType; } export interface IInterpreterHandlers { @@ -99,3 +101,15 @@ export interface IInterpreterSuccessResult { } export type IInterpreterResult = IInterpreterSuccessResult & IInterpreterErrorResult; + +export { ExpressionRenderDefinition }; + +export interface RenderError extends Error { + type?: string; +} + +export type RenderErrorHandlerFnType = ( + domNode: HTMLElement, + error: RenderError, + handlers: IInterpreterRenderHandlers +) => void; diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts index 2d82f646c827b9..46f330ea0a2c5e 100644 --- a/src/plugins/kibana_react/public/index.ts +++ b/src/plugins/kibana_react/public/index.ts @@ -24,4 +24,4 @@ export * from './overlays'; export * from './ui_settings'; export * from './field_icon'; export * from './table_list_view'; -export { toMountPoint } from './util'; +export { toMountPoint, useShallowCompareEffect } from './util'; diff --git a/src/plugins/kibana_react/public/util/index.ts b/src/plugins/kibana_react/public/util/index.ts index 1053ca01603e3f..4f64d6c9c81ab6 100644 --- a/src/plugins/kibana_react/public/util/index.ts +++ b/src/plugins/kibana_react/public/util/index.ts @@ -20,3 +20,4 @@ export * from './use_observable'; export * from './use_unmount'; export * from './react_mount'; +export * from './use_shallow_compare_effect'; diff --git a/src/plugins/kibana_react/public/util/use_shallow_compare_effect.test.ts b/src/plugins/kibana_react/public/util/use_shallow_compare_effect.test.ts new file mode 100644 index 00000000000000..e5d9c44727c3a3 --- /dev/null +++ b/src/plugins/kibana_react/public/util/use_shallow_compare_effect.test.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 { renderHook } from 'react-hooks-testing-library'; +import { useShallowCompareEffect } from './use_shallow_compare_effect'; + +describe('useShallowCompareEffect', () => { + test("doesn't run effect on shallow change", () => { + const callback = jest.fn(); + let deps = [1, { a: 'b' }, true]; + const { rerender } = renderHook(() => useShallowCompareEffect(callback, deps)); + + expect(callback).toHaveBeenCalledTimes(1); + callback.mockClear(); + + // no change + rerender(); + expect(callback).toHaveBeenCalledTimes(0); + callback.mockClear(); + + // no-change (new object with same properties) + deps = [1, { a: 'b' }, true]; + rerender(); + expect(callback).toHaveBeenCalledTimes(0); + callback.mockClear(); + + // change (new primitive value) + deps = [2, { a: 'b' }, true]; + rerender(); + expect(callback).toHaveBeenCalledTimes(1); + callback.mockClear(); + + // no-change + rerender(); + expect(callback).toHaveBeenCalledTimes(0); + callback.mockClear(); + + // change (new primitive value) + deps = [1, { a: 'b' }, false]; + rerender(); + expect(callback).toHaveBeenCalledTimes(1); + callback.mockClear(); + + // change (new properties on object) + deps = [1, { a: 'c' }, false]; + rerender(); + expect(callback).toHaveBeenCalledTimes(1); + callback.mockClear(); + }); + + test('runs effect on deep change', () => { + const callback = jest.fn(); + let deps = [1, { a: { b: 'c' } }, true]; + const { rerender } = renderHook(() => useShallowCompareEffect(callback, deps)); + + expect(callback).toHaveBeenCalledTimes(1); + callback.mockClear(); + + // no change + rerender(); + expect(callback).toHaveBeenCalledTimes(0); + callback.mockClear(); + + // change (new nested object ) + deps = [1, { a: { b: 'c' } }, true]; + rerender(); + expect(callback).toHaveBeenCalledTimes(1); + callback.mockClear(); + }); +}); diff --git a/src/plugins/kibana_react/public/util/use_shallow_compare_effect.ts b/src/plugins/kibana_react/public/util/use_shallow_compare_effect.ts new file mode 100644 index 00000000000000..dfba7b907f5fbd --- /dev/null +++ b/src/plugins/kibana_react/public/util/use_shallow_compare_effect.ts @@ -0,0 +1,80 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect, useRef } from 'react'; + +/** + * Similar to https://github.com/kentcdodds/use-deep-compare-effect + * but uses shallow compare instead of deep + */ +export function useShallowCompareEffect( + callback: React.EffectCallback, + deps: React.DependencyList +) { + useEffect(callback, useShallowCompareMemoize(deps)); +} +function useShallowCompareMemoize(deps: React.DependencyList) { + const ref = useRef(undefined); + + if (!ref.current || deps.some((dep, index) => !shallowEqual(dep, ref.current![index]))) { + ref.current = deps; + } + + return ref.current; +} +// https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/shallowEqual.js +function shallowEqual(objA: any, objB: any): boolean { + if (is(objA, objB)) { + return true; + } + + if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { + return false; + } + + const keysA = Object.keys(objA); + const keysB = Object.keys(objB); + + if (keysA.length !== keysB.length) { + return false; + } + + // Test for A's keys different from B. + for (let i = 0; i < keysA.length; i++) { + if ( + !Object.prototype.hasOwnProperty.call(objB, keysA[i]) || + !is(objA[keysA[i]], objB[keysA[i]]) + ) { + return false; + } + } + + return true; +} + +/** + * IE11 does not support Object.is + */ +function is(x: any, y: any): boolean { + if (x === y) { + return x !== 0 || y !== 0 || 1 / x === 1 / y; + } else { + return x !== x && y !== y; + } +} diff --git a/src/plugins/usage_collection/README.md b/src/plugins/usage_collection/README.md new file mode 100644 index 00000000000000..4502e1a6ceacfe --- /dev/null +++ b/src/plugins/usage_collection/README.md @@ -0,0 +1,139 @@ +# Kibana Usage Collection Service + +Usage Collection allows collecting usage data for other services to consume (telemetry and monitoring). +To integrate with the telemetry services for usage collection of your feature, there are 2 steps: + +1. Create a usage collector. +2. Register the usage collector. + +## 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. + +### New Platform: + +1. Make sure `usageCollection` is in your optional Plugins: + +```json +// plugin/kibana.json +{ + "id": "...", + "optionalPlugins": ["usageCollection"] +} +``` + +2. Register Usage collector in the `setup` function: + +```ts +// server/plugin.ts +class Plugin { + setup(core, plugins) { + registerMyPluginUsageCollector(plugins.usageCollection); + } +} +``` + +3. Creating and registering a Usage Collector. Ideally collectors would be defined in a separate directory `server/collectors/register.ts`. + +```ts +// server/collectors/register.ts +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; + +export function registerMyPluginUsageCollector(usageCollection?: UsageCollectionSetup): void { + // usageCollection is an optional dependency, so make sure to return if it is not registered. + if (!usageCollection) { + return; + } + + // create usage collector + const myCollector = usageCollection.makeUsageCollector({ + type: MY_USAGE_TYPE, + fetch: async (callCluster: CallCluster) => { + + // query ES and get some data + // summarize the data into a model + // return the modeled object that includes whatever you want to track + + return { + my_objects: { + total: SOME_NUMBER + } + }; + }, + }); + + // register usage collector + usageCollection.registerCollector(myCollector); +} +``` + +Some background: The `callCluster` that gets passed to the `fetch` method is created in a way that's a bit tricky, to support multiple contexts the `fetch` method could be called. Your `fetch` method could get called as a result of an HTTP API request: in this case, the `callCluster` function wraps `callWithRequest`, and the request headers are expected to have read privilege on the entire `.kibana` index. The use case for this is stats pulled from a Kibana Metricbeat module, where the Beat calls Kibana's stats API in Kibana to invoke collection. + +Note: there will be many cases where you won't need to use the `callCluster` function that gets passed in to your `fetch` method at all. Your feature might have an accumulating value in server memory, or read something from the OS. + +### Migrating to NP from Legacy Plugins: + +Pass `usageCollection` to the setup NP plugin setup function under plugins. Inside the `setup` function call the `registerCollector` like what you'd do in the NP example above. + +```js +// index.js +export const myPlugin = (kibana: any) => { + return new kibana.Plugin({ + init: async function (server) { + const { usageCollection } = server.newPlatform.setup.plugins; + const plugins = { + usageCollection, + }; + plugin(initializerContext).setup(core, plugins); + } + }); +} +``` + +### Legacy Plugins: + +Typically, a plugin will create the collector object and register it with the Telemetry service from the `init` method of the plugin definition, or a helper module called from `init`. + +```js +// index.js +export const myPlugin = (kibana: any) => { + return new kibana.Plugin({ + init: async function (server) { + const { usageCollection } = server.newPlatform.setup.plugins; + registerMyPluginUsageCollector(usageCollection); + } + }); +} +``` + +## 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. + +New fields added to the telemetry payload currently mean that telemetry cluster field mappings have to be updated, so they can be searched and aggregated in Kibana visualizations. This is also a short-term obligation. In the next refactoring phase, collectors will need to use a proscribed data model that eliminates maintenance of mappings in the telemetry cluster. + +## Testing + +There are a few ways you can test that your usage collector is working properly. + +1. The `/api/stats?extended=true` HTTP API in Kibana (added in 6.4.0) will call the fetch methods of all the registered collectors, and add them to a stats object you can see in a browser or in curl. To test that your usage collector has been registered correctly and that it has the model of data you expected it to have, call that HTTP API manually and you should see a key in the `usage` object of the response named after your usage collector's `type` field. This method tests the Metricbeat scenario described above where `callCluster` wraps `callWithRequest`. +2. There is a dev script in x-pack that will give a sample of a payload of data that gets sent up to the telemetry cluster for the sending phase of telemetry. Collected data comes from: + - The `.monitoring-*` indices, when Monitoring is enabled. Monitoring enhances the sent payload of telemetry by producing usage data potentially of multiple clusters that exist in the monitoring data. Monitoring data is time-based, and the time frame of collection is the last 15 minutes. + - Live-pulled from ES API endpoints. This will get just real-time stats without context of historical data. + - The dev script in x-pack can be run on the command-line with: + ``` + cd x-pack + node scripts/api_debug.js telemetry --host=http://localhost:5601 + ``` + Where `http://localhost:5601` is a Kibana server running in dev mode. If needed, authentication and basePath info can be provided in the command as well. + - Automatic inclusion of all the stats fetched by collectors is added in https://github.com/elastic/kibana/pull/22336 / 6.5.0 +3. In Dev mode, Kibana will send telemetry data to a staging telemetry cluster. Assuming you have access to the staging cluster, you can log in and check the latest documents for your new fields. +4. If you catch the network traffic coming from your browser when a telemetry payload is sent, you can examine the request payload body to see the data. This can be tricky as telemetry payloads are sent only once per day per browser. Use incognito mode or clear your localStorage data to force a telemetry payload. + +## FAQ + +1. **How should I design my data model?** + Keep it simple, and keep it to a model that Kibana will be able to understand. In short, that means don't rely on nested fields (arrays with objects). Flat arrays, such as arrays of strings are fine. +2. **If I accumulate an event counter in server memory, which my fetch method returns, won't it reset when the Kibana server restarts?** + Yes, but that is not a major concern. A visualization on such info might be a date histogram that gets events-per-second or something, which would be impacted by server restarts, so we'll have to offset the beginning of the time range when we detect that the latest metric is smaller than the earliest metric. That would be a pretty custom visualization, but perhaps future Kibana enhancements will be able to support that. diff --git a/packages/kbn-es-query/scripts/build.js b/src/plugins/usage_collection/common/constants.ts similarity index 94% rename from packages/kbn-es-query/scripts/build.js rename to src/plugins/usage_collection/common/constants.ts index 6d53a8469b0e04..edd06b171a72c8 100644 --- a/packages/kbn-es-query/scripts/build.js +++ b/src/plugins/usage_collection/common/constants.ts @@ -17,4 +17,4 @@ * under the License. */ -require('../tasks/build_cli'); +export const KIBANA_STATS_TYPE = 'kibana_stats'; diff --git a/src/plugins/usage_collection/kibana.json b/src/plugins/usage_collection/kibana.json new file mode 100644 index 00000000000000..145cd89ff884d8 --- /dev/null +++ b/src/plugins/usage_collection/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "usageCollection", + "configPath": ["usageCollection"], + "version": "kibana", + "server": true, + "ui": false +} diff --git a/src/legacy/server/usage/classes/__tests__/collector_set.js b/src/plugins/usage_collection/server/collector/__tests__/collector_set.js similarity index 85% rename from src/legacy/server/usage/classes/__tests__/collector_set.js rename to src/plugins/usage_collection/server/collector/__tests__/collector_set.js index 5cf18a8a152000..a2e400b876ff7e 100644 --- a/src/legacy/server/usage/classes/__tests__/collector_set.js +++ b/src/plugins/usage_collection/server/collector/__tests__/collector_set.js @@ -24,22 +24,25 @@ import { Collector } from '../collector'; import { CollectorSet } from '../collector_set'; import { UsageCollector } from '../usage_collector'; +const mockLogger = () => ({ + debug: sinon.spy(), + warn: sinon.spy(), +}); + describe('CollectorSet', () => { describe('registers a collector set and runs lifecycle events', () => { - let server; let init; let fetch; - beforeEach(() => { - server = { log: sinon.spy() }; init = noop; fetch = noop; }); it('should throw an error if non-Collector type of object is registered', () => { - const collectors = new CollectorSet(server); + const logger = mockLogger(); + const collectors = new CollectorSet({ logger }); const registerPojo = () => { - collectors.register({ + collectors.registerCollector({ type: 'type_collector_test', init, fetch, @@ -53,17 +56,17 @@ describe('CollectorSet', () => { it('should log debug status of fetching from the collector', async () => { const mockCallCluster = () => Promise.resolve({ passTest: 1000 }); - const collectors = new CollectorSet(server); - collectors.register(new Collector(server, { + const logger = mockLogger(); + const collectors = new CollectorSet({ logger }); + collectors.registerCollector(new Collector(logger, { type: 'MY_TEST_COLLECTOR', fetch: caller => caller() })); const result = await collectors.bulkFetch(mockCallCluster); - const calls = server.log.getCalls(); + const calls = logger.debug.getCalls(); expect(calls.length).to.be(1); expect(calls[0].args).to.eql([ - ['debug', 'stats-collection'], 'Fetching data from MY_TEST_COLLECTOR collector', ]); expect(result).to.eql([{ @@ -74,8 +77,9 @@ describe('CollectorSet', () => { it('should gracefully handle a collector fetch method throwing an error', async () => { const mockCallCluster = () => Promise.resolve({ passTest: 1000 }); - const collectors = new CollectorSet(server); - collectors.register(new Collector(server, { + const logger = mockLogger(); + const collectors = new CollectorSet({ logger }); + collectors.registerCollector(new Collector(logger, { type: 'MY_TEST_COLLECTOR', fetch: () => new Promise((_resolve, reject) => reject()) })); @@ -95,7 +99,8 @@ describe('CollectorSet', () => { let collectorSet; beforeEach(() => { - collectorSet = new CollectorSet(); + const logger = mockLogger(); + collectorSet = new CollectorSet({ logger }); }); it('should snake_case and convert field names to api standards', () => { @@ -161,14 +166,13 @@ describe('CollectorSet', () => { }); describe('isUsageCollector', () => { - const server = { }; const collectorOptions = { type: 'MY_TEST_COLLECTOR', fetch: () => {} }; it('returns true only for UsageCollector instances', () => { - const collectors = new CollectorSet(server); - - const usageCollector = new UsageCollector(server, collectorOptions); - const collector = new Collector(server, collectorOptions); + const logger = mockLogger(); + const collectors = new CollectorSet({ logger }); + const usageCollector = new UsageCollector(logger, collectorOptions); + const collector = new Collector(logger, collectorOptions); const randomClass = new (class Random {}); expect(collectors.isUsageCollector(usageCollector)).to.be(true); expect(collectors.isUsageCollector(collector)).to.be(false); diff --git a/src/legacy/server/usage/classes/collector.js b/src/plugins/usage_collection/server/collector/collector.js similarity index 93% rename from src/legacy/server/usage/classes/collector.js rename to src/plugins/usage_collection/server/collector/collector.js index 40b004f51e49ac..ab723edf5b7198 100644 --- a/src/legacy/server/usage/classes/collector.js +++ b/src/plugins/usage_collection/server/collector/collector.js @@ -17,18 +17,17 @@ * under the License. */ -import { getCollectorLogger } from '../lib'; export class Collector { /* - * @param {Object} server - server object + * @param {Object} logger - logger object * @param {String} options.type - property name as the key for the data * @param {Function} options.init (optional) - initialization function * @param {Function} options.fetch - function to query data * @param {Function} options.formatForBulkUpload - optional * @param {Function} options.rest - optional other properties */ - constructor(server, { type, init, fetch, formatForBulkUpload = null, isReady = null, ...options } = {}) { + constructor(logger, { type, init, fetch, formatForBulkUpload = null, isReady = null, ...options } = {}) { if (type === undefined) { throw new Error('Collector must be instantiated with a options.type string property'); } @@ -39,7 +38,7 @@ export class Collector { throw new Error('Collector must be instantiated with a options.fetch function property'); } - this.log = getCollectorLogger(server); + this.log = logger; Object.assign(this, options); // spread in other properties and mutate "this" diff --git a/src/plugins/usage_collection/server/collector/collector_set.ts b/src/plugins/usage_collection/server/collector/collector_set.ts new file mode 100644 index 00000000000000..a87accc47535ec --- /dev/null +++ b/src/plugins/usage_collection/server/collector/collector_set.ts @@ -0,0 +1,209 @@ +/* + * 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 { snakeCase } from 'lodash'; +import { Logger } from 'kibana/server'; +import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; +// @ts-ignore +import { Collector } from './collector'; +// @ts-ignore +import { UsageCollector } from './usage_collector'; + +interface CollectorSetConfig { + logger: Logger; + maximumWaitTimeForAllCollectorsInS: number; + collectors?: Collector[]; +} + +export class CollectorSet { + private _waitingForAllCollectorsTimestamp?: number; + private logger: Logger; + private readonly maximumWaitTimeForAllCollectorsInS: number; + private collectors: Collector[] = []; + constructor({ logger, maximumWaitTimeForAllCollectorsInS, collectors = [] }: CollectorSetConfig) { + this.logger = logger; + this.collectors = collectors; + this.maximumWaitTimeForAllCollectorsInS = maximumWaitTimeForAllCollectorsInS || 60; + } + + public makeStatsCollector = (options: any) => { + return new Collector(this.logger, options); + }; + public makeUsageCollector = (options: any) => { + return new UsageCollector(this.logger, options); + }; + + /* + * @param collector {Collector} collector object + */ + public registerCollector = (collector: Collector) => { + // check instanceof + if (!(collector instanceof Collector)) { + throw new Error('CollectorSet can only have Collector instances registered'); + } + + this.collectors.push(collector); + + if (collector.init) { + this.logger.debug(`Initializing ${collector.type} collector`); + collector.init(); + } + }; + + public getCollectorByType = (type: string) => { + return this.collectors.find(c => c.type === type); + }; + + public isUsageCollector = (x: UsageCollector | any): x is UsageCollector => { + return x instanceof UsageCollector; + }; + + public areAllCollectorsReady = async (collectorSet = this) => { + if (!(collectorSet instanceof CollectorSet)) { + throw new Error( + `areAllCollectorsReady method given bad collectorSet parameter: ` + typeof collectorSet + ); + } + + const collectorTypesNotReady: string[] = []; + let allReady = true; + for (const collector of collectorSet.collectors) { + if (!(await collector.isReady())) { + allReady = false; + collectorTypesNotReady.push(collector.type); + } + } + + if (!allReady && this.maximumWaitTimeForAllCollectorsInS >= 0) { + const nowTimestamp = +new Date(); + this._waitingForAllCollectorsTimestamp = + this._waitingForAllCollectorsTimestamp || nowTimestamp; + const timeWaitedInMS = nowTimestamp - this._waitingForAllCollectorsTimestamp; + const timeLeftInMS = this.maximumWaitTimeForAllCollectorsInS * 1000 - timeWaitedInMS; + if (timeLeftInMS <= 0) { + this.logger.debug( + `All collectors are not ready (waiting for ${collectorTypesNotReady.join(',')}) ` + + `but we have waited the required ` + + `${this.maximumWaitTimeForAllCollectorsInS}s and will return data from all collectors that are ready.` + ); + return true; + } else { + this.logger.debug(`All collectors are not ready. Waiting for ${timeLeftInMS}ms longer.`); + } + } else { + this._waitingForAllCollectorsTimestamp = undefined; + } + + return allReady; + }; + + public bulkFetch = async ( + callCluster: CallCluster, + collectors: Collector[] = this.collectors + ) => { + const responses = []; + for (const collector of collectors) { + this.logger.debug(`Fetching data from ${collector.type} collector`); + try { + responses.push({ + type: collector.type, + result: await collector.fetchInternal(callCluster), + }); + } catch (err) { + this.logger.warn(err); + this.logger.warn(`Unable to fetch data from ${collector.type} collector`); + } + } + + return responses; + }; + + /* + * @return {new CollectorSet} + */ + public getFilteredCollectorSet = (filter: any) => { + const filtered = this.collectors.filter(filter); + return this.makeCollectorSetFromArray(filtered); + }; + + public bulkFetchUsage = async (callCluster: CallCluster) => { + const usageCollectors = this.getFilteredCollectorSet((c: any) => c instanceof UsageCollector); + return await this.bulkFetch(callCluster, usageCollectors.collectors); + }; + + // convert an array of fetched stats results into key/object + public toObject = (statsData: any) => { + if (!statsData) return {}; + return statsData.reduce((accumulatedStats: any, { type, result }: any) => { + return { + ...accumulatedStats, + [type]: result, + }; + }, {}); + }; + + // rename fields to use api conventions + public toApiFieldNames = (apiData: any): any => { + const getValueOrRecurse = (value: any) => { + if (value == null || typeof value !== 'object') { + return value; + } else { + return this.toApiFieldNames(value); // recurse + } + }; + + // handle array and return early, or return a reduced object + + if (Array.isArray(apiData)) { + return apiData.map(getValueOrRecurse); + } + + return Object.keys(apiData).reduce((accum, field) => { + const value = apiData[field]; + let newName = field; + newName = snakeCase(newName); + newName = newName.replace(/^(1|5|15)_m/, '$1m'); // os.load.15m, os.load.5m, os.load.1m + newName = newName.replace('_in_bytes', '_bytes'); + newName = newName.replace('_in_millis', '_ms'); + + return { + ...accum, + [newName]: getValueOrRecurse(value), + }; + }, {}); + }; + + // TODO: remove + public map = (mapFn: any) => { + return this.collectors.map(mapFn); + }; + + // TODO: remove + public some = (someFn: any) => { + return this.collectors.some(someFn); + }; + + private makeCollectorSetFromArray = (collectors: Collector[]) => { + return new CollectorSet({ + logger: this.logger, + maximumWaitTimeForAllCollectorsInS: this.maximumWaitTimeForAllCollectorsInS, + collectors, + }); + }; +} diff --git a/src/legacy/server/usage/classes/index.js b/src/plugins/usage_collection/server/collector/index.ts similarity index 97% rename from src/legacy/server/usage/classes/index.js rename to src/plugins/usage_collection/server/collector/index.ts index 0d3939e1dc681b..962f61474c250e 100644 --- a/src/legacy/server/usage/classes/index.js +++ b/src/plugins/usage_collection/server/collector/index.ts @@ -18,5 +18,7 @@ */ export { CollectorSet } from './collector_set'; +// @ts-ignore export { Collector } from './collector'; +// @ts-ignore export { UsageCollector } from './usage_collector'; diff --git a/src/legacy/server/usage/classes/usage_collector.js b/src/plugins/usage_collection/server/collector/usage_collector.js similarity index 88% rename from src/legacy/server/usage/classes/usage_collector.js rename to src/plugins/usage_collection/server/collector/usage_collector.js index 559deaef2ce15c..1e2806ea15f3b2 100644 --- a/src/legacy/server/usage/classes/usage_collector.js +++ b/src/plugins/usage_collection/server/collector/usage_collector.js @@ -17,20 +17,20 @@ * under the License. */ -import { KIBANA_STATS_TYPE } from '../../status/constants'; +import { KIBANA_STATS_TYPE } from '../../common/constants'; import { Collector } from './collector'; export class UsageCollector extends Collector { /* - * @param {Object} server - server object + * @param {Object} logger - logger object * @param {String} options.type - property name as the key for the data * @param {Function} options.init (optional) - initialization function * @param {Function} options.fetch - function to query data * @param {Function} options.formatForBulkUpload - optional * @param {Function} options.rest - optional other properties */ - constructor(server, { type, init, fetch, formatForBulkUpload = null, ...options } = {}) { - super(server, { type, init, fetch, formatForBulkUpload, ...options }); + constructor(logger, { type, init, fetch, formatForBulkUpload = null, ...options } = {}) { + super(logger, { type, init, fetch, formatForBulkUpload, ...options }); /* * Currently, for internal bulk uploading, usage stats are part of diff --git a/packages/kbn-es-query/index.d.ts b/src/plugins/usage_collection/server/config.ts similarity index 82% rename from packages/kbn-es-query/index.d.ts rename to src/plugins/usage_collection/server/config.ts index 9bbd0a193dfed1..987db1f2b0ff3c 100644 --- a/packages/kbn-es-query/index.d.ts +++ b/src/plugins/usage_collection/server/config.ts @@ -17,4 +17,8 @@ * under the License. */ -export * from './src'; +import { schema } from '@kbn/config-schema'; + +export const ConfigSchema = schema.object({ + maximumWaitTimeForAllCollectorsInS: schema.number({ defaultValue: 60 }), +}); diff --git a/packages/kbn-es-query/src/utils/get_time_zone_from_settings.js b/src/plugins/usage_collection/server/index.ts similarity index 69% rename from packages/kbn-es-query/src/utils/get_time_zone_from_settings.js rename to src/plugins/usage_collection/server/index.ts index 1a06941ece1274..33a1a0adc67135 100644 --- a/packages/kbn-es-query/src/utils/get_time_zone_from_settings.js +++ b/src/plugins/usage_collection/server/index.ts @@ -17,12 +17,11 @@ * under the License. */ -import moment from 'moment-timezone'; -const detectedTimezone = moment.tz.guess(); +import { PluginInitializerContext } from '../../../../src/core/server'; +import { Plugin } from './plugin'; +import { ConfigSchema } from './config'; -export function getTimeZoneFromSettings(dateFormatTZ) { - if (dateFormatTZ === 'Browser') { - return detectedTimezone; - } - return dateFormatTZ; -} +export { UsageCollectionSetup } from './plugin'; +export const config = { schema: ConfigSchema }; +export const plugin = (initializerContext: PluginInitializerContext) => + new Plugin(initializerContext); diff --git a/src/plugins/usage_collection/server/plugin.ts b/src/plugins/usage_collection/server/plugin.ts new file mode 100644 index 00000000000000..e8bbc8e512a41d --- /dev/null +++ b/src/plugins/usage_collection/server/plugin.ts @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { first } from 'rxjs/operators'; +import { TypeOf } from '@kbn/config-schema'; +import { ConfigSchema } from './config'; +import { PluginInitializerContext, Logger } from '../../../../src/core/server'; +import { CollectorSet } from './collector'; + +export type UsageCollectionSetup = CollectorSet; + +export class Plugin { + logger: Logger; + constructor(private readonly initializerContext: PluginInitializerContext) { + this.logger = this.initializerContext.logger.get(); + } + + public async setup(): Promise { + const config = await this.initializerContext.config + .create>() + .pipe(first()) + .toPromise(); + + const collectorSet = new CollectorSet({ + logger: this.logger, + maximumWaitTimeForAllCollectorsInS: config.maximumWaitTimeForAllCollectorsInS, + }); + + return collectorSet; + } + + public start() { + this.logger.debug('Starting plugin'); + } + + public stop() { + this.logger.debug('Stopping plugin'); + } +} diff --git a/tasks/config/peg.js b/tasks/config/peg.js index 7c3e597ae12d2c..a9d066f3cd49fa 100644 --- a/tasks/config/peg.js +++ b/tasks/config/peg.js @@ -19,8 +19,8 @@ module.exports = { kuery: { - src: 'packages/kbn-es-query/src/kuery/ast/kuery.peg', - dest: 'packages/kbn-es-query/src/kuery/ast/kuery.js', + src: 'src/plugins/data/common/es_query/kuery/ast/kuery.peg', + dest: 'src/plugins/data/common/es_query/kuery/ast/_generated_/kuery.js', options: { allowedStartRules: ['start', 'Literal'] } diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index 769acc52e207b6..97ad71eaddd7cb 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -8,7 +8,7 @@ "license": "Apache-2.0", "dependencies": { "@elastic/eui": "16.0.0", - "react": "^16.8.0", - "react-dom": "^16.8.0" + "react": "^16.8.6", + "react-dom": "^16.8.6" } } diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/components/main.tsx b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/components/main.tsx index c091765619a194..daa19f22a70232 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/components/main.tsx +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/components/main.tsx @@ -29,7 +29,7 @@ import { Context, ExpressionRenderHandler, ExpressionDataHandler, - RenderResult, + RenderId, } from '../../types'; import { getExpressions } from '../../services'; @@ -40,7 +40,7 @@ declare global { context?: Context, initialContext?: Context ) => ReturnType; - renderPipelineResponse: (context?: Context) => Promise; + renderPipelineResponse: (context?: Context) => Promise; } } @@ -85,16 +85,16 @@ class Main extends React.Component<{}, State> { lastRenderHandler.destroy(); } - lastRenderHandler = getExpressions().render(this.chartRef.current!, context); - const renderResult = await lastRenderHandler.render$.pipe(first()).toPromise(); + lastRenderHandler = getExpressions().render(this.chartRef.current!, context, { + onRenderError: (el, error, handler) => { + this.setState({ + expression: 'Render error!\n\n' + JSON.stringify(error), + }); + handler.done(); + }, + }); - if (typeof renderResult === 'object' && renderResult.type === 'error') { - this.setState({ - expression: 'Render error!\n\n' + JSON.stringify(renderResult.error), - }); - } - - return renderResult; + return lastRenderHandler.render$.pipe(first()).toPromise(); }; } diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/types.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/types.ts index 082bb47d80066a..cc4190bd099fae 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/types.ts +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/types.ts @@ -22,7 +22,7 @@ import { Context, ExpressionRenderHandler, ExpressionDataHandler, - RenderResult, + RenderId, } from 'src/plugins/expressions/public'; import { Adapters } from 'src/plugins/inspector/public'; @@ -32,6 +32,6 @@ export { Context, ExpressionRenderHandler, ExpressionDataHandler, - RenderResult, + RenderId, Adapters, }; diff --git a/test/interpreter_functional/test_suites/run_pipeline/helpers.ts b/test/interpreter_functional/test_suites/run_pipeline/helpers.ts index e1ec18fae5e3a8..7fedf1723908a9 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/helpers.ts +++ b/test/interpreter_functional/test_suites/run_pipeline/helpers.ts @@ -21,8 +21,8 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../functional/ftr_provider_context'; import { ExpressionDataHandler, - RenderResult, Context, + RenderId, } from '../../plugins/kbn_tp_run_pipeline/public/np_ready/types'; type UnWrapPromise = T extends Promise ? U : T; @@ -168,8 +168,8 @@ export function expectExpressionProvider({ toMatchScreenshot: async () => { const pipelineResponse = await handler.getResponse(); log.debug('starting to render'); - const result = await browser.executeAsync( - (_context: ExpressionResult, done: (renderResult: RenderResult) => void) => + const result = await browser.executeAsync( + (_context: ExpressionResult, done: (renderResult: RenderId) => void) => window.renderPipelineResponse(_context).then(renderResult => { done(renderResult); return renderResult; diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index 41e1e6baca0ecc..ca584b4b4e7714 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -8,6 +8,6 @@ "license": "Apache-2.0", "dependencies": { "@elastic/eui": "16.0.0", - "react": "^16.8.0" + "react": "^16.8.6" } } diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_vis.js b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_vis.js index c2d8ed7f5f9c17..c24dd077b447e4 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_vis.js +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/public/self_changing_vis/self_changing_vis.js @@ -17,37 +17,31 @@ * under the License. */ -import { visFactory } from 'ui/vis/vis_factory'; - import { SelfChangingEditor } from './self_changing_editor'; import { SelfChangingComponent } from './self_changing_components'; import { setup as visualizations } from '../../../../../../src/legacy/core_plugins/visualizations/public/np_ready/public/legacy'; -function SelfChangingVisType() { - return visFactory.createReactVisualization({ - name: 'self_changing_vis', - title: 'Self Changing Vis', - icon: 'visControls', - description: 'This visualization is able to change its own settings, that you could also set in the editor.', - visConfig: { - component: SelfChangingComponent, - defaults: { - counter: 0, - }, - }, - editorConfig: { - optionTabs: [ - { - name: 'options', - title: 'Options', - editor: SelfChangingEditor, - }, - ], +visualizations.types.createReactVisualization({ + name: 'self_changing_vis', + title: 'Self Changing Vis', + icon: 'visControls', + description: 'This visualization is able to change its own settings, that you could also set in the editor.', + visConfig: { + component: SelfChangingComponent, + defaults: { + counter: 0, }, - requestHandler: 'none', - }); -} - -visualizations.types.registerVisualization(SelfChangingVisType); + }, + editorConfig: { + optionTabs: [ + { + name: 'options', + title: 'Options', + editor: SelfChangingEditor, + }, + ], + }, + requestHandler: 'none', +}); diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json index a0b03e52640fce..71545fa582c664 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json @@ -9,7 +9,7 @@ "license": "Apache-2.0", "dependencies": { "@elastic/eui": "16.0.0", - "react": "^16.8.0" + "react": "^16.8.6" }, "scripts": { "kbn": "node ../../../../scripts/kbn.js", diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json index 952d06c4873d4d..d5c97bb212ea05 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json @@ -9,7 +9,7 @@ "license": "Apache-2.0", "dependencies": { "@elastic/eui": "16.0.0", - "react": "^16.8.0" + "react": "^16.8.6" }, "scripts": { "kbn": "node ../../../../scripts/kbn.js", diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts index c16847dab9dc23..a3c9d9d63e3534 100644 --- a/test/plugin_functional/test_suites/core_plugins/applications.ts +++ b/test/plugin_functional/test_suites/core_plugins/applications.ts @@ -107,13 +107,13 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider expect(await testSubjects.exists('headerGlobalNav')).to.be(true); }); - it('can navigate from NP apps to legacy apps', async () => { + it.skip('can navigate from NP apps to legacy apps', async () => { await appsMenu.clickLink('Management'); await loadingScreenShown(); await testSubjects.existOrFail('managementNav'); }); - it('can navigate from legacy apps to NP apps', async () => { + it.skip('can navigate from legacy apps to NP apps', async () => { await appsMenu.clickLink('Foo'); await loadingScreenShown(); await testSubjects.existOrFail('fooAppHome'); diff --git a/x-pack/legacy/plugins/apm/index.ts b/x-pack/legacy/plugins/apm/index.ts index 0cac20ef340d24..1784ed22a2b4dc 100644 --- a/x-pack/legacy/plugins/apm/index.ts +++ b/x-pack/legacy/plugins/apm/index.ts @@ -108,7 +108,8 @@ export const apm: LegacyPluginInitializer = kibana => { } } }); - makeApmUsageCollector(server); + const { usageCollection } = server.newPlatform.setup.plugins; + makeApmUsageCollector(usageCollection, server); const apmPlugin = server.newPlatform.setup.plugins .apm as APMPluginContract; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx index 52be4d4fba7748..32fbe46ac560c4 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/KueryBar/index.tsx @@ -7,7 +7,6 @@ import React, { useState } from 'react'; import { uniqueId, startsWith } from 'lodash'; import styled from 'styled-components'; -import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { fromQuery, toQuery } from '../Links/url_helpers'; // @ts-ignore @@ -16,13 +15,14 @@ import { getBoolFilter } from './get_bool_filter'; import { useLocation } from '../../../hooks/useLocation'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { history } from '../../../utils/history'; +import { usePlugins } from '../../../new-platform/plugin'; +import { useDynamicIndexPattern } from '../../../hooks/useDynamicIndexPattern'; import { - AutocompleteSuggestion, AutocompleteProvider, + AutocompleteSuggestion, + esKuery, IIndexPattern } from '../../../../../../../../src/plugins/data/public'; -import { useDynamicIndexPattern } from '../../../hooks/useDynamicIndexPattern'; -import { usePlugins } from '../../../new-platform/plugin'; const Container = styled.div` margin-bottom: 10px; @@ -34,8 +34,8 @@ interface State { } function convertKueryToEsQuery(kuery: string, indexPattern: IIndexPattern) { - const ast = fromKueryExpression(kuery); - return toElasticsearchQuery(ast, indexPattern); + const ast = esKuery.fromKueryExpression(kuery); + return esKuery.toElasticsearchQuery(ast, indexPattern); } function getSuggestions( diff --git a/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/index.ts b/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/index.ts index de8846a8f9fb41..ddfb4144d96364 100644 --- a/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/apm_telemetry/index.ts @@ -13,6 +13,7 @@ import { APM_SERVICES_TELEMETRY_SAVED_OBJECT_ID } from '../../../common/apm_saved_object_constants'; import { APMLegacyServer } from '../../routes/typings'; +import { UsageCollectionSetup } from '../../../../../../../src/plugins/usage_collection/server'; export function createApmTelementry( agentNames: string[] = [] @@ -43,8 +44,11 @@ export async function storeApmServicesTelemetry( } } -export function makeApmUsageCollector(server: APMLegacyServer) { - const apmUsageCollector = server.usage.collectorSet.makeUsageCollector({ +export function makeApmUsageCollector( + usageCollector: UsageCollectionSetup, + server: APMLegacyServer +) { + const apmUsageCollector = usageCollector.makeUsageCollector({ type: 'apm', fetch: async () => { const internalSavedObjectsClient = getInternalSavedObjectsClient(server); @@ -60,5 +64,6 @@ export function makeApmUsageCollector(server: APMLegacyServer) { }, isReady: () => true }); - server.usage.collectorSet.register(apmUsageCollector); + + usageCollector.registerCollector(apmUsageCollector); } diff --git a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts index cee097d010212b..a6f6d36ecfc81a 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/convert_ui_filters/get_ui_filters_es.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { toElasticsearchQuery, fromKueryExpression } from '@kbn/es-query'; import { ESFilter } from '../../../../typings/elasticsearch'; import { UIFilters } from '../../../../typings/ui-filters'; import { getEnvironmentUiFilterES } from './get_environment_ui_filter_es'; @@ -12,10 +11,13 @@ import { localUIFilters, localUIFilterNames } from '../../ui_filters/local_ui_filters/config'; -import { StaticIndexPattern } from '../../../../../../../../src/legacy/core_plugins/data/public'; +import { + esKuery, + IIndexPattern +} from '../../../../../../../../src/plugins/data/server'; export function getUiFiltersES( - indexPattern: StaticIndexPattern | undefined, + indexPattern: IIndexPattern | undefined, uiFilters: UIFilters ) { const { kuery, environment, ...localFilterValues } = uiFilters; @@ -43,13 +45,13 @@ export function getUiFiltersES( } function getKueryUiFilterES( - indexPattern: StaticIndexPattern | undefined, + indexPattern: IIndexPattern | undefined, kuery?: string ) { if (!kuery || !indexPattern) { return; } - const ast = fromKueryExpression(kuery); - return toElasticsearchQuery(ast, indexPattern) as ESFilter; + const ast = esKuery.fromKueryExpression(kuery); + return esKuery.toElasticsearchQuery(ast, indexPattern) as ESFilter; } diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.ts index 805f8f192bdb19..5d140155f75e48 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/avg_duration_by_browser/transformer.ts @@ -14,8 +14,6 @@ export function transformer({ response: ESResponse; }): AvgDurationByBrowserAPIResponse { const allUserAgentKeys = new Set( - // TODO(TS-3.7-ESLINT) - // eslint-disable-next-line @typescript-eslint/camelcase (response.aggregations?.user_agent_keys?.buckets ?? []).map(({ key }) => key.toString() ) diff --git a/x-pack/legacy/plugins/apm/server/routes/typings.ts b/x-pack/legacy/plugins/apm/server/routes/typings.ts index 207fe7fe5da339..9b114eba72626f 100644 --- a/x-pack/legacy/plugins/apm/server/routes/typings.ts +++ b/x-pack/legacy/plugins/apm/server/routes/typings.ts @@ -49,13 +49,7 @@ export interface Route< }) => Promise; } -export type APMLegacyServer = Pick & { - usage: { - collectorSet: { - makeUsageCollector: (options: unknown) => unknown; - register: (options: unknown) => unknown; - }; - }; +export type APMLegacyServer = Pick & { plugins: { elasticsearch: Server['plugins']['elasticsearch']; }; diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts index 526728bd77cac6..83c610800b89ba 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts @@ -4,11 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { isEmpty } from 'lodash'; import { npStart } from 'ui/new_platform'; import { ElasticsearchAdapter } from './adapter_types'; -import { AutocompleteSuggestion } from '../../../../../../../../src/plugins/data/public'; +import { AutocompleteSuggestion, esKuery } from '../../../../../../../../src/plugins/data/public'; import { setup as data } from '../../../../../../../../src/legacy/core_plugins/data/public/legacy'; const getAutocompleteProvider = (language: string) => @@ -20,7 +19,7 @@ export class RestElasticsearchAdapter implements ElasticsearchAdapter { public isKueryValid(kuery: string): boolean { try { - fromKueryExpression(kuery); + esKuery.fromKueryExpression(kuery); } catch (err) { return false; } @@ -31,9 +30,9 @@ export class RestElasticsearchAdapter implements ElasticsearchAdapter { if (!this.isKueryValid(kuery)) { return ''; } - const ast = fromKueryExpression(kuery); + const ast = esKuery.fromKueryExpression(kuery); const indexPattern = await this.getIndexPattern(); - return JSON.stringify(toElasticsearchQuery(ast, indexPattern)); + return JSON.stringify(esKuery.toElasticsearchQuery(ast, indexPattern)); } public async getSuggestions( kuery: string, diff --git a/x-pack/legacy/plugins/canvas/__tests__/fixtures/kibana.js b/x-pack/legacy/plugins/canvas/__tests__/fixtures/kibana.js index ed83dbfcb75b7d..141beb3d34d783 100644 --- a/x-pack/legacy/plugins/canvas/__tests__/fixtures/kibana.js +++ b/x-pack/legacy/plugins/canvas/__tests__/fixtures/kibana.js @@ -29,12 +29,6 @@ export class Plugin { has: key => has(config, key), }), route: def => this.routes.push(def), - usage: { - collectorSet: { - makeUsageCollector: () => {}, - register: () => {}, - }, - }, }; const { init } = this.props; diff --git a/x-pack/legacy/plugins/canvas/__tests__/fixtures/workpads.ts b/x-pack/legacy/plugins/canvas/__tests__/fixtures/workpads.ts index d7ebbd87c97e6c..271fc7a9790577 100644 --- a/x-pack/legacy/plugins/canvas/__tests__/fixtures/workpads.ts +++ b/x-pack/legacy/plugins/canvas/__tests__/fixtures/workpads.ts @@ -192,3 +192,16 @@ export const elements: CanvasElement[] = [ { ...BaseElement, expression: 'filters | demodata | pointseries | pie | render' }, { ...BaseElement, expression: 'image | render' }, ]; + +export const workpadWithGroupAsElement: CanvasWorkpad = { + ...BaseWorkpad, + pages: [ + { + ...BasePage, + elements: [ + { ...BaseElement, expression: 'image | render' }, + { ...BaseElement, id: 'group-1234' }, + ], + }, + ], +}; diff --git a/x-pack/legacy/plugins/canvas/public/lib/workpad_service.js b/x-pack/legacy/plugins/canvas/public/lib/workpad_service.js index 33067f1837f41d..f1ed069c15d4d7 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/workpad_service.js +++ b/x-pack/legacy/plugins/canvas/public/lib/workpad_service.js @@ -29,6 +29,7 @@ export function get(workpadId) { }); } +// TODO: I think this function is never used. Look into and remove the corresponding route as well export function update(id, workpad) { return fetch.put(`${apiPath}/${id}`, workpad); } diff --git a/x-pack/legacy/plugins/canvas/server/plugin.ts b/x-pack/legacy/plugins/canvas/server/plugin.ts index 888d9a5f36c325..b3389711033815 100644 --- a/x-pack/legacy/plugins/canvas/server/plugin.ts +++ b/x-pack/legacy/plugins/canvas/server/plugin.ts @@ -61,7 +61,7 @@ export class Plugin { }, }); - registerCanvasUsageCollector(core, plugins); + registerCanvasUsageCollector(plugins.usageCollection, core); loadSampleData( plugins.sampleData.addSavedObjectsToSampleDataset, plugins.sampleData.addAppLinksToSampleDataset diff --git a/x-pack/legacy/plugins/canvas/server/routes/index.ts b/x-pack/legacy/plugins/canvas/server/routes/index.ts index a0502c5e891a22..515d5b5e895edf 100644 --- a/x-pack/legacy/plugins/canvas/server/routes/index.ts +++ b/x-pack/legacy/plugins/canvas/server/routes/index.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { workpad } from './workpad'; import { esFields } from './es_fields'; import { customElements } from './custom_elements'; import { shareableWorkpads } from './shareables'; @@ -13,6 +12,5 @@ import { CoreSetup } from '../shim'; export function routes(setup: CoreSetup): void { customElements(setup.http.route, setup.elasticsearch); esFields(setup.http.route, setup.elasticsearch); - workpad(setup.http.route, setup.elasticsearch); shareableWorkpads(setup.http.route); } diff --git a/x-pack/legacy/plugins/canvas/server/routes/workpad.test.js b/x-pack/legacy/plugins/canvas/server/routes/workpad.test.js deleted file mode 100644 index 09a5c3b89c31eb..00000000000000 --- a/x-pack/legacy/plugins/canvas/server/routes/workpad.test.js +++ /dev/null @@ -1,462 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Hapi from 'hapi'; -import { - CANVAS_TYPE, - API_ROUTE_WORKPAD, - API_ROUTE_WORKPAD_ASSETS, - API_ROUTE_WORKPAD_STRUCTURES, -} from '../../common/lib/constants'; -import { workpad } from './workpad'; - -const routePrefix = API_ROUTE_WORKPAD; -const routePrefixAssets = API_ROUTE_WORKPAD_ASSETS; -const routePrefixStructures = API_ROUTE_WORKPAD_STRUCTURES; - -jest.mock('uuid/v4', () => jest.fn().mockReturnValue('123abc')); - -describe(`${CANVAS_TYPE} API`, () => { - const savedObjectsClient = { - get: jest.fn(), - create: jest.fn(), - delete: jest.fn(), - find: jest.fn(), - }; - - afterEach(() => { - savedObjectsClient.get.mockReset(); - savedObjectsClient.create.mockReset(); - savedObjectsClient.delete.mockReset(); - savedObjectsClient.find.mockReset(); - }); - - // Mock toISOString function of all Date types - global.Date = class Date extends global.Date { - toISOString() { - return '2019-02-12T21:01:22.479Z'; - } - }; - - // Setup mock server - const mockServer = new Hapi.Server({ debug: false, port: 0 }); - const mockEs = { - getCluster: () => ({ - errors: { - // formatResponse will fail without objects here - '400': Error, - '401': Error, - '403': Error, - '404': Error, - }, - }), - }; - - mockServer.ext('onRequest', (req, h) => { - req.getSavedObjectsClient = () => savedObjectsClient; - return h.continue; - }); - workpad(mockServer.route.bind(mockServer), mockEs); - - describe(`GET ${routePrefix}/{id}`, () => { - test('returns successful response', async () => { - const request = { - method: 'GET', - url: `${routePrefix}/123`, - }; - - savedObjectsClient.get.mockResolvedValueOnce({ id: '123', attributes: { foo: true } }); - - const { payload, statusCode } = await mockServer.inject(request); - const response = JSON.parse(payload); - - expect(statusCode).toBe(200); - expect(response).toMatchInlineSnapshot(` -Object { - "foo": true, - "id": "123", -} -`); - expect(savedObjectsClient.get.mock.calls).toMatchInlineSnapshot(` -Array [ - Array [ - "canvas-workpad", - "123", - ], -] -`); - }); - }); - - describe(`POST ${routePrefix}`, () => { - test('returns successful response without id in payload', async () => { - const request = { - method: 'POST', - url: routePrefix, - payload: { - foo: true, - }, - }; - - savedObjectsClient.create.mockResolvedValueOnce({}); - - const { payload, statusCode } = await mockServer.inject(request); - const response = JSON.parse(payload); - - expect(statusCode).toBe(200); - expect(response).toMatchInlineSnapshot(` -Object { - "ok": true, -} -`); - expect(savedObjectsClient.create.mock.calls).toMatchInlineSnapshot(` -Array [ - Array [ - "canvas-workpad", - Object { - "@created": "2019-02-12T21:01:22.479Z", - "@timestamp": "2019-02-12T21:01:22.479Z", - "foo": true, - }, - Object { - "id": "workpad-123abc", - }, - ], -] -`); - }); - - test('returns succesful response with id in payload', async () => { - const request = { - method: 'POST', - url: routePrefix, - payload: { - id: '123', - foo: true, - }, - }; - - savedObjectsClient.create.mockResolvedValueOnce({}); - - const { payload, statusCode } = await mockServer.inject(request); - const response = JSON.parse(payload); - - expect(statusCode).toBe(200); - expect(response).toMatchInlineSnapshot(` -Object { - "ok": true, -} -`); - expect(savedObjectsClient.create.mock.calls).toMatchInlineSnapshot(` -Array [ - Array [ - "canvas-workpad", - Object { - "@created": "2019-02-12T21:01:22.479Z", - "@timestamp": "2019-02-12T21:01:22.479Z", - "foo": true, - }, - Object { - "id": "123", - }, - ], -] -`); - }); - }); - - describe(`PUT ${routePrefix}/{id}`, () => { - test('formats successful response', async () => { - const request = { - method: 'PUT', - url: `${routePrefix}/123`, - payload: { - id: '234', - foo: true, - }, - }; - - savedObjectsClient.get.mockResolvedValueOnce({ - attributes: { - '@created': new Date().toISOString(), - }, - }); - savedObjectsClient.create.mockResolvedValueOnce({}); - - const { payload, statusCode } = await mockServer.inject(request); - const response = JSON.parse(payload); - - expect(statusCode).toBe(200); - expect(response).toMatchInlineSnapshot(` -Object { - "ok": true, -} -`); - expect(savedObjectsClient.get.mock.calls).toMatchInlineSnapshot(` -Array [ - Array [ - "canvas-workpad", - "123", - ], -] -`); - expect(savedObjectsClient.create.mock.calls).toMatchInlineSnapshot(` -Array [ - Array [ - "canvas-workpad", - Object { - "@created": "2019-02-12T21:01:22.479Z", - "@timestamp": "2019-02-12T21:01:22.479Z", - "foo": true, - }, - Object { - "id": "123", - "overwrite": true, - }, - ], -] -`); - }); - }); - - describe(`DELETE ${routePrefix}/{id}`, () => { - test('formats successful response', async () => { - const request = { - method: 'DELETE', - url: `${routePrefix}/123`, - }; - - savedObjectsClient.delete.mockResolvedValueOnce({}); - - const { payload, statusCode } = await mockServer.inject(request); - const response = JSON.parse(payload); - - expect(statusCode).toBe(200); - expect(response).toMatchInlineSnapshot(` -Object { - "ok": true, -} -`); - expect(savedObjectsClient.delete.mock.calls).toMatchInlineSnapshot(` -Array [ - Array [ - "canvas-workpad", - "123", - ], -] -`); - }); - }); - - it(`GET ${routePrefix}/find`, async () => { - const request = { - method: 'GET', - url: `${routePrefix}/find?name=abc&page=2&perPage=10`, - }; - - savedObjectsClient.find.mockResolvedValueOnce({ - saved_objects: [ - { - id: '1', - attributes: { - foo: true, - }, - }, - ], - }); - - const { payload, statusCode } = await mockServer.inject(request); - const response = JSON.parse(payload); - - expect(statusCode).toBe(200); - expect(response).toMatchInlineSnapshot(` -Object { - "workpads": Array [ - Object { - "foo": true, - "id": "1", - }, - ], -} -`); - expect(savedObjectsClient.find.mock.calls).toMatchInlineSnapshot(` -Array [ - Array [ - Object { - "fields": Array [ - "id", - "name", - "@created", - "@timestamp", - ], - "page": "2", - "perPage": "10", - "search": "abc* | abc", - "searchFields": Array [ - "name", - ], - "sortField": "@timestamp", - "sortOrder": "desc", - "type": "canvas-workpad", - }, - ], -] -`); - }); - - describe(`PUT ${routePrefixAssets}/{id}`, () => { - test('only updates assets', async () => { - const request = { - method: 'PUT', - url: `${routePrefixAssets}/123`, - payload: { - 'asset-123': { - id: 'asset-123', - '@created': '2019-02-14T00:00:00.000Z', - type: 'dataurl', - value: 'mockbase64data', - }, - 'asset-456': { - id: 'asset-456', - '@created': '2019-02-15T00:00:00.000Z', - type: 'dataurl', - value: 'mockbase64data', - }, - }, - }; - - // provide some existing workpad data to check that it's preserved - savedObjectsClient.get.mockResolvedValueOnce({ - attributes: { - '@created': new Date().toISOString(), - name: 'fake workpad', - }, - }); - savedObjectsClient.create.mockResolvedValueOnce({}); - - const { payload, statusCode } = await mockServer.inject(request); - const response = JSON.parse(payload); - - expect(statusCode).toBe(200); - expect(response).toMatchInlineSnapshot(` -Object { - "ok": true, -} -`); - expect(savedObjectsClient.get.mock.calls).toMatchInlineSnapshot(` -Array [ - Array [ - "canvas-workpad", - "123", - ], -] -`); - expect(savedObjectsClient.create.mock.calls).toMatchInlineSnapshot(` -Array [ - Array [ - "canvas-workpad", - Object { - "@created": "2019-02-12T21:01:22.479Z", - "@timestamp": "2019-02-12T21:01:22.479Z", - "assets": Object { - "asset-123": Object { - "@created": "2019-02-14T00:00:00.000Z", - "id": "asset-123", - "type": "dataurl", - "value": "mockbase64data", - }, - "asset-456": Object { - "@created": "2019-02-15T00:00:00.000Z", - "id": "asset-456", - "type": "dataurl", - "value": "mockbase64data", - }, - }, - "name": "fake workpad", - }, - Object { - "id": "123", - "overwrite": true, - }, - ], -] -`); - }); - }); - - describe(`PUT ${routePrefixStructures}/{id}`, () => { - test('only updates workpad', async () => { - const request = { - method: 'PUT', - url: `${routePrefixStructures}/123`, - payload: { - name: 'renamed workpad', - css: '.canvasPage { color: LavenderBlush; }', - }, - }; - - // provide some existing asset data and a name to replace - savedObjectsClient.get.mockResolvedValueOnce({ - attributes: { - '@created': new Date().toISOString(), - name: 'fake workpad', - assets: { - 'asset-123': { - id: 'asset-123', - '@created': '2019-02-14T00:00:00.000Z', - type: 'dataurl', - value: 'mockbase64data', - }, - }, - }, - }); - savedObjectsClient.create.mockResolvedValueOnce({}); - - const { payload, statusCode } = await mockServer.inject(request); - const response = JSON.parse(payload); - - expect(statusCode).toBe(200); - expect(response).toMatchInlineSnapshot(` -Object { - "ok": true, -} -`); - expect(savedObjectsClient.get.mock.calls).toMatchInlineSnapshot(` -Array [ - Array [ - "canvas-workpad", - "123", - ], -] -`); - expect(savedObjectsClient.create.mock.calls).toMatchInlineSnapshot(` -Array [ - Array [ - "canvas-workpad", - Object { - "@created": "2019-02-12T21:01:22.479Z", - "@timestamp": "2019-02-12T21:01:22.479Z", - "assets": Object { - "asset-123": Object { - "@created": "2019-02-14T00:00:00.000Z", - "id": "asset-123", - "type": "dataurl", - "value": "mockbase64data", - }, - }, - "css": ".canvasPage { color: LavenderBlush; }", - "name": "renamed workpad", - }, - Object { - "id": "123", - "overwrite": true, - }, - ], -] -`); - }); - }); -}); diff --git a/x-pack/legacy/plugins/canvas/server/routes/workpad.ts b/x-pack/legacy/plugins/canvas/server/routes/workpad.ts deleted file mode 100644 index 380fe97ca9ef10..00000000000000 --- a/x-pack/legacy/plugins/canvas/server/routes/workpad.ts +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import boom from 'boom'; -import { omit } from 'lodash'; -import { SavedObjectsClientContract, SavedObjectAttributes } from 'src/core/server'; -import { - CANVAS_TYPE, - API_ROUTE_WORKPAD, - API_ROUTE_WORKPAD_ASSETS, - API_ROUTE_WORKPAD_STRUCTURES, -} from '../../common/lib/constants'; -import { getId } from '../../public/lib/get_id'; -import { CoreSetup } from '../shim'; -// @ts-ignore Untyped Local -import { formatResponse as formatRes } from '../lib/format_response'; -import { CanvasWorkpad } from '../../types'; - -type WorkpadAttributes = Pick> & { - '@timestamp': string; - '@created': string; -}; - -interface WorkpadRequestFacade { - getSavedObjectsClient: () => SavedObjectsClientContract; -} - -type WorkpadRequest = WorkpadRequestFacade & { - params: { - id: string; - }; - payload: CanvasWorkpad; -}; - -type FindWorkpadRequest = WorkpadRequestFacade & { - query: { - name: string; - page: number; - perPage: number; - }; -}; - -type AssetsRequest = WorkpadRequestFacade & { - params: { - id: string; - }; - payload: CanvasWorkpad['assets']; -}; - -export function workpad( - route: CoreSetup['http']['route'], - elasticsearch: CoreSetup['elasticsearch'] -) { - // @ts-ignore EsErrors is not on the Cluster type - const { errors: esErrors } = elasticsearch.getCluster('data'); - const routePrefix = API_ROUTE_WORKPAD; - const routePrefixAssets = API_ROUTE_WORKPAD_ASSETS; - const routePrefixStructures = API_ROUTE_WORKPAD_STRUCTURES; - const formatResponse = formatRes(esErrors); - - function createWorkpad(req: WorkpadRequest) { - const savedObjectsClient = req.getSavedObjectsClient(); - - if (!req.payload) { - return Promise.reject(boom.badRequest('A workpad payload is required')); - } - - const now = new Date().toISOString(); - const { id, ...payload } = req.payload; - return savedObjectsClient.create( - CANVAS_TYPE, - { - ...payload, - '@timestamp': now, - '@created': now, - }, - { id: id || getId('workpad') } - ); - } - - function updateWorkpad( - req: WorkpadRequest | AssetsRequest, - newPayload?: CanvasWorkpad | { assets: CanvasWorkpad['assets'] } - ) { - const savedObjectsClient = req.getSavedObjectsClient(); - const { id } = req.params; - const payload = newPayload ? newPayload : req.payload; - - const now = new Date().toISOString(); - - return savedObjectsClient.get(CANVAS_TYPE, id).then(workpadObject => { - // TODO: Using create with force over-write because of version conflict issues with update - return savedObjectsClient.create( - CANVAS_TYPE, - { - ...(workpadObject.attributes as SavedObjectAttributes), - ...omit(payload, 'id'), // never write the id property - '@timestamp': now, // always update the modified time - '@created': workpadObject.attributes['@created'], // ensure created is not modified - }, - { overwrite: true, id } - ); - }); - } - - function deleteWorkpad(req: WorkpadRequest) { - const savedObjectsClient = req.getSavedObjectsClient(); - const { id } = req.params; - - return savedObjectsClient.delete(CANVAS_TYPE, id); - } - - function findWorkpad(req: FindWorkpadRequest) { - const savedObjectsClient = req.getSavedObjectsClient(); - const { name, page, perPage } = req.query; - - return savedObjectsClient.find({ - type: CANVAS_TYPE, - sortField: '@timestamp', - sortOrder: 'desc', - search: name ? `${name}* | ${name}` : '*', - searchFields: ['name'], - fields: ['id', 'name', '@created', '@timestamp'], - page, - perPage, - }); - } - - // get workpad - route({ - method: 'GET', - path: `${routePrefix}/{id}`, - handler(req: WorkpadRequest) { - const savedObjectsClient = req.getSavedObjectsClient(); - const { id } = req.params; - - return savedObjectsClient - .get(CANVAS_TYPE, id) - .then(obj => { - if ( - // not sure if we need to be this defensive - obj.type === 'canvas-workpad' && - obj.attributes && - obj.attributes.pages && - obj.attributes.pages.length - ) { - obj.attributes.pages.forEach(page => { - const elements = (page.elements || []).filter( - ({ id: pageId }) => !pageId.startsWith('group') - ); - const groups = (page.groups || []).concat( - (page.elements || []).filter(({ id: pageId }) => pageId.startsWith('group')) - ); - page.elements = elements; - page.groups = groups; - }); - } - return obj; - }) - .then(obj => ({ id: obj.id, ...obj.attributes })) - .then(formatResponse) - .catch(formatResponse); - }, - }); - - // create workpad - route({ - method: 'POST', - path: routePrefix, - // @ts-ignore config option missing on route method type - config: { payload: { allow: 'application/json', maxBytes: 26214400 } }, // 25MB payload limit - handler(request: WorkpadRequest) { - return createWorkpad(request) - .then(() => ({ ok: true })) - .catch(formatResponse); - }, - }); - - // update workpad - route({ - method: 'PUT', - path: `${routePrefix}/{id}`, - // @ts-ignore config option missing on route method type - config: { payload: { allow: 'application/json', maxBytes: 26214400 } }, // 25MB payload limit - handler(request: WorkpadRequest) { - return updateWorkpad(request) - .then(() => ({ ok: true })) - .catch(formatResponse); - }, - }); - - // update workpad assets - route({ - method: 'PUT', - path: `${routePrefixAssets}/{id}`, - // @ts-ignore config option missing on route method type - config: { payload: { allow: 'application/json', maxBytes: 26214400 } }, // 25MB payload limit - handler(request: AssetsRequest) { - const payload = { assets: request.payload }; - return updateWorkpad(request, payload) - .then(() => ({ ok: true })) - .catch(formatResponse); - }, - }); - - // update workpad structures - route({ - method: 'PUT', - path: `${routePrefixStructures}/{id}`, - // @ts-ignore config option missing on route method type - config: { payload: { allow: 'application/json', maxBytes: 26214400 } }, // 25MB payload limit - handler(request: WorkpadRequest) { - return updateWorkpad(request) - .then(() => ({ ok: true })) - .catch(formatResponse); - }, - }); - - // delete workpad - route({ - method: 'DELETE', - path: `${routePrefix}/{id}`, - handler(request: WorkpadRequest) { - return deleteWorkpad(request) - .then(() => ({ ok: true })) - .catch(formatResponse); - }, - }); - - // find workpads - route({ - method: 'GET', - path: `${routePrefix}/find`, - handler(request: FindWorkpadRequest) { - return findWorkpad(request) - .then(formatResponse) - .then(resp => { - return { - total: resp.total, - workpads: resp.saved_objects.map(hit => ({ id: hit.id, ...hit.attributes })), - }; - }) - .catch(() => { - return { - total: 0, - workpads: [], - }; - }); - }, - }); -} diff --git a/x-pack/legacy/plugins/canvas/server/shim.ts b/x-pack/legacy/plugins/canvas/server/shim.ts index c043f268af8ead..7641e51f14e564 100644 --- a/x-pack/legacy/plugins/canvas/server/shim.ts +++ b/x-pack/legacy/plugins/canvas/server/shim.ts @@ -8,6 +8,7 @@ import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; import { Legacy } from 'kibana'; import { CoreSetup as ExistingCoreSetup } from 'src/core/server'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { PluginSetupContract } from '../../../../plugins/features/server'; export interface CoreSetup { @@ -32,7 +33,7 @@ export interface PluginsSetup { addSavedObjectsToSampleDataset: any; addAppLinksToSampleDataset: any; }; - usage: Legacy.Server['usage']; + usageCollection: UsageCollectionSetup; } export async function createSetupShim( @@ -68,7 +69,7 @@ export async function createSetupShim( // @ts-ignore: Missing from Legacy Server Type addAppLinksToSampleDataset: server.addAppLinksToSampleDataset, }, - usage: server.usage, + usageCollection: server.newPlatform.setup.plugins.usageCollection, }, }; } diff --git a/x-pack/legacy/plugins/canvas/server/usage/collector.ts b/x-pack/legacy/plugins/canvas/server/usage/collector.ts index 7e6ef31d93ba52..ae009f9265722f 100644 --- a/x-pack/legacy/plugins/canvas/server/usage/collector.ts +++ b/x-pack/legacy/plugins/canvas/server/usage/collector.ts @@ -5,7 +5,8 @@ */ import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; -import { CoreSetup, PluginsSetup } from '../shim'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { CoreSetup } from '../shim'; // @ts-ignore missing local declaration import { CANVAS_USAGE_TYPE } from '../../common/lib/constants'; import { workpadCollector } from './workpad_collector'; @@ -22,9 +23,12 @@ const collectors: TelemetryCollector[] = [workpadCollector, customElementCollect A usage collector function returns an object derived from current data in the ES Cluster. */ -export function registerCanvasUsageCollector(setup: CoreSetup, plugins: PluginsSetup) { - const kibanaIndex = setup.getServerConfig().get('kibana.index'); - const canvasCollector = plugins.usage.collectorSet.makeUsageCollector({ +export function registerCanvasUsageCollector( + usageCollection: UsageCollectionSetup, + core: CoreSetup +) { + const kibanaIndex = core.getServerConfig().get('kibana.index'); + const canvasCollector = usageCollection.makeUsageCollector({ type: CANVAS_USAGE_TYPE, isReady: () => true, fetch: async (callCluster: CallCluster) => { @@ -42,5 +46,5 @@ export function registerCanvasUsageCollector(setup: CoreSetup, plugins: PluginsS }, }); - plugins.usage.collectorSet.register(canvasCollector); + usageCollection.registerCollector(canvasCollector); } diff --git a/x-pack/legacy/plugins/cloud/get_cloud_usage_collector.test.ts b/x-pack/legacy/plugins/cloud/cloud_usage_collector.test.ts similarity index 56% rename from x-pack/legacy/plugins/cloud/get_cloud_usage_collector.test.ts rename to x-pack/legacy/plugins/cloud/cloud_usage_collector.test.ts index ee80875890480e..660cd256cebcdf 100644 --- a/x-pack/legacy/plugins/cloud/get_cloud_usage_collector.test.ts +++ b/x-pack/legacy/plugins/cloud/cloud_usage_collector.test.ts @@ -5,37 +5,39 @@ */ import sinon from 'sinon'; -import { - createCollectorFetch, - getCloudUsageCollector, - KibanaHapiServer, -} from './get_cloud_usage_collector'; +import { Server } from 'hapi'; +import { createCollectorFetch, createCloudUsageCollector } from './cloud_usage_collector'; const CLOUD_ID_STAGING = 'staging:dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw=='; const CLOUD_ID = 'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw=='; -const getMockServer = (cloudId?: string) => ({ - usage: { collectorSet: { makeUsageCollector: sinon.stub() } }, - config() { - return { - get(path: string) { - switch (path) { - case 'xpack.cloud': - return { id: cloudId }; - default: - throw Error(`server.config().get(${path}) should not be called by this collector.`); - } - }, - }; - }, +const mockUsageCollection = () => ({ + makeUsageCollector: sinon.stub(), }); +const getMockServer = (cloudId?: string) => + ({ + config() { + return { + get(path: string) { + switch (path) { + case 'xpack.cloud': + return { id: cloudId }; + default: + throw Error(`server.config().get(${path}) should not be called by this collector.`); + } + }, + }; + }, + } as Server); + describe('Cloud usage collector', () => { describe('collector', () => { it('returns `isCloudEnabled: false` if `xpack.cloud.id` is not defined', async () => { - const collector = await createCollectorFetch(getMockServer())(); + const mockServer = getMockServer(); + const collector = await createCollectorFetch(mockServer)(); expect(collector.isCloudEnabled).toBe(false); }); @@ -48,11 +50,11 @@ describe('Cloud usage collector', () => { }); }); -describe('getCloudUsageCollector', () => { - it('returns calls `collectorSet.makeUsageCollector`', () => { +describe('createCloudUsageCollector', () => { + it('returns calls `makeUsageCollector`', () => { const mockServer = getMockServer(); - getCloudUsageCollector((mockServer as any) as KibanaHapiServer); - const { makeUsageCollector } = mockServer.usage.collectorSet; - expect(makeUsageCollector.calledOnce).toBe(true); + const usageCollection = mockUsageCollection(); + createCloudUsageCollector(usageCollection as any, mockServer); + expect(usageCollection.makeUsageCollector.calledOnce).toBe(true); }); }); diff --git a/x-pack/legacy/plugins/cloud/get_cloud_usage_collector.ts b/x-pack/legacy/plugins/cloud/cloud_usage_collector.ts similarity index 57% rename from x-pack/legacy/plugins/cloud/get_cloud_usage_collector.ts rename to x-pack/legacy/plugins/cloud/cloud_usage_collector.ts index 5ce7be59a1c9cc..7fdf32144972c1 100644 --- a/x-pack/legacy/plugins/cloud/get_cloud_usage_collector.ts +++ b/x-pack/legacy/plugins/cloud/cloud_usage_collector.ts @@ -5,21 +5,14 @@ */ import { Server } from 'hapi'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { KIBANA_CLOUD_STATS_TYPE } from './constants'; export interface UsageStats { isCloudEnabled: boolean; } -export interface KibanaHapiServer extends Server { - usage: { - collectorSet: { - makeUsageCollector: any; - }; - }; -} - -export function createCollectorFetch(server: any) { +export function createCollectorFetch(server: Server) { return async function fetchUsageStats(): Promise { const { id } = server.config().get(`xpack.cloud`); @@ -29,15 +22,15 @@ export function createCollectorFetch(server: any) { }; } -/* - * @param {Object} server - * @return {Object} kibana usage stats type collection object - */ -export function getCloudUsageCollector(server: KibanaHapiServer) { - const { collectorSet } = server.usage; - return collectorSet.makeUsageCollector({ +export function createCloudUsageCollector(usageCollection: UsageCollectionSetup, server: Server) { + return usageCollection.makeUsageCollector({ type: KIBANA_CLOUD_STATS_TYPE, isReady: () => true, fetch: createCollectorFetch(server), }); } + +export function registerCloudUsageCollector(usageCollection: UsageCollectionSetup, server: Server) { + const collector = createCloudUsageCollector(usageCollection, server); + usageCollection.registerCollector(collector); +} diff --git a/x-pack/legacy/plugins/cloud/index.js b/x-pack/legacy/plugins/cloud/index.js index 0cca122b52316c..c2fd35eea5292e 100644 --- a/x-pack/legacy/plugins/cloud/index.js +++ b/x-pack/legacy/plugins/cloud/index.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getCloudUsageCollector } from './get_cloud_usage_collector'; +import { registerCloudUsageCollector } from './cloud_usage_collector'; export const cloud = kibana => { return new kibana.Plugin({ @@ -40,7 +40,8 @@ export const cloud = kibana => { server.expose('config', { isCloudEnabled: !!config.id }); - server.usage.collectorSet.register(getCloudUsageCollector(server)); + const { usageCollection } = server.newPlatform.setup.plugins; + registerCloudUsageCollector(usageCollection, server); } }); }; diff --git a/x-pack/legacy/plugins/file_upload/index.js b/x-pack/legacy/plugins/file_upload/index.js index 37d4ad80fa2cae..1eefc0afa8f9c7 100644 --- a/x-pack/legacy/plugins/file_upload/index.js +++ b/x-pack/legacy/plugins/file_upload/index.js @@ -22,7 +22,10 @@ export const fileUpload = kibana => { init(server) { const coreSetup = server.newPlatform.setup.core; - const pluginsSetup = {}; + const { usageCollection } = server.newPlatform.setup.plugins; + const pluginsSetup = { + usageCollection, + }; // legacy dependencies const __LEGACY = { @@ -33,11 +36,6 @@ export const fileUpload = kibana => { savedObjects: { getSavedObjectsRepository: server.savedObjects.getSavedObjectsRepository }, - usage: { - collectorSet: { - makeUsageCollector: server.usage.collectorSet.makeUsageCollector - } - } }; new FileUploadPlugin().setup(coreSetup, pluginsSetup, __LEGACY); diff --git a/x-pack/legacy/plugins/file_upload/server/plugin.js b/x-pack/legacy/plugins/file_upload/server/plugin.js index 0baef6f8ffa407..d9819bf26faea8 100644 --- a/x-pack/legacy/plugins/file_upload/server/plugin.js +++ b/x-pack/legacy/plugins/file_upload/server/plugin.js @@ -5,16 +5,13 @@ */ import { getImportRouteHandler } from './routes/file_upload'; -import { getTelemetry, initTelemetry } from './telemetry/telemetry'; import { MAX_BYTES } from '../common/constants/file_import'; - -const TELEMETRY_TYPE = 'fileUploadTelemetry'; +import { registerFileUploadUsageCollector } from './telemetry'; export class FileUploadPlugin { setup(core, plugins, __LEGACY) { const elasticsearchPlugin = __LEGACY.plugins.elasticsearch; const getSavedObjectsRepository = __LEGACY.savedObjects.getSavedObjectsRepository; - const makeUsageCollector = __LEGACY.usage.collectorSet.makeUsageCollector; // Set up route __LEGACY.route({ @@ -26,11 +23,9 @@ export class FileUploadPlugin { } }); - // Make usage collector - makeUsageCollector({ - type: TELEMETRY_TYPE, - isReady: () => true, - fetch: async () => (await getTelemetry(elasticsearchPlugin, getSavedObjectsRepository)) || initTelemetry() + registerFileUploadUsageCollector(plugins.usageCollection, { + elasticsearchPlugin, + getSavedObjectsRepository, }); } } diff --git a/x-pack/legacy/plugins/file_upload/server/telemetry/file_upload_usage_collector.ts b/x-pack/legacy/plugins/file_upload/server/telemetry/file_upload_usage_collector.ts new file mode 100644 index 00000000000000..a2b359ae11638a --- /dev/null +++ b/x-pack/legacy/plugins/file_upload/server/telemetry/file_upload_usage_collector.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { getTelemetry, initTelemetry } from './telemetry'; + +const TELEMETRY_TYPE = 'fileUploadTelemetry'; + +export function registerFileUploadUsageCollector( + usageCollection: UsageCollectionSetup, + deps: { + elasticsearchPlugin: any; + getSavedObjectsRepository: any; + } +): void { + const { elasticsearchPlugin, getSavedObjectsRepository } = deps; + const fileUploadUsageCollector = usageCollection.makeUsageCollector({ + type: TELEMETRY_TYPE, + isReady: () => true, + fetch: async () => + (await getTelemetry(elasticsearchPlugin, getSavedObjectsRepository)) || initTelemetry(), + }); + + usageCollection.registerCollector(fileUploadUsageCollector); +} diff --git a/x-pack/legacy/plugins/file_upload/server/telemetry/index.ts b/x-pack/legacy/plugins/file_upload/server/telemetry/index.ts index 46da040dc34f03..7969dd04ce31f7 100644 --- a/x-pack/legacy/plugins/file_upload/server/telemetry/index.ts +++ b/x-pack/legacy/plugins/file_upload/server/telemetry/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export * from './telemetry'; +export { registerFileUploadUsageCollector } from './file_upload_usage_collector'; diff --git a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx index fd2004558be77a..a91e91258e240b 100644 --- a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx @@ -9,7 +9,7 @@ import { SearchBar, OuterSearchBarProps } from './search_bar'; import React, { ReactElement } from 'react'; import { CoreStart } from 'src/core/public'; import { act } from 'react-dom/test-utils'; -import { QueryBarInput, IndexPattern } from 'src/legacy/core_plugins/data/public'; +import { QueryStringInput, IndexPattern } from 'src/legacy/core_plugins/data/public'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { I18nProvider } from '@kbn/i18n/react'; @@ -25,7 +25,7 @@ import { Provider } from 'react-redux'; jest.mock('../services/source_modal', () => ({ openSourceModal: jest.fn() })); jest.mock('../../../../../../src/legacy/core_plugins/data/public', () => ({ - QueryBarInput: () => null, + QueryStringInput: () => null, })); const waitForIndexPatternFetch = () => new Promise(r => setTimeout(r)); @@ -106,7 +106,7 @@ describe('search_bar', () => { await waitForIndexPatternFetch(); act(() => { - instance.find(QueryBarInput).prop('onChange')!({ language: 'lucene', query: 'testQuery' }); + instance.find(QueryStringInput).prop('onChange')!({ language: 'lucene', query: 'testQuery' }); }); act(() => { @@ -122,7 +122,7 @@ describe('search_bar', () => { await waitForIndexPatternFetch(); act(() => { - instance.find(QueryBarInput).prop('onChange')!({ language: 'kuery', query: 'test: abc' }); + instance.find(QueryStringInput).prop('onChange')!({ language: 'kuery', query: 'test: abc' }); }); act(() => { @@ -140,7 +140,9 @@ describe('search_bar', () => { // pick the button component out of the tree because // it's part of a popover and thus not covered by enzyme - (instance.find(QueryBarInput).prop('prepend') as ReactElement).props.children.props.onClick(); + (instance + .find(QueryStringInput) + .prop('prepend') as ReactElement).props.children.props.onClick(); expect(openSourceModal).toHaveBeenCalled(); }); diff --git a/x-pack/legacy/plugins/graph/public/components/search_bar.tsx b/x-pack/legacy/plugins/graph/public/components/search_bar.tsx index 82e50c702997f5..79ffad26cf9817 100644 --- a/x-pack/legacy/plugins/graph/public/components/search_bar.tsx +++ b/x-pack/legacy/plugins/graph/public/components/search_bar.tsx @@ -9,12 +9,12 @@ import React, { useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { connect } from 'react-redux'; -import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; -import { IDataPluginServices, Query } from 'src/plugins/data/public'; import { IndexPatternSavedObject, IndexPatternProvider } from '../types'; -import { QueryBarInput, IndexPattern } from '../../../../../../src/legacy/core_plugins/data/public'; +import { + QueryStringInput, + IndexPattern, +} from '../../../../../../src/legacy/core_plugins/data/public'; import { openSourceModal } from '../services/source_modal'; - import { GraphState, datasourceSelector, @@ -23,6 +23,7 @@ import { } from '../state_management'; import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { IDataPluginServices, Query, esKuery } from '../../../../../../src/plugins/data/public'; export interface OuterSearchBarProps { isLoading: boolean; @@ -44,7 +45,10 @@ export interface SearchBarProps extends OuterSearchBarProps { function queryToString(query: Query, indexPattern: IndexPattern) { if (query.language === 'kuery' && typeof query.query === 'string') { - const dsl = toElasticsearchQuery(fromKueryExpression(query.query as string), indexPattern); + const dsl = esKuery.toElasticsearchQuery( + esKuery.fromKueryExpression(query.query as string), + indexPattern + ); // JSON representation of query will be handled by existing logic. // TODO clean this up and handle it in the data fetch layer once // it moved to typescript. @@ -100,7 +104,7 @@ export function SearchBarComponent(props: SearchBarProps) { > - state.filterQuery ? state.filterQuery.query : null; @@ -23,7 +21,7 @@ export const selectIsLogFilterQueryDraftValid = createSelector( filterQueryDraft => { if (filterQueryDraft && filterQueryDraft.kind === 'kuery') { try { - fromKueryExpression(filterQueryDraft.expression); + esKuery.fromKueryExpression(filterQueryDraft.expression); } catch (err) { return false; } diff --git a/x-pack/legacy/plugins/infra/public/store/local/waffle_filter/selectors.ts b/x-pack/legacy/plugins/infra/public/store/local/waffle_filter/selectors.ts index 7d518b5e20f2dd..0acce82950f779 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/waffle_filter/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/waffle_filter/selectors.ts @@ -6,8 +6,7 @@ import { createSelector } from 'reselect'; -import { fromKueryExpression } from '@kbn/es-query'; - +import { esKuery } from '../../../../../../../../src/plugins/data/public'; import { WaffleFilterState } from './reducer'; export const selectWaffleFilterQuery = (state: WaffleFilterState) => @@ -23,7 +22,7 @@ export const selectIsWaffleFilterQueryDraftValid = createSelector( filterQueryDraft => { if (filterQueryDraft && filterQueryDraft.kind === 'kuery') { try { - fromKueryExpression(filterQueryDraft.expression); + esKuery.fromKueryExpression(filterQueryDraft.expression); } catch (err) { return false; } diff --git a/x-pack/legacy/plugins/infra/public/utils/kuery.ts b/x-pack/legacy/plugins/infra/public/utils/kuery.ts index 4a767f2777512f..2e793d53b46226 100644 --- a/x-pack/legacy/plugins/infra/public/utils/kuery.ts +++ b/x-pack/legacy/plugins/infra/public/utils/kuery.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { StaticIndexPattern } from 'ui/index_patterns'; +import { esKuery } from '../../../../../../src/plugins/data/public'; export const convertKueryToElasticSearchQuery = ( kueryExpression: string, @@ -13,7 +13,9 @@ export const convertKueryToElasticSearchQuery = ( ) => { try { return kueryExpression - ? JSON.stringify(toElasticsearchQuery(fromKueryExpression(kueryExpression), indexPattern)) + ? JSON.stringify( + esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(kueryExpression), indexPattern) + ) : ''; } catch (err) { return ''; diff --git a/x-pack/legacy/plugins/infra/server/kibana.index.ts b/x-pack/legacy/plugins/infra/server/kibana.index.ts index 48ef846ec52750..91bcd6be95a75f 100644 --- a/x-pack/legacy/plugins/infra/server/kibana.index.ts +++ b/x-pack/legacy/plugins/infra/server/kibana.index.ts @@ -13,11 +13,8 @@ import { UsageCollector } from './usage/usage_collector'; import { inventoryViewSavedObjectType } from '../common/saved_objects/inventory_view'; import { metricsExplorerViewSavedObjectType } from '../common/saved_objects/metrics_explorer_view'; -export interface KbnServer extends Server { - usage: any; -} - -export const initServerWithKibana = (kbnServer: KbnServer) => { +export const initServerWithKibana = (kbnServer: Server) => { + const { usageCollection } = kbnServer.newPlatform.setup.plugins; const libs = compose(kbnServer); initInfraServer(libs); @@ -27,7 +24,7 @@ export const initServerWithKibana = (kbnServer: KbnServer) => { ); // Register a function with server to manage the collection of usage stats - kbnServer.usage.collectorSet.register(UsageCollector.getUsageCollector(kbnServer)); + UsageCollector.registerUsageCollector(usageCollection); const xpackMainPlugin = kbnServer.plugins.xpack_main; xpackMainPlugin.registerFeature({ diff --git a/x-pack/legacy/plugins/infra/server/usage/usage_collector.ts b/x-pack/legacy/plugins/infra/server/usage/usage_collector.ts index 018c903009bbec..601beddc0a2db9 100644 --- a/x-pack/legacy/plugins/infra/server/usage/usage_collector.ts +++ b/x-pack/legacy/plugins/infra/server/usage/usage_collector.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { InfraNodeType } from '../graphql/types'; -import { KbnServer } from '../kibana.index'; - const KIBANA_REPORTING_TYPE = 'infraops'; interface InfraopsSum { @@ -17,10 +16,13 @@ interface InfraopsSum { } export class UsageCollector { - public static getUsageCollector(server: KbnServer) { - const { collectorSet } = server.usage; + public static registerUsageCollector(usageCollection: UsageCollectionSetup): void { + const collector = UsageCollector.getUsageCollector(usageCollection); + usageCollection.registerCollector(collector); + } - return collectorSet.makeUsageCollector({ + public static getUsageCollector(usageCollection: UsageCollectionSetup) { + return usageCollection.makeUsageCollector({ type: KIBANA_REPORTING_TYPE, isReady: () => true, fetch: async () => { diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js index 8a82194470ace4..7b0e42283d5f59 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/index.js @@ -5,11 +5,11 @@ */ import { flatten, mapValues, uniq } from 'lodash'; -import { fromKueryExpression } from '@kbn/es-query'; import { getSuggestionsProvider as field } from './field'; import { getSuggestionsProvider as value } from './value'; import { getSuggestionsProvider as operator } from './operator'; import { getSuggestionsProvider as conjunction } from './conjunction'; +import { esKuery } from '../../../../../../src/plugins/data/public'; const cursorSymbol = '@kuery-cursor@'; @@ -27,7 +27,7 @@ export const kueryProvider = ({ config, indexPatterns, boolFilter }) => { let cursorNode; try { - cursorNode = fromKueryExpression(cursoredQuery, { cursorSymbol, parseCursor: true }); + cursorNode = esKuery.fromKueryExpression(cursoredQuery, { cursorSymbol, parseCursor: true }); } catch (e) { cursorNode = {}; } diff --git a/x-pack/legacy/plugins/lens/index.ts b/x-pack/legacy/plugins/lens/index.ts index d4cea28d140859..a79b9907f64377 100644 --- a/x-pack/legacy/plugins/lens/index.ts +++ b/x-pack/legacy/plugins/lens/index.ts @@ -58,10 +58,12 @@ export const lens: LegacyPluginInitializer = kibana => { // Set up with the new platform plugin lifecycle API. const plugin = lensServerPlugin(); + const { usageCollection } = server.newPlatform.setup.plugins; + plugin.setup(kbnServer.newPlatform.setup.core, { + usageCollection, // Legacy APIs savedObjects: server.savedObjects, - usage: server.usage, config: server.config(), server, }); diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx index 21a69bfc3a0b3c..3dd43733471297 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx @@ -50,6 +50,7 @@ export function ExpressionWrapper({ padding="m" expression={expression} searchContext={{ ...context, type: 'kibana_context' }} + renderError={error =>
{error}
} />
)} diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx index a50d3371f47cf2..d0b77a425d14a8 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx @@ -18,15 +18,18 @@ import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { createMockedIndexPattern } from '../../mocks'; import { IndexPatternPrivateState } from '../../types'; -jest.mock('ui/new_platform'); -jest.mock('ui/chrome', () => ({ - getUiSettingsClient: () => ({ - get(path: string) { - if (path === 'histogram:maxBars') { - return 10; - } +jest.mock('ui/new_platform', () => ({ + npStart: { + core: { + uiSettings: { + get: (path: string) => { + if (path === 'histogram:maxBars') { + return 10; + } + }, + }, }, - }), + }, })); const defaultOptions = { diff --git a/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts b/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts index 185df12054a3c6..0c4e6d9f7cb10f 100644 --- a/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts +++ b/x-pack/legacy/plugins/lens/public/register_vis_type_alias.ts @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; -import { visualizations } from '../../../../../src/legacy/core_plugins/visualizations/public'; +import { setup as visualizations } from '../../../../../src/legacy/core_plugins/visualizations/public/np_ready/public/legacy'; import { getBasePath, getEditPath } from '../common'; visualizations.types.registerAlias({ diff --git a/x-pack/legacy/plugins/lens/server/plugin.tsx b/x-pack/legacy/plugins/lens/server/plugin.tsx index a4c8e9b268df5f..0223b90c370469 100644 --- a/x-pack/legacy/plugins/lens/server/plugin.tsx +++ b/x-pack/legacy/plugins/lens/server/plugin.tsx @@ -6,27 +6,22 @@ import { Server, KibanaConfig } from 'src/legacy/server/kbn_server'; import { Plugin, CoreSetup, SavedObjectsLegacyService } from 'src/core/server'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { setupRoutes } from './routes'; import { registerLensUsageCollector, initializeLensTelemetry } from './usage'; +export interface PluginSetupContract { + savedObjects: SavedObjectsLegacyService; + usageCollection: UsageCollectionSetup; + config: KibanaConfig; + server: Server; +} + export class LensServer implements Plugin<{}, {}, {}, {}> { - setup( - core: CoreSetup, - plugins: { - savedObjects: SavedObjectsLegacyService; - usage: { - collectorSet: { - makeUsageCollector: (options: unknown) => unknown; - register: (options: unknown) => unknown; - }; - }; - config: KibanaConfig; - server: Server; - } - ) { + setup(core: CoreSetup, plugins: PluginSetupContract) { setupRoutes(core, plugins); - registerLensUsageCollector(core, plugins); - initializeLensTelemetry(core, plugins); + registerLensUsageCollector(plugins.usageCollection, plugins.server); + initializeLensTelemetry(core, plugins.server); return {}; } diff --git a/x-pack/legacy/plugins/lens/server/usage/collectors.ts b/x-pack/legacy/plugins/lens/server/usage/collectors.ts index 94a7c8e0d85c1c..274b72c33e59ac 100644 --- a/x-pack/legacy/plugins/lens/server/usage/collectors.ts +++ b/x-pack/legacy/plugins/lens/server/usage/collectors.ts @@ -6,29 +6,17 @@ import moment from 'moment'; import { get } from 'lodash'; -import { Server, KibanaConfig } from 'src/legacy/server/kbn_server'; -import { CoreSetup, SavedObjectsLegacyService } from 'src/core/server'; +import { Server } from 'src/legacy/server/kbn_server'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; + import { LensUsage, LensTelemetryState } from './types'; -export function registerLensUsageCollector( - core: CoreSetup, - plugins: { - savedObjects: SavedObjectsLegacyService; - usage: { - collectorSet: { - makeUsageCollector: (options: unknown) => unknown; - register: (options: unknown) => unknown; - }; - }; - config: KibanaConfig; - server: Server; - } -) { +export function registerLensUsageCollector(usageCollection: UsageCollectionSetup, server: Server) { let isCollectorReady = false; async function determineIfTaskManagerIsReady() { let isReady = false; try { - isReady = await isTaskManagerReady(plugins.server); + isReady = await isTaskManagerReady(server); } catch (err) {} // eslint-disable-line if (isReady) { @@ -39,11 +27,11 @@ export function registerLensUsageCollector( } determineIfTaskManagerIsReady(); - const lensUsageCollector = plugins.usage.collectorSet.makeUsageCollector({ + const lensUsageCollector = usageCollection.makeUsageCollector({ type: 'lens', fetch: async (): Promise => { try { - const docs = await getLatestTaskState(plugins.server); + const docs = await getLatestTaskState(server); // get the accumulated state from the recurring task const state: LensTelemetryState = get(docs, '[0].state'); @@ -75,7 +63,8 @@ export function registerLensUsageCollector( }, isReady: () => isCollectorReady, }); - plugins.usage.collectorSet.register(lensUsageCollector); + + usageCollection.registerCollector(lensUsageCollector); } function addEvents(prevEvents: Record, newEvents: Record) { diff --git a/x-pack/legacy/plugins/lens/server/usage/task.ts b/x-pack/legacy/plugins/lens/server/usage/task.ts index 03e085cc9e669f..feb73538f44f05 100644 --- a/x-pack/legacy/plugins/lens/server/usage/task.ts +++ b/x-pack/legacy/plugins/lens/server/usage/task.ts @@ -39,12 +39,12 @@ type ClusterDeleteType = ( options?: CallClusterOptions ) => Promise; -export function initializeLensTelemetry(core: CoreSetup, { server }: { server: Server }) { - registerLensTelemetryTask(core, { server }); +export function initializeLensTelemetry(core: CoreSetup, server: Server) { + registerLensTelemetryTask(core, server); scheduleTasks(server); } -function registerLensTelemetryTask(core: CoreSetup, { server }: { server: Server }) { +function registerLensTelemetryTask(core: CoreSetup, server: Server) { const taskManager = server.plugins.task_manager; if (!taskManager) { diff --git a/x-pack/legacy/plugins/maps/index.js b/x-pack/legacy/plugins/maps/index.js index 739e98beec10f1..c59fbe42a17547 100644 --- a/x-pack/legacy/plugins/maps/index.js +++ b/x-pack/legacy/plugins/maps/index.js @@ -101,12 +101,12 @@ export function maps(kibana) { init(server) { const mapsEnabled = server.config().get('xpack.maps.enabled'); - + const { usageCollection } = server.newPlatform.setup.plugins; if (!mapsEnabled) { server.log(['info', 'maps'], 'Maps app disabled by configuration'); return; } - initTelemetryCollection(server); + initTelemetryCollection(usageCollection, server); const xpackMainPlugin = server.plugins.xpack_main; let routesInitialized = false; diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js index c0ac5a781b796b..c4d755b5908f03 100644 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js +++ b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js @@ -7,10 +7,10 @@ import _ from 'lodash'; import { TASK_ID, scheduleTask, registerMapsTelemetryTask } from './telemetry_task'; -export function initTelemetryCollection(server) { +export function initTelemetryCollection(usageCollection, server) { registerMapsTelemetryTask(server); scheduleTask(server); - registerMapsUsageCollector(server); + registerMapsUsageCollector(usageCollection, server); } async function isTaskManagerReady(server) { @@ -81,9 +81,8 @@ export function buildCollectorObj(server) { }; } -export function registerMapsUsageCollector(server) { +export function registerMapsUsageCollector(usageCollection, server) { const collectorObj = buildCollectorObj(server); - const mapsUsageCollector = server.usage.collectorSet - .makeUsageCollector(collectorObj); - server.usage.collectorSet.register(mapsUsageCollector); + const mapsUsageCollector = usageCollection.makeUsageCollector(collectorObj); + usageCollection.registerCollector(mapsUsageCollector); } diff --git a/x-pack/legacy/plugins/maps/server/test_utils/index.js b/x-pack/legacy/plugins/maps/server/test_utils/index.js index 13b7c56d6fc8b1..e9f97101759f0b 100644 --- a/x-pack/legacy/plugins/maps/server/test_utils/index.js +++ b/x-pack/legacy/plugins/maps/server/test_utils/index.js @@ -40,12 +40,6 @@ export const getMockKbnServer = ( fetch: mockTaskFetch, }, }, - usage: { - collectorSet: { - makeUsageCollector: () => '', - register: () => undefined, - }, - }, config: () => ({ get: () => '' }), log: () => undefined }); diff --git a/x-pack/legacy/plugins/ml/index.ts b/x-pack/legacy/plugins/ml/index.ts index 3cafa232f0744d..90e1e748492cb6 100755 --- a/x-pack/legacy/plugins/ml/index.ts +++ b/x-pack/legacy/plugins/ml/index.ts @@ -79,7 +79,6 @@ export const ml = (kibana: any) => { injectUiAppVars: server.injectUiAppVars, http: mlHttpService, savedObjects: server.savedObjects, - usage: server.usage, }; const plugins = { @@ -87,6 +86,7 @@ export const ml = (kibana: any) => { security: server.plugins.security, xpackMain: server.plugins.xpack_main, spaces: server.plugins.spaces, + usageCollection: kbnServer.newPlatform.setup.plugins.usageCollection, ml: this, }; diff --git a/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/utils.js b/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/utils.js index 16e4a563c33ae8..18b3382175fdd7 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/utils.js +++ b/x-pack/legacy/plugins/ml/public/application/components/kql_filter_bar/utils.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { npStart } from 'ui/new_platform'; -import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; +import { esKuery } from '../../../../../../../../src/plugins/data/public'; const getAutocompleteProvider = language => npStart.plugins.data.autocomplete.getProvider(language); @@ -35,8 +35,8 @@ export async function getSuggestions( } function convertKueryToEsQuery(kuery, indexPattern) { - const ast = fromKueryExpression(kuery); - return toElasticsearchQuery(ast, indexPattern); + const ast = esKuery.fromKueryExpression(kuery); + return esKuery.toElasticsearchQuery(ast, indexPattern); } // Recommended by MDN for escaping user input to be treated as a literal string within a regular expression // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions @@ -53,7 +53,7 @@ export function escapeDoubleQuotes(string) { } export function getKqlQueryValues(inputValue, indexPattern) { - const ast = fromKueryExpression(inputValue); + const ast = esKuery.fromKueryExpression(inputValue); const isAndOperator = (ast.function === 'and'); const query = convertKueryToEsQuery(inputValue, indexPattern); const filteredFields = []; diff --git a/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/make_ml_usage_collector.ts b/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/make_ml_usage_collector.ts index 6bc98ba68f60b5..7a9766f36a6ed3 100644 --- a/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/make_ml_usage_collector.ts +++ b/x-pack/legacy/plugins/ml/server/lib/ml_telemetry/make_ml_usage_collector.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { createMlTelemetry, getSavedObjectsClient, @@ -14,12 +15,11 @@ import { import { UsageInitialization } from '../../new_platform/plugin'; -export function makeMlUsageCollector({ - elasticsearchPlugin, - usage, - savedObjects, -}: UsageInitialization): void { - const mlUsageCollector = usage.collectorSet.makeUsageCollector({ +export function makeMlUsageCollector( + usageCollection: UsageCollectionSetup, + { elasticsearchPlugin, savedObjects }: UsageInitialization +): void { + const mlUsageCollector = usageCollection.makeUsageCollector({ type: 'ml', isReady: () => true, fetch: async (): Promise => { @@ -35,5 +35,6 @@ export function makeMlUsageCollector({ } }, }); - usage.collectorSet.register(mlUsageCollector); + + usageCollection.registerCollector(mlUsageCollector); } diff --git a/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts b/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts index b2b697a8517032..b789121beebfc0 100644 --- a/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts +++ b/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts @@ -10,6 +10,7 @@ import { ServerRoute } from 'hapi'; import { KibanaConfig, SavedObjectsLegacyService } from 'src/legacy/server/kbn_server'; import { Logger, PluginInitializerContext, CoreSetup } from 'src/core/server'; import { ElasticsearchPlugin } from 'src/legacy/core_plugins/elasticsearch'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { XPackMainPlugin } from '../../../xpack_main/xpack_main'; import { addLinksToSampleDatasets } from '../lib/sample_data_sets'; import { checkLicense } from '../lib/check_license'; @@ -68,12 +69,6 @@ export interface MlCoreSetup { injectUiAppVars: (id: string, callback: () => {}) => any; http: MlHttpServiceSetup; savedObjects: SavedObjectsLegacyService; - usage: { - collectorSet: { - makeUsageCollector: any; - register: (collector: any) => void; - }; - }; } export interface MlInitializerContext extends PluginInitializerContext { legacyConfig: KibanaConfig; @@ -84,6 +79,7 @@ export interface PluginsSetup { xpackMain: MlXpackMainPlugin; security: any; spaces: any; + usageCollection: UsageCollectionSetup; // TODO: this is temporary for `mirrorPluginStatus` ml: any; } @@ -98,12 +94,6 @@ export interface RouteInitialization { } export interface UsageInitialization { elasticsearchPlugin: ElasticsearchPlugin; - usage: { - collectorSet: { - makeUsageCollector: any; - register: (collector: any) => void; - }; - }; savedObjects: SavedObjectsLegacyService; } @@ -201,10 +191,8 @@ export class Plugin { savedObjects: core.savedObjects, spacesPlugin: plugins.spaces, }; - const usageInitializationDeps: UsageInitialization = { elasticsearchPlugin: plugins.elasticsearch, - usage: core.usage, savedObjects: core.savedObjects, }; @@ -231,7 +219,7 @@ export class Plugin { fileDataVisualizerRoutes(extendedRouteInitializationDeps); initMlServerLog(logInitializationDeps); - makeMlUsageCollector(usageInitializationDeps); + makeMlUsageCollector(plugins.usageCollection, usageInitializationDeps); } public stop() {} diff --git a/x-pack/legacy/plugins/monitoring/index.js b/x-pack/legacy/plugins/monitoring/index.js index 3cb72066b2ff22..f9c62b3ee69252 100644 --- a/x-pack/legacy/plugins/monitoring/index.js +++ b/x-pack/legacy/plugins/monitoring/index.js @@ -62,9 +62,6 @@ export const monitoring = (kibana) => new kibana.Plugin({ throw `Unknown key '${key}'`; } }), - usage: { - collectorSet: server.usage.collectorSet - }, injectUiAppVars: server.injectUiAppVars, log: (...args) => server.log(...args), getOSInfo: server.getOSInfo, @@ -76,11 +73,13 @@ export const monitoring = (kibana) => new kibana.Plugin({ _hapi: server, _kbnServer: this.kbnServer }; - + const { usageCollection } = server.newPlatform.setup.plugins; const plugins = { xpack_main: server.plugins.xpack_main, elasticsearch: server.plugins.elasticsearch, alerting: server.plugins.alerting, + infra: server.plugins.infra, + usageCollection, }; const plugin = new Plugin(); const { initializeAlerting } = plugin.setup(serverFacade, plugins); diff --git a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js index da23d4b77a323e..b0367bc0784739 100644 --- a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js +++ b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js @@ -68,21 +68,21 @@ export class BulkUploader { /* * Start the interval timer - * @param {CollectorSet} collectorSet object to use for initial the fetch/upload and fetch/uploading on interval + * @param {usageCollection} usageCollection object to use for initial the fetch/upload and fetch/uploading on interval * @return undefined */ - start(collectorSet) { + start(usageCollection) { this._log.info('Starting monitoring stats collection'); - const filterCollectorSet = _collectorSet => { + const filterCollectorSet = _usageCollection => { const successfulUploadInLastDay = this._lastFetchUsageTime && this._lastFetchUsageTime + this._usageInterval > Date.now(); - return _collectorSet.getFilteredCollectorSet(c => { + return _usageCollection.getFilteredCollectorSet(c => { // this is internal bulk upload, so filter out API-only collectors if (c.ignoreForInternalUploader) { return false; } // Only collect usage data at the same interval as telemetry would (default to once a day) - if (successfulUploadInLastDay && _collectorSet.isUsageCollector(c)) { + if (successfulUploadInLastDay && _usageCollection.isUsageCollector(c)) { return false; } return true; @@ -92,11 +92,11 @@ export class BulkUploader { if (this._timer) { clearInterval(this._timer); } else { - this._fetchAndUpload(filterCollectorSet(collectorSet)); // initial fetch + this._fetchAndUpload(filterCollectorSet(usageCollection)); // initial fetch } this._timer = setInterval(() => { - this._fetchAndUpload(filterCollectorSet(collectorSet)); + this._fetchAndUpload(filterCollectorSet(usageCollection)); }, this._interval); } @@ -121,12 +121,12 @@ export class BulkUploader { } /* - * @param {CollectorSet} collectorSet + * @param {usageCollection} usageCollection * @return {Promise} - resolves to undefined */ - async _fetchAndUpload(collectorSet) { - const collectorsReady = await collectorSet.areAllCollectorsReady(); - const hasUsageCollectors = collectorSet.some(collectorSet.isUsageCollector); + async _fetchAndUpload(usageCollection) { + const collectorsReady = await usageCollection.areAllCollectorsReady(); + const hasUsageCollectors = usageCollection.some(usageCollection.isUsageCollector); if (!collectorsReady) { this._log.debug('Skipping bulk uploading because not all collectors are ready'); if (hasUsageCollectors) { @@ -136,8 +136,8 @@ export class BulkUploader { return; } - const data = await collectorSet.bulkFetch(this._callClusterWithInternalUser); - const payload = this.toBulkUploadFormat(compact(data), collectorSet); + const data = await usageCollection.bulkFetch(this._callClusterWithInternalUser); + const payload = this.toBulkUploadFormat(compact(data), usageCollection); if (payload) { try { @@ -202,7 +202,7 @@ export class BulkUploader { * } * ] */ - toBulkUploadFormat(rawData, collectorSet) { + toBulkUploadFormat(rawData, usageCollection) { if (rawData.length === 0) { return; } @@ -210,7 +210,7 @@ export class BulkUploader { // convert the raw data to a nested object by taking each payload through // its formatter, organizing it per-type const typesNested = rawData.reduce((accum, { type, result }) => { - const { type: uploadType, payload: uploadData } = collectorSet.getCollectorByType(type).formatForBulkUpload(result); + const { type: uploadType, payload: uploadData } = usageCollection.getCollectorByType(type).formatForBulkUpload(result); return defaultsDeep(accum, { [uploadType]: uploadData }); }, {}); // convert the nested object into a flat array, with each payload prefixed diff --git a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/get_kibana_usage_collector.js b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/get_kibana_usage_collector.js index 25efc63fafb5d5..5d2ebf8dc2abc8 100644 --- a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/get_kibana_usage_collector.js +++ b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/get_kibana_usage_collector.js @@ -19,8 +19,8 @@ const TYPES = [ /** * Fetches saved object counts by querying the .kibana index */ -export function getKibanaUsageCollector({ collectorSet, config }) { - return collectorSet.makeUsageCollector({ +export function getKibanaUsageCollector(usageCollection, config) { + return usageCollection.makeUsageCollector({ type: KIBANA_USAGE_TYPE, isReady: () => true, async fetch(callCluster) { diff --git a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/get_ops_stats_collector.js b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/get_ops_stats_collector.js index f1f47761d9f0cd..2c0250fb785922 100644 --- a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/get_ops_stats_collector.js +++ b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/get_ops_stats_collector.js @@ -49,14 +49,13 @@ class OpsMonitor { /* * Initialize a collector for Kibana Ops Stats */ -export function getOpsStatsCollector({ +export function getOpsStatsCollector(usageCollection, { elasticsearchPlugin, kbnServerConfig, log, config, getOSInfo, hapiServer, - collectorSet }) { const buffer = opsBuffer({ log, config, getOSInfo }); const interval = kbnServerConfig.get('ops.interval'); @@ -85,7 +84,7 @@ export function getOpsStatsCollector({ }, 5 * 1000); // wait 5 seconds to avoid race condition with reloading logging configuration }); - return collectorSet.makeStatsCollector({ + return usageCollection.makeStatsCollector({ type: KIBANA_STATS_TYPE_MONITORING, init: opsMonitor.start, isReady: () => { diff --git a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js index bb561ddda42abc..2a56deaad4f8aa 100644 --- a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js +++ b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/get_settings_collector.js @@ -46,8 +46,8 @@ export async function checkForEmailValue( } } -export function getSettingsCollector({ config, collectorSet }) { - return collectorSet.makeStatsCollector({ +export function getSettingsCollector(usageCollection, config) { + return usageCollection.makeStatsCollector({ type: KIBANA_SETTINGS_TYPE, isReady: () => true, async fetch(callCluster) { diff --git a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/index.js b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/index.js index 3c8eb5ebdf2d36..1099a23dea1030 100644 --- a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/index.js +++ b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/collectors/index.js @@ -4,6 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -export { getKibanaUsageCollector } from './get_kibana_usage_collector'; -export { getOpsStatsCollector } from './get_ops_stats_collector'; -export { getSettingsCollector } from './get_settings_collector'; +import { getKibanaUsageCollector } from './get_kibana_usage_collector'; +import { getOpsStatsCollector } from './get_ops_stats_collector'; +import { getSettingsCollector } from './get_settings_collector'; + +export function registerCollectors(usageCollection, collectorsConfigs) { + const { config } = collectorsConfigs; + + usageCollection.registerCollector(getOpsStatsCollector(usageCollection, collectorsConfigs)); + usageCollection.registerCollector(getKibanaUsageCollector(usageCollection, config)); + usageCollection.registerCollector(getSettingsCollector(usageCollection, config)); +} diff --git a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/index.js b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/index.js index ae691f49e2b801..c202fe9589ab37 100644 --- a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/index.js +++ b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/index.js @@ -5,3 +5,4 @@ */ export { initBulkUploader } from './init'; +export { registerCollectors } from './collectors'; diff --git a/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status.js b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status.js index bb42dad26786a5..36f085c424881c 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/__test__/get_collection_status.js @@ -13,6 +13,17 @@ const liveClusterUuid = 'a12'; const mockReq = (searchResult = {}) => { return { server: { + newPlatform: { + setup: { + plugins: { + usageCollection: { + getCollectorByType: () => ({ + isReady: () => false + }), + }, + }, + }, + }, config() { return { get: sinon.stub() diff --git a/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js index d25d8af4aaa205..540de7d1e3a7fc 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js @@ -273,13 +273,15 @@ function shouldSkipBucket(product, bucket) { return false; } -async function getLiveKibanaInstance(req) { - const { collectorSet } = req.server.usage; - const kibanaStatsCollector = collectorSet.getCollectorByType(KIBANA_STATS_TYPE); +async function getLiveKibanaInstance(usageCollection) { + if (!usageCollection) { + return null; + } + const kibanaStatsCollector = usageCollection.getCollectorByType(KIBANA_STATS_TYPE); if (!await kibanaStatsCollector.isReady()) { return null; } - return collectorSet.toApiFieldNames(await kibanaStatsCollector.fetch()); + return usageCollection.toApiFieldNames(await kibanaStatsCollector.fetch()); } async function getLiveElasticsearchClusterUuid(req) { @@ -341,9 +343,11 @@ async function getLiveElasticsearchCollectionEnabled(req) { * @param {*} skipLiveData Optional and will not make any live api calls if set to true */ export const getCollectionStatus = async (req, indexPatterns, clusterUuid, nodeUuid, skipLiveData) => { + const config = req.server.config(); const kibanaUuid = config.get('server.uuid'); const hasPermissions = await hasNecessaryPermissions(req); + if (!hasPermissions) { return { _meta: { @@ -351,6 +355,7 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid, nodeU } }; } + console.log('OKOKOKOK'); const liveClusterUuid = skipLiveData ? null : await getLiveElasticsearchClusterUuid(req); const isLiveCluster = !clusterUuid || liveClusterUuid === clusterUuid; @@ -372,7 +377,8 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid, nodeU const liveEsNodes = skipLiveData || !isLiveCluster ? [] : await getLivesNodes(req); - const liveKibanaInstance = skipLiveData || !isLiveCluster ? {} : await getLiveKibanaInstance(req); + const { usageCollection } = req.server.newPlatform.setup.plugins; + const liveKibanaInstance = skipLiveData || !isLiveCluster ? {} : await getLiveKibanaInstance(usageCollection); const indicesBuckets = get(recentDocuments, 'aggregations.indices.buckets', []); const liveClusterInternalCollectionEnabled = await getLiveElasticsearchCollectionEnabled(req); diff --git a/x-pack/legacy/plugins/monitoring/server/plugin.js b/x-pack/legacy/plugins/monitoring/server/plugin.js index 6f56cee4ff0355..02c1c8590a4b9e 100644 --- a/x-pack/legacy/plugins/monitoring/server/plugin.js +++ b/x-pack/legacy/plugins/monitoring/server/plugin.js @@ -9,36 +9,28 @@ import { LOGGING_TAG, KIBANA_MONITORING_LOGGING_TAG, KIBANA_ALERTING_ENABLED } f import { requireUIRoutes } from './routes'; import { instantiateClient } from './es_client/instantiate_client'; import { initMonitoringXpackInfo } from './init_monitoring_xpack_info'; -import { initBulkUploader } from './kibana_monitoring'; +import { initBulkUploader, registerCollectors } from './kibana_monitoring'; import { registerMonitoringCollection } from './telemetry_collection'; - -import { - getKibanaUsageCollector, - getOpsStatsCollector, - getSettingsCollector, -} from './kibana_monitoring/collectors'; import { getLicenseExpiration } from './alerts/license_expiration'; export class Plugin { setup(core, plugins) { const kbnServer = core._kbnServer; const config = core.config(); - const { collectorSet } = core.usage; + const usageCollection = plugins.usageCollection; + registerMonitoringCollection(); /* * Register collector objects for stats to show up in the APIs */ - collectorSet.register(getOpsStatsCollector({ + registerCollectors(usageCollection, { elasticsearchPlugin: plugins.elasticsearch, kbnServerConfig: kbnServer.config, log: core.log, config, getOSInfo: core.getOSInfo, hapiServer: core._hapi, - collectorSet: core.usage.collectorSet, - })); - collectorSet.register(getKibanaUsageCollector({ collectorSet, config })); - collectorSet.register(getSettingsCollector({ collectorSet, config })); - registerMonitoringCollection(); + }); + /* * Instantiate and start the internal background task that calls collector @@ -111,7 +103,7 @@ export class Plugin { const mainMonitoring = xpackMainInfo.feature('monitoring'); const monitoringBulkEnabled = mainMonitoring && mainMonitoring.isAvailable() && mainMonitoring.isEnabled(); if (monitoringBulkEnabled) { - bulkUploader.start(collectorSet); + bulkUploader.start(usageCollection); } else { bulkUploader.handleNotEnabled(); } diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_cluster_uuids.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_cluster_uuids.js index a0072e52fc7f75..c6bb3687458306 100644 --- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_cluster_uuids.js +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_cluster_uuids.js @@ -8,9 +8,8 @@ import expect from '@kbn/expect'; import sinon from 'sinon'; import { getClusterUuids, fetchClusterUuids, handleClusterUuidsResponse } from '../get_cluster_uuids'; -// FAILING: https://github.com/elastic/kibana/issues/51371 -describe.skip('get_cluster_uuids', () => { - const callWith = sinon.stub(); +describe('get_cluster_uuids', () => { + const callCluster = sinon.stub(); const size = 123; const server = { config: sinon.stub().returns({ @@ -29,23 +28,23 @@ describe.skip('get_cluster_uuids', () => { } } }; - const expectedUuids = response.aggregations.cluster_uuids.buckets.map(bucket => bucket.key); + const expectedUuids = response.aggregations.cluster_uuids.buckets + .map(bucket => bucket.key) + .map(expectedUuid => ({ clusterUuid: expectedUuid })); const start = new Date(); const end = new Date(); describe('getClusterUuids', () => { it('returns cluster UUIDs', async () => { - callWith.withArgs('search').returns(Promise.resolve(response)); - - expect(await getClusterUuids(server, callWith, start, end)).to.eql(expectedUuids); + callCluster.withArgs('search').returns(Promise.resolve(response)); + expect(await getClusterUuids({ server, callCluster, start, end })).to.eql(expectedUuids); }); }); describe('fetchClusterUuids', () => { it('searches for clusters', async () => { - callWith.returns(Promise.resolve(response)); - - expect(await fetchClusterUuids(server, callWith, start, end)).to.be(response); + callCluster.returns(Promise.resolve(response)); + expect(await fetchClusterUuids({ server, callCluster, start, end })).to.be(response); }); }); @@ -53,13 +52,11 @@ describe.skip('get_cluster_uuids', () => { // filterPath makes it easy to ignore anything unexpected because it will come back empty it('handles unexpected response', () => { const clusterUuids = handleClusterUuidsResponse({}); - expect(clusterUuids.length).to.be(0); }); it('handles valid response', () => { const clusterUuids = handleClusterUuidsResponse(response); - expect(clusterUuids).to.eql(expectedUuids); }); diff --git a/x-pack/legacy/plugins/oss_telemetry/index.d.ts b/x-pack/legacy/plugins/oss_telemetry/index.d.ts index 012f9876273694..1b592dabf20535 100644 --- a/x-pack/legacy/plugins/oss_telemetry/index.d.ts +++ b/x-pack/legacy/plugins/oss_telemetry/index.d.ts @@ -54,12 +54,6 @@ export interface HapiServer { }>; }; }; - usage: { - collectorSet: { - register: (collector: any) => void; - makeUsageCollector: (collectorOpts: any) => void; - }; - }; config: () => { get: (prop: string) => any; }; diff --git a/x-pack/legacy/plugins/oss_telemetry/index.js b/x-pack/legacy/plugins/oss_telemetry/index.js index eeee9e18f9112b..f86baef020aa29 100644 --- a/x-pack/legacy/plugins/oss_telemetry/index.js +++ b/x-pack/legacy/plugins/oss_telemetry/index.js @@ -15,7 +15,8 @@ export const ossTelemetry = (kibana) => { configPrefix: 'xpack.oss_telemetry', init(server) { - registerCollectors(server); + const { usageCollection } = server.newPlatform.setup.plugins; + registerCollectors(usageCollection, server); registerTasks(server); scheduleTasks(server); } diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts index 8b825b13178f20..0121ed4304d261 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { HapiServer } from '../../../'; import { registerVisualizationsCollector } from './visualizations/register_usage_collector'; -export function registerCollectors(server: HapiServer) { - registerVisualizationsCollector(server); +export function registerCollectors(usageCollection: UsageCollectionSetup, server: HapiServer) { + registerVisualizationsCollector(usageCollection, server); } diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts index 555c7ac27b49d0..09843a6f87ad7a 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts @@ -4,11 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { HapiServer } from '../../../../'; import { getUsageCollector } from './get_usage_collector'; -export function registerVisualizationsCollector(server: HapiServer): void { - const { usage } = server; - const collector = usage.collectorSet.makeUsageCollector(getUsageCollector(server)); - usage.collectorSet.register(collector); +export function registerVisualizationsCollector( + usageCollection: UsageCollectionSetup, + server: HapiServer +): void { + const collector = usageCollection.makeUsageCollector(getUsageCollector(server)); + usageCollection.registerCollector(collector); } diff --git a/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts b/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts index 998a1d2beeab14..1cebe78b9c7f08 100644 --- a/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts +++ b/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts @@ -54,12 +54,6 @@ export const getMockKbnServer = ( fetch: mockTaskFetch, }, }, - usage: { - collectorSet: { - makeUsageCollector: () => '', - register: () => undefined, - }, - }, config: () => mockConfig, log: () => undefined, }); diff --git a/x-pack/legacy/plugins/reporting/index.ts b/x-pack/legacy/plugins/reporting/index.ts index e2b5970d1efb78..9add3accd262f2 100644 --- a/x-pack/legacy/plugins/reporting/index.ts +++ b/x-pack/legacy/plugins/reporting/index.ts @@ -20,7 +20,7 @@ import { import { config as reportingConfig } from './config'; import { logConfiguration } from './log_configuration'; import { createBrowserDriverFactory } from './server/browsers'; -import { getReportingUsageCollector } from './server/usage'; +import { registerReportingUsageCollector } from './server/usage'; import { ReportingConfigOptions, ReportingPluginSpecOptions, ServerFacade } from './types.d'; const kbToBase64Length = (kb: number) => { @@ -76,9 +76,8 @@ export const reporting = (kibana: any) => { async init(server: ServerFacade) { let isCollectorReady = false; // Register a function with server to manage the collection of usage stats - server.usage.collectorSet.register( - getReportingUsageCollector(server, () => isCollectorReady) - ); + const { usageCollection } = server.newPlatform.setup.plugins; + registerReportingUsageCollector(usageCollection, server, () => isCollectorReady); const logger = LevelLogger.createForServer(server, [PLUGIN_ID]); const [exportTypesRegistry, browserFactory] = await Promise.all([ diff --git a/x-pack/legacy/plugins/reporting/server/usage/index.ts b/x-pack/legacy/plugins/reporting/server/usage/index.ts index 91e2a9284550b1..141ecb9c77656a 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/index.ts +++ b/x-pack/legacy/plugins/reporting/server/usage/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { getReportingUsageCollector } from './get_reporting_usage_collector'; +export { registerReportingUsageCollector } from './reporting_usage_collector'; diff --git a/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage_collector.test.js b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js similarity index 93% rename from x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage_collector.test.js rename to x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js index 32022c6fa642c2..f23f6798651463 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage_collector.test.js +++ b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.test.js @@ -4,15 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ import sinon from 'sinon'; -import { getReportingUsageCollector } from './get_reporting_usage_collector'; +import { getReportingUsageCollector } from './reporting_usage_collector'; -function getServerMock(customization) { +function getMockUsageCollection() { class MockUsageCollector { constructor(_server, { fetch }) { this.fetch = fetch; } } + return { + makeUsageCollector: options => { + return new MockUsageCollector(this, options); + }, + }; +} +function getServerMock(customization) { const getLicenseCheckResults = sinon.stub().returns({}); const defaultServerMock = { plugins: { @@ -44,13 +51,6 @@ function getServerMock(customization) { } }, }), - usage: { - collectorSet: { - makeUsageCollector: options => { - return new MockUsageCollector(this, options); - }, - }, - }, }; return Object.assign(defaultServerMock, customization); } @@ -66,7 +66,8 @@ describe('license checks', () => { .stub() .returns('basic'); const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock())); - const { fetch: getReportingUsage } = getReportingUsageCollector(serverWithBasicLicenseMock); + const usageCollection = getMockUsageCollection(); + const { fetch: getReportingUsage } = getReportingUsageCollector(usageCollection, serverWithBasicLicenseMock); usageStats = await getReportingUsage(callClusterMock); }); @@ -91,7 +92,8 @@ describe('license checks', () => { .stub() .returns('none'); const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock())); - const { fetch: getReportingUsage } = getReportingUsageCollector(serverWithNoLicenseMock); + const usageCollection = getMockUsageCollection(); + const { fetch: getReportingUsage } = getReportingUsageCollector(usageCollection, serverWithNoLicenseMock); usageStats = await getReportingUsage(callClusterMock); }); @@ -116,7 +118,9 @@ describe('license checks', () => { .stub() .returns('platinum'); const callClusterMock = jest.fn(() => Promise.resolve(getResponseMock())); + const usageCollection = getMockUsageCollection(); const { fetch: getReportingUsage } = getReportingUsageCollector( + usageCollection, serverWithPlatinumLicenseMock ); usageStats = await getReportingUsage(callClusterMock); @@ -143,7 +147,8 @@ describe('license checks', () => { .stub() .returns('basic'); const callClusterMock = jest.fn(() => Promise.resolve({})); - const { fetch: getReportingUsage } = getReportingUsageCollector(serverWithBasicLicenseMock); + const usageCollection = getMockUsageCollection(); + const { fetch: getReportingUsage } = getReportingUsageCollector(usageCollection, serverWithBasicLicenseMock); usageStats = await getReportingUsage(callClusterMock); }); @@ -160,11 +165,12 @@ describe('license checks', () => { describe('data modeling', () => { let getReportingUsage; beforeAll(async () => { + const usageCollection = getMockUsageCollection(); const serverWithPlatinumLicenseMock = getServerMock(); serverWithPlatinumLicenseMock.plugins.xpack_main.info.license.getType = sinon .stub() .returns('platinum'); - ({ fetch: getReportingUsage } = getReportingUsageCollector(serverWithPlatinumLicenseMock)); + ({ fetch: getReportingUsage } = getReportingUsageCollector(usageCollection, serverWithPlatinumLicenseMock)); }); test('with normal looking usage data', async () => { diff --git a/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage_collector.ts b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.ts similarity index 70% rename from x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage_collector.ts rename to x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.ts index 5c521937690574..0a7ef0a1944344 100644 --- a/x-pack/legacy/plugins/reporting/server/usage/get_reporting_usage_collector.ts +++ b/x-pack/legacy/plugins/reporting/server/usage/reporting_usage_collector.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; // @ts-ignore untyped module import { KIBANA_STATS_TYPE_MONITORING } from '../../../monitoring/common/constants'; import { ServerFacade, ESCallCluster } from '../../types'; @@ -15,9 +16,12 @@ import { RangeStats } from './types'; * @param {Object} server * @return {Object} kibana usage stats type collection object */ -export function getReportingUsageCollector(server: ServerFacade, isReady: () => boolean) { - const { collectorSet } = server.usage; - return collectorSet.makeUsageCollector({ +export function getReportingUsageCollector( + usageCollection: UsageCollectionSetup, + server: ServerFacade, + isReady: () => boolean +) { + return usageCollection.makeUsageCollector({ type: KIBANA_REPORTING_TYPE, isReady, fetch: (callCluster: ESCallCluster) => getReportingUsage(server, callCluster), @@ -41,3 +45,12 @@ export function getReportingUsageCollector(server: ServerFacade, isReady: () => }, }); } + +export function registerReportingUsageCollector( + usageCollection: UsageCollectionSetup, + server: ServerFacade, + isReady: () => boolean +) { + const collector = getReportingUsageCollector(usageCollection, server, isReady); + usageCollection.registerCollector(collector); +} diff --git a/x-pack/legacy/plugins/rollup/index.js b/x-pack/legacy/plugins/rollup/index.js index 3b6c033a2d85af..e0c00a7db62f05 100644 --- a/x-pack/legacy/plugins/rollup/index.js +++ b/x-pack/legacy/plugins/rollup/index.js @@ -57,12 +57,13 @@ export function rollup(kibana) { ], }, init: function (server) { + const { usageCollection } = server.newPlatform.setup.plugins; registerLicenseChecker(server); registerIndicesRoute(server); registerFieldsForWildcardRoute(server); registerSearchRoute(server); registerJobsRoute(server); - registerRollupUsageCollector(server); + registerRollupUsageCollector(usageCollection, server); if ( server.plugins.index_management && server.plugins.index_management.addIndexManagementDataEnricher diff --git a/x-pack/legacy/plugins/rollup/server/usage/collector.js b/x-pack/legacy/plugins/rollup/server/usage/collector.js index 977253dfa53fb9..99fffa774baaf7 100644 --- a/x-pack/legacy/plugins/rollup/server/usage/collector.js +++ b/x-pack/legacy/plugins/rollup/server/usage/collector.js @@ -163,10 +163,10 @@ async function fetchRollupVisualizations(kibanaIndex, callCluster, rollupIndexPa }; } -export function registerRollupUsageCollector(server) { +export function registerRollupUsageCollector(usageCollection, server) { const kibanaIndex = server.config().get('kibana.index'); - const collector = server.usage.collectorSet.makeUsageCollector({ + const collector = usageCollection.makeUsageCollector({ type: ROLLUP_USAGE_TYPE, isReady: () => true, fetch: async callCluster => { @@ -198,5 +198,5 @@ export function registerRollupUsageCollector(server) { }, }); - server.usage.collectorSet.register(collector); + usageCollection.registerCollector(collector); } diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/drag_n_drop/helpers.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/drag_n_drop/helpers.ts index e42a01f4ad8c12..39a61401c15b3c 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/drag_n_drop/helpers.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/drag_n_drop/helpers.ts @@ -23,19 +23,23 @@ export const drag = (subject: JQuery) => { clientY: subjectLocation.top, force: true, }) + .wait(1) .trigger('mousemove', { button: primaryButton, clientX: subjectLocation.left + dndSloppyClickDetectionThreshold, clientY: subjectLocation.top, force: true, - }); + }) + .wait(1); }; /** "Drops" the subject being dragged on the specified drop target */ export const drop = (dropTarget: JQuery) => { cy.wrap(dropTarget) - .trigger('mousemove', { button: primaryButton }) - .trigger('mouseup'); + .trigger('mousemove', { button: primaryButton, force: true }) + .wait(1) + .trigger('mouseup', { force: true }) + .wait(1); }; /** Drags the subject being dragged on the specified drop target, but does not drop it */ diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/fields_browser/helpers.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/fields_browser/helpers.ts index bc6d037432771d..e3495b6a781271 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/fields_browser/helpers.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/fields_browser/helpers.ts @@ -19,7 +19,7 @@ import { TIMELINE_DATA_PROVIDERS } from '../timeline/selectors'; /** Opens the timeline's Field Browser */ export const openTimelineFieldsBrowser = () => { - cy.get(TIMELINE_FIELDS_BUTTON).click(); + cy.get(TIMELINE_FIELDS_BUTTON).click({ force: true }); cy.get(FIELDS_BROWSER_CONTAINER).should('exist'); }; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/url_state/index.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/url_state/index.ts index 5c12bd528030e1..ef1892b3d382c0 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/url_state/index.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/url_state/index.ts @@ -16,20 +16,20 @@ export const ABSOLUTE_DATE_RANGE = { endTimeFormat: '2019-08-01T20:33:29.186Z', endTimeTimeline: '1564779809186', endTimeTimelineFormat: '2019-08-02T21:03:29.186Z', - endTimeTimelineTyped: '2019-08-02 21:03:29.186', - endTimeTyped: '2019-08-01 14:33:29.186', + endTimeTimelineTyped: 'Aug 02, 2019 @ 21:03:29.186', + endTimeTyped: 'Aug 01, 2019 @ 14:33:29.186', newEndTime: '1564693409186', newEndTimeFormat: '2019-08-01T21:03:29.186Z', - newEndTimeTyped: '2019-08-01 15:03:29.186', + newEndTimeTyped: 'Aug 01, 2019 @ 15:03:29.186', newStartTime: '1564691609186', newStartTimeFormat: '2019-08-01T20:33:29.186Z', - newStartTimeTyped: '2019-08-01 14:33:29.186', + newStartTimeTyped: 'Aug 01, 2019 @ 14:33:29.186', startTime: '1564689809186', startTimeFormat: '2019-08-01T20:03:29.186Z', startTimeTimeline: '1564776209186', startTimeTimelineFormat: '2019-08-02T20:03:29.186Z', - startTimeTimelineTyped: '2019-08-02 14:03:29.186', - startTimeTyped: '2019-08-01 14:03:29.186', + startTimeTimelineTyped: 'Aug 02, 2019 @ 14:03:29.186', + startTimeTyped: 'Aug 01, 2019 @ 14:03:29.186', url: '/app/siem#/network/?timerange=(global:(linkTo:!(timeline),timerange:(from:1564689809186,kind:absolute,to:1564691609186)),timeline:(linkTo:!(global),timerange:(from:1564689809186,kind:absolute,to:1564691609186)))', @@ -52,7 +52,7 @@ export const DATE_PICKER_END_DATE_POPOVER_BUTTON_TIMELINE = '[data-test-subj="timeline-properties"] [data-test-subj="superDatePickerendDatePopoverButton"]'; export const DATE_PICKER_ABSOLUTE_TAB = '[data-test-subj="superDatePickerAbsoluteTab"]'; export const DATE_PICKER_APPLY_BUTTON = - '[data-test-subj="globalDatePicker"] button[data-test-subj="superDatePickerApplyTimeButton"]'; + '[data-test-subj="globalDatePicker"] button[data-test-subj="querySubmitButton"]'; export const DATE_PICKER_APPLY_BUTTON_TIMELINE = '[data-test-subj="timeline-properties"] button[data-test-subj="superDatePickerApplyTimeButton"]'; export const DATE_PICKER_ABSOLUTE_INPUT = '[data-test-subj="superDatePickerAbsoluteDateInput"]'; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts index baf6b7cd2027d8..8f5c6e6f660cc1 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts @@ -34,7 +34,7 @@ const defaultHeaders = [ { id: 'user.name' }, ]; -describe.skip('Fields Browser', () => { +describe('Fields Browser', () => { beforeEach(() => { loginAndWaitForPage(HOSTS_PAGE); }); @@ -104,9 +104,9 @@ describe.skip('Fields Browser', () => { openTimelineFieldsBrowser(); - cy.get( - `[data-test-subj="timeline"] [data-test-subj="field-${toggleField}-checkbox"]` - ).uncheck(); + cy.get(`[data-test-subj="timeline"] [data-test-subj="field-${toggleField}-checkbox"]`).uncheck({ + force: true, + }); clickOutsideFieldsBrowser(); @@ -185,7 +185,9 @@ describe.skip('Fields Browser', () => { 'not.exist' ); - cy.get(`[data-test-subj="timeline"] [data-test-subj="field-${toggleField}-checkbox"]`).check(); + cy.get(`[data-test-subj="timeline"] [data-test-subj="field-${toggleField}-checkbox"]`).check({ + force: true, + }); clickOutsideFieldsBrowser(); @@ -235,7 +237,9 @@ describe.skip('Fields Browser', () => { 'not.exist' ); - cy.get(`[data-test-subj="timeline"] [data-test-subj="field-${toggleField}-checkbox"]`).check(); + cy.get(`[data-test-subj="timeline"] [data-test-subj="field-${toggleField}-checkbox"]`).check({ + force: true, + }); clickOutsideFieldsBrowser(); @@ -245,7 +249,7 @@ describe.skip('Fields Browser', () => { openTimelineFieldsBrowser(); - cy.get('[data-test-subj="timeline"] [data-test-subj="reset-fields"]').click(); + cy.get('[data-test-subj="timeline"] [data-test-subj="reset-fields"]').click({ force: true }); cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( 'not.exist' diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts index 8c2902fd804ac7..8197f77db9a081 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts @@ -73,7 +73,7 @@ describe('toggle column in timeline', () => { cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${idField}"]`).should('exist'); }); - it.skip('adds the _id field to the timeline via drag and drop', () => { + it('adds the _id field to the timeline via drag and drop', () => { populateTimeline(); toggleFirstTimelineEventDetails(); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts index 4ba8b8c44f3665..b1867a437f7f41 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts @@ -51,7 +51,7 @@ describe('url state', () => { ); }); - it.skip('sets the url state when start and end date are set', () => { + it('sets the url state when start and end date are set', () => { loginAndWaitForPage(ABSOLUTE_DATE_RANGE.url); cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON).click({ force: true }); @@ -64,7 +64,7 @@ describe('url state', () => { `{selectall}{backspace}${ABSOLUTE_DATE_RANGE.newStartTimeTyped}` ); - cy.get(DATE_PICKER_APPLY_BUTTON).click({ force: true }); + cy.get(DATE_PICKER_APPLY_BUTTON, { timeout: 5000 }).click(); cy.get(DATE_PICKER_END_DATE_POPOVER_BUTTON).click({ force: true }); @@ -76,7 +76,7 @@ describe('url state', () => { `{selectall}{backspace}${ABSOLUTE_DATE_RANGE.newEndTimeTyped}` ); - cy.get(DATE_PICKER_APPLY_BUTTON).click({ force: true }); + cy.get(DATE_PICKER_APPLY_BUTTON, { timeout: 5000 }).click(); cy.url().should( 'include', @@ -127,7 +127,7 @@ describe('url state', () => { ); }); - it.skip('sets the url state when timeline/global date pickers are unlinked and timeline start and end date are set', () => { + it('sets the url state when timeline/global date pickers are unlinked and timeline start and end date are set', () => { loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlUnlinked); toggleTimelineVisibility(); @@ -165,17 +165,17 @@ describe('url state', () => { ); }); - it.skip('sets kql on network page', () => { + it('sets kql on network page', () => { loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlKqlNetworkNetwork); cy.get(KQL_INPUT, { timeout: 5000 }).should('have.attr', 'value', 'source.ip: "10.142.0.9"'); }); - it.skip('sets kql on hosts page', () => { + it('sets kql on hosts page', () => { loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlKqlHostsHosts); cy.get(KQL_INPUT, { timeout: 5000 }).should('have.attr', 'value', 'source.ip: "10.142.0.9"'); }); - it.skip('sets the url state when kql is set', () => { + it('sets the url state when kql is set', () => { loginAndWaitForPage(ABSOLUTE_DATE_RANGE.url); cy.get(KQL_INPUT, { timeout: 5000 }).type('source.ip: "10.142.0.9" {enter}'); cy.url().should('include', `query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')`); @@ -241,7 +241,7 @@ describe('url state', () => { ); }); - it.skip('Do not clears kql when navigating to a new page', () => { + it('Do not clears kql when navigating to a new page', () => { loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlKqlHostsHosts); cy.get(NAVIGATION_NETWORK).click({ force: true }); cy.get(KQL_INPUT, { timeout: 5000 }).should('have.attr', 'value', 'source.ip: "10.142.0.9"'); diff --git a/x-pack/legacy/plugins/siem/package.json b/x-pack/legacy/plugins/siem/package.json index 29c26c5f674e38..d239961ee75d71 100644 --- a/x-pack/legacy/plugins/siem/package.json +++ b/x-pack/legacy/plugins/siem/package.json @@ -12,11 +12,11 @@ "devDependencies": { "@types/lodash": "^4.14.110", "@types/js-yaml": "^3.12.1", - "@types/react-beautiful-dnd": "^10.0.1" + "@types/react-beautiful-dnd": "^11.0.3" }, "dependencies": { "lodash": "^4.17.15", - "react-beautiful-dnd": "^10.0.1", + "react-beautiful-dnd": "^12.1.1", "react-markdown": "^4.0.6" } } diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context.tsx new file mode 100644 index 00000000000000..ee9533341a4f8a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context.tsx @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// https://github.com/DefinitelyTyped/DefinitelyTyped/pull/40309 + +import { MovementMode, DraggableId } from 'react-beautiful-dnd'; + +export interface BeforeCapture { + draggableId: DraggableId; + mode: MovementMode; +} diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx index c513f7a4512401..a3528158a03173 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.tsx @@ -6,10 +6,11 @@ import { defaultTo, noop } from 'lodash/fp'; import React, { useCallback } from 'react'; -import { DragDropContext, DropResult, DragStart } from 'react-beautiful-dnd'; +import { DropResult, DragDropContext } from 'react-beautiful-dnd'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; +import { BeforeCapture } from './drag_drop_context'; import { BrowserFields } from '../../containers/source'; import { dragAndDropModel, dragAndDropSelectors } from '../../store'; import { IdToDataProvider } from '../../store/drag_and_drop/model'; @@ -20,6 +21,7 @@ import { addProviderToTimeline, fieldWasDroppedOnTimelineColumns, IS_DRAGGING_CLASS_NAME, + IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME, providerWasDroppedOnTimeline, providerWasDroppedOnTimelineButton, draggableIsField, @@ -75,11 +77,16 @@ export const DragDropContextWrapperComponent = React.memo( if (!draggableIsField(result)) { document.body.classList.remove(IS_DRAGGING_CLASS_NAME); } + + if (draggableIsField(result)) { + document.body.classList.remove(IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME); + } }, [browserFields, dataProviders] ); return ( - + // @ts-ignore + {children} ); @@ -107,7 +114,7 @@ const mapStateToProps = (state: State) => { export const DragDropContextWrapper = connect(mapStateToProps)(DragDropContextWrapperComponent); -const onDragStart = (initial: DragStart) => { +const onBeforeCapture = (before: BeforeCapture) => { const x = window.pageXOffset !== undefined ? window.pageXOffset @@ -120,9 +127,13 @@ const onDragStart = (initial: DragStart) => { window.onscroll = () => window.scrollTo(x, y); - if (!draggableIsField(initial)) { + if (!draggableIsField(before)) { document.body.classList.add(IS_DRAGGING_CLASS_NAME); } + + if (draggableIsField(before)) { + document.body.classList.add(IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME); + } }; const enableScrolling = () => (window.onscroll = () => noop); diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx index 0f0e61e0206ec9..c3147855112014 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx @@ -34,6 +34,10 @@ export const useDraggablePortalContext = () => useContext(DraggablePortalContext const Wrapper = styled.div` display: inline-block; max-width: 100%; + + [data-rbd-placeholder-context-id] { + display: none !important; + } `; Wrapper.displayName = 'Wrapper'; diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/helpers.test.ts b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/helpers.test.ts index 8d3334b05bfafe..af4b9b280f3cde 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/helpers.test.ts +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/helpers.test.ts @@ -116,7 +116,7 @@ describe('helpers', () => { test('it returns false when the draggable is NOT content', () => { expect( draggableIsContent({ - destination: null, + destination: undefined, draggableId: `${draggableIdPrefix}.timeline.timeline.dataProvider.685260508808089`, reason: 'DROP', source: { @@ -230,10 +230,10 @@ describe('helpers', () => { ).toEqual(true); }); - test('it returns false when the destination is null', () => { + test('it returns false when the destination is undefined', () => { expect( destinationIsTimelineProviders({ - destination: null, + destination: undefined, draggableId: getDraggableId('685260508808089'), reason: 'DROP', source: { @@ -286,10 +286,10 @@ describe('helpers', () => { ).toEqual(true); }); - test('it returns returns false when the destination is null', () => { + test('it returns returns false when the destination is undefined', () => { expect( destinationIsTimelineColumns({ - destination: null, + destination: undefined, draggableId: getDraggableFieldId({ contextId: 'test', fieldId: 'event.action' }), reason: 'DROP', source: { @@ -342,10 +342,10 @@ describe('helpers', () => { ).toEqual(true); }); - test('it returns false when the destination is null', () => { + test('it returns false when the destination is undefined', () => { expect( destinationIsTimelineButton({ - destination: null, + destination: undefined, draggableId: getDraggableId('685260508808089'), reason: 'DROP', source: { @@ -436,10 +436,10 @@ describe('helpers', () => { ).toEqual('timeline'); }); - test('it returns returns an empty string when the destination is null', () => { + test('it returns returns an empty string when the destination is undefined', () => { expect( getTimelineIdFromDestination({ - destination: null, + destination: undefined, draggableId: `${draggableIdPrefix}.timeline.timeline.dataProvider.685260508808089`, reason: 'DROP', source: { @@ -558,7 +558,7 @@ describe('helpers', () => { test('it returns false when the draggable is NOT content', () => { expect( providerWasDroppedOnTimeline({ - destination: null, + destination: undefined, draggableId: `${draggableIdPrefix}.timeline.timeline.dataProvider.685260508808089`, reason: 'DROP', source: { diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/helpers.ts b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/helpers.ts index 415970474db4c3..ae3a8828491e3e 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/helpers.ts @@ -224,3 +224,6 @@ export const DRAG_TYPE_FIELD = 'drag-type-field'; /** This class is added to the document body while dragging */ export const IS_DRAGGING_CLASS_NAME = 'is-dragging'; + +/** This class is added to the document body while timeline field dragging */ +export const IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME = 'is-timeline-field-dragging'; diff --git a/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.tsx b/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.tsx index 214ac926e88682..18b271a3abc297 100644 --- a/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.tsx @@ -49,8 +49,7 @@ HeaderContainer.displayName = 'HeaderContainer'; // SIDE EFFECT: the following `createGlobalStyle` overrides the default styling // of euiComboBoxOptionsList because it's implemented as a popover, so it's // not selectable as a child of the styled component -// eslint-disable-next-line no-unused-expressions -createGlobalStyle` +const StatefulEditDataProviderGlobalStyle = createGlobalStyle` .euiComboBoxOptionsList { z-index: 9999; } @@ -158,104 +157,107 @@ export const StatefulEditDataProvider = React.memo( }, []); return ( - - - - - - - 0 ? updatedField[0].label : null}> + <> + + + + + + + 0 ? updatedField[0].label : null}> + + + + + + + - - - + + + + + + + + + {updatedOperator.length > 0 && + updatedOperator[0].label !== i18n.EXISTS && + updatedOperator[0].label !== i18n.DOES_NOT_EXIST ? ( - - + - - - - - - + ) : null} - {updatedOperator.length > 0 && - updatedOperator[0].label !== i18n.EXISTS && - updatedOperator[0].label !== i18n.DOES_NOT_EXIST ? ( - - - + - ) : null} - - - - - - - - { - onDataProviderEdited({ - andProviderId, - excluded: getExcludedFromSelection(updatedOperator), - field: updatedField.length > 0 ? updatedField[0].label : '', - id: timelineId, - operator: getQueryOperatorFromSelection(updatedOperator), - providerId, - value: updatedValue, - }); - }} - size="s" - > - {i18n.SAVE} - - - - - - + + + + { + onDataProviderEdited({ + andProviderId, + excluded: getExcludedFromSelection(updatedOperator), + field: updatedField.length > 0 ? updatedField[0].label : '', + id: timelineId, + operator: getQueryOperatorFromSelection(updatedOperator), + providerId, + value: updatedValue, + }); + }} + size="s" + > + {i18n.SAVE} + + + + + + + + ); } ); diff --git a/x-pack/legacy/plugins/siem/public/components/error_toast_dispatcher/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/error_toast_dispatcher/index.test.tsx index 6c4bc797d39f84..6233fcfe7c823c 100644 --- a/x-pack/legacy/plugins/siem/public/components/error_toast_dispatcher/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/error_toast_dispatcher/index.test.tsx @@ -30,7 +30,7 @@ describe('Error Toast Dispatcher', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('Connect(ErrorToastDispatcherComponent)'))).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/loading/index.tsx b/x-pack/legacy/plugins/siem/public/components/loading/index.tsx index eb85edce78a8ff..42867c09b971b2 100644 --- a/x-pack/legacy/plugins/siem/public/components/loading/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/loading/index.tsx @@ -10,8 +10,7 @@ import { pure } from 'recompose'; import styled, { createGlobalStyle } from 'styled-components'; // SIDE EFFECT: the following `createGlobalStyle` overrides default styling in angular code that was not theme-friendly -// eslint-disable-next-line no-unused-expressions -createGlobalStyle` +const LoadingPanelGlobalStyle = createGlobalStyle` .euiPanel-loading-hide-border { border: none; } @@ -41,27 +40,30 @@ export const LoadingPanel = pure( position = 'relative', zIndex = 'inherit', }) => ( - - - - - - - + <> + + + + + + + - - {text} - - - - - + + {text} + + + + + + + ) ); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx index 7dd5eccb4a6c67..71e61e2425373c 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx @@ -49,7 +49,7 @@ describe('Authentication Table Component', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('Connect(AuthenticationTableComponent)'))).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/index.tsx index bc701006c3a9c0..582ef2d01fb7e0 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/index.tsx @@ -15,9 +15,11 @@ import { } from '@elastic/eui'; import styled, { createGlobalStyle } from 'styled-components'; -// SIDE EFFECT: the following `createGlobalStyle` overrides default styling in angular code that was not theme-friendly -// eslint-disable-next-line no-unused-expressions -createGlobalStyle` +/* + SIDE EFFECT: the following `createGlobalStyle` overrides default styling in angular code that was not theme-friendly + and `EuiPopover`, `EuiToolTip` global styles +*/ +export const AppGlobalStyle = createGlobalStyle` div.app-wrapper { background-color: rgba(0,0,0,0); } @@ -25,6 +27,13 @@ createGlobalStyle` div.application { background-color: rgba(0,0,0,0); } + + .euiPopover__panel.euiPopover__panel-isOpen { + z-index: 9900 !important; + } + .euiToolTip { + z-index: 9950 !important; + } `; export const DescriptionListStyled = styled(EuiDescriptionList)` diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.test.tsx index eb6204044bdb77..964617c4c85b14 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.test.tsx @@ -42,7 +42,7 @@ describe('KpiNetwork Component', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('KpiNetworkComponent'))).toMatchSnapshot(); }); test('it renders the default widget', () => { @@ -59,7 +59,7 @@ describe('KpiNetwork Component', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('KpiNetworkComponent'))).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx index 98f55b29c8fc4c..8bf338d17c47bc 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx @@ -51,7 +51,7 @@ describe('NetworkTopNFlow Table Component', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('Connect(NetworkDnsTableComponent)'))).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx index 277e136d776fa6..c92661a909a6e1 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx @@ -51,7 +51,7 @@ describe('NetworkHttp Table Component', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('Connect(NetworkHttpTableComponent)'))).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx index d8a5da6036f950..ca7a3c0bb4387e 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx @@ -57,7 +57,7 @@ describe('NetworkTopCountries Table Component', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('Connect(NetworkTopCountriesTableComponent)'))).toMatchSnapshot(); }); test('it renders the IP Details NetworkTopCountries table', () => { const wrapper = shallow( @@ -82,7 +82,7 @@ describe('NetworkTopCountries Table Component', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('Connect(NetworkTopCountriesTableComponent)'))).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx index df9e0f9f89645e..884825422beb0a 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx @@ -57,7 +57,7 @@ describe('NetworkTopNFlow Table Component', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('Connect(NetworkTopNFlowTableComponent)'))).toMatchSnapshot(); }); test('it renders the default NetworkTopNFlow table on the IP Details page', () => { @@ -83,7 +83,7 @@ describe('NetworkTopNFlow Table Component', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('Connect(NetworkTopNFlowTableComponent)'))).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx index 612896c878ef90..8c397053380c5c 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx @@ -47,7 +47,7 @@ describe('Tls Table Component', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('Connect(TlsTableComponent)'))).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx index 00a0a34a2b30b8..d178164fd3fd73 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx @@ -55,7 +55,7 @@ describe('Users Table Component', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(toJson(wrapper.find('Connect(UsersTableComponent)'))).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap index 8bf7b1543b9231..65818b697e0b34 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap @@ -483,8 +483,12 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx index 747ef8f3ffe47b..4f414af74a9143 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx @@ -17,8 +17,7 @@ export const EVENTS_SELECT_WIDTH = 60; // px // SIDE EFFECT: the following `createGlobalStyle` overrides // the style of the select items -// eslint-disable-next-line -createGlobalStyle` +const EventsSelectGlobalStyle = createGlobalStyle` .eventsSelectItem { width: 100% !important; @@ -73,6 +72,7 @@ export const EventsSelect = pure(({ checkState, timelineId }) => { /> +
); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx index 47fbcec4aab23a..07e37346ac968d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx @@ -19,7 +19,7 @@ import { OnUnPinEvent, OnUpdateColumns, } from '../events'; -import { EventsTable, TimelineBody } from '../styles'; +import { EventsTable, TimelineBody, TimelineBodyGlobalStyle } from '../styles'; import { ColumnHeaders } from './column_headers'; import { ColumnHeader } from './column_headers/column_header'; import { Events } from './events'; @@ -86,50 +86,53 @@ export const Body = React.memo( ); return ( - - - + <> + + + - - - + + + + + ); } ); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx index 29d2df51724576..3ef7240ee03758 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx @@ -50,7 +50,7 @@ const HighlightedBackground = styled.span` HighlightedBackground.displayName = 'HighlightedBackground'; const EmptyContainer = styled.div<{ showSmallMsg: boolean }>` - width: ${props => (props.showSmallMsg ? '60px' : 'auto')} + width: ${props => (props.showSmallMsg ? '60px' : 'auto')}; align-items: center; display: flex; flex-direction: row; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx index 112962367cd362..5a8654509fa881 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx @@ -46,7 +46,7 @@ interface Props { const ROW_OF_DATA_PROVIDERS_HEIGHT = 43; // px const PanelProviders = styled.div` - position: relative + position: relative; display: flex; flex-direction: row; min-height: 100px; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx index ccc222673d7bc5..7b69e006f48ad3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.tsx @@ -4,9 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiAvatar, EuiFlexItem, EuiIcon } from '@elastic/eui'; import React, { useState, useCallback } from 'react'; -import styled, { createGlobalStyle } from 'styled-components'; import { Note } from '../../../lib/note'; import { InputsModelId } from '../../../store/inputs/constants'; @@ -22,43 +20,6 @@ type UpdateTitle = ({ id, title }: { id: string; title: string }) => void; type UpdateDescription = ({ id, description }: { id: string; description: string }) => void; type ToggleLock = ({ linkToId }: { linkToId: InputsModelId }) => void; -// SIDE EFFECT: the following `createGlobalStyle` overrides `EuiPopover` -// and `EuiToolTip` global styles: -// eslint-disable-next-line no-unused-expressions -createGlobalStyle` - .euiPopover__panel.euiPopover__panel-isOpen { - z-index: 9900 !important; - } - .euiToolTip { - z-index: 9950 !important; - } -`; - -const Avatar = styled(EuiAvatar)` - margin-left: 5px; -`; - -Avatar.displayName = 'Avatar'; - -const DescriptionPopoverMenuContainer = styled.div` - margin-top: 15px; -`; - -DescriptionPopoverMenuContainer.displayName = 'DescriptionPopoverMenuContainer'; - -const SettingsIcon = styled(EuiIcon)` - margin-left: 4px; - cursor: pointer; -`; - -SettingsIcon.displayName = 'SettingsIcon'; - -const HiddenFlexItem = styled(EuiFlexItem)` - display: none; -`; - -HiddenFlexItem.displayName = 'HiddenFlexItem'; - interface Props { associateNote: AssociateNote; createTimeline: CreateTimeline; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx index 2d953ce3cfc951..eaa476bf3e2b27 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx @@ -26,8 +26,7 @@ const searchOrFilterPopoverClassName = 'searchOrFilterPopover'; const searchOrFilterPopoverWidth = '352px'; // SIDE EFFECT: the following creates a global class selector -// eslint-disable-next-line no-unused-expressions -createGlobalStyle` +const SearchOrFilterGlobalStyle = createGlobalStyle` .${timelineSelectModeItemsClassName} { width: 350px !important; } @@ -110,48 +109,51 @@ export const SearchOrFilter = pure( updateKqlMode, updateReduxTime, }) => ( - - - - - updateKqlMode({ id: timelineId, kqlMode: mode })} - options={options} - popoverClassName={searchOrFilterPopoverClassName} - valueOfSelected={kqlMode} + <> + + + + + updateKqlMode({ id: timelineId, kqlMode: mode })} + options={options} + popoverClassName={searchOrFilterPopoverClassName} + valueOfSelected={kqlMode} + /> + + + + - - - - - - - + + + + + ) ); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx index 856259f11ced90..1c1c8fac75cdce 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx @@ -6,7 +6,9 @@ import { EuiLoadingSpinner } from '@elastic/eui'; import { rgba } from 'polished'; -import styled from 'styled-components'; +import styled, { createGlobalStyle } from 'styled-components'; + +import { IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME } from '../drag_and_drop/helpers'; /** * OFFSET PIXEL VALUES @@ -18,6 +20,13 @@ export const OFFSET_SCROLLBAR = 17; * TIMELINE BODY */ +// SIDE EFFECT: the following creates a global class selector +export const TimelineBodyGlobalStyle = createGlobalStyle` + body.${IS_TIMELINE_FIELD_DRAGGING_CLASS_NAME} .siemTimeline__body { + overflow: hidden; + } +`; + export const TimelineBody = styled.div.attrs(({ className }) => ({ className: `siemTimeline__body ${className}`, }))<{ bodyHeight: number }>` diff --git a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx index 5998aa527206e7..309693427459e4 100644 --- a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.tsx @@ -9,6 +9,7 @@ import React from 'react'; import styled, { css } from 'styled-components'; import { gutterTimeline } from '../../lib/helpers'; +import { AppGlobalStyle } from '../page/index'; const Wrapper = styled.div` ${({ theme }) => css` @@ -54,6 +55,7 @@ export const WrapperPage = React.memo( return ( {children} + ); } diff --git a/x-pack/legacy/plugins/siem/public/lib/keury/index.ts b/x-pack/legacy/plugins/siem/public/lib/keury/index.ts index b6819e54575d60..d82079dd05d31e 100644 --- a/x-pack/legacy/plugins/siem/public/lib/keury/index.ts +++ b/x-pack/legacy/plugins/siem/public/lib/keury/index.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { fromKueryExpression, toElasticsearchQuery, JsonObject } from '@kbn/es-query'; import { isEmpty, isString, flow } from 'lodash/fp'; import { Query, esFilters, esQuery, + esKuery, IIndexPattern, } from '../../../../../../../src/plugins/data/public'; @@ -21,7 +21,9 @@ export const convertKueryToElasticSearchQuery = ( ) => { try { return kueryExpression - ? JSON.stringify(toElasticsearchQuery(fromKueryExpression(kueryExpression), indexPattern)) + ? JSON.stringify( + esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(kueryExpression), indexPattern) + ) : ''; } catch (err) { return ''; @@ -31,10 +33,10 @@ export const convertKueryToElasticSearchQuery = ( export const convertKueryToDslFilter = ( kueryExpression: string, indexPattern: IIndexPattern -): JsonObject => { +): esKuery.JsonObject => { try { return kueryExpression - ? toElasticsearchQuery(fromKueryExpression(kueryExpression), indexPattern) + ? esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(kueryExpression), indexPattern) : {}; } catch (err) { return {}; @@ -55,7 +57,7 @@ export const escapeQueryValue = (val: number | string = ''): string | number => export const isFromKueryExpressionValid = (kqlFilterQuery: KueryFilterQuery | null): boolean => { if (kqlFilterQuery && kqlFilterQuery.kind === 'kuery') { try { - fromKueryExpression(kqlFilterQuery.expression); + esKuery.fromKueryExpression(kqlFilterQuery.expression); } catch (err) { return false; } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx index 500557a2c2a96f..58a9e57b32ce60 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/create_rule/components/step_define_rule/schema.tsx @@ -6,7 +6,6 @@ import { i18n } from '@kbn/i18n'; import { EuiText } from '@elastic/eui'; -import { fromKueryExpression } from '@kbn/es-query'; import { isEmpty } from 'lodash/fp'; import React from 'react'; @@ -17,6 +16,7 @@ import { } from '../../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; import { fieldValidators } from '../../../../../../../../../../src/plugins/es_ui_shared/static/forms/helpers'; import { ERROR_CODE } from '../../../../../../../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types'; +import { esKuery } from '../../../../../../../../../../src/plugins/data/public'; import * as CreateRuleI18n from '../../translations'; @@ -106,7 +106,7 @@ export const schema: FormSchema = { const { query } = value as FieldValueQueryBar; if (!isEmpty(query.query as string) && query.language === 'kuery') { try { - fromKueryExpression(query.query); + esKuery.fromKueryExpression(query.query); } catch (err) { return { code: 'ERR_FIELD_FORMAT', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts index 079d3658461fa5..8080bd5ddd9139 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/__mocks__/es_results.ts @@ -4,10 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SignalSourceHit, SignalSearchResponse, RuleTypeParams } from '../types'; +import { + SignalSourceHit, + SignalSearchResponse, + RuleTypeParams, + OutputRuleAlertRest, +} from '../types'; export const sampleRuleAlertParams = ( - maxSignals: number | undefined, + maxSignals?: number | undefined, riskScore?: number | undefined ): RuleTypeParams => ({ ruleId: 'rule-1', @@ -32,7 +37,7 @@ export const sampleRuleAlertParams = ( meta: undefined, }); -export const sampleDocNoSortId = (someUuid: string): SignalSourceHit => ({ +export const sampleDocNoSortId = (someUuid: string = sampleIdGuid): SignalSourceHit => ({ _index: 'myFakeSignalIndex', _type: 'doc', _score: 100, @@ -44,7 +49,7 @@ export const sampleDocNoSortId = (someUuid: string): SignalSourceHit => ({ }, }); -export const sampleDocNoSortIdNoVersion = (someUuid: string): SignalSourceHit => ({ +export const sampleDocNoSortIdNoVersion = (someUuid: string = sampleIdGuid): SignalSourceHit => ({ _index: 'myFakeSignalIndex', _type: 'doc', _score: 100, @@ -55,7 +60,7 @@ export const sampleDocNoSortIdNoVersion = (someUuid: string): SignalSourceHit => }, }); -export const sampleDocWithSortId = (someUuid: string): SignalSourceHit => ({ +export const sampleDocWithSortId = (someUuid: string = sampleIdGuid): SignalSourceHit => ({ _index: 'myFakeSignalIndex', _type: 'doc', _score: 100, @@ -138,7 +143,9 @@ export const sampleBulkCreateDuplicateResult = { ], }; -export const sampleDocSearchResultsNoSortId = (someUuid: string): SignalSearchResponse => ({ +export const sampleDocSearchResultsNoSortId = ( + someUuid: string = sampleIdGuid +): SignalSearchResponse => ({ took: 10, timed_out: false, _shards: { @@ -159,7 +166,7 @@ export const sampleDocSearchResultsNoSortId = (someUuid: string): SignalSearchRe }); export const sampleDocSearchResultsNoSortIdNoVersion = ( - someUuid: string + someUuid: string = sampleIdGuid ): SignalSearchResponse => ({ took: 10, timed_out: false, @@ -180,7 +187,9 @@ export const sampleDocSearchResultsNoSortIdNoVersion = ( }, }); -export const sampleDocSearchResultsNoSortIdNoHits = (someUuid: string): SignalSearchResponse => ({ +export const sampleDocSearchResultsNoSortIdNoHits = ( + someUuid: string = sampleIdGuid +): SignalSearchResponse => ({ took: 10, timed_out: false, _shards: { @@ -222,7 +231,9 @@ export const repeatedSearchResultsWithSortId = ( }, }); -export const sampleDocSearchResultsWithSortId = (someUuid: string): SignalSearchResponse => ({ +export const sampleDocSearchResultsWithSortId = ( + someUuid: string = sampleIdGuid +): SignalSearchResponse => ({ took: 10, timed_out: false, _shards: { @@ -243,3 +254,31 @@ export const sampleDocSearchResultsWithSortId = (someUuid: string): SignalSearch }); export const sampleRuleGuid = '04128c15-0d1b-4716-a4c5-46997ac7f3bd'; +export const sampleIdGuid = 'e1e08ddc-5e37-49ff-a258-5393aa44435a'; + +export const sampleRule = (): Partial => { + return { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + to: 'now', + type: 'query', + }; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts index 9f72da44e963bf..c55c99fb291c44 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/get_filter.test.ts @@ -7,6 +7,7 @@ import { getQueryFilter, getFilter } from './get_filter'; import { savedObjectsClientMock } from 'src/core/server/mocks'; import { AlertServices } from '../../../../../alerting/server/types'; +import { PartialFilter } from './types'; describe('get_filter', () => { let savedObjectsClient = savedObjectsClientMock.create(); @@ -145,6 +146,103 @@ describe('get_filter', () => { }); }); + test('it should work with a simple filter as a kuery without meta information', () => { + const esQuery = getQueryFilter( + 'host.name: windows', + 'kuery', + [ + { + query: { + match_phrase: { + 'host.name': 'siem-windows', + }, + }, + }, + ], + ['auditbeat-*'] + ); + expect(esQuery).toEqual({ + bool: { + must: [], + filter: [ + { + bool: { + should: [ + { + match: { + 'host.name': 'windows', + }, + }, + ], + minimum_should_match: 1, + }, + }, + { + match_phrase: { + 'host.name': 'siem-windows', + }, + }, + ], + should: [], + must_not: [], + }, + }); + }); + + test('it should work with a simple filter as a kuery without meta information with an exists', () => { + const query: PartialFilter = { + query: { + match_phrase: { + 'host.name': 'siem-windows', + }, + }, + } as PartialFilter; + + const exists: PartialFilter = { + exists: { + field: 'host.hostname', + }, + } as PartialFilter; + + const esQuery = getQueryFilter( + 'host.name: windows', + 'kuery', + [query, exists], + ['auditbeat-*'] + ); + expect(esQuery).toEqual({ + bool: { + must: [], + filter: [ + { + bool: { + should: [ + { + match: { + 'host.name': 'windows', + }, + }, + ], + minimum_should_match: 1, + }, + }, + { + match_phrase: { + 'host.name': 'siem-windows', + }, + }, + { + exists: { + field: 'host.hostname', + }, + }, + ], + should: [], + must_not: [], + }, + }); + }); + test('it should work with a simple filter that is disabled as a kuery', () => { const esQuery = getQueryFilter( 'host.name: windows', diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts index 28431b81652660..462a9b7d65ee22 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/types.ts @@ -65,10 +65,6 @@ export type OutputRuleAlertRest = RuleAlertParamsRest & { updated_by: string | undefined | null; }; -export type OutputRuleES = OutputRuleAlertRest & { - status: 'open' | 'closed'; -}; - export type UpdateRuleAlertParamsRest = Partial & { id: string | undefined; rule_id: RuleAlertParams['ruleId'] | undefined; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts index 19c8d5ccc87ca2..fc50e54e06e4e1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.test.ts @@ -14,6 +14,9 @@ import { singleBulkCreate, singleSearchAfter, searchAfterAndBulkCreate, + buildEventTypeSignal, + buildSignal, + buildRule, } from './utils'; import { sampleDocNoSortId, @@ -26,8 +29,12 @@ import { repeatedSearchResultsWithSortId, sampleBulkCreateDuplicateResult, sampleRuleGuid, + sampleRule, + sampleIdGuid, } from './__mocks__/es_results'; import { DEFAULT_SIGNALS_INDEX } from '../../../../common/constants'; +import { OutputRuleAlertRest } from './types'; +import { Signal } from '../../types'; const mockLogger: Logger = { log: jest.fn(), @@ -51,10 +58,9 @@ describe('utils', () => { }); describe('buildBulkBody', () => { test('if bulk body builds well-defined body', () => { - const fakeUuid = uuid.v4(); - const sampleParams = sampleRuleAlertParams(undefined); + const sampleParams = sampleRuleAlertParams(); const fakeSignalSourceHit = buildBulkBody({ - doc: sampleDocNoSortId(fakeUuid), + doc: sampleDocNoSortId(), ruleParams: sampleParams, id: sampleRuleGuid, name: 'rule-name', @@ -65,18 +71,225 @@ describe('utils', () => { }); // Timestamp will potentially always be different so remove it for the test delete fakeSignalSourceHit['@timestamp']; - if (fakeSignalSourceHit.signal.parent) { - delete fakeSignalSourceHit.signal.parent.id; - } expect(fakeSignalSourceHit).toEqual({ someKey: 'someValue', + event: { + kind: 'signal', + }, signal: { parent: { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + status: 'open', + rule: { + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + rule_id: 'rule-1', + false_positives: [], + max_signals: 10000, + risk_score: 50, + output_index: '.siem-signals', + description: 'Detecting root and admin users', + from: 'now-6m', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + name: 'rule-name', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + severity: 'high', + tags: ['some fake tag'], + type: 'query', + to: 'now', + enabled: true, + created_by: 'elastic', + updated_by: 'elastic', + }, + }, + }); + }); + + test('if bulk body builds original_event if it exists on the event to begin with', () => { + const sampleParams = sampleRuleAlertParams(); + const doc = sampleDocNoSortId(); + doc._source.event = { + action: 'socket_opened', + module: 'system', + dataset: 'socket', + kind: 'event', + }; + const fakeSignalSourceHit = buildBulkBody({ + doc, + ruleParams: sampleParams, + id: sampleRuleGuid, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + }); + // Timestamp will potentially always be different so remove it for the test + delete fakeSignalSourceHit['@timestamp']; + expect(fakeSignalSourceHit).toEqual({ + someKey: 'someValue', + event: { + action: 'socket_opened', + dataset: 'socket', + kind: 'signal', + module: 'system', + }, + signal: { + original_event: { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }, + parent: { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + status: 'open', + rule: { + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + rule_id: 'rule-1', + false_positives: [], + max_signals: 10000, + risk_score: 50, + output_index: '.siem-signals', + description: 'Detecting root and admin users', + from: 'now-6m', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + name: 'rule-name', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + severity: 'high', + tags: ['some fake tag'], + type: 'query', + to: 'now', + enabled: true, + created_by: 'elastic', + updated_by: 'elastic', + }, + }, + }); + }); + + test('if bulk body builds original_event if it exists on the event to begin with but no kind information', () => { + const sampleParams = sampleRuleAlertParams(); + const doc = sampleDocNoSortId(); + doc._source.event = { + action: 'socket_opened', + module: 'system', + dataset: 'socket', + }; + const fakeSignalSourceHit = buildBulkBody({ + doc, + ruleParams: sampleParams, + id: sampleRuleGuid, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + }); + // Timestamp will potentially always be different so remove it for the test + delete fakeSignalSourceHit['@timestamp']; + expect(fakeSignalSourceHit).toEqual({ + someKey: 'someValue', + event: { + action: 'socket_opened', + dataset: 'socket', + kind: 'signal', + module: 'system', + }, + signal: { + original_event: { + action: 'socket_opened', + dataset: 'socket', + module: 'system', + }, + parent: { + id: sampleIdGuid, type: 'event', index: 'myFakeSignalIndex', depth: 1, }, original_time: 'someTimeStamp', + status: 'open', + rule: { + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + rule_id: 'rule-1', + false_positives: [], + max_signals: 10000, + risk_score: 50, + output_index: '.siem-signals', + description: 'Detecting root and admin users', + from: 'now-6m', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + name: 'rule-name', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + severity: 'high', + tags: ['some fake tag'], + type: 'query', + to: 'now', + enabled: true, + created_by: 'elastic', + updated_by: 'elastic', + }, + }, + }); + }); + + test('if bulk body builds original_event if it exists on the event to begin with with only kind information', () => { + const sampleParams = sampleRuleAlertParams(); + const doc = sampleDocNoSortId(); + doc._source.event = { + kind: 'event', + }; + const fakeSignalSourceHit = buildBulkBody({ + doc, + ruleParams: sampleParams, + id: sampleRuleGuid, + name: 'rule-name', + createdBy: 'elastic', + updatedBy: 'elastic', + interval: '5m', + enabled: true, + }); + // Timestamp will potentially always be different so remove it for the test + delete fakeSignalSourceHit['@timestamp']; + expect(fakeSignalSourceHit).toEqual({ + someKey: 'someValue', + event: { + kind: 'signal', + }, + signal: { + original_event: { + kind: 'event', + }, + parent: { + id: sampleIdGuid, + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + status: 'open', rule: { id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', rule_id: 'rule-1', @@ -96,7 +309,6 @@ describe('utils', () => { severity: 'high', tags: ['some fake tag'], type: 'query', - status: 'open', to: 'now', enabled: true, created_by: 'elastic', @@ -213,8 +425,7 @@ describe('utils', () => { }); }); test('create successful bulk create', async () => { - const fakeUuid = uuid.v4(); - const sampleParams = sampleRuleAlertParams(undefined); + const sampleParams = sampleRuleAlertParams(); const sampleSearchResult = sampleDocSearchResultsNoSortId; mockService.callCluster.mockReturnValueOnce({ took: 100, @@ -226,7 +437,7 @@ describe('utils', () => { ], }); const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult(fakeUuid), + someResult: sampleSearchResult(), ruleParams: sampleParams, services: mockService, logger: mockLogger, @@ -241,8 +452,7 @@ describe('utils', () => { expect(successfulsingleBulkCreate).toEqual(true); }); test('create successful bulk create with docs with no versioning', async () => { - const fakeUuid = uuid.v4(); - const sampleParams = sampleRuleAlertParams(undefined); + const sampleParams = sampleRuleAlertParams(); const sampleSearchResult = sampleDocSearchResultsNoSortIdNoVersion; mockService.callCluster.mockReturnValueOnce({ took: 100, @@ -254,7 +464,7 @@ describe('utils', () => { ], }); const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult(fakeUuid), + someResult: sampleSearchResult(), ruleParams: sampleParams, services: mockService, logger: mockLogger, @@ -269,7 +479,7 @@ describe('utils', () => { expect(successfulsingleBulkCreate).toEqual(true); }); test('create unsuccessful bulk create due to empty search results', async () => { - const sampleParams = sampleRuleAlertParams(undefined); + const sampleParams = sampleRuleAlertParams(); const sampleSearchResult = sampleEmptyDocSearchResults; mockService.callCluster.mockReturnValue(false); const successfulsingleBulkCreate = await singleBulkCreate({ @@ -288,12 +498,11 @@ describe('utils', () => { expect(successfulsingleBulkCreate).toEqual(true); }); test('create successful bulk create when bulk create has errors', async () => { - const fakeUuid = uuid.v4(); - const sampleParams = sampleRuleAlertParams(undefined); + const sampleParams = sampleRuleAlertParams(); const sampleSearchResult = sampleDocSearchResultsNoSortId; mockService.callCluster.mockReturnValue(sampleBulkCreateDuplicateResult); const successfulsingleBulkCreate = await singleBulkCreate({ - someResult: sampleSearchResult(fakeUuid), + someResult: sampleSearchResult(), ruleParams: sampleParams, services: mockService, logger: mockLogger, @@ -312,7 +521,7 @@ describe('utils', () => { describe('singleSearchAfter', () => { test('if singleSearchAfter works without a given sort id', async () => { let searchAfterSortId; - const sampleParams = sampleRuleAlertParams(undefined); + const sampleParams = sampleRuleAlertParams(); mockService.callCluster.mockReturnValue(sampleDocSearchResultsNoSortId); await expect( singleSearchAfter({ @@ -326,7 +535,7 @@ describe('utils', () => { }); test('if singleSearchAfter works with a given sort id', async () => { const searchAfterSortId = '1234567891111'; - const sampleParams = sampleRuleAlertParams(undefined); + const sampleParams = sampleRuleAlertParams(); mockService.callCluster.mockReturnValue(sampleDocSearchResultsWithSortId); const searchAfterResult = await singleSearchAfter({ searchAfterSortId, @@ -339,7 +548,7 @@ describe('utils', () => { }); test('if singleSearchAfter throws error', async () => { const searchAfterSortId = '1234567891111'; - const sampleParams = sampleRuleAlertParams(undefined); + const sampleParams = sampleRuleAlertParams(); mockService.callCluster.mockImplementation(async () => { throw Error('Fake Error'); }); @@ -356,7 +565,7 @@ describe('utils', () => { }); describe('searchAfterAndBulkCreate', () => { test('if successful with empty search results', async () => { - const sampleParams = sampleRuleAlertParams(undefined); + const sampleParams = sampleRuleAlertParams(); const result = await searchAfterAndBulkCreate({ someResult: sampleEmptyDocSearchResults, ruleParams: sampleParams, @@ -446,8 +655,7 @@ describe('utils', () => { expect(result).toEqual(false); }); test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids', async () => { - const sampleParams = sampleRuleAlertParams(undefined); - const someUuid = uuid.v4(); + const sampleParams = sampleRuleAlertParams(); mockService.callCluster.mockReturnValueOnce({ took: 100, errors: false, @@ -458,7 +666,7 @@ describe('utils', () => { ], }); const result = await searchAfterAndBulkCreate({ - someResult: sampleDocSearchResultsNoSortId(someUuid), + someResult: sampleDocSearchResultsNoSortId(), ruleParams: sampleParams, services: mockService, logger: mockLogger, @@ -475,8 +683,7 @@ describe('utils', () => { expect(result).toEqual(false); }); test('if unsuccessful iteration of searchAfterAndBulkCreate due to empty sort ids and 0 total hits', async () => { - const sampleParams = sampleRuleAlertParams(undefined); - const someUuid = uuid.v4(); + const sampleParams = sampleRuleAlertParams(); mockService.callCluster.mockReturnValueOnce({ took: 100, errors: false, @@ -487,7 +694,7 @@ describe('utils', () => { ], }); const result = await searchAfterAndBulkCreate({ - someResult: sampleDocSearchResultsNoSortIdNoHits(someUuid), + someResult: sampleDocSearchResultsNoSortIdNoHits(), ruleParams: sampleParams, services: mockService, logger: mockLogger, @@ -504,7 +711,6 @@ describe('utils', () => { }); test('if successful iteration of while loop with maxDocs and search after returns results with no sort ids', async () => { const sampleParams = sampleRuleAlertParams(10); - const oneGuid = uuid.v4(); const someGuids = Array.from({ length: 4 }).map(x => uuid.v4()); mockService.callCluster .mockReturnValueOnce({ @@ -516,7 +722,7 @@ describe('utils', () => { }, ], }) - .mockReturnValueOnce(sampleDocSearchResultsNoSortId(oneGuid)); + .mockReturnValueOnce(sampleDocSearchResultsNoSortId()); const result = await searchAfterAndBulkCreate({ someResult: repeatedSearchResultsWithSortId(4, 1, someGuids), ruleParams: sampleParams, @@ -596,4 +802,276 @@ describe('utils', () => { expect(result).toEqual(false); }); }); + + describe('buildEventTypeSignal', () => { + test('it returns the event appended of kind signal if it does not exist', () => { + const doc = sampleDocNoSortId(); + delete doc._source.event; + const eventType = buildEventTypeSignal(doc); + const expected: object = { kind: 'signal' }; + expect(eventType).toEqual(expected); + }); + + test('it returns the event appended of kind signal if it is an empty object', () => { + const doc = sampleDocNoSortId(); + doc._source.event = {}; + const eventType = buildEventTypeSignal(doc); + const expected: object = { kind: 'signal' }; + expect(eventType).toEqual(expected); + }); + + test('it returns the event with kind signal and other properties if they exist', () => { + const doc = sampleDocNoSortId(); + doc._source.event = { + action: 'socket_opened', + module: 'system', + dataset: 'socket', + }; + const eventType = buildEventTypeSignal(doc); + const expected: object = { + action: 'socket_opened', + module: 'system', + dataset: 'socket', + kind: 'signal', + }; + expect(eventType).toEqual(expected); + }); + }); + + describe('buildSignal', () => { + test('it builds a signal as expected without original_event if event does not exist', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + delete doc._source.event; + const rule: Partial = sampleRule(); + const signal = buildSignal(doc, rule); + const expected: Signal = { + parent: { + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + status: 'open', + rule: { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + to: 'now', + type: 'query', + }, + }; + expect(signal).toEqual(expected); + }); + + test('it builds a signal as expected with original_event if is present', () => { + const doc = sampleDocNoSortId('d5e8eb51-a6a0-456d-8a15-4b79bfec3d71'); + doc._source.event = { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }; + const rule: Partial = sampleRule(); + const signal = buildSignal(doc, rule); + const expected: Signal = { + parent: { + id: 'd5e8eb51-a6a0-456d-8a15-4b79bfec3d71', + type: 'event', + index: 'myFakeSignalIndex', + depth: 1, + }, + original_time: 'someTimeStamp', + original_event: { + action: 'socket_opened', + dataset: 'socket', + kind: 'event', + module: 'system', + }, + status: 'open', + rule: { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + risk_score: 50, + rule_id: 'rule-1', + language: 'kuery', + max_signals: 100, + name: 'Detect Root/Admin Users', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + severity: 'high', + updated_by: 'elastic', + tags: [], + to: 'now', + type: 'query', + }, + }; + expect(signal).toEqual(expected); + }); + }); + + describe('buildRule', () => { + test('it builds a rule as expected with filters present', () => { + const ruleParams = sampleRuleAlertParams(); + ruleParams.filters = [ + { + query: 'host.name: Rebecca', + }, + { + query: 'host.name: Evan', + }, + { + query: 'host.name: Braden', + }, + ]; + const rule = buildRule({ + ruleParams, + name: 'some-name', + id: sampleRuleGuid, + enabled: false, + createdBy: 'elastic', + updatedBy: 'elastic', + interval: 'some interval', + }); + const expected: Partial = { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: false, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: 'some interval', + language: 'kuery', + max_signals: 10000, + name: 'some-name', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + risk_score: 50, + rule_id: 'rule-1', + severity: 'high', + tags: ['some fake tag'], + to: 'now', + type: 'query', + updated_by: 'elastic', + filters: [ + { + query: 'host.name: Rebecca', + }, + { + query: 'host.name: Evan', + }, + { + query: 'host.name: Braden', + }, + ], + }; + expect(rule).toEqual(expected); + }); + + test('it omits a null value such as if enabled is null if is present', () => { + const ruleParams = sampleRuleAlertParams(); + ruleParams.filters = undefined; + const rule = buildRule({ + ruleParams, + name: 'some-name', + id: sampleRuleGuid, + enabled: true, + createdBy: 'elastic', + updatedBy: 'elastic', + interval: 'some interval', + }); + const expected: Partial = { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: 'some interval', + language: 'kuery', + max_signals: 10000, + name: 'some-name', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + risk_score: 50, + rule_id: 'rule-1', + severity: 'high', + tags: ['some fake tag'], + to: 'now', + type: 'query', + updated_by: 'elastic', + }; + expect(rule).toEqual(expected); + }); + + test('it omits a null value such as if filters is undefined if is present', () => { + const ruleParams = sampleRuleAlertParams(); + ruleParams.filters = undefined; + const rule = buildRule({ + ruleParams, + name: 'some-name', + id: sampleRuleGuid, + enabled: true, + createdBy: 'elastic', + updatedBy: 'elastic', + interval: 'some interval', + }); + const expected: Partial = { + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: 'some interval', + language: 'kuery', + max_signals: 10000, + name: 'some-name', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://google.com'], + risk_score: 50, + rule_id: 'rule-1', + severity: 'high', + tags: ['some fake tag'], + to: 'now', + type: 'query', + updated_by: 'elastic', + }; + expect(rule).toEqual(expected); + }); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts index ba3f310c886ceb..c3988b8fea458c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/alerts/utils.ts @@ -14,7 +14,7 @@ import { SignalSearchResponse, BulkResponse, RuleTypeParams, - OutputRuleES, + OutputRuleAlertRest, } from './types'; import { buildEventsSearchQuery } from './build_events_query'; @@ -36,10 +36,9 @@ export const buildRule = ({ createdBy, updatedBy, interval, -}: BuildRuleParams): Partial => { - return pickBy((value: unknown) => value != null, { +}: BuildRuleParams): Partial => { + return pickBy((value: unknown) => value != null, { id, - status: 'open', rule_id: ruleParams.ruleId, false_positives: ruleParams.falsePositives, saved_id: ruleParams.savedId, @@ -68,8 +67,8 @@ export const buildRule = ({ }); }; -export const buildSignal = (doc: SignalSourceHit, rule: Partial): Signal => { - return { +export const buildSignal = (doc: SignalSourceHit, rule: Partial): Signal => { + const signal: Signal = { parent: { id: doc._id, type: 'event', @@ -77,8 +76,13 @@ export const buildSignal = (doc: SignalSourceHit, rule: Partial): depth: 1, }, original_time: doc._source['@timestamp'], + status: 'open', rule, }; + if (doc._source.event != null) { + return { ...signal, original_event: doc._source.event }; + } + return signal; }; interface BuildBulkBodyParams { @@ -92,6 +96,14 @@ interface BuildBulkBodyParams { enabled: boolean; } +export const buildEventTypeSignal = (doc: SignalSourceHit): object => { + if (doc._source.event != null && doc._source.event instanceof Object) { + return { ...doc._source.event, kind: 'signal' }; + } else { + return { kind: 'signal' }; + } +}; + // format search_after result for signals index. export const buildBulkBody = ({ doc, @@ -113,9 +125,11 @@ export const buildBulkBody = ({ interval, }); const signal = buildSignal(doc, rule); + const event = buildEventTypeSignal(doc); const signalHit: SignalHit = { ...doc._source, '@timestamp': new Date().toISOString(), + event, signal, }; return signalHit; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals_mapping.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals_mapping.json index a95c9625a0003c..dfe3caed5b71a9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals_mapping.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals_mapping.json @@ -107,6 +107,78 @@ }, "original_time": { "type": "date" + }, + "original_event": { + "properties": { + "action": { + "type": "keyword" + }, + "category": { + "type": "keyword" + }, + "code": { + "type": "keyword" + }, + "created": { + "type": "date" + }, + "dataset": { + "type": "keyword" + }, + "duration": { + "type": "long" + }, + "end": { + "type": "date" + }, + "hash": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + }, + "module": { + "type": "keyword" + }, + "original": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "outcome": { + "type": "keyword" + }, + "provider": { + "type": "keyword" + }, + "risk_score": { + "type": "float" + }, + "risk_score_norm": { + "type": "float" + }, + "sequence": { + "type": "long" + }, + "severity": { + "type": "long" + }, + "start": { + "type": "date" + }, + "timezone": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" } } }, diff --git a/x-pack/legacy/plugins/siem/server/lib/types.ts b/x-pack/legacy/plugins/siem/server/lib/types.ts index 9c0059d0d109db..c53805dc95fe79 100644 --- a/x-pack/legacy/plugins/siem/server/lib/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/types.ts @@ -23,7 +23,7 @@ import { Note } from './note/saved_object'; import { PinnedEvent } from './pinned_event/saved_object'; import { Timeline } from './timeline/saved_object'; import { TLS } from './tls'; -import { RuleAlertParamsRest } from './detection_engine/alerts/types'; +import { SearchTypes, OutputRuleAlertRest } from './detection_engine/alerts/types'; export * from './hosts'; @@ -66,7 +66,7 @@ export interface SiemContext { } export interface Signal { - rule: Partial; + rule: Partial; parent: { id: string; type: string; @@ -74,10 +74,13 @@ export interface Signal { depth: number; }; original_time: string; + original_event?: SearchTypes; + status: 'open' | 'closed'; } export interface SignalHit { '@timestamp': string; + event: object; signal: Partial; } diff --git a/x-pack/legacy/plugins/spaces/index.ts b/x-pack/legacy/plugins/spaces/index.ts index 598d115a39e499..8f995d3c12c2aa 100644 --- a/x-pack/legacy/plugins/spaces/index.ts +++ b/x-pack/legacy/plugins/spaces/index.ts @@ -126,7 +126,6 @@ export const spaces = (kibana: Record) => kibanaIndex: config.get('kibana.index'), }, savedObjects: server.savedObjects, - usage: server.usage, tutorial: { addScopedTutorialContextFactory: server.addScopedTutorialContextFactory, }, diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts b/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts index 16100089a9e56f..b465392a50ae14 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts +++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts @@ -104,7 +104,7 @@ export function createSearchItems( const filters = fs.length ? fs : []; const esQueryConfigs = esQuery.getEsQueryConfig(config); - combinedQuery = esQuery.buildEsQuery(indexPattern || null, [query], filters, esQueryConfigs); + combinedQuery = esQuery.buildEsQuery(indexPattern, [query], filters, esQueryConfigs); } return { diff --git a/x-pack/legacy/plugins/upgrade_assistant/index.ts b/x-pack/legacy/plugins/upgrade_assistant/index.ts index f1762498246c76..1be728d2633728 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/index.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/index.ts @@ -43,11 +43,12 @@ export function upgradeAssistant(kibana: any) { init(server: Legacy.Server) { // Add server routes and initialize the plugin here const instance = plugin({} as any); + const { usageCollection } = server.newPlatform.setup.plugins; instance.setup(server.newPlatform.setup.core, { + usageCollection, __LEGACY: { // Legacy objects events: server.events, - usage: server.usage, savedObjects: server.savedObjects, // Legacy functions diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.test.ts index 4c378ba25430e4..5f95f6e9fd5559 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.test.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_open_apis.test.ts @@ -15,12 +15,6 @@ import { upsertUIOpenOption } from './es_ui_open_apis'; describe('Upgrade Assistant Telemetry SavedObject UIOpen', () => { const mockIncrementCounter = jest.fn(); const server = jest.fn().mockReturnValue({ - usage: { - collectorSet: { - makeUsageCollector: {}, - register: {}, - }, - }, savedObjects: { getSavedObjectsRepository: jest.fn().mockImplementation(() => { return { diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.test.ts index 26302de74743f1..3f2c80f7d6b751 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.test.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/es_ui_reindex_apis.test.ts @@ -15,12 +15,6 @@ import { upsertUIReindexOption } from './es_ui_reindex_apis'; describe('Upgrade Assistant Telemetry SavedObject UIReindex', () => { const mockIncrementCounter = jest.fn(); const server = jest.fn().mockReturnValue({ - usage: { - collectorSet: { - makeUsageCollector: {}, - register: {}, - }, - }, savedObjects: { getSavedObjectsRepository: jest.fn().mockImplementation(() => { return { diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/index.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/index.ts index 7d1d734748a82c..898da4ab0073b5 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/index.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { makeUpgradeAssistantUsageCollector } from './usage_collector'; +export { registerUpgradeAssistantUsageCollector } from './usage_collector'; diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.test.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.test.ts index f0553578b86c8e..27a0eef0d16f6f 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.test.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as usageCollector from './usage_collector'; +import { registerUpgradeAssistantUsageCollector } from './usage_collector'; /** * Since these route callbacks are so thin, these serve simply as integration tests @@ -16,15 +16,16 @@ describe('Upgrade Assistant Usage Collector', () => { let registerStub: any; let server: any; let callClusterStub: any; + let usageCollection: any; beforeEach(() => { makeUsageCollectorStub = jest.fn(); registerStub = jest.fn(); + usageCollection = { + makeUsageCollector: makeUsageCollectorStub, + registerCollector: registerStub, + }; server = jest.fn().mockReturnValue({ - usage: { - collectorSet: { makeUsageCollector: makeUsageCollectorStub, register: registerStub }, - register: {}, - }, savedObjects: { getSavedObjectsRepository: jest.fn().mockImplementation(() => { return { @@ -55,20 +56,20 @@ describe('Upgrade Assistant Usage Collector', () => { }); }); - describe('makeUpgradeAssistantUsageCollector', () => { - it('should call collectorSet.register', () => { - usageCollector.makeUpgradeAssistantUsageCollector(server()); + describe('registerUpgradeAssistantUsageCollector', () => { + it('should registerCollector', () => { + registerUpgradeAssistantUsageCollector(usageCollection, server()); expect(registerStub).toHaveBeenCalledTimes(1); }); it('should call makeUsageCollector with type = upgrade-assistant', () => { - usageCollector.makeUpgradeAssistantUsageCollector(server()); + registerUpgradeAssistantUsageCollector(usageCollection, server()); expect(makeUsageCollectorStub).toHaveBeenCalledTimes(1); expect(makeUsageCollectorStub.mock.calls[0][0].type).toBe('upgrade-assistant-telemetry'); }); it('fetchUpgradeAssistantMetrics should return correct info', async () => { - usageCollector.makeUpgradeAssistantUsageCollector(server()); + registerUpgradeAssistantUsageCollector(usageCollection, server()); const upgradeAssistantStats = await makeUsageCollectorStub.mock.calls[0][0].fetch( callClusterStub ); diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.ts index 47a2cd5d51fd4e..99c0441063ce62 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/lib/telemetry/usage_collector.ts @@ -7,6 +7,7 @@ import { set } from 'lodash'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { SavedObjectsRepository } from 'src/core/server/saved_objects/service/lib/repository'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { UPGRADE_ASSISTANT_DOC_ID, UPGRADE_ASSISTANT_TYPE, @@ -97,12 +98,15 @@ export async function fetchUpgradeAssistantMetrics( }; } -export function makeUpgradeAssistantUsageCollector(server: ServerShim) { - const upgradeAssistantUsageCollector = server.usage.collectorSet.makeUsageCollector({ +export function registerUpgradeAssistantUsageCollector( + usageCollection: UsageCollectionSetup, + server: ServerShim +) { + const upgradeAssistantUsageCollector = usageCollection.makeUsageCollector({ type: UPGRADE_ASSISTANT_TYPE, isReady: () => true, fetch: async (callCluster: any) => fetchUpgradeAssistantMetrics(callCluster, server), }); - server.usage.collectorSet.register(upgradeAssistantUsageCollector); + usageCollection.registerCollector(upgradeAssistantUsageCollector); } diff --git a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/plugin.ts b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/plugin.ts index 7bc33142ca3213..3d4247ffe70bbd 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/plugin.ts +++ b/x-pack/legacy/plugins/upgrade_assistant/server/np_ready/plugin.ts @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ import { Plugin, CoreSetup, CoreStart } from 'src/core/server'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { ServerShim, ServerShimWithRouter } from './types'; import { credentialStoreFactory } from './lib/reindexing/credential_store'; -import { makeUpgradeAssistantUsageCollector } from './lib/telemetry'; +import { registerUpgradeAssistantUsageCollector } from './lib/telemetry'; import { registerClusterCheckupRoutes } from './routes/cluster_checkup'; import { registerDeprecationLoggingRoutes } from './routes/deprecation_logging'; import { registerReindexIndicesRoutes, registerReindexWorker } from './routes/reindex_indices'; @@ -14,7 +15,10 @@ import { registerReindexIndicesRoutes, registerReindexWorker } from './routes/re import { registerTelemetryRoutes } from './routes/telemetry'; export class UpgradeAssistantServerPlugin implements Plugin { - setup({ http }: CoreSetup, { __LEGACY }: { __LEGACY: ServerShim }) { + setup( + { http }: CoreSetup, + { __LEGACY, usageCollection }: { usageCollection: UsageCollectionSetup; __LEGACY: ServerShim } + ) { const router = http.createRouter(); const shimWithRouter: ServerShimWithRouter = { ...__LEGACY, router }; registerClusterCheckupRoutes(shimWithRouter); @@ -33,7 +37,7 @@ export class UpgradeAssistantServerPlugin implements Plugin; diff --git a/x-pack/legacy/plugins/uptime/index.ts b/x-pack/legacy/plugins/uptime/index.ts index c2fb0a2ad6bd70..690807cc91e27f 100644 --- a/x-pack/legacy/plugins/uptime/index.ts +++ b/x-pack/legacy/plugins/uptime/index.ts @@ -37,6 +37,8 @@ export const uptime = (kibana: any) => const initializerContext = {} as PluginInitializerContext; const { savedObjects } = server; const { elasticsearch, xpack_main } = server.plugins; + const { usageCollection } = server.newPlatform.setup.plugins; + plugin(initializerContext).setup( { route: (arg: any) => server.route(arg), @@ -44,7 +46,7 @@ export const uptime = (kibana: any) => { elasticsearch, savedObjects, - usageCollector: server.usage, + usageCollection, xpack: xpack_main, } ); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot.test.tsx index 40e3daae671854..193f37c8fe56b5 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot.test.tsx @@ -6,21 +6,19 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { Snapshot as SnapshotType } from '../../../../common/graphql/types'; -import { SnapshotComponent } from '../snapshot'; +import { Snapshot } from '../../../../common/runtime_types'; +import { PresentationalComponent } from '../snapshot'; describe('Snapshot component', () => { - const snapshot: SnapshotType = { - counts: { - up: 8, - down: 2, - mixed: 0, - total: 10, - }, + const snapshot: Snapshot = { + up: 8, + down: 2, + mixed: 0, + total: 10, }; it('renders without errors', () => { - const wrapper = shallowWithIntl(); + const wrapper = shallowWithIntl(); expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx index f529c9cd9d53fc..da392660eb70e8 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/kuery_bar/index.tsx @@ -9,14 +9,17 @@ import { uniqueId, startsWith } from 'lodash'; import { EuiCallOut } from '@elastic/eui'; import styled from 'styled-components'; import { FormattedMessage } from '@kbn/i18n/react'; -import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; -import { AutocompleteProviderRegister, AutocompleteSuggestion } from 'src/plugins/data/public'; -import { StaticIndexPattern } from 'src/legacy/core_plugins/data/public/index_patterns/index_patterns'; import { Typeahead } from './typeahead'; import { getIndexPattern } from '../../../lib/adapters/index_pattern'; import { UptimeSettingsContext } from '../../../contexts'; import { useUrlParams } from '../../../hooks'; import { toStaticIndexPattern } from '../../../lib/helper'; +import { + AutocompleteProviderRegister, + AutocompleteSuggestion, + esKuery, + IIndexPattern, +} from '../../../../../../../../src/plugins/data/public'; const Container = styled.div` margin-bottom: 10px; @@ -27,15 +30,15 @@ interface State { isLoadingIndexPattern: boolean; } -function convertKueryToEsQuery(kuery: string, indexPattern: unknown) { - const ast = fromKueryExpression(kuery); - return toElasticsearchQuery(ast, indexPattern); +function convertKueryToEsQuery(kuery: string, indexPattern: IIndexPattern) { + const ast = esKuery.fromKueryExpression(kuery); + return esKuery.toElasticsearchQuery(ast, indexPattern); } function getSuggestions( query: string, selectionStart: number, - apmIndexPattern: StaticIndexPattern, + apmIndexPattern: IIndexPattern, autocomplete: Pick ) { const autocompleteProvider = autocomplete.getProvider('kuery'); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx index ddc6df14c2adee..e0d282a5348a0f 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx @@ -5,46 +5,69 @@ */ import { EuiSpacer } from '@elastic/eui'; -import React from 'react'; +import React, { useEffect } from 'react'; import { get } from 'lodash'; +import { connect } from 'react-redux'; +import { Snapshot as SnapshotType } from '../../../common/runtime_types'; import { DonutChart } from './charts'; -import { Snapshot as SnapshotType } from '../../../common/graphql/types'; -import { UptimeGraphQLQueryProps, withUptimeGraphQL } from '../higher_order'; -import { snapshotQuery } from '../../queries'; +import { fetchSnapshotCount } from '../../state/actions'; import { ChartWrapper } from './charts/chart_wrapper'; import { SnapshotHeading } from './snapshot_heading'; +import { AppState } from '../../state'; const SNAPSHOT_CHART_WIDTH = 144; const SNAPSHOT_CHART_HEIGHT = 144; -interface SnapshotQueryResult { - snapshot?: SnapshotType; -} - -interface SnapshotProps { +/** + * Props expected from parent components. + */ +interface OwnProps { + dateRangeStart: string; + dateRangeEnd: string; + filters?: string; /** * Height is needed, since by default charts takes height of 100% */ height?: string; + statusFilter?: string; } -export type SnapshotComponentProps = SnapshotProps & UptimeGraphQLQueryProps; +/** + * Props given by the Redux store based on action input. + */ +interface StoreProps { + count: SnapshotType; + lastRefresh: number; + loading: boolean; +} /** - * This component visualizes a KPI and histogram chart to help users quickly - * glean the status of their uptime environment. - * @param props the props required by the component + * Contains functions that will dispatch actions used + * for this component's lifecyclel + */ +interface DispatchProps { + loadSnapshotCount: typeof fetchSnapshotCount; +} + +/** + * Props used to render the Snapshot component. */ -export const SnapshotComponent = ({ data, loading, height }: SnapshotComponentProps) => ( +type Props = OwnProps & StoreProps & DispatchProps; + +type PresentationalComponentProps = Pick & + Pick; + +export const PresentationalComponent: React.FC = ({ + count, + height, + loading, +}) => ( - (data, 'snapshot.counts.down', 0)} - total={get(data, 'snapshot.counts.total', 0)} - /> + (count, 'down', 0)} total={get(count, 'total', 0)} /> (data, 'snapshot.counts.up', 0)} - down={get(data, 'snapshot.counts.down', 0)} + up={get(count, 'up', 0)} + down={get(count, 'down', 0)} height={SNAPSHOT_CHART_HEIGHT} width={SNAPSHOT_CHART_WIDTH} /> @@ -54,8 +77,55 @@ export const SnapshotComponent = ({ data, loading, height }: SnapshotComponentPr /** * This component visualizes a KPI and histogram chart to help users quickly * glean the status of their uptime environment. + * @param props the props required by the component */ -export const Snapshot = withUptimeGraphQL( - SnapshotComponent, - snapshotQuery -); +export const Container: React.FC = ({ + count, + dateRangeStart, + dateRangeEnd, + filters, + height, + statusFilter, + lastRefresh, + loading, + loadSnapshotCount, +}: Props) => { + useEffect(() => { + loadSnapshotCount(dateRangeStart, dateRangeEnd, filters, statusFilter); + }, [dateRangeStart, dateRangeEnd, filters, lastRefresh, statusFilter]); + return ; +}; + +/** + * Provides state to connected component. + * @param state the root app state + */ +const mapStateToProps = ({ + snapshot: { count, loading }, + ui: { lastRefresh }, +}: AppState): StoreProps => ({ + count, + lastRefresh, + loading, +}); + +/** + * Used for fetching snapshot counts. + * @param dispatch redux-provided action dispatcher + */ +const mapDispatchToProps = (dispatch: any) => ({ + loadSnapshotCount: ( + dateRangeStart: string, + dateRangeEnd: string, + filters?: string, + statusFilter?: string + ): DispatchProps => { + return dispatch(fetchSnapshotCount(dateRangeStart, dateRangeEnd, filters, statusFilter)); + }, +}); + +export const Snapshot = connect( + // @ts-ignore connect is expecting null | undefined for some reason + mapStateToProps, + mapDispatchToProps +)(Container); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx index a58d06ece0ede7..b74bc943dc3eb7 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx @@ -12,6 +12,10 @@ import { Snapshot } from './snapshot'; interface StatusPanelProps { absoluteDateRangeStart: number; absoluteDateRangeEnd: number; + dateRangeStart: string; + dateRangeEnd: string; + filters?: string; + statusFilter?: string; sharedProps: { [key: string]: any }; } @@ -20,12 +24,22 @@ const STATUS_CHART_HEIGHT = '160px'; export const StatusPanel = ({ absoluteDateRangeStart, absoluteDateRangeEnd, + dateRangeStart, + dateRangeEnd, + filters, + statusFilter, sharedProps, }: StatusPanelProps) => ( - + { updateUrl({ dateRangeStart: start, dateRangeEnd: end }); refreshApp(); }} - // @ts-ignore onRefresh is not defined on EuiSuperDatePicker's type yet onRefresh={refreshApp} onRefreshChange={({ isPaused, refreshInterval }: SuperDateRangePickerRefreshChangedEvent) => { updateUrl({ diff --git a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx index ded16c3f8eb2f0..561cc934a9b761 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/overview.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/overview.tsx @@ -6,10 +6,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import React, { Fragment, useContext, useEffect, useState } from 'react'; import styled from 'styled-components'; -import { AutocompleteProviderRegister } from 'src/plugins/data/public'; import { getOverviewPageBreadcrumbs } from '../breadcrumbs'; import { EmptyState, @@ -26,6 +24,7 @@ import { stringifyUrlParams } from '../lib/helper/stringify_url_params'; import { useTrackPageview } from '../../../infra/public'; import { getIndexPattern } from '../lib/adapters/index_pattern'; import { combineFiltersAndUserSearch, stringifyKueries, toStaticIndexPattern } from '../lib/helper'; +import { AutocompleteProviderRegister, esKuery } from '../../../../../../src/plugins/data/public'; interface OverviewPageProps { basePath: string; @@ -109,8 +108,8 @@ export const OverviewPage = ({ if (indexPattern) { const staticIndexPattern = toStaticIndexPattern(indexPattern); const combinedFilterString = combineFiltersAndUserSearch(filterQueryString, kueryString); - const ast = fromKueryExpression(combinedFilterString); - const elasticsearchQuery = toElasticsearchQuery(ast, staticIndexPattern); + const ast = esKuery.fromKueryExpression(combinedFilterString); + const elasticsearchQuery = esKuery.toElasticsearchQuery(ast, staticIndexPattern); filters = JSON.stringify(elasticsearchQuery); } } @@ -151,6 +150,10 @@ export const OverviewPage = ({ diff --git a/x-pack/legacy/plugins/uptime/public/queries/index.ts b/x-pack/legacy/plugins/uptime/public/queries/index.ts index d680ec6c543c4e..b86522c03aba8c 100644 --- a/x-pack/legacy/plugins/uptime/public/queries/index.ts +++ b/x-pack/legacy/plugins/uptime/public/queries/index.ts @@ -10,4 +10,3 @@ export { monitorChartsQuery, monitorChartsQueryString } from './monitor_charts_q export { monitorPageTitleQuery } from './monitor_page_title_query'; export { monitorStatusBarQuery, monitorStatusBarQueryString } from './monitor_status_bar_query'; export { pingsQuery, pingsQueryString } from './pings_query'; -export { snapshotQuery, snapshotQueryString } from './snapshot_query'; diff --git a/x-pack/legacy/plugins/uptime/public/queries/snapshot_query.ts b/x-pack/legacy/plugins/uptime/public/queries/snapshot_query.ts deleted file mode 100644 index 2db226876d2202..00000000000000 --- a/x-pack/legacy/plugins/uptime/public/queries/snapshot_query.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const snapshotQueryString = ` -query Snapshot( - $dateRangeStart: String! - $dateRangeEnd: String! - $filters: String - $statusFilter: String -) { - snapshot: getSnapshot( - dateRangeStart: $dateRangeStart - dateRangeEnd: $dateRangeEnd - filters: $filters - statusFilter: $statusFilter - ) { - counts { - down - mixed - up - total - } - } -} -`; - -export const snapshotQuery = gql` - ${snapshotQueryString} -`; diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/index.ts b/x-pack/legacy/plugins/uptime/public/state/actions/index.ts index 1a33812ca85662..6b896b07bb066c 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './snapshot'; export * from './ui'; diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/snapshot.ts b/x-pack/legacy/plugins/uptime/public/state/actions/snapshot.ts new file mode 100644 index 00000000000000..fe87a6a5960eef --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/actions/snapshot.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Snapshot } from '../../../common/runtime_types'; +export const FETCH_SNAPSHOT_COUNT = 'FETCH_SNAPSHOT_COUNT'; +export const FETCH_SNAPSHOT_COUNT_FAIL = 'FETCH_SNAPSHOT_COUNT_FAIL'; +export const FETCH_SNAPSHOT_COUNT_SUCCESS = 'FETCH_SNAPSHOT_COUNT_SUCCESS'; + +export interface GetSnapshotPayload { + dateRangeStart: string; + dateRangeEnd: string; + filters?: string; + statusFilter?: string; +} + +interface GetSnapshotCountFetchAction { + type: typeof FETCH_SNAPSHOT_COUNT; + payload: GetSnapshotPayload; +} + +interface GetSnapshotCountSuccessAction { + type: typeof FETCH_SNAPSHOT_COUNT_SUCCESS; + payload: Snapshot; +} + +interface GetSnapshotCountFailAction { + type: typeof FETCH_SNAPSHOT_COUNT_FAIL; + payload: Error; +} + +export type SnapshotActionTypes = + | GetSnapshotCountFetchAction + | GetSnapshotCountSuccessAction + | GetSnapshotCountFailAction; + +export const fetchSnapshotCount = ( + dateRangeStart: string, + dateRangeEnd: string, + filters?: string, + statusFilter?: string +): GetSnapshotCountFetchAction => ({ + type: FETCH_SNAPSHOT_COUNT, + payload: { + dateRangeStart, + dateRangeEnd, + filters, + statusFilter, + }, +}); + +export const fetchSnapshotCountFail = (error: Error): GetSnapshotCountFailAction => ({ + type: FETCH_SNAPSHOT_COUNT_FAIL, + payload: error, +}); + +export const fetchSnapshotCountSuccess = (snapshot: Snapshot) => ({ + type: FETCH_SNAPSHOT_COUNT_SUCCESS, + payload: snapshot, +}); diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts b/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts index f0234f903d3d8d..0bb2d8447419b4 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts @@ -6,22 +6,33 @@ export const SET_INTEGRATION_POPOVER_STATE = 'SET_INTEGRATION_POPOVER_STATE'; export const SET_BASE_PATH = 'SET_BASE_PATH'; +export const REFRESH_APP = 'REFRESH_APP'; export interface PopoverState { id: string; open: boolean; } +interface SetBasePathAction { + type: typeof SET_BASE_PATH; + payload: string; +} + interface SetIntegrationPopoverAction { type: typeof SET_INTEGRATION_POPOVER_STATE; payload: PopoverState; } -interface SetBasePathAction { - type: typeof SET_BASE_PATH; - payload: string; +interface TriggerAppRefreshAction { + type: typeof REFRESH_APP; + payload: number; } +export type UiActionTypes = + | SetIntegrationPopoverAction + | SetBasePathAction + | TriggerAppRefreshAction; + export function toggleIntegrationsPopover(popoverState: PopoverState): SetIntegrationPopoverAction { return { type: SET_INTEGRATION_POPOVER_STATE, @@ -36,4 +47,9 @@ export function setBasePath(basePath: string): SetBasePathAction { }; } -export type UiActionTypes = SetIntegrationPopoverAction | SetBasePathAction; +export function triggerAppRefresh(refreshTime: number): TriggerAppRefreshAction { + return { + type: REFRESH_APP, + payload: refreshTime, + }; +} diff --git a/x-pack/legacy/plugins/uptime/public/state/api/__tests__/__snapshots__/snapshot.test.ts.snap b/x-pack/legacy/plugins/uptime/public/state/api/__tests__/__snapshots__/snapshot.test.ts.snap new file mode 100644 index 00000000000000..53716681664c23 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/api/__tests__/__snapshots__/snapshot.test.ts.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot API throws when server response doesn't correspond to expected type 1`] = ` +[Error: Invalid value undefined supplied to : { down: number, mixed: number, total: number, up: number }/down: number +Invalid value undefined supplied to : { down: number, mixed: number, total: number, up: number }/mixed: number +Invalid value undefined supplied to : { down: number, mixed: number, total: number, up: number }/total: number +Invalid value undefined supplied to : { down: number, mixed: number, total: number, up: number }/up: number] +`; diff --git a/x-pack/legacy/plugins/uptime/public/state/api/__tests__/snapshot.test.ts b/x-pack/legacy/plugins/uptime/public/state/api/__tests__/snapshot.test.ts new file mode 100644 index 00000000000000..f5fdfb172bc58c --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/api/__tests__/snapshot.test.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fetchSnapshotCount } from '../snapshot'; + +describe('snapshot API', () => { + let fetchMock: jest.SpyInstance>>; + let mockResponse: Partial; + + beforeEach(() => { + fetchMock = jest.spyOn(window, 'fetch'); + mockResponse = { + ok: true, + json: () => new Promise(r => r({ up: 3, down: 12, mixed: 0, total: 15 })), + }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('calls url with expected params and returns response body on 200', async () => { + fetchMock.mockReturnValue(new Promise(r => r(mockResponse))); + const resp = await fetchSnapshotCount({ + basePath: '', + dateRangeStart: 'now-15m', + dateRangeEnd: 'now', + filters: 'monitor.id:"auto-http-0X21EE76EAC459873F"', + statusFilter: 'up', + }); + expect(fetchMock).toHaveBeenCalledWith( + '/api/uptime/snapshot/count?dateRangeStart=now-15m&dateRangeEnd=now&filters=monitor.id%3A%22auto-http-0X21EE76EAC459873F%22&statusFilter=up' + ); + expect(resp).toEqual({ up: 3, down: 12, mixed: 0, total: 15 }); + }); + + it(`throws when server response doesn't correspond to expected type`, async () => { + mockResponse = { ok: true, json: () => new Promise(r => r({ foo: 'bar' })) }; + fetchMock.mockReturnValue(new Promise(r => r(mockResponse))); + let error: Error | undefined; + try { + await fetchSnapshotCount({ + basePath: '', + dateRangeStart: 'now-15m', + dateRangeEnd: 'now', + filters: 'monitor.id: baz', + statusFilter: 'up', + }); + } catch (e) { + error = e; + } + expect(error).toMatchSnapshot(); + }); + + it('throws an error when response is not ok', async () => { + mockResponse = { ok: false, statusText: 'There was an error fetching your data.' }; + fetchMock.mockReturnValue(new Promise(r => r(mockResponse))); + let error: Error | undefined; + try { + await fetchSnapshotCount({ + basePath: '', + dateRangeStart: 'now-15m', + dateRangeEnd: 'now', + }); + } catch (e) { + error = e; + } + expect(error).toEqual(new Error('There was an error fetching your data.')); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/state/api/index.ts b/x-pack/legacy/plugins/uptime/public/state/api/index.ts index e9b8082b417ba6..a4429868494f15 100644 --- a/x-pack/legacy/plugins/uptime/public/state/api/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/api/index.ts @@ -3,4 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + export * from './monitor'; +export * from './snapshot'; diff --git a/x-pack/legacy/plugins/uptime/public/state/api/snapshot.ts b/x-pack/legacy/plugins/uptime/public/state/api/snapshot.ts new file mode 100644 index 00000000000000..cbfe00a4a87467 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/api/snapshot.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ThrowReporter } from 'io-ts/lib/ThrowReporter'; +import { isRight } from 'fp-ts/lib/Either'; +import { getApiPath } from '../../lib/helper'; +import { SnapshotType, Snapshot } from '../../../common/runtime_types'; + +interface ApiRequest { + basePath: string; + dateRangeStart: string; + dateRangeEnd: string; + filters?: string; + statusFilter?: string; +} + +export const fetchSnapshotCount = async ({ + basePath, + dateRangeStart, + dateRangeEnd, + filters, + statusFilter, +}: ApiRequest): Promise => { + const url = getApiPath(`/api/uptime/snapshot/count`, basePath); + const params = { + dateRangeStart, + dateRangeEnd, + ...(filters && { filters }), + ...(statusFilter && { statusFilter }), + }; + const urlParams = new URLSearchParams(params).toString(); + const response = await fetch(`${url}?${urlParams}`); + if (!response.ok) { + throw new Error(response.statusText); + } + const responseData = await response.json(); + const decoded = SnapshotType.decode(responseData); + ThrowReporter.report(decoded); + if (isRight(decoded)) { + return decoded.right; + } + throw new Error('`getSnapshotCount` response did not correspond to expected type'); +}; diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/index.ts b/x-pack/legacy/plugins/uptime/public/state/effects/index.ts index 92802f2e0c84ad..4eb027d642974f 100644 --- a/x-pack/legacy/plugins/uptime/public/state/effects/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/effects/index.ts @@ -6,7 +6,9 @@ import { fork } from 'redux-saga/effects'; import { fetchMonitorDetailsEffect } from './monitor'; +import { fetchSnapshotCountSaga } from './snapshot'; export function* rootEffect() { yield fork(fetchMonitorDetailsEffect); + yield fork(fetchSnapshotCountSaga); } diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/snapshot.ts b/x-pack/legacy/plugins/uptime/public/state/effects/snapshot.ts new file mode 100644 index 00000000000000..23ac1016d22447 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/effects/snapshot.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { call, put, takeLatest, select } from 'redux-saga/effects'; +import { Action } from 'redux-actions'; +import { + FETCH_SNAPSHOT_COUNT, + GetSnapshotPayload, + fetchSnapshotCountFail, + fetchSnapshotCountSuccess, +} from '../actions'; +import { fetchSnapshotCount } from '../api'; +import { getBasePath } from '../selectors'; + +function* snapshotSaga(action: Action) { + try { + if (!action.payload) { + yield put( + fetchSnapshotCountFail(new Error('Cannot fetch snapshot for undefined parameters.')) + ); + return; + } + const { + payload: { dateRangeStart, dateRangeEnd, filters, statusFilter }, + } = action; + const basePath = yield select(getBasePath); + const response = yield call(fetchSnapshotCount, { + basePath, + dateRangeStart, + dateRangeEnd, + filters, + statusFilter, + }); + yield put(fetchSnapshotCountSuccess(response)); + } catch (error) { + yield put(fetchSnapshotCountFail(error)); + } +} + +export function* fetchSnapshotCountSaga() { + yield takeLatest(FETCH_SNAPSHOT_COUNT, snapshotSaga); +} diff --git a/x-pack/legacy/plugins/uptime/public/state/index.ts b/x-pack/legacy/plugins/uptime/public/state/index.ts index 01cffb636d33ca..e3563c74294d29 100644 --- a/x-pack/legacy/plugins/uptime/public/state/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/index.ts @@ -3,11 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { compose, createStore, applyMiddleware } from 'redux'; import createSagaMiddleware from 'redux-saga'; - -import { rootReducer } from './reducers'; import { rootEffect } from './effects'; +import { rootReducer } from './reducers'; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/snapshot.test.ts.snap b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/snapshot.test.ts.snap new file mode 100644 index 00000000000000..d3a21ec9eece33 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/snapshot.test.ts.snap @@ -0,0 +1,55 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`snapshot reducer appends a current error to existing errors list 1`] = ` +Object { + "count": Object { + "down": 0, + "mixed": 0, + "total": 0, + "up": 0, + }, + "errors": Array [ + [Error: I couldn't get your data because the server denied the request], + ], + "loading": false, +} +`; + +exports[`snapshot reducer changes the count when a snapshot fetch succeeds 1`] = ` +Object { + "count": Object { + "down": 15, + "mixed": 0, + "total": 25, + "up": 10, + }, + "errors": Array [], + "loading": false, +} +`; + +exports[`snapshot reducer sets the state's status to loading during a fetch 1`] = ` +Object { + "count": Object { + "down": 0, + "mixed": 0, + "total": 0, + "up": 0, + }, + "errors": Array [], + "loading": true, +} +`; + +exports[`snapshot reducer updates existing state 1`] = ` +Object { + "count": Object { + "down": 1, + "mixed": 0, + "total": 4, + "up": 3, + }, + "errors": Array [], + "loading": true, +} +`; diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap new file mode 100644 index 00000000000000..75516da18c6336 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ui reducer adds integration popover status to state 1`] = ` +Object { + "basePath": "", + "integrationsPopoverOpen": Object { + "id": "popover-2", + "open": true, + }, + "lastRefresh": 125, +} +`; + +exports[`ui reducer sets the application's base path 1`] = ` +Object { + "basePath": "yyz", + "integrationsPopoverOpen": null, + "lastRefresh": 125, +} +`; + +exports[`ui reducer updates the refresh value 1`] = ` +Object { + "basePath": "", + "integrationsPopoverOpen": null, + "lastRefresh": 125, +} +`; diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/snapshot.test.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/snapshot.test.ts new file mode 100644 index 00000000000000..a4b317d5af1979 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/snapshot.test.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { snapshotReducer } from '../snapshot'; +import { SnapshotActionTypes } from '../../actions'; + +describe('snapshot reducer', () => { + it('updates existing state', () => { + const action: SnapshotActionTypes = { + type: 'FETCH_SNAPSHOT_COUNT', + payload: { + dateRangeStart: 'now-15m', + dateRangeEnd: 'now', + filters: 'foo: bar', + statusFilter: 'up', + }, + }; + expect( + snapshotReducer( + { + count: { down: 1, mixed: 0, total: 4, up: 3 }, + errors: [], + loading: false, + }, + action + ) + ).toMatchSnapshot(); + }); + + it(`sets the state's status to loading during a fetch`, () => { + const action: SnapshotActionTypes = { + type: 'FETCH_SNAPSHOT_COUNT', + payload: { + dateRangeStart: 'now-15m', + dateRangeEnd: 'now', + }, + }; + expect(snapshotReducer(undefined, action)).toMatchSnapshot(); + }); + + it('changes the count when a snapshot fetch succeeds', () => { + const action: SnapshotActionTypes = { + type: 'FETCH_SNAPSHOT_COUNT_SUCCESS', + payload: { + up: 10, + down: 15, + mixed: 0, + total: 25, + }, + }; + expect(snapshotReducer(undefined, action)).toMatchSnapshot(); + }); + + it('appends a current error to existing errors list', () => { + const action: SnapshotActionTypes = { + type: 'FETCH_SNAPSHOT_COUNT_FAIL', + payload: new Error(`I couldn't get your data because the server denied the request`), + }; + expect(snapshotReducer(undefined, action)).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/ui.test.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/ui.test.ts new file mode 100644 index 00000000000000..9be863f0b700d5 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/ui.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { UiActionTypes } from '../../actions'; +import { uiReducer } from '../ui'; + +describe('ui reducer', () => { + it(`sets the application's base path`, () => { + const action: UiActionTypes = { + type: 'SET_BASE_PATH', + payload: 'yyz', + }; + expect( + uiReducer( + { + basePath: 'abc', + integrationsPopoverOpen: null, + lastRefresh: 125, + }, + action + ) + ).toMatchSnapshot(); + }); + + it('adds integration popover status to state', () => { + const action: UiActionTypes = { + type: 'SET_INTEGRATION_POPOVER_STATE', + payload: { + id: 'popover-2', + open: true, + }, + }; + expect( + uiReducer( + { + basePath: '', + integrationsPopoverOpen: null, + lastRefresh: 125, + }, + action + ) + ).toMatchSnapshot(); + }); + + it('updates the refresh value', () => { + const action: UiActionTypes = { + type: 'REFRESH_APP', + payload: 125, + }; + expect(uiReducer(undefined, action)).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts index 186b02395b779b..f0c3d1c2cbecf6 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts @@ -5,10 +5,12 @@ */ import { combineReducers } from 'redux'; -import { uiReducer } from './ui'; import { monitorReducer } from './monitor'; +import { snapshotReducer } from './snapshot'; +import { uiReducer } from './ui'; export const rootReducer = combineReducers({ - ui: uiReducer, monitor: monitorReducer, + snapshot: snapshotReducer, + ui: uiReducer, }); diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/snapshot.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/snapshot.ts new file mode 100644 index 00000000000000..dd9449325f4fb9 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/snapshot.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Snapshot } from '../../../common/runtime_types'; +import { + FETCH_SNAPSHOT_COUNT, + FETCH_SNAPSHOT_COUNT_FAIL, + FETCH_SNAPSHOT_COUNT_SUCCESS, + SnapshotActionTypes, +} from '../actions'; + +export interface SnapshotState { + count: Snapshot; + errors: any[]; + loading: boolean; +} + +const initialState: SnapshotState = { + count: { + down: 0, + mixed: 0, + total: 0, + up: 0, + }, + errors: [], + loading: false, +}; + +export function snapshotReducer(state = initialState, action: SnapshotActionTypes): SnapshotState { + switch (action.type) { + case FETCH_SNAPSHOT_COUNT: + return { + ...state, + loading: true, + }; + case FETCH_SNAPSHOT_COUNT_SUCCESS: + return { + ...state, + count: action.payload, + loading: false, + }; + case FETCH_SNAPSHOT_COUNT_FAIL: + return { + ...state, + errors: [...state.errors, action.payload], + }; + default: + return state; + } +} diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts index d095d6ba961ca7..be95c8fff6bec2 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts @@ -9,20 +9,28 @@ import { PopoverState, SET_INTEGRATION_POPOVER_STATE, SET_BASE_PATH, + REFRESH_APP, } from '../actions/ui'; export interface UiState { integrationsPopoverOpen: PopoverState | null; basePath: string; + lastRefresh: number; } const initialState: UiState = { integrationsPopoverOpen: null, basePath: '', + lastRefresh: Date.now(), }; export function uiReducer(state = initialState, action: UiActionTypes): UiState { switch (action.type) { + case REFRESH_APP: + return { + ...state, + lastRefresh: action.payload, + }; case SET_INTEGRATION_POPOVER_STATE: const popoverState = action.payload; return { diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts new file mode 100644 index 00000000000000..70cd2b19860baf --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getBasePath, isIntegrationsPopupOpen } from '../index'; +import { AppState } from '../../../state'; + +describe('state selectors', () => { + const state: AppState = { + monitor: { + monitorDetailsList: [], + loading: false, + errors: [], + }, + snapshot: { + count: { + up: 2, + down: 0, + mixed: 0, + total: 2, + }, + errors: [], + loading: false, + }, + ui: { basePath: 'yyz', integrationsPopoverOpen: null, lastRefresh: 125 }, + }; + + it('selects base path from state', () => { + expect(getBasePath(state)).toBe('yyz'); + }); + + it('gets integrations popup state', () => { + const integrationsPopupOpen = { + id: 'popup-id', + open: true, + }; + state.ui.integrationsPopoverOpen = integrationsPopupOpen; + expect(isIntegrationsPopupOpen(state)).toBe(integrationsPopupOpen); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts index 59c3f0c31539fd..245b45a9399505 100644 --- a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts @@ -3,11 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { AppState } from '../../state'; -export const isIntegrationsPopupOpen = (state: AppState) => state.ui.integrationsPopoverOpen; +export const getBasePath = ({ ui: { basePath } }: AppState) => basePath; -export const getBasePath = (state: AppState) => state.ui.basePath; +export const isIntegrationsPopupOpen = ({ ui: { integrationsPopoverOpen } }: AppState) => + integrationsPopoverOpen; export const getMonitorDetails = (state: AppState, summary: any) => { return state.monitor.monitorDetailsList[summary.monitor_id]; diff --git a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx index 3fdceb8b1b2bd8..47743729c1e762 100644 --- a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx +++ b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx @@ -22,7 +22,7 @@ import { UptimeDatePicker } from './components/functional/uptime_date_picker'; import { useUrlParams } from './hooks'; import { getTitle } from './lib/helper/get_title'; import { store } from './state'; -import { setBasePath } from './state/actions'; +import { setBasePath, triggerAppRefresh } from './state/actions'; export interface UptimeAppColors { danger: string; @@ -116,7 +116,9 @@ const Application = (props: UptimeAppProps) => { }, []); const refreshApp = () => { - setLastRefresh(Date.now()); + const refreshTime = Date.now(); + setLastRefresh(refreshTime); + store.dispatch(triggerAppRefresh(refreshTime)); }; const [getUrlParams] = useUrlParams(); diff --git a/x-pack/legacy/plugins/uptime/server/graphql/monitors/resolvers.ts b/x-pack/legacy/plugins/uptime/server/graphql/monitors/resolvers.ts index 96a386b6a68487..415afc87e201e4 100644 --- a/x-pack/legacy/plugins/uptime/server/graphql/monitors/resolvers.ts +++ b/x-pack/legacy/plugins/uptime/server/graphql/monitors/resolvers.ts @@ -12,24 +12,15 @@ import { GetLatestMonitorsQueryArgs, GetMonitorChartsDataQueryArgs, GetMonitorPageTitleQueryArgs, - GetSnapshotQueryArgs, MonitorChart, MonitorPageTitle, Ping, - Snapshot, GetSnapshotHistogramQueryArgs, } from '../../../common/graphql/types'; import { UMServerLibs } from '../../lib/lib'; import { CreateUMGraphQLResolvers, UMContext } from '../types'; import { HistogramResult } from '../../../common/domain_types'; -export type UMSnapshotResolver = UMResolver< - Snapshot | Promise, - any, - GetSnapshotQueryArgs, - UMContext ->; - export type UMMonitorsResolver = UMResolver, any, UMGqlRange, UMContext>; export type UMLatestMonitorsResolver = UMResolver< @@ -71,7 +62,6 @@ export const createMonitorsResolvers: CreateUMGraphQLResolvers = ( libs: UMServerLibs ): { Query: { - getSnapshot: UMSnapshotResolver; getSnapshotHistogram: UMGetSnapshotHistogram; getMonitorChartsData: UMGetMonitorChartsResolver; getLatestMonitors: UMLatestMonitorsResolver; @@ -80,23 +70,6 @@ export const createMonitorsResolvers: CreateUMGraphQLResolvers = ( }; } => ({ Query: { - async getSnapshot( - resolver, - { dateRangeStart, dateRangeEnd, filters, statusFilter }, - { req } - ): Promise { - const counts = await libs.monitors.getSnapshotCount( - req, - dateRangeStart, - dateRangeEnd, - filters, - statusFilter - ); - - return { - counts, - }; - }, async getSnapshotHistogram( resolver, { dateRangeStart, dateRangeEnd, filters, monitorId, statusFilter }, diff --git a/x-pack/legacy/plugins/uptime/server/graphql/monitors/schema.gql.ts b/x-pack/legacy/plugins/uptime/server/graphql/monitors/schema.gql.ts index 97dcbd12fff2e5..f9b14c63e70bbc 100644 --- a/x-pack/legacy/plugins/uptime/server/graphql/monitors/schema.gql.ts +++ b/x-pack/legacy/plugins/uptime/server/graphql/monitors/schema.gql.ts @@ -31,17 +31,6 @@ export const monitorsSchema = gql` y: UnsignedInteger } - type SnapshotCount { - up: Int! - down: Int! - mixed: Int! - total: Int! - } - - type Snapshot { - counts: SnapshotCount! - } - type DataPoint { x: UnsignedInteger y: Float @@ -139,13 +128,6 @@ export const monitorsSchema = gql` statusFilter: String ): LatestMonitorsResult - getSnapshot( - dateRangeStart: String! - dateRangeEnd: String! - filters: String - statusFilter: String - ): Snapshot - getSnapshotHistogram( dateRangeStart: String! dateRangeEnd: String! diff --git a/x-pack/legacy/plugins/uptime/server/kibana.index.ts b/x-pack/legacy/plugins/uptime/server/kibana.index.ts index 874fb2e37e9021..73fabc629946b6 100644 --- a/x-pack/legacy/plugins/uptime/server/kibana.index.ts +++ b/x-pack/legacy/plugins/uptime/server/kibana.index.ts @@ -22,17 +22,13 @@ export interface KibanaRouteOptions { export interface KibanaServer extends Server { route: (options: KibanaRouteOptions) => void; - usage: { - collectorSet: { - register: (usageCollector: any) => any; - }; - }; } export const initServerWithKibana = (server: UptimeCoreSetup, plugins: UptimeCorePlugins) => { - const { usageCollector, xpack } = plugins; + const { usageCollection, xpack } = plugins; const libs = compose(server, plugins); - usageCollector.collectorSet.register(KibanaTelemetryAdapter.initUsageCollector(usageCollector)); + KibanaTelemetryAdapter.registerUsageCollector(usageCollection); + initUptimeServer(libs); xpack.registerFeature({ diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/framework/adapter_types.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/framework/adapter_types.ts index a31b4f99c522a3..df2723283f88ce 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/framework/adapter_types.ts @@ -9,6 +9,7 @@ import { GraphQLSchema } from 'graphql'; import { Lifecycle, ResponseToolkit } from 'hapi'; import { RouteOptions } from 'hapi'; import { SavedObjectsLegacyService } from 'src/core/server'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; export interface UMFrameworkRequest { user: string; @@ -37,7 +38,7 @@ export interface UptimeCoreSetup { export interface UptimeCorePlugins { elasticsearch: any; savedObjects: SavedObjectsLegacyService; - usageCollector: any; + usageCollection: UsageCollectionSetup; xpack: any; } diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/__tests__/__snapshots__/get_snapshot_helper.test.ts.snap b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/__tests__/__snapshots__/get_snapshot_helper.test.ts.snap new file mode 100644 index 00000000000000..29c82ff455d369 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/__tests__/__snapshots__/get_snapshot_helper.test.ts.snap @@ -0,0 +1,10 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`get snapshot helper reduces check groups as expected 1`] = ` +Object { + "down": 1, + "mixed": 0, + "total": 3, + "up": 2, +} +`; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/__tests__/example_filter.json b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/__tests__/example_filter.json deleted file mode 100644 index bd4248755095df..00000000000000 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/__tests__/example_filter.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "bool": { - "filter": [ - { - "bool": { - "filter": [ - { - "bool": { - "should": [{ "match_phrase": { "monitor.id": "green-0001" } }], - "minimum_should_match": 1 - } - }, - { - "bool": { - "should": [{ "match_phrase": { "monitor.name": "" } }], - "minimum_should_match": 1 - } - } - ] - } - }, - { - "bool": { - "should": [ - { - "bool": { - "should": [{ "match": { "monitor.id": "green-0000" } }], - "minimum_should_match": 1 - } - }, - { - "bool": { - "should": [ - { - "bool": { - "should": [{ "match": { "monitor.id": "green-0001" } }], - "minimum_should_match": 1 - } - }, - { - "bool": { - "should": [ - { - "bool": { - "should": [{ "match": { "monitor.id": "green-0002" } }], - "minimum_should_match": 1 - } - }, - { - "bool": { - "should": [ - { - "bool": { - "should": [{ "match": { "monitor.id": "green-0003" } }], - "minimum_should_match": 1 - } - }, - { - "bool": { - "should": [{ "match": { "monitor.id": "green-0004" } }], - "minimum_should_match": 1 - } - } - ], - "minimum_should_match": 1 - } - } - ], - "minimum_should_match": 1 - } - } - ], - "minimum_should_match": 1 - } - } - ], - "minimum_should_match": 1 - } - } - ] - } -} diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/__tests__/get_snapshot_helper.test.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/__tests__/get_snapshot_helper.test.ts new file mode 100644 index 00000000000000..917e4a149de677 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/__tests__/get_snapshot_helper.test.ts @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getSnapshotCountHelper } from '../get_snapshot_helper'; +import { MonitorGroups } from '../search'; + +describe('get snapshot helper', () => { + let mockIterator: any; + beforeAll(() => { + mockIterator = jest.fn(); + const summaryTimestamp = new Date('2019-01-01'); + const firstResult: MonitorGroups = { + id: 'firstGroup', + groups: [ + { + monitorId: 'first-monitor', + location: 'us-east-1', + checkGroup: 'abc', + status: 'down', + summaryTimestamp, + }, + { + monitorId: 'first-monitor', + location: 'us-west-1', + checkGroup: 'abc', + status: 'up', + summaryTimestamp, + }, + { + monitorId: 'first-monitor', + location: 'amsterdam', + checkGroup: 'abc', + status: 'down', + summaryTimestamp, + }, + ], + }; + const secondResult: MonitorGroups = { + id: 'secondGroup', + groups: [ + { + monitorId: 'second-monitor', + location: 'us-east-1', + checkGroup: 'yyz', + status: 'up', + summaryTimestamp, + }, + { + monitorId: 'second-monitor', + location: 'us-west-1', + checkGroup: 'yyz', + status: 'up', + summaryTimestamp, + }, + { + monitorId: 'second-monitor', + location: 'amsterdam', + checkGroup: 'yyz', + status: 'up', + summaryTimestamp, + }, + ], + }; + const thirdResult: MonitorGroups = { + id: 'thirdGroup', + groups: [ + { + monitorId: 'third-monitor', + location: 'us-east-1', + checkGroup: 'dt', + status: 'up', + summaryTimestamp, + }, + { + monitorId: 'third-monitor', + location: 'us-west-1', + checkGroup: 'dt', + status: 'up', + summaryTimestamp, + }, + { + monitorId: 'third-monitor', + location: 'amsterdam', + checkGroup: 'dt', + status: 'up', + summaryTimestamp, + }, + ], + }; + + const mockNext = jest + .fn() + .mockReturnValueOnce(firstResult) + .mockReturnValueOnce(secondResult) + .mockReturnValueOnce(thirdResult) + .mockReturnValueOnce(null); + mockIterator.next = mockNext; + }); + + it('reduces check groups as expected', async () => { + expect(await getSnapshotCountHelper(mockIterator)).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/adapter_types.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/adapter_types.ts index 781f30314d350a..57b1744f5d324c 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/adapter_types.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/adapter_types.ts @@ -21,6 +21,13 @@ export interface UMMonitorStatesAdapter { statusFilter?: string | null ): Promise; statesIndexExists(request: any): Promise; + getSnapshotCount( + request: any, + dateRangeStart: string, + dateRangeEnd: string, + filters?: string, + statusFilter?: string + ): Promise; } export interface CursorPagination { diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/elasticsearch_monitor_states_adapter.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/elasticsearch_monitor_states_adapter.ts index 59c3e022e7d049..c3593854fa53f6 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/elasticsearch_monitor_states_adapter.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/elasticsearch_monitor_states_adapter.ts @@ -9,6 +9,9 @@ import { UMMonitorStatesAdapter, GetMonitorStatesResult, CursorPagination } from import { StatesIndexStatus } from '../../../../common/graphql/types'; import { INDEX_NAMES, CONTEXT_DEFAULTS } from '../../../../common/constants'; import { fetchPage } from './search'; +import { MonitorGroupIterator } from './search/monitor_group_iterator'; +import { Snapshot } from '../../../../common/runtime_types'; +import { getSnapshotCountHelper } from './get_snapshot_helper'; export interface QueryContext { database: any; @@ -57,6 +60,26 @@ export class ElasticsearchMonitorStatesAdapter implements UMMonitorStatesAdapter }; } + public async getSnapshotCount( + request: any, + dateRangeStart: string, + dateRangeEnd: string, + filters?: string, + statusFilter?: string + ): Promise { + const context: QueryContext = { + database: this.database, + request, + dateRangeStart, + dateRangeEnd, + pagination: CONTEXT_DEFAULTS.CURSOR_PAGINATION, + filterClause: filters && filters !== '' ? JSON.parse(filters) : null, + size: CONTEXT_DEFAULTS.MAX_MONITORS_FOR_SNAPSHOT_COUNT, + statusFilter, + }; + return getSnapshotCountHelper(new MonitorGroupIterator(context)); + } + public async statesIndexExists(request: any): Promise { // TODO: adapt this to the states index in future release const { diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/get_snapshot_helper.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/get_snapshot_helper.ts new file mode 100644 index 00000000000000..8bd21b77406df1 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/get_snapshot_helper.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MonitorGroups, MonitorGroupIterator } from './search'; +import { Snapshot } from '../../../../common/runtime_types'; + +const reduceItemsToCounts = (items: MonitorGroups[]) => { + let down = 0; + let up = 0; + items.forEach(item => { + if (item.groups.some(group => group.status === 'down')) { + down++; + } else { + up++; + } + }); + return { + down, + mixed: 0, + total: down + up, + up, + }; +}; + +export const getSnapshotCountHelper = async (iterator: MonitorGroupIterator): Promise => { + const items: MonitorGroups[] = []; + let res: MonitorGroups | null; + // query the index to find the most recent check group for each monitor/location + do { + res = await iterator.next(); + if (res) { + items.push(res); + } + } while (res !== null); + + return reduceItemsToCounts(items); +}; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/index.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/index.ts index 2fa2112161dcdb..040c256935692f 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/index.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/index.ts @@ -5,3 +5,4 @@ */ export { fetchPage, MonitorGroups, MonitorLocCheckGroup, MonitorGroupsPage } from './fetch_page'; +export { MonitorGroupIterator } from './monitor_group_iterator'; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/monitor_group_iterator.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/monitor_group_iterator.ts index 2fec58593e5d8d..1de2dbb0e364d6 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/monitor_group_iterator.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/monitor_group_iterator.ts @@ -97,9 +97,12 @@ export class MonitorGroupIterator { } } - // Attempts to buffer more results fetching a single chunk. - // If trim is set to true, which is the default, it will delete all items in the buffer prior to the current item. - // to free up space. + /** + * Attempts to buffer more results fetching a single chunk. + * If trim is set to true, which is the default, it will delete all items in the buffer prior to the current item. + * to free up space. + * @param size the number of items to chunk + */ async attemptBufferMore( size: number = CHUNK_SIZE ): Promise<{ hasMore: boolean; gotHit: boolean }> { diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/adapter_types.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/adapter_types.ts index 996e80d2c8613b..f6ac587b0ceec6 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/adapter_types.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/adapter_types.ts @@ -14,13 +14,6 @@ export interface UMMonitorsAdapter { dateRangeEnd: string, location?: string | null ): Promise; - getSnapshotCount( - request: any, - dateRangeStart: string, - dateRangeEnd: string, - filters?: string | null, - statusFilter?: string | null - ): Promise; getFilterBar(request: any, dateRangeStart: string, dateRangeEnd: string): Promise; getMonitorPageTitle(request: any, monitorId: string): Promise; getMonitorDetails(request: any, monitorId: string): Promise; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts index 1a391e90f2a5e3..ef0837a0431720 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get, set, reduce } from 'lodash'; -import { INDEX_NAMES, QUERY } from '../../../../common/constants'; +import { get } from 'lodash'; +import { INDEX_NAMES } from '../../../../common/constants'; import { FilterBar, MonitorChart, @@ -13,7 +13,7 @@ import { Ping, LocationDurationLine, } from '../../../../common/graphql/types'; -import { getFilterClause, parseFilterQuery, getHistogramIntervalFormatted } from '../../helper'; +import { getHistogramIntervalFormatted } from '../../helper'; import { DatabaseAdapter } from '../database'; import { UMMonitorsAdapter } from './adapter_types'; import { MonitorDetails, Error } from '../../../../common/runtime_types'; @@ -184,155 +184,6 @@ export class ElasticsearchMonitorsAdapter implements UMMonitorsAdapter { return monitorChartsData; } - /** - * Provides a count of the current monitors - * @param request Kibana request - * @param dateRangeStart timestamp bounds - * @param dateRangeEnd timestamp bounds - * @param filters filters defined by client - */ - public async getSnapshotCount( - request: any, - dateRangeStart: string, - dateRangeEnd: string, - filters?: string | null, - statusFilter?: string | null - ): Promise { - const query = parseFilterQuery(filters); - const additionalFilters = [{ exists: { field: 'summary.up' } }]; - if (query) { - additionalFilters.push(query); - } - const filter = getFilterClause(dateRangeStart, dateRangeEnd, additionalFilters); - const params = { - index: INDEX_NAMES.HEARTBEAT, - body: { - query: { - bool: { - filter, - }, - }, - size: 0, - aggs: { - ids: { - composite: { - sources: [ - { - id: { - terms: { - field: 'monitor.id', - }, - }, - }, - { - location: { - terms: { - field: 'observer.geo.name', - missing_bucket: true, - }, - }, - }, - ], - size: QUERY.DEFAULT_AGGS_CAP, - }, - aggs: { - latest: { - top_hits: { - sort: [{ '@timestamp': { order: 'desc' } }], - _source: { - includes: ['summary.*', 'monitor.id', '@timestamp', 'observer.geo.name'], - }, - size: 1, - }, - }, - }, - }, - }, - }, - }; - - let searchAfter: any = null; - - const summaryByIdLocation: { - // ID - [key: string]: { - // Location - [key: string]: { up: number; down: number; timestamp: number }; - }; - } = {}; - - do { - if (searchAfter) { - set(params, 'body.aggs.ids.composite.after', searchAfter); - } - - const queryResult = await this.database.search(request, params); - const idBuckets = get(queryResult, 'aggregations.ids.buckets', []); - - idBuckets.forEach(bucket => { - // We only get the latest doc - const source: any = get(bucket, 'latest.hits.hits[0]._source'); - const { - summary: { up, down }, - monitor: { id }, - } = source; - const timestamp = get(source, '@timestamp', 0); - const location = get(source, 'observer.geo.name', ''); - - let idSummary = summaryByIdLocation[id]; - if (!idSummary) { - idSummary = {}; - summaryByIdLocation[id] = idSummary; - } - const locationSummary = idSummary[location]; - if (!locationSummary || locationSummary.timestamp < timestamp) { - idSummary[location] = { timestamp, up, down }; - } - }); - - searchAfter = get(queryResult, 'aggregations.ids.after_key'); - } while (searchAfter); - - let up: number = 0; - let mixed: number = 0; - let down: number = 0; - - for (const id in summaryByIdLocation) { - if (!summaryByIdLocation.hasOwnProperty(id)) { - continue; - } - const locationInfo = summaryByIdLocation[id]; - const { up: locationUp, down: locationDown } = reduce( - locationInfo, - (acc, value, key) => { - acc.up += value.up; - acc.down += value.down; - return acc; - }, - { up: 0, down: 0 } - ); - - if (locationDown === 0) { - up++; - } else if (locationUp > 0) { - mixed++; - } else { - down++; - } - } - - const result: any = { up, down, mixed, total: up + down + mixed }; - if (statusFilter) { - for (const status in result) { - if (status !== 'total' && status !== statusFilter) { - result[status] = 0; - } - } - } - - return result; - } - /** * Fetch options for the filter bar. * @param request Kibana request object diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/__tests__/kibana_telemetry_adapter.test.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/__tests__/kibana_telemetry_adapter.test.ts index d8279cb3399bd1..8e4011b4cf0eba 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/__tests__/kibana_telemetry_adapter.test.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/__tests__/kibana_telemetry_adapter.test.ts @@ -7,21 +7,19 @@ import { KibanaTelemetryAdapter } from '../kibana_telemetry_adapter'; describe('KibanaTelemetryAdapter', () => { - let telemetry: any; + let usageCollection: any; let collector: { type: string; fetch: () => Promise; isReady: () => boolean }; beforeEach(() => { - telemetry = { - collectorSet: { - makeUsageCollector: (val: any) => { - collector = val; - }, + usageCollection = { + makeUsageCollector: (val: any) => { + collector = val; }, }; }); it('collects monitor and overview data', async () => { expect.assertions(1); - KibanaTelemetryAdapter.initUsageCollector(telemetry); + KibanaTelemetryAdapter.initUsageCollector(usageCollection); KibanaTelemetryAdapter.countMonitor(); KibanaTelemetryAdapter.countOverview(); KibanaTelemetryAdapter.countOverview(); @@ -33,7 +31,7 @@ describe('KibanaTelemetryAdapter', () => { expect.assertions(1); // give a time of > 24 hours ago Date.now = jest.fn(() => 1559053560000); - KibanaTelemetryAdapter.initUsageCollector(telemetry); + KibanaTelemetryAdapter.initUsageCollector(usageCollection); KibanaTelemetryAdapter.countMonitor(); KibanaTelemetryAdapter.countOverview(); // give a time of now @@ -47,7 +45,7 @@ describe('KibanaTelemetryAdapter', () => { }); it('defaults ready to `true`', async () => { - KibanaTelemetryAdapter.initUsageCollector(telemetry); + KibanaTelemetryAdapter.initUsageCollector(usageCollection); expect(collector.isReady()).toBe(true); }); }); diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts index a906c741c5241f..8dec0c1d2d485a 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/telemetry/kibana_telemetry_adapter.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; interface UptimeTelemetry { overview_page: number; @@ -19,9 +20,13 @@ const BUCKET_SIZE = 3600; const BUCKET_NUMBER = 24; export class KibanaTelemetryAdapter { - public static initUsageCollector(usageCollector: any) { - const { collectorSet } = usageCollector; - return collectorSet.makeUsageCollector({ + public static registerUsageCollector = (usageCollector: UsageCollectionSetup) => { + const collector = KibanaTelemetryAdapter.initUsageCollector(usageCollector); + usageCollector.registerCollector(collector); + }; + + public static initUsageCollector(usageCollector: UsageCollectionSetup) { + return usageCollector.makeUsageCollector({ type: 'uptime', fetch: async () => { const report = this.getReport(); diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/index.ts b/x-pack/legacy/plugins/uptime/server/rest_api/index.ts index cc702362a57a83..889f8a820b2f38 100644 --- a/x-pack/legacy/plugins/uptime/server/rest_api/index.ts +++ b/x-pack/legacy/plugins/uptime/server/rest_api/index.ts @@ -8,16 +8,18 @@ import { createIsValidRoute } from './auth'; import { createGetAllRoute } from './pings'; import { createGetIndexPatternRoute } from './index_pattern'; import { createLogMonitorPageRoute, createLogOverviewPageRoute } from './telemetry'; +import { createGetSnapshotCount } from './snapshot'; import { UMRestApiRouteCreator } from './types'; import { createGetMonitorDetailsRoute } from './monitors'; export * from './types'; export { createRouteWithAuth } from './create_route_with_auth'; export const restApiRoutes: UMRestApiRouteCreator[] = [ - createIsValidRoute, createGetAllRoute, - createLogMonitorPageRoute, - createLogOverviewPageRoute, createGetIndexPatternRoute, createGetMonitorDetailsRoute, + createGetSnapshotCount, + createIsValidRoute, + createLogMonitorPageRoute, + createLogOverviewPageRoute, ]; diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts b/x-pack/legacy/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts new file mode 100644 index 00000000000000..ddca622005d637 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/rest_api/snapshot/get_snapshot_count.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { UMServerLibs } from '../../lib/lib'; +import { Snapshot } from '../../../common/runtime_types'; + +export const createGetSnapshotCount = (libs: UMServerLibs) => ({ + method: 'GET', + path: '/api/uptime/snapshot/count', + options: { + validate: { + query: Joi.object({ + dateRangeStart: Joi.string().required(), + dateRangeEnd: Joi.string().required(), + filters: Joi.string(), + statusFilter: Joi.string(), + }), + }, + tags: ['access:uptime'], + }, + handler: async (request: any): Promise => { + const { dateRangeStart, dateRangeEnd, filters, statusFilter } = request.query; + return await libs.monitorStates.getSnapshotCount( + request, + dateRangeStart, + dateRangeEnd, + filters, + statusFilter + ); + }, +}); diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/snapshot/index.ts b/x-pack/legacy/plugins/uptime/server/rest_api/snapshot/index.ts new file mode 100644 index 00000000000000..934b34ef1b3976 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/rest_api/snapshot/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { createGetSnapshotCount } from './get_snapshot_count'; diff --git a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/settings.js b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/settings.js index d5753ef6f3c851..021464f32a203c 100644 --- a/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/settings.js +++ b/x-pack/legacy/plugins/xpack_main/server/routes/api/v1/settings.js @@ -23,8 +23,8 @@ export function settingsRoute(server, kbnServer) { const callCluster = (...args) => callWithRequest(req, ...args); // All queries from HTTP API must use authentication headers from the request try { - const { collectorSet } = server.usage; - const settingsCollector = collectorSet.getCollectorByType(KIBANA_SETTINGS_TYPE); + const { usageCollection } = server.newPlatform.setup.plugins; + const settingsCollector = usageCollection.getCollectorByType(KIBANA_SETTINGS_TYPE); let settings = await settingsCollector.fetch(callCluster); if (!settings) { diff --git a/x-pack/package.json b/x-pack/package.json index eccc5918e6d506..c5114500c6f61d 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -92,7 +92,7 @@ "@types/react-resize-detector": "^4.0.1", "@types/react-router-dom": "^4.3.1", "@types/react-sticky": "^6.0.3", - "@types/react-test-renderer": "^16.8.0", + "@types/react-test-renderer": "^16.8.3", "@types/recompose": "^0.30.6", "@types/reduce-reducers": "^0.3.0", "@types/redux-actions": "^2.2.1", @@ -153,7 +153,7 @@ "proxyquire": "1.8.0", "react-docgen-typescript-loader": "^3.1.1", "react-hooks-testing-library": "^0.3.8", - "react-test-renderer": "^16.8.0", + "react-test-renderer": "^16.8.6", "react-testing-library": "^6.0.0", "sass-loader": "^7.3.1", "sass-resources-loader": "^2.0.1", @@ -185,7 +185,6 @@ "@kbn/babel-preset": "1.0.0", "@kbn/config-schema": "1.0.0", "@kbn/elastic-idx": "1.0.0", - "@kbn/es-query": "1.0.0", "@kbn/i18n": "1.0.0", "@kbn/interpreter": "1.0.0", "@kbn/ui-framework": "1.0.0", @@ -294,11 +293,11 @@ "puid": "1.0.7", "puppeteer-core": "^1.19.0", "raw-loader": "3.1.0", - "react": "^16.8.0", + "react": "^16.8.6", "react-apollo": "^2.1.4", "react-beautiful-dnd": "^8.0.7", "react-datetime": "^2.14.0", - "react-dom": "^16.8.0", + "react-dom": "^16.8.6", "react-dropzone": "^4.2.9", "react-fast-compare": "^2.0.4", "react-markdown": "^3.4.1", diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index b66850ff569cb7..082216a78ce5e3 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -13,8 +13,11 @@ export const config = { schema: schema.object({ serviceMapEnabled: schema.boolean({ defaultValue: false }), autocreateApmIndexPattern: schema.boolean({ defaultValue: true }), - 'ui.transactionGroupBucketSize': schema.number({ defaultValue: 100 }), - 'ui.maxTraceItems': schema.number({ defaultValue: 1000 }), + ui: schema.object({ + enabled: schema.boolean({ defaultValue: true }), + transactionGroupBucketSize: schema.number({ defaultValue: 100 }), + maxTraceItems: schema.number({ defaultValue: 1000 }), + }), }), }; @@ -30,8 +33,9 @@ export function mergeConfigs(apmOssConfig: APMOSSConfig, apmConfig: APMXPackConf 'apm_oss.onboardingIndices': apmOssConfig.onboardingIndices, 'apm_oss.indexPattern': apmOssConfig.indexPattern, 'xpack.apm.serviceMapEnabled': apmConfig.serviceMapEnabled, - 'xpack.apm.ui.maxTraceItems': apmConfig['ui.maxTraceItems'], - 'xpack.apm.ui.transactionGroupBucketSize': apmConfig['ui.transactionGroupBucketSize'], + 'xpack.apm.ui.enabled': apmConfig.ui.enabled, + 'xpack.apm.ui.maxTraceItems': apmConfig.ui.maxTraceItems, + 'xpack.apm.ui.transactionGroupBucketSize': apmConfig.ui.transactionGroupBucketSize, 'xpack.apm.autocreateApmIndexPattern': apmConfig.autocreateApmIndexPattern, }; } diff --git a/x-pack/plugins/canvas/kibana.json b/x-pack/plugins/canvas/kibana.json new file mode 100644 index 00000000000000..87214f0287054c --- /dev/null +++ b/x-pack/plugins/canvas/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "canvas", + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": ["xpack", "canvas"], + "server": true, + "ui": false, + "requiredPlugins": [] + } + \ No newline at end of file diff --git a/x-pack/plugins/canvas/server/index.ts b/x-pack/plugins/canvas/server/index.ts new file mode 100644 index 00000000000000..e881f7db69c785 --- /dev/null +++ b/x-pack/plugins/canvas/server/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'src/core/server'; +import { CanvasPlugin } from './plugin'; + +export const plugin = (initializerContext: PluginInitializerContext) => + new CanvasPlugin(initializerContext); diff --git a/x-pack/plugins/canvas/server/plugin.ts b/x-pack/plugins/canvas/server/plugin.ts new file mode 100644 index 00000000000000..76b86c2ac39b43 --- /dev/null +++ b/x-pack/plugins/canvas/server/plugin.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup, PluginInitializerContext, Plugin, Logger } from 'src/core/server'; +import { initRoutes } from './routes'; + +export class CanvasPlugin implements Plugin { + private readonly logger: Logger; + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(coreSetup: CoreSetup): void { + const canvasRouter = coreSetup.http.createRouter(); + + initRoutes({ router: canvasRouter, logger: this.logger }); + } + + public start() {} + + public stop() {} +} diff --git a/x-pack/plugins/canvas/server/routes/catch_error_handler.ts b/x-pack/plugins/canvas/server/routes/catch_error_handler.ts new file mode 100644 index 00000000000000..fb7f4d6ee26006 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/catch_error_handler.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ObjectType } from '@kbn/config-schema'; +import { RequestHandler } from 'src/core/server'; + +export const catchErrorHandler: < + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType +>( + fn: RequestHandler +) => RequestHandler = fn => { + return async (context, request, response) => { + try { + return await fn(context, request, response); + } catch (error) { + if (error.isBoom) { + return response.customError({ + body: error.output.payload, + statusCode: error.output.statusCode, + }); + } + return response.internalError({ body: error }); + } + }; +}; diff --git a/x-pack/plugins/canvas/server/routes/index.ts b/x-pack/plugins/canvas/server/routes/index.ts new file mode 100644 index 00000000000000..46873a6b325423 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter, Logger } from 'src/core/server'; +import { initWorkpadRoutes } from './workpad'; + +export interface RouteInitializerDeps { + router: IRouter; + logger: Logger; +} + +export function initRoutes(deps: RouteInitializerDeps) { + initWorkpadRoutes(deps); +} diff --git a/x-pack/plugins/canvas/server/routes/workpad/create.test.ts b/x-pack/plugins/canvas/server/routes/workpad/create.test.ts new file mode 100644 index 00000000000000..dbad1a97dc4588 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/create.test.ts @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import sinon from 'sinon'; +import { + savedObjectsClientMock, + httpServiceMock, + httpServerMock, + loggingServiceMock, +} from 'src/core/server/mocks'; +import { CANVAS_TYPE } from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { initializeCreateWorkpadRoute } from './create'; +import { + IRouter, + kibanaResponseFactory, + RequestHandlerContext, + RequestHandler, +} from 'src/core/server'; + +const mockRouteContext = ({ + core: { + savedObjects: { + client: savedObjectsClientMock.create(), + }, + }, +} as unknown) as RequestHandlerContext; + +const mockedUUID = '123abc'; +const now = new Date(); +const nowIso = now.toISOString(); + +jest.mock('uuid/v4', () => jest.fn().mockReturnValue('123abc')); + +describe('POST workpad', () => { + let routeHandler: RequestHandler; + let clock: sinon.SinonFakeTimers; + + beforeEach(() => { + clock = sinon.useFakeTimers(now); + + const httpService = httpServiceMock.createSetupContract(); + + const router = httpService.createRouter('') as jest.Mocked; + initializeCreateWorkpadRoute({ + router, + logger: loggingServiceMock.create().get(), + }); + + routeHandler = router.post.mock.calls[0][1]; + }); + + afterEach(() => { + clock.restore(); + }); + + it(`returns 200 when the workpad is created`, async () => { + const mockWorkpad = { + pages: [], + }; + + const request = httpServerMock.createKibanaRequest({ + method: 'post', + path: 'api/canvas/workpad', + body: mockWorkpad, + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toEqual({ ok: true }); + expect(mockRouteContext.core.savedObjects.client.create).toBeCalledWith( + CANVAS_TYPE, + { + ...mockWorkpad, + '@timestamp': nowIso, + '@created': nowIso, + }, + { + id: `workpad-${mockedUUID}`, + } + ); + }); + + it(`returns bad request if create is unsuccessful`, async () => { + const request = httpServerMock.createKibanaRequest({ + method: 'post', + path: 'api/canvas/workpad', + body: {}, + }); + + (mockRouteContext.core.savedObjects.client.create as jest.Mock).mockImplementation(() => { + throw mockRouteContext.core.savedObjects.client.errors.createBadRequestError('bad request'); + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(400); + }); +}); diff --git a/x-pack/plugins/canvas/server/routes/workpad/create.ts b/x-pack/plugins/canvas/server/routes/workpad/create.ts new file mode 100644 index 00000000000000..be904356720b68 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/create.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RouteInitializerDeps } from '../'; +import { + CANVAS_TYPE, + API_ROUTE_WORKPAD, +} from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { CanvasWorkpad } from '../../../../../legacy/plugins/canvas/types'; +import { getId } from '../../../../../legacy/plugins/canvas/public/lib/get_id'; +import { WorkpadSchema } from './workpad_schema'; +import { okResponse } from './ok_response'; +import { catchErrorHandler } from '../catch_error_handler'; + +export type WorkpadAttributes = Pick> & { + '@timestamp': string; + '@created': string; +}; + +export function initializeCreateWorkpadRoute(deps: RouteInitializerDeps) { + const { router } = deps; + router.post( + { + path: `${API_ROUTE_WORKPAD}`, + validate: { + body: WorkpadSchema, + }, + }, + catchErrorHandler(async (context, request, response) => { + if (!request.body) { + return response.badRequest({ body: 'A workpad payload is required' }); + } + + const workpad = request.body as CanvasWorkpad; + + const now = new Date().toISOString(); + const { id, ...payload } = workpad; + + await context.core.savedObjects.client.create( + CANVAS_TYPE, + { + ...payload, + '@timestamp': now, + '@created': now, + }, + { id: id || getId('workpad') } + ); + + return response.ok({ + body: okResponse, + }); + }) + ); +} diff --git a/x-pack/plugins/canvas/server/routes/workpad/delete.test.ts b/x-pack/plugins/canvas/server/routes/workpad/delete.test.ts new file mode 100644 index 00000000000000..e693840826b7ab --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/delete.test.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CANVAS_TYPE } from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { initializeDeleteWorkpadRoute } from './delete'; +import { + IRouter, + kibanaResponseFactory, + RequestHandlerContext, + RequestHandler, +} from 'src/core/server'; +import { + savedObjectsClientMock, + httpServiceMock, + httpServerMock, + loggingServiceMock, +} from 'src/core/server/mocks'; + +const mockRouteContext = ({ + core: { + savedObjects: { + client: savedObjectsClientMock.create(), + }, + }, +} as unknown) as RequestHandlerContext; + +describe('DELETE workpad', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const httpService = httpServiceMock.createSetupContract(); + const router = httpService.createRouter('') as jest.Mocked; + initializeDeleteWorkpadRoute({ + router, + logger: loggingServiceMock.create().get(), + }); + + routeHandler = router.delete.mock.calls[0][1]; + }); + + it(`returns 200 ok when the workpad is deleted`, async () => { + const id = 'some-id'; + const request = httpServerMock.createKibanaRequest({ + method: 'delete', + path: `api/canvas/workpad/${id}`, + params: { + id, + }, + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toEqual({ ok: true }); + expect(mockRouteContext.core.savedObjects.client.delete).toBeCalledWith(CANVAS_TYPE, id); + }); + + it(`returns bad request if delete is unsuccessful`, async () => { + const request = httpServerMock.createKibanaRequest({ + method: 'delete', + path: `api/canvas/workpad/some-id`, + params: { + id: 'some-id', + }, + }); + + (mockRouteContext.core.savedObjects.client.delete as jest.Mock).mockImplementationOnce(() => { + throw mockRouteContext.core.savedObjects.client.errors.createBadRequestError('bad request'); + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(400); + }); +}); diff --git a/x-pack/plugins/canvas/server/routes/workpad/delete.ts b/x-pack/plugins/canvas/server/routes/workpad/delete.ts new file mode 100644 index 00000000000000..7adf11e7a887be --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/delete.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { RouteInitializerDeps } from '../'; +import { + CANVAS_TYPE, + API_ROUTE_WORKPAD, +} from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { okResponse } from './ok_response'; +import { catchErrorHandler } from '../catch_error_handler'; + +export function initializeDeleteWorkpadRoute(deps: RouteInitializerDeps) { + const { router } = deps; + router.delete( + { + path: `${API_ROUTE_WORKPAD}/{id}`, + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + catchErrorHandler(async (context, request, response) => { + context.core.savedObjects.client.delete(CANVAS_TYPE, request.params.id); + return response.ok({ body: okResponse }); + }) + ); +} diff --git a/x-pack/plugins/canvas/server/routes/workpad/find.test.ts b/x-pack/plugins/canvas/server/routes/workpad/find.test.ts new file mode 100644 index 00000000000000..08de9b20e98185 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/find.test.ts @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { initializeFindWorkpadsRoute } from './find'; +import { + IRouter, + kibanaResponseFactory, + RequestHandlerContext, + RequestHandler, +} from 'src/core/server'; +import { + savedObjectsClientMock, + httpServiceMock, + httpServerMock, + loggingServiceMock, +} from 'src/core/server/mocks'; + +const mockRouteContext = ({ + core: { + savedObjects: { + client: savedObjectsClientMock.create(), + }, + }, +} as unknown) as RequestHandlerContext; + +describe('Find workpad', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const httpService = httpServiceMock.createSetupContract(); + const router = httpService.createRouter('') as jest.Mocked; + initializeFindWorkpadsRoute({ + router, + logger: loggingServiceMock.create().get(), + }); + + routeHandler = router.get.mock.calls[0][1]; + }); + + it(`returns 200 with the found workpads`, async () => { + const name = 'something'; + const perPage = 10000; + const mockResults = { + total: 2, + saved_objects: [ + { id: 1, attributes: { key: 'value' } }, + { id: 2, attributes: { key: 'other-value' } }, + ], + }; + + const findMock = mockRouteContext.core.savedObjects.client.find as jest.Mock; + + findMock.mockResolvedValueOnce(mockResults); + + const request = httpServerMock.createKibanaRequest({ + method: 'get', + path: `api/canvas/workpad/find`, + query: { + name, + perPage, + }, + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + expect(response.status).toBe(200); + expect(findMock.mock.calls[0][0].search).toBe(`${name}* | ${name}`); + expect(findMock.mock.calls[0][0].perPage).toBe(perPage); + + expect(response.payload).toMatchInlineSnapshot(` + Object { + "total": 2, + "workpads": Array [ + Object { + "id": 1, + "key": "value", + }, + Object { + "id": 2, + "key": "other-value", + }, + ], + } + `); + }); + + it(`returns 200 with empty results on error`, async () => { + (mockRouteContext.core.savedObjects.client.find as jest.Mock).mockImplementationOnce(() => { + throw new Error('generic error'); + }); + + const request = httpServerMock.createKibanaRequest({ + method: 'get', + path: `api/canvas/workpad/find`, + query: { + name: 'something', + perPage: 1000, + }, + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toMatchInlineSnapshot(` + Object { + "total": 0, + "workpads": Array [], + } + `); + }); +}); diff --git a/x-pack/plugins/canvas/server/routes/workpad/find.ts b/x-pack/plugins/canvas/server/routes/workpad/find.ts new file mode 100644 index 00000000000000..a528a756116093 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/find.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { SavedObjectAttributes } from 'src/core/server'; +import { RouteInitializerDeps } from '../'; +import { + CANVAS_TYPE, + API_ROUTE_WORKPAD, +} from '../../../../../legacy/plugins/canvas/common/lib/constants'; + +export function initializeFindWorkpadsRoute(deps: RouteInitializerDeps) { + const { router } = deps; + router.get( + { + path: `${API_ROUTE_WORKPAD}/find`, + validate: { + query: schema.object({ + name: schema.string(), + page: schema.maybe(schema.number()), + perPage: schema.number(), + }), + }, + }, + async (context, request, response) => { + const savedObjectsClient = context.core.savedObjects.client; + const { name, page, perPage } = request.query; + + try { + const workpads = await savedObjectsClient.find({ + type: CANVAS_TYPE, + sortField: '@timestamp', + sortOrder: 'desc', + search: name ? `${name}* | ${name}` : '*', + searchFields: ['name'], + fields: ['id', 'name', '@created', '@timestamp'], + page, + perPage, + }); + + return response.ok({ + body: { + total: workpads.total, + workpads: workpads.saved_objects.map(hit => ({ id: hit.id, ...hit.attributes })), + }, + }); + } catch (error) { + return response.ok({ + body: { + total: 0, + workpads: [], + }, + }); + } + } + ); +} diff --git a/x-pack/plugins/canvas/server/routes/workpad/get.test.ts b/x-pack/plugins/canvas/server/routes/workpad/get.test.ts new file mode 100644 index 00000000000000..a31293f572c75f --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/get.test.ts @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CANVAS_TYPE } from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { initializeGetWorkpadRoute } from './get'; +import { + IRouter, + kibanaResponseFactory, + RequestHandlerContext, + RequestHandler, +} from 'src/core/server'; +import { + savedObjectsClientMock, + httpServiceMock, + httpServerMock, + loggingServiceMock, +} from 'src/core/server/mocks'; +import { workpadWithGroupAsElement } from '../../../../../legacy/plugins/canvas/__tests__/fixtures/workpads'; +import { CanvasWorkpad } from '../../../../../legacy/plugins/canvas/types'; + +const mockRouteContext = ({ + core: { + savedObjects: { + client: savedObjectsClientMock.create(), + }, + }, +} as unknown) as RequestHandlerContext; + +describe('GET workpad', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const httpService = httpServiceMock.createSetupContract(); + const router = httpService.createRouter('') as jest.Mocked; + initializeGetWorkpadRoute({ + router, + logger: loggingServiceMock.create().get(), + }); + + routeHandler = router.get.mock.calls[0][1]; + }); + + it(`returns 200 when the workpad is found`, async () => { + const request = httpServerMock.createKibanaRequest({ + method: 'get', + path: 'api/canvas/workpad/123', + params: { + id: '123', + }, + }); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockResolvedValueOnce({ + id: '123', + type: CANVAS_TYPE, + attributes: { foo: true }, + references: [], + }); + + mockRouteContext.core.savedObjects.client = savedObjectsClient; + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toMatchInlineSnapshot(` + Object { + "foo": true, + "id": "123", + } + `); + + expect(savedObjectsClient.get.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "canvas-workpad", + "123", + ], + ] + `); + }); + + it('corrects elements that should be groups', async () => { + const request = httpServerMock.createKibanaRequest({ + method: 'get', + path: 'api/canvas/workpad/123', + params: { + id: '123', + }, + }); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockResolvedValueOnce({ + id: '123', + type: CANVAS_TYPE, + attributes: workpadWithGroupAsElement as any, + references: [], + }); + + mockRouteContext.core.savedObjects.client = savedObjectsClient; + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + const workpad = response.payload as CanvasWorkpad; + + expect(response.status).toBe(200); + expect(workpad).not.toBeUndefined(); + + expect(workpad.pages[0].elements.length).toBe(1); + expect(workpad.pages[0].groups.length).toBe(1); + }); + + it('returns 404 if the workpad is not found', async () => { + const id = '123'; + const request = httpServerMock.createKibanaRequest({ + method: 'get', + path: 'api/canvas/workpad/123', + params: { + id, + }, + }); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockImplementation(() => { + throw savedObjectsClient.errors.createGenericNotFoundError(CANVAS_TYPE, id); + }); + mockRouteContext.core.savedObjects.client = savedObjectsClient; + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.payload).toMatchInlineSnapshot(` + Object { + "error": "Not Found", + "message": "Saved object [canvas-workpad/123] not found", + "statusCode": 404, + } + `); + }); +}); diff --git a/x-pack/plugins/canvas/server/routes/workpad/get.ts b/x-pack/plugins/canvas/server/routes/workpad/get.ts new file mode 100644 index 00000000000000..7a51006aa9f024 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/get.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { RouteInitializerDeps } from '../'; +import { + CANVAS_TYPE, + API_ROUTE_WORKPAD, +} from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { CanvasWorkpad } from '../../../../../legacy/plugins/canvas/types'; +import { catchErrorHandler } from '../catch_error_handler'; + +export type WorkpadAttributes = Pick> & { + '@timestamp': string; + '@created': string; +}; + +export function initializeGetWorkpadRoute(deps: RouteInitializerDeps) { + const { router } = deps; + router.get( + { + path: `${API_ROUTE_WORKPAD}/{id}`, + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + catchErrorHandler(async (context, request, response) => { + const workpad = await context.core.savedObjects.client.get( + CANVAS_TYPE, + request.params.id + ); + + if ( + // not sure if we need to be this defensive + workpad.type === 'canvas-workpad' && + workpad.attributes && + workpad.attributes.pages && + workpad.attributes.pages.length + ) { + workpad.attributes.pages.forEach(page => { + const elements = (page.elements || []).filter( + ({ id: pageId }) => !pageId.startsWith('group') + ); + const groups = (page.groups || []).concat( + (page.elements || []).filter(({ id: pageId }) => pageId.startsWith('group')) + ); + page.elements = elements; + page.groups = groups; + }); + } + + return response.ok({ + body: { + id: workpad.id, + ...workpad.attributes, + }, + }); + }) + ); +} diff --git a/x-pack/plugins/canvas/server/routes/workpad/index.ts b/x-pack/plugins/canvas/server/routes/workpad/index.ts new file mode 100644 index 00000000000000..8a61b30be54149 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RouteInitializerDeps } from '../'; +import { initializeFindWorkpadsRoute } from './find'; +import { initializeGetWorkpadRoute } from './get'; +import { initializeCreateWorkpadRoute } from './create'; +import { initializeUpdateWorkpadRoute, initializeUpdateWorkpadAssetsRoute } from './update'; +import { initializeDeleteWorkpadRoute } from './delete'; + +export function initWorkpadRoutes(deps: RouteInitializerDeps) { + initializeFindWorkpadsRoute(deps); + initializeGetWorkpadRoute(deps); + initializeCreateWorkpadRoute(deps); + initializeUpdateWorkpadRoute(deps); + initializeUpdateWorkpadAssetsRoute(deps); + initializeDeleteWorkpadRoute(deps); +} diff --git a/x-pack/plugins/canvas/server/routes/workpad/ok_response.ts b/x-pack/plugins/canvas/server/routes/workpad/ok_response.ts new file mode 100644 index 00000000000000..43d545a5183fed --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/ok_response.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const okResponse = { + ok: true, +}; diff --git a/x-pack/plugins/canvas/server/routes/workpad/update.test.ts b/x-pack/plugins/canvas/server/routes/workpad/update.test.ts new file mode 100644 index 00000000000000..492a6c98d71ee3 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/update.test.ts @@ -0,0 +1,223 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import sinon from 'sinon'; +import { CANVAS_TYPE } from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { initializeUpdateWorkpadRoute, initializeUpdateWorkpadAssetsRoute } from './update'; +import { + IRouter, + kibanaResponseFactory, + RequestHandlerContext, + RequestHandler, +} from 'src/core/server'; +import { + savedObjectsClientMock, + httpServiceMock, + httpServerMock, + loggingServiceMock, +} from 'src/core/server/mocks'; +import { workpads } from '../../../../../legacy/plugins/canvas/__tests__/fixtures/workpads'; +import { okResponse } from './ok_response'; + +const mockRouteContext = ({ + core: { + savedObjects: { + client: savedObjectsClientMock.create(), + }, + }, +} as unknown) as RequestHandlerContext; + +const workpad = workpads[0]; +const now = new Date(); +const nowIso = now.toISOString(); + +jest.mock('uuid/v4', () => jest.fn().mockReturnValue('123abc')); + +describe('PUT workpad', () => { + let routeHandler: RequestHandler; + let clock: sinon.SinonFakeTimers; + + beforeEach(() => { + clock = sinon.useFakeTimers(now); + + const httpService = httpServiceMock.createSetupContract(); + const router = httpService.createRouter('') as jest.Mocked; + initializeUpdateWorkpadRoute({ + router, + logger: loggingServiceMock.create().get(), + }); + + routeHandler = router.put.mock.calls[0][1]; + }); + + afterEach(() => { + jest.resetAllMocks(); + clock.restore(); + }); + + it(`returns 200 ok when the workpad is updated`, async () => { + const updatedWorkpad = { name: 'new name' }; + const { id, ...workpadAttributes } = workpad; + + const request = httpServerMock.createKibanaRequest({ + method: 'put', + path: `api/canvas/workpad/${id}`, + params: { + id, + }, + body: updatedWorkpad, + }); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockResolvedValueOnce({ + id, + type: CANVAS_TYPE, + attributes: workpadAttributes as any, + references: [], + }); + + mockRouteContext.core.savedObjects.client = savedObjectsClient; + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toEqual(okResponse); + expect(mockRouteContext.core.savedObjects.client.create).toBeCalledWith( + CANVAS_TYPE, + { + ...workpadAttributes, + ...updatedWorkpad, + '@timestamp': nowIso, + '@created': workpad['@created'], + }, + { + overwrite: true, + id, + } + ); + }); + + it(`returns not found if existing workpad is not found`, async () => { + const request = httpServerMock.createKibanaRequest({ + method: 'put', + path: 'api/canvas/workpad/some-id', + params: { + id: 'not-found', + }, + body: {}, + }); + + (mockRouteContext.core.savedObjects.client.get as jest.Mock).mockImplementationOnce(() => { + throw mockRouteContext.core.savedObjects.client.errors.createGenericNotFoundError( + 'not found' + ); + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(404); + }); + + it(`returns bad request if the write fails`, async () => { + const request = httpServerMock.createKibanaRequest({ + method: 'put', + path: 'api/canvas/workpad/some-id', + params: { + id: 'some-id', + }, + body: {}, + }); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockResolvedValueOnce({ + id: 'some-id', + type: CANVAS_TYPE, + attributes: {}, + references: [], + }); + + mockRouteContext.core.savedObjects.client = savedObjectsClient; + + (mockRouteContext.core.savedObjects.client.create as jest.Mock).mockImplementationOnce(() => { + throw mockRouteContext.core.savedObjects.client.errors.createBadRequestError('bad request'); + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(400); + }); +}); + +describe('update assets', () => { + let routeHandler: RequestHandler; + let clock: sinon.SinonFakeTimers; + + beforeEach(() => { + clock = sinon.useFakeTimers(now); + const httpService = httpServiceMock.createSetupContract(); + const router = httpService.createRouter('') as jest.Mocked; + initializeUpdateWorkpadAssetsRoute({ + router, + logger: loggingServiceMock.create().get(), + }); + + routeHandler = router.put.mock.calls[0][1]; + }); + + afterEach(() => { + clock.restore(); + }); + + it('updates assets', async () => { + const { id, ...attributes } = workpad; + const assets = { + 'asset-1': { + '@created': new Date().toISOString(), + id: 'asset-1', + type: 'asset', + value: 'some-url-encoded-asset', + }, + 'asset-2': { + '@created': new Date().toISOString(), + id: 'asset-2', + type: 'asset', + value: 'some-other asset', + }, + }; + + const request = httpServerMock.createKibanaRequest({ + method: 'put', + path: 'api/canvas/workpad-assets/some-id', + params: { + id, + }, + body: assets, + }); + + (mockRouteContext.core.savedObjects.client.get as jest.Mock).mockResolvedValueOnce({ + id, + type: CANVAS_TYPE, + attributes: attributes as any, + references: [], + }); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + expect(response.status).toBe(200); + + expect(mockRouteContext.core.savedObjects.client.create).toBeCalledWith( + CANVAS_TYPE, + { + ...attributes, + '@timestamp': nowIso, + assets, + }, + { + id, + overwrite: true, + } + ); + }); +}); diff --git a/x-pack/plugins/canvas/server/routes/workpad/update.ts b/x-pack/plugins/canvas/server/routes/workpad/update.ts new file mode 100644 index 00000000000000..460aa174038ae8 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/update.ts @@ -0,0 +1,129 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; +import { omit } from 'lodash'; +import { KibanaResponseFactory } from 'src/core/server'; +import { SavedObjectsClientContract } from 'src/core/server'; +import { RouteInitializerDeps } from '../'; +import { + CANVAS_TYPE, + API_ROUTE_WORKPAD, + API_ROUTE_WORKPAD_STRUCTURES, + API_ROUTE_WORKPAD_ASSETS, +} from '../../../../../legacy/plugins/canvas/common/lib/constants'; +import { CanvasWorkpad } from '../../../../../legacy/plugins/canvas/types'; +import { WorkpadSchema, WorkpadAssetSchema } from './workpad_schema'; +import { okResponse } from './ok_response'; +import { catchErrorHandler } from '../catch_error_handler'; + +export type WorkpadAttributes = Pick> & { + '@timestamp': string; + '@created': string; +}; + +const AssetsRecordSchema = schema.recordOf(schema.string(), WorkpadAssetSchema); + +const AssetPayloadSchema = schema.object({ + assets: AssetsRecordSchema, +}); + +const workpadUpdateHandler = async ( + payload: TypeOf | TypeOf, + id: string, + savedObjectsClient: SavedObjectsClientContract, + response: KibanaResponseFactory +) => { + const now = new Date().toISOString(); + + const workpadObject = await savedObjectsClient.get(CANVAS_TYPE, id); + await savedObjectsClient.create( + CANVAS_TYPE, + { + ...workpadObject.attributes, + ...omit(payload, 'id'), // never write the id property + '@timestamp': now, // always update the modified time + '@created': workpadObject.attributes['@created'], // ensure created is not modified + }, + { overwrite: true, id } + ); + + return response.ok({ + body: okResponse, + }); +}; + +export function initializeUpdateWorkpadRoute(deps: RouteInitializerDeps) { + const { router } = deps; + // TODO: This route is likely deprecated and everything is using the workpad_structures + // path instead. Investigate further. + router.put( + { + path: `${API_ROUTE_WORKPAD}/{id}`, + validate: { + params: schema.object({ + id: schema.string(), + }), + body: WorkpadSchema, + }, + }, + catchErrorHandler(async (context, request, response) => { + return workpadUpdateHandler( + request.body, + request.params.id, + context.core.savedObjects.client, + response + ); + }) + ); + + router.put( + { + path: `${API_ROUTE_WORKPAD_STRUCTURES}/{id}`, + validate: { + params: schema.object({ + id: schema.string(), + }), + body: WorkpadSchema, + }, + }, + catchErrorHandler(async (context, request, response) => { + return workpadUpdateHandler( + request.body, + request.params.id, + context.core.savedObjects.client, + response + ); + }) + ); +} + +export function initializeUpdateWorkpadAssetsRoute(deps: RouteInitializerDeps) { + const { router } = deps; + + router.put( + { + path: `${API_ROUTE_WORKPAD_ASSETS}/{id}`, + validate: { + params: schema.object({ + id: schema.string(), + }), + // ToDo: Currently the validation must be a schema.object + // Because we don't know what keys the assets will have, we have to allow + // unknowns and then validate in the handler + body: schema.object({}, { allowUnknowns: true }), + }, + }, + async (context, request, response) => { + return workpadUpdateHandler( + { assets: AssetsRecordSchema.validate(request.body) }, + request.params.id, + context.core.savedObjects.client, + response + ); + } + ); +} diff --git a/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.ts b/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.ts new file mode 100644 index 00000000000000..0bcb161575901b --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; + +export const PositionSchema = schema.object({ + angle: schema.number(), + height: schema.number(), + left: schema.number(), + parent: schema.nullable(schema.string()), + top: schema.number(), + width: schema.number(), +}); + +export const WorkpadElementSchema = schema.object({ + expression: schema.string(), + filter: schema.maybe(schema.nullable(schema.string())), + id: schema.string(), + position: PositionSchema, +}); + +export const WorkpadPageSchema = schema.object({ + elements: schema.arrayOf(WorkpadElementSchema), + groups: schema.arrayOf( + schema.object({ + id: schema.string(), + position: PositionSchema, + }) + ), + id: schema.string(), + style: schema.recordOf(schema.string(), schema.string()), + transition: schema.maybe( + schema.oneOf([ + schema.object({}), + schema.object({ + name: schema.string(), + }), + ]) + ), +}); + +export const WorkpadAssetSchema = schema.object({ + '@created': schema.string(), + id: schema.string(), + type: schema.string(), + value: schema.string(), +}); + +export const WorkpadSchema = schema.object({ + '@created': schema.maybe(schema.string()), + '@timestamp': schema.maybe(schema.string()), + assets: schema.maybe(schema.recordOf(schema.string(), WorkpadAssetSchema)), + colors: schema.arrayOf(schema.string()), + css: schema.string(), + height: schema.number(), + id: schema.string(), + isWriteable: schema.maybe(schema.boolean()), + name: schema.string(), + page: schema.number(), + pages: schema.arrayOf(WorkpadPageSchema), + width: schema.number(), +}); diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index 12b4620d554a2e..1ba98d58a3a5f7 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -78,12 +78,20 @@ describe('Authenticator', () => { let authenticator: Authenticator; let mockOptions: ReturnType; let mockSessionStorage: jest.Mocked>; + let mockSessVal: any; beforeEach(() => { mockOptions = getMockOptions({ authc: { providers: ['basic'], oidc: {}, saml: {} }, }); mockSessionStorage = sessionStorageMock.create(); mockOptions.sessionStorageFactory.asScoped.mockReturnValue(mockSessionStorage); + mockSessVal = { + idleTimeoutExpiration: null, + lifespanExpiration: null, + state: { authorization: 'Basic xxx' }, + provider: 'basic', + path: mockOptions.basePath.serverBasePath, + }; authenticator = new Authenticator(mockOptions); }); @@ -159,10 +167,8 @@ describe('Authenticator', () => { expect(mockSessionStorage.set).toHaveBeenCalledTimes(1); expect(mockSessionStorage.set).toHaveBeenCalledWith({ - idleTimeoutExpiration: null, - lifespanExpiration: null, + ...mockSessVal, state: { authorization }, - provider: 'basic', }); }); @@ -176,18 +182,12 @@ describe('Authenticator', () => { }); it('clears session if it belongs to a different provider.', async () => { - const state = { authorization: 'Basic xxx' }; const user = mockAuthenticatedUser(); const credentials = { username: 'user', password: 'password' }; const request = httpServerMock.createKibanaRequest(); mockBasicAuthenticationProvider.login.mockResolvedValue(AuthenticationResult.succeeded(user)); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'token', - }); + mockSessionStorage.get.mockResolvedValue({ ...mockSessVal, provider: 'token' }); const authenticationResult = await authenticator.login(request, { provider: 'basic', @@ -299,12 +299,20 @@ describe('Authenticator', () => { let authenticator: Authenticator; let mockOptions: ReturnType; let mockSessionStorage: jest.Mocked>; + let mockSessVal: any; beforeEach(() => { mockOptions = getMockOptions({ authc: { providers: ['basic'], oidc: {}, saml: {} }, }); mockSessionStorage = sessionStorageMock.create(); mockOptions.sessionStorageFactory.asScoped.mockReturnValue(mockSessionStorage); + mockSessVal = { + idleTimeoutExpiration: null, + lifespanExpiration: null, + state: { authorization: 'Basic xxx' }, + provider: 'basic', + path: mockOptions.basePath.serverBasePath, + }; authenticator = new Authenticator(mockOptions); }); @@ -360,10 +368,8 @@ describe('Authenticator', () => { expect(mockSessionStorage.set).toHaveBeenCalledTimes(1); expect(mockSessionStorage.set).toHaveBeenCalledWith({ - idleTimeoutExpiration: null, - lifespanExpiration: null, + ...mockSessVal, state: { authorization }, - provider: 'basic', }); }); @@ -383,28 +389,20 @@ describe('Authenticator', () => { expect(mockSessionStorage.set).toHaveBeenCalledTimes(1); expect(mockSessionStorage.set).toHaveBeenCalledWith({ - idleTimeoutExpiration: null, - lifespanExpiration: null, + ...mockSessVal, state: { authorization }, - provider: 'basic', }); }); it('does not extend session for system API calls.', async () => { const user = mockAuthenticatedUser(); - const state = { authorization: 'Basic xxx' }; const request = httpServerMock.createKibanaRequest(); mockOptions.isSystemAPIRequest.mockReturnValue(true); mockBasicAuthenticationProvider.authenticate.mockResolvedValue( AuthenticationResult.succeeded(user) ); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'basic', - }); + mockSessionStorage.get.mockResolvedValue(mockSessVal); const authenticationResult = await authenticator.authenticate(request); expect(authenticationResult.succeeded()).toBe(true); @@ -416,37 +414,25 @@ describe('Authenticator', () => { it('extends session for non-system API calls.', async () => { const user = mockAuthenticatedUser(); - const state = { authorization: 'Basic xxx' }; const request = httpServerMock.createKibanaRequest(); mockOptions.isSystemAPIRequest.mockReturnValue(false); mockBasicAuthenticationProvider.authenticate.mockResolvedValue( AuthenticationResult.succeeded(user) ); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'basic', - }); + mockSessionStorage.get.mockResolvedValue(mockSessVal); const authenticationResult = await authenticator.authenticate(request); expect(authenticationResult.succeeded()).toBe(true); expect(authenticationResult.user).toEqual(user); expect(mockSessionStorage.set).toHaveBeenCalledTimes(1); - expect(mockSessionStorage.set).toHaveBeenCalledWith({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'basic', - }); + expect(mockSessionStorage.set).toHaveBeenCalledWith(mockSessVal); expect(mockSessionStorage.clear).not.toHaveBeenCalled(); }); it('properly extends session expiration if it is defined.', async () => { const user = mockAuthenticatedUser(); - const state = { authorization: 'Basic xxx' }; const request = httpServerMock.createKibanaRequest(); const currentDate = new Date(Date.UTC(2019, 10, 10)).valueOf(); @@ -460,12 +446,7 @@ describe('Authenticator', () => { }); mockSessionStorage = sessionStorageMock.create(); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'basic', - }); + mockSessionStorage.get.mockResolvedValue(mockSessVal); mockOptions.sessionStorageFactory.asScoped.mockReturnValue(mockSessionStorage); authenticator = new Authenticator(mockOptions); @@ -482,17 +463,14 @@ describe('Authenticator', () => { expect(mockSessionStorage.set).toHaveBeenCalledTimes(1); expect(mockSessionStorage.set).toHaveBeenCalledWith({ + ...mockSessVal, idleTimeoutExpiration: currentDate + 3600 * 24, - lifespanExpiration: null, - state, - provider: 'basic', }); expect(mockSessionStorage.clear).not.toHaveBeenCalled(); }); it('does not extend session lifespan expiration.', async () => { const user = mockAuthenticatedUser(); - const state = { authorization: 'Basic xxx' }; const request = httpServerMock.createKibanaRequest(); const currentDate = new Date(Date.UTC(2019, 10, 10)).valueOf(); const hr = 1000 * 60 * 60; @@ -508,12 +486,11 @@ describe('Authenticator', () => { mockSessionStorage = sessionStorageMock.create(); mockSessionStorage.get.mockResolvedValue({ + ...mockSessVal, // this session was created 6.5 hrs ago (and has 1.5 hrs left in its lifespan) // it was last extended 1 hour ago, which means it will expire in 1 hour idleTimeoutExpiration: currentDate + hr * 1, lifespanExpiration: currentDate + hr * 1.5, - state, - provider: 'basic', }); mockOptions.sessionStorageFactory.asScoped.mockReturnValue(mockSessionStorage); @@ -531,17 +508,15 @@ describe('Authenticator', () => { expect(mockSessionStorage.set).toHaveBeenCalledTimes(1); expect(mockSessionStorage.set).toHaveBeenCalledWith({ + ...mockSessVal, idleTimeoutExpiration: currentDate + hr * 2, lifespanExpiration: currentDate + hr * 1.5, - state, - provider: 'basic', }); expect(mockSessionStorage.clear).not.toHaveBeenCalled(); }); it('only updates the session lifespan expiration if it does not match the current server config.', async () => { const user = mockAuthenticatedUser(); - const state = { authorization: 'Basic xxx' }; const request = httpServerMock.createKibanaRequest(); const hr = 1000 * 60 * 60; @@ -560,10 +535,9 @@ describe('Authenticator', () => { mockSessionStorage = sessionStorageMock.create(); mockSessionStorage.get.mockResolvedValue({ + ...mockSessVal, idleTimeoutExpiration: 1, lifespanExpiration: oldExpiration, - state, - provider: 'basic', }); mockOptions.sessionStorageFactory.asScoped.mockReturnValue(mockSessionStorage); @@ -579,10 +553,9 @@ describe('Authenticator', () => { expect(mockSessionStorage.set).toHaveBeenCalledTimes(1); expect(mockSessionStorage.set).toHaveBeenCalledWith({ + ...mockSessVal, idleTimeoutExpiration: 1, lifespanExpiration: newExpiration, - state, - provider: 'basic', }); expect(mockSessionStorage.clear).not.toHaveBeenCalled(); } @@ -595,19 +568,13 @@ describe('Authenticator', () => { }); it('does not touch session for system API calls if authentication fails with non-401 reason.', async () => { - const state = { authorization: 'Basic xxx' }; const request = httpServerMock.createKibanaRequest(); mockOptions.isSystemAPIRequest.mockReturnValue(true); mockBasicAuthenticationProvider.authenticate.mockResolvedValue( AuthenticationResult.failed(new Error('some error')) ); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'basic', - }); + mockSessionStorage.get.mockResolvedValue(mockSessVal); const authenticationResult = await authenticator.authenticate(request); expect(authenticationResult.failed()).toBe(true); @@ -617,19 +584,13 @@ describe('Authenticator', () => { }); it('does not touch session for non-system API calls if authentication fails with non-401 reason.', async () => { - const state = { authorization: 'Basic xxx' }; const request = httpServerMock.createKibanaRequest(); mockOptions.isSystemAPIRequest.mockReturnValue(false); mockBasicAuthenticationProvider.authenticate.mockResolvedValue( AuthenticationResult.failed(new Error('some error')) ); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'basic', - }); + mockSessionStorage.get.mockResolvedValue(mockSessVal); const authenticationResult = await authenticator.authenticate(request); expect(authenticationResult.failed()).toBe(true); @@ -640,7 +601,6 @@ describe('Authenticator', () => { it('replaces existing session with the one returned by authentication provider for system API requests', async () => { const user = mockAuthenticatedUser(); - const existingState = { authorization: 'Basic xxx' }; const newState = { authorization: 'Basic yyy' }; const request = httpServerMock.createKibanaRequest(); @@ -648,12 +608,7 @@ describe('Authenticator', () => { mockBasicAuthenticationProvider.authenticate.mockResolvedValue( AuthenticationResult.succeeded(user, { state: newState }) ); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state: existingState, - provider: 'basic', - }); + mockSessionStorage.get.mockResolvedValue(mockSessVal); const authenticationResult = await authenticator.authenticate(request); expect(authenticationResult.succeeded()).toBe(true); @@ -661,17 +616,14 @@ describe('Authenticator', () => { expect(mockSessionStorage.set).toHaveBeenCalledTimes(1); expect(mockSessionStorage.set).toHaveBeenCalledWith({ - idleTimeoutExpiration: null, - lifespanExpiration: null, + ...mockSessVal, state: newState, - provider: 'basic', }); expect(mockSessionStorage.clear).not.toHaveBeenCalled(); }); it('replaces existing session with the one returned by authentication provider for non-system API requests', async () => { const user = mockAuthenticatedUser(); - const existingState = { authorization: 'Basic xxx' }; const newState = { authorization: 'Basic yyy' }; const request = httpServerMock.createKibanaRequest(); @@ -679,12 +631,7 @@ describe('Authenticator', () => { mockBasicAuthenticationProvider.authenticate.mockResolvedValue( AuthenticationResult.succeeded(user, { state: newState }) ); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state: existingState, - provider: 'basic', - }); + mockSessionStorage.get.mockResolvedValue(mockSessVal); const authenticationResult = await authenticator.authenticate(request); expect(authenticationResult.succeeded()).toBe(true); @@ -692,28 +639,20 @@ describe('Authenticator', () => { expect(mockSessionStorage.set).toHaveBeenCalledTimes(1); expect(mockSessionStorage.set).toHaveBeenCalledWith({ - idleTimeoutExpiration: null, - lifespanExpiration: null, + ...mockSessVal, state: newState, - provider: 'basic', }); expect(mockSessionStorage.clear).not.toHaveBeenCalled(); }); it('clears session if provider failed to authenticate system API request with 401 with active session.', async () => { - const state = { authorization: 'Basic xxx' }; const request = httpServerMock.createKibanaRequest(); mockOptions.isSystemAPIRequest.mockReturnValue(true); mockBasicAuthenticationProvider.authenticate.mockResolvedValue( AuthenticationResult.failed(Boom.unauthorized()) ); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'basic', - }); + mockSessionStorage.get.mockResolvedValue(mockSessVal); const authenticationResult = await authenticator.authenticate(request); expect(authenticationResult.failed()).toBe(true); @@ -723,19 +662,13 @@ describe('Authenticator', () => { }); it('clears session if provider failed to authenticate non-system API request with 401 with active session.', async () => { - const state = { authorization: 'Basic xxx' }; const request = httpServerMock.createKibanaRequest(); mockOptions.isSystemAPIRequest.mockReturnValue(false); mockBasicAuthenticationProvider.authenticate.mockResolvedValue( AuthenticationResult.failed(Boom.unauthorized()) ); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'basic', - }); + mockSessionStorage.get.mockResolvedValue(mockSessVal); const authenticationResult = await authenticator.authenticate(request); expect(authenticationResult.failed()).toBe(true); @@ -745,18 +678,12 @@ describe('Authenticator', () => { }); it('clears session if provider requested it via setting state to `null`.', async () => { - const state = { authorization: 'Basic xxx' }; const request = httpServerMock.createKibanaRequest(); mockBasicAuthenticationProvider.authenticate.mockResolvedValue( AuthenticationResult.redirectTo('some-url', { state: null }) ); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'basic', - }); + mockSessionStorage.get.mockResolvedValue(mockSessVal); const authenticationResult = await authenticator.authenticate(request); expect(authenticationResult.redirected()).toBe(true); @@ -766,19 +693,13 @@ describe('Authenticator', () => { }); it('does not clear session if provider can not handle system API request authentication with active session.', async () => { - const state = { authorization: 'Basic xxx' }; const request = httpServerMock.createKibanaRequest(); mockOptions.isSystemAPIRequest.mockReturnValue(true); mockBasicAuthenticationProvider.authenticate.mockResolvedValue( AuthenticationResult.notHandled() ); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'basic', - }); + mockSessionStorage.get.mockResolvedValue(mockSessVal); const authenticationResult = await authenticator.authenticate(request); expect(authenticationResult.notHandled()).toBe(true); @@ -788,19 +709,13 @@ describe('Authenticator', () => { }); it('does not clear session if provider can not handle non-system API request authentication with active session.', async () => { - const state = { authorization: 'Basic xxx' }; const request = httpServerMock.createKibanaRequest(); mockOptions.isSystemAPIRequest.mockReturnValue(false); mockBasicAuthenticationProvider.authenticate.mockResolvedValue( AuthenticationResult.notHandled() ); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'basic', - }); + mockSessionStorage.get.mockResolvedValue(mockSessVal); const authenticationResult = await authenticator.authenticate(request); expect(authenticationResult.notHandled()).toBe(true); @@ -810,19 +725,13 @@ describe('Authenticator', () => { }); it('clears session for system API request if it belongs to not configured provider.', async () => { - const state = { authorization: 'Basic xxx' }; const request = httpServerMock.createKibanaRequest(); mockOptions.isSystemAPIRequest.mockReturnValue(true); mockBasicAuthenticationProvider.authenticate.mockResolvedValue( AuthenticationResult.notHandled() ); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'token', - }); + mockSessionStorage.get.mockResolvedValue({ ...mockSessVal, provider: 'token' }); const authenticationResult = await authenticator.authenticate(request); expect(authenticationResult.notHandled()).toBe(true); @@ -832,19 +741,13 @@ describe('Authenticator', () => { }); it('clears session for non-system API request if it belongs to not configured provider.', async () => { - const state = { authorization: 'Basic xxx' }; const request = httpServerMock.createKibanaRequest(); mockOptions.isSystemAPIRequest.mockReturnValue(false); mockBasicAuthenticationProvider.authenticate.mockResolvedValue( AuthenticationResult.notHandled() ); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'token', - }); + mockSessionStorage.get.mockResolvedValue({ ...mockSessVal, provider: 'token' }); const authenticationResult = await authenticator.authenticate(request); expect(authenticationResult.notHandled()).toBe(true); @@ -858,12 +761,20 @@ describe('Authenticator', () => { let authenticator: Authenticator; let mockOptions: ReturnType; let mockSessionStorage: jest.Mocked>; + let mockSessVal: any; beforeEach(() => { mockOptions = getMockOptions({ authc: { providers: ['basic'], oidc: {}, saml: {} }, }); mockSessionStorage = sessionStorageMock.create(); mockOptions.sessionStorageFactory.asScoped.mockReturnValue(mockSessionStorage); + mockSessVal = { + idleTimeoutExpiration: null, + lifespanExpiration: null, + state: { authorization: 'Basic xxx' }, + provider: 'basic', + path: mockOptions.basePath.serverBasePath, + }; authenticator = new Authenticator(mockOptions); }); @@ -886,16 +797,10 @@ describe('Authenticator', () => { it('clears session and returns whatever authentication provider returns.', async () => { const request = httpServerMock.createKibanaRequest(); - const state = { authorization: 'Basic xxx' }; mockBasicAuthenticationProvider.logout.mockResolvedValue( DeauthenticationResult.redirectTo('some-url') ); - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'basic', - }); + mockSessionStorage.get.mockResolvedValue(mockSessVal); const deauthenticationResult = await authenticator.logout(request); @@ -934,12 +839,7 @@ describe('Authenticator', () => { it('only clears session if it belongs to not configured provider.', async () => { const request = httpServerMock.createKibanaRequest(); const state = { authorization: 'Bearer xxx' }; - mockSessionStorage.get.mockResolvedValue({ - idleTimeoutExpiration: null, - lifespanExpiration: null, - state, - provider: 'token', - }); + mockSessionStorage.get.mockResolvedValue({ ...mockSessVal, state, provider: 'token' }); const deauthenticationResult = await authenticator.logout(request); @@ -978,6 +878,7 @@ describe('Authenticator', () => { lifespanExpiration: mockInfo.lifespanExpiration, state, provider: mockInfo.provider, + path: mockOptions.basePath.serverBasePath, }); jest.spyOn(Date, 'now').mockImplementation(() => currentDate); diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index 17a773c6b6e8ce..8f947349cb2e86 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -59,6 +59,11 @@ export interface ProviderSession { * entirely determined by the authentication provider that owns the current session. */ state: unknown; + + /** + * Cookie "Path" attribute that is validated against the current Kibana server configuration. + */ + path: string; } /** @@ -159,6 +164,11 @@ export class Authenticator { */ private readonly providers: Map; + /** + * Which base path the HTTP server is hosted on. + */ + private readonly serverBasePath: string; + /** * Session timeout in ms. If `null` session will stay active until the browser is closed. */ @@ -213,6 +223,7 @@ export class Authenticator { ] as [string, BaseAuthenticationProvider]; }) ); + this.serverBasePath = this.options.basePath.serverBasePath || '/'; // only set these vars if they are defined in options (otherwise coalesce to existing/default) this.idleTimeout = this.options.config.session.idleTimeout; @@ -277,6 +288,7 @@ export class Authenticator { provider: attempt.provider, idleTimeoutExpiration, lifespanExpiration, + path: this.serverBasePath, }); } @@ -465,6 +477,7 @@ export class Authenticator { provider: providerType, idleTimeoutExpiration, lifespanExpiration, + path: this.serverBasePath, }); } } diff --git a/x-pack/plugins/security/server/authentication/index.ts b/x-pack/plugins/security/server/authentication/index.ts index 2e67a0eaaa6d51..de2fb54ab8c2a1 100644 --- a/x-pack/plugins/security/server/authentication/index.ts +++ b/x-pack/plugins/security/server/authentication/index.ts @@ -65,6 +65,22 @@ export async function setupAuthentication({ .callAsCurrentUser('shield.authenticate')) as AuthenticatedUser; }; + const isValid = (sessionValue: ProviderSession) => { + // ensure that this cookie was created with the current Kibana configuration + const { path, idleTimeoutExpiration, lifespanExpiration } = sessionValue; + if (path !== undefined && path !== (http.basePath.serverBasePath || '/')) { + authLogger.debug(`Outdated session value with path "${sessionValue.path}"`); + return false; + } + // ensure that this cookie is not expired + if (idleTimeoutExpiration && idleTimeoutExpiration < Date.now()) { + return false; + } else if (lifespanExpiration && lifespanExpiration < Date.now()) { + return false; + } + return true; + }; + const authenticator = new Authenticator({ clusterClient, basePath: http.basePath, @@ -75,14 +91,14 @@ export async function setupAuthentication({ encryptionKey: config.encryptionKey, isSecure: config.secureCookies, name: config.cookieName, - validate: (sessionValue: ProviderSession) => { - const { idleTimeoutExpiration, lifespanExpiration } = sessionValue; - if (idleTimeoutExpiration && idleTimeoutExpiration < Date.now()) { - return false; - } else if (lifespanExpiration && lifespanExpiration < Date.now()) { - return false; + validate: (session: ProviderSession | ProviderSession[]) => { + const array: ProviderSession[] = Array.isArray(session) ? session : [session]; + for (const sess of array) { + if (!isValid(sess)) { + return { isValid: false, path: sess.path }; + } } - return true; + return { isValid: true }; }, }), }); diff --git a/x-pack/plugins/spaces/kibana.json b/x-pack/plugins/spaces/kibana.json index 313e4415a8e7c7..d806aaf1807ef1 100644 --- a/x-pack/plugins/spaces/kibana.json +++ b/x-pack/plugins/spaces/kibana.json @@ -4,7 +4,7 @@ "kibanaVersion": "kibana", "configPath": ["xpack", "spaces"], "requiredPlugins": ["features", "licensing"], - "optionalPlugins": ["security", "home"], + "optionalPlugins": ["security", "home", "usageCollection"], "server": true, "ui": false } diff --git a/x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.test.ts b/x-pack/plugins/spaces/server/lib/spaces_usage_collector.test.ts similarity index 83% rename from x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.test.ts rename to x-pack/plugins/spaces/server/lib/spaces_usage_collector.test.ts index 912cccbc01782d..b343bac9343a33 100644 --- a/x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.test.ts +++ b/x-pack/plugins/spaces/server/lib/spaces_usage_collector.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getSpacesUsageCollector, UsageStats } from './get_spaces_usage_collector'; +import { getSpacesUsageCollector, UsageStats } from './spaces_usage_collector'; import * as Rx from 'rxjs'; import { PluginsSetup } from '../plugin'; import { Feature } from '../../../features/server'; @@ -42,10 +42,8 @@ function setup({ return { licensing, features: featuresSetup, - usage: { - collectorSet: { - makeUsageCollector: (options: any) => new MockUsageCollector(options), - }, + usageCollecion: { + makeUsageCollector: (options: any) => new MockUsageCollector(options), }, }; } @@ -71,10 +69,11 @@ const defaultCallClusterMock = jest.fn().mockResolvedValue({ describe('with a basic license', () => { let usageStats: UsageStats; beforeAll(async () => { - const { features, licensing, usage } = setup({ license: { isAvailable: true, type: 'basic' } }); - const { fetch: getSpacesUsage } = getSpacesUsageCollector({ + const { features, licensing, usageCollecion } = setup({ + license: { isAvailable: true, type: 'basic' }, + }); + const { fetch: getSpacesUsage } = getSpacesUsageCollector(usageCollecion as any, { kibanaIndex: '.kibana', - usage, features, licensing, }); @@ -106,10 +105,9 @@ describe('with a basic license', () => { describe('with no license', () => { let usageStats: UsageStats; beforeAll(async () => { - const { features, licensing, usage } = setup({ license: { isAvailable: false } }); - const { fetch: getSpacesUsage } = getSpacesUsageCollector({ + const { features, licensing, usageCollecion } = setup({ license: { isAvailable: false } }); + const { fetch: getSpacesUsage } = getSpacesUsageCollector(usageCollecion as any, { kibanaIndex: '.kibana', - usage, features, licensing, }); @@ -136,12 +134,11 @@ describe('with no license', () => { describe('with platinum license', () => { let usageStats: UsageStats; beforeAll(async () => { - const { features, licensing, usage } = setup({ + const { features, licensing, usageCollecion } = setup({ license: { isAvailable: true, type: 'platinum' }, }); - const { fetch: getSpacesUsage } = getSpacesUsageCollector({ + const { fetch: getSpacesUsage } = getSpacesUsageCollector(usageCollecion as any, { kibanaIndex: '.kibana', - usage, features, licensing, }); diff --git a/x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.ts b/x-pack/plugins/spaces/server/lib/spaces_usage_collector.ts similarity index 88% rename from x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.ts rename to x-pack/plugins/spaces/server/lib/spaces_usage_collector.ts index bfbc5e6ab775d9..eb6843cfe4538a 100644 --- a/x-pack/plugins/spaces/server/lib/get_spaces_usage_collector.ts +++ b/x-pack/plugins/spaces/server/lib/spaces_usage_collector.ts @@ -7,6 +7,7 @@ import { get } from 'lodash'; import { CallAPIOptions } from 'src/core/server'; import { take } from 'rxjs/operators'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; // @ts-ignore import { KIBANA_STATS_TYPE_MONITORING } from '../../../../legacy/plugins/monitoring/common/constants'; import { KIBANA_SPACES_STATS_TYPE } from '../../common/constants'; @@ -116,7 +117,6 @@ export interface UsageStats { interface CollectorDeps { kibanaIndex: string; - usage: { collectorSet: any }; features: PluginsSetup['features']; licensing: PluginsSetup['licensing']; } @@ -125,9 +125,11 @@ interface CollectorDeps { * @param {Object} server * @return {Object} kibana usage stats type collection object */ -export function getSpacesUsageCollector(deps: CollectorDeps) { - const { collectorSet } = deps.usage; - return collectorSet.makeUsageCollector({ +export function getSpacesUsageCollector( + usageCollection: UsageCollectionSetup, + deps: CollectorDeps +) { + return usageCollection.makeUsageCollector({ type: KIBANA_SPACES_STATS_TYPE, isReady: () => true, fetch: async (callCluster: CallCluster) => { @@ -165,3 +167,14 @@ export function getSpacesUsageCollector(deps: CollectorDeps) { }, }); } + +export function registerSpacesUsageCollector( + usageCollection: UsageCollectionSetup | undefined, + deps: CollectorDeps +) { + if (!usageCollection) { + return; + } + const collector = getSpacesUsageCollector(usageCollection, deps); + usageCollection.registerCollector(collector); +} diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index 6511a5dc3f31b0..9d45dbb1b748d2 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -7,6 +7,8 @@ import { Observable } from 'rxjs'; import { take } from 'rxjs/operators'; import { CapabilitiesModifier } from 'src/legacy/server/capabilities'; +import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { HomeServerPluginSetup } from 'src/plugins/home/server'; import { SavedObjectsLegacyService, CoreSetup, @@ -24,25 +26,19 @@ import { AuditLogger } from '../../../../server/lib/audit_logger'; import { spacesSavedObjectsClientWrapperFactory } from './lib/saved_objects_client/saved_objects_client_wrapper_factory'; import { SpacesAuditLogger } from './lib/audit_logger'; import { createSpacesTutorialContextFactory } from './lib/spaces_tutorial_context_factory'; -import { getSpacesUsageCollector } from './lib/get_spaces_usage_collector'; +import { registerSpacesUsageCollector } from './lib/spaces_usage_collector'; import { SpacesService } from './spaces_service'; import { SpacesServiceSetup } from './spaces_service/spaces_service'; import { ConfigType } from './config'; import { toggleUICapabilities } from './lib/toggle_ui_capabilities'; import { initSpacesRequestInterceptors } from './lib/request_interceptors'; import { initExternalSpacesApi } from './routes/api/external'; -import { HomeServerPluginSetup } from '../../../../src/plugins/home/server'; /** * Describes a set of APIs that is available in the legacy platform only and required by this plugin * to function properly. */ export interface LegacyAPI { savedObjects: SavedObjectsLegacyService; - usage: { - collectorSet: { - register: (collector: any) => void; - }; - }; tutorial: { addScopedTutorialContextFactory: (factory: any) => void; }; @@ -62,6 +58,7 @@ export interface PluginsSetup { features: FeaturesPluginSetup; licensing: LicensingPluginSetup; security?: SecurityPluginSetup; + usageCollection?: UsageCollectionSetup; home?: HomeServerPluginSetup; } @@ -150,7 +147,12 @@ export class Plugin { __legacyCompat: { registerLegacyAPI: (legacyAPI: LegacyAPI) => { this.legacyAPI = legacyAPI; - this.setupLegacyComponents(spacesService, plugins.features, plugins.licensing); + this.setupLegacyComponents( + spacesService, + plugins.features, + plugins.licensing, + plugins.usageCollection + ); }, createDefaultSpace: async () => { const esClient = await core.elasticsearch.adminClient$.pipe(take(1)).toPromise(); @@ -168,7 +170,8 @@ export class Plugin { private setupLegacyComponents( spacesService: SpacesServiceSetup, featuresSetup: FeaturesPluginSetup, - licensingSetup: LicensingPluginSetup + licensingSetup: LicensingPluginSetup, + usageCollectionSetup?: UsageCollectionSetup ) { const legacyAPI = this.getLegacyAPI(); const { addScopedSavedObjectsClientWrapperFactory, types } = legacyAPI.savedObjects; @@ -180,6 +183,12 @@ export class Plugin { legacyAPI.tutorial.addScopedTutorialContextFactory( createSpacesTutorialContextFactory(spacesService) ); + // Register a function with server to manage the collection of usage stats + registerSpacesUsageCollector(usageCollectionSetup, { + kibanaIndex: legacyAPI.legacyConfig.kibanaIndex, + features: featuresSetup, + licensing: licensingSetup, + }); legacyAPI.capabilities.registerCapabilitiesModifier(async (request, uiCapabilities) => { try { const activeSpace = await spacesService.getActiveSpace(KibanaRequest.from(request)); @@ -189,14 +198,5 @@ export class Plugin { return uiCapabilities; } }); - // Register a function with server to manage the collection of usage stats - legacyAPI.usage.collectorSet.register( - getSpacesUsageCollector({ - kibanaIndex: legacyAPI.legacyConfig.kibanaIndex, - usage: legacyAPI.usage, - features: featuresSetup, - licensing: licensingSetup, - }) - ); } } diff --git a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts index 38a973c1203d58..62820466b571cb 100644 --- a/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts +++ b/x-pack/plugins/spaces/server/routes/api/__fixtures__/create_legacy_api.ts @@ -106,7 +106,6 @@ export const createLegacyAPI = ({ auditLogger: {} as any, capabilities: {} as any, tutorial: {} as any, - usage: {} as any, xpackMain: {} as any, savedObjects: savedObjectsService, }; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index f5fc453557122e..217b20797492a6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -2527,12 +2527,12 @@ "kbnDocViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel": "警告", "kbnDocViews.table.noCachedMappingForThisFieldAriaLabel": "警告", "kbnDocViews.table.toggleFieldDetails": "フィールド詳細を切り替える", - "kbnESQuery.kql.errors.endOfInputText": "インプットの終わり", - "kbnESQuery.kql.errors.fieldNameText": "フィールド名", - "kbnESQuery.kql.errors.literalText": "文字通り", - "kbnESQuery.kql.errors.syntaxError": "{expectedList} が予測されましたが {foundInput} が検出されました。", - "kbnESQuery.kql.errors.valueText": "値", - "kbnESQuery.kql.errors.whitespaceText": "ホワイトスペース", + "data.common.esQuery.kql.errors.endOfInputText": "インプットの終わり", + "data.common.esQuery.kql.errors.fieldNameText": "フィールド名", + "data.common.esQuery.kql.errors.literalText": "文字通り", + "data.common.esQuery.kql.errors.syntaxError": "{expectedList} が予測されましたが {foundInput} が検出されました。", + "data.common.esQuery.kql.errors.valueText": "値", + "data.common.esQuery.kql.errors.whitespaceText": "ホワイトスペース", "kbnVislibVisTypes.area.areaDescription": "折れ線グラフの下の数量を強調します。", "kbnVislibVisTypes.area.areaTitle": "エリア", "kbnVislibVisTypes.area.groupTitle": "系列を分割", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 288fc92be3cbd8..6a2ba20af7714b 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -2528,12 +2528,12 @@ "kbnDocViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel": "警告", "kbnDocViews.table.noCachedMappingForThisFieldAriaLabel": "警告", "kbnDocViews.table.toggleFieldDetails": "切换字段详细信息", - "kbnESQuery.kql.errors.endOfInputText": "输入结束", - "kbnESQuery.kql.errors.fieldNameText": "字段名称", - "kbnESQuery.kql.errors.literalText": "文本", - "kbnESQuery.kql.errors.syntaxError": "应为 {expectedList},但却找到了 {foundInput}。", - "kbnESQuery.kql.errors.valueText": "值", - "kbnESQuery.kql.errors.whitespaceText": "空白", + "data.common.esQuery.kql.errors.endOfInputText": "输入结束", + "data.common.esQuery.kql.errors.fieldNameText": "字段名称", + "data.common.esQuery.kql.errors.literalText": "文本", + "data.common.esQuery.kql.errors.syntaxError": "应为 {expectedList},但却找到了 {foundInput}。", + "data.common.esQuery.kql.errors.valueText": "值", + "data.common.esQuery.kql.errors.whitespaceText": "空白", "kbnVislibVisTypes.area.areaDescription": "突出折线图下方的数量", "kbnVislibVisTypes.area.areaTitle": "面积图", "kbnVislibVisTypes.area.groupTitle": "拆分序列", diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot.json index 0ac6c67e23d2bc..93d63bad66e30a 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot.json @@ -1,10 +1,6 @@ { - "snapshot": { - "counts": { - "down": 7, - "mixed": 0, - "up": 93, - "total": 100 - } - } + "up": 93, + "down": 7, + "mixed": 0, + "total": 100 } \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_empty.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_empty.json index 8639f0ec0feea2..94c1ffbc742908 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_empty.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_empty.json @@ -1,10 +1,6 @@ { - "snapshot": { - "counts": { - "down": 13, - "mixed": 0, - "up": 0, - "total": 13 - } - } + "up": 0, + "down": 7, + "mixed": 0, + "total": 7 } \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_down.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_down.json index 8639f0ec0feea2..94c1ffbc742908 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_down.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_down.json @@ -1,10 +1,6 @@ { - "snapshot": { - "counts": { - "down": 13, - "mixed": 0, - "up": 0, - "total": 13 - } - } + "up": 0, + "down": 7, + "mixed": 0, + "total": 7 } \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_up.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_up.json index 065c3f90e932ea..2d79880e7c0ee0 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_up.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_up.json @@ -1,10 +1,6 @@ { - "snapshot": { - "counts": { - "down": 0, - "mixed": 0, - "up": 94, - "total": 94 - } - } + "up": 93, + "down": 0, + "mixed": 0, + "total": 93 } \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/index.js b/x-pack/test/api_integration/apis/uptime/graphql/index.js index f7fafa94196570..346032f87dc4da 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/index.js +++ b/x-pack/test/api_integration/apis/uptime/graphql/index.js @@ -17,7 +17,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./monitor_states')); loadTestFile(require.resolve('./monitor_status_bar')); loadTestFile(require.resolve('./ping_list')); - loadTestFile(require.resolve('./snapshot')); loadTestFile(require.resolve('./snapshot_histogram')); }); } diff --git a/x-pack/test/api_integration/apis/uptime/graphql/snapshot.js b/x-pack/test/api_integration/apis/uptime/graphql/snapshot.js deleted file mode 100644 index 004b87571eab4a..00000000000000 --- a/x-pack/test/api_integration/apis/uptime/graphql/snapshot.js +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { snapshotQueryString } from '../../../../../legacy/plugins/uptime/public/queries'; -import { expectFixtureEql } from './helpers/expect_fixture_eql'; - -export default function ({ getService }) { - describe('snapshot query', () => { - before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat')); - after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat')); - - const supertest = getService('supertest'); - - it('will fetch a monitor snapshot summary', async () => { - const getSnapshotQuery = { - operationName: 'Snapshot', - query: snapshotQueryString, - variables: { - dateRangeStart: '2019-01-28T17:40:08.078Z', - dateRangeEnd: '2025-01-28T19:00:16.078Z', - }, - }; - const { - body: { data }, - } = await supertest - .post('/api/uptime/graphql') - .set('kbn-xsrf', 'foo') - .send({ ...getSnapshotQuery }); - - expectFixtureEql(data, 'snapshot'); - }); - - it('will fetch a monitor snapshot filtered by down status', async () => { - const getSnapshotQuery = { - operationName: 'Snapshot', - query: snapshotQueryString, - variables: { - dateRangeStart: '2019-01-28T17:40:08.078Z', - dateRangeEnd: '2025-01-28T19:00:16.078Z', - filters: `{"bool":{"must":[{"match":{"monitor.status":{"query":"down","operator":"and"}}}]}}`, - statusFilter: 'down', - }, - }; - const { - body: { data }, - } = await supertest - .post('/api/uptime/graphql') - .set('kbn-xsrf', 'foo') - .send({ ...getSnapshotQuery }); - - expectFixtureEql(data, 'snapshot_filtered_by_down'); - }); - - it('will fetch a monitor snapshot filtered by up status', async () => { - const getSnapshotQuery = { - operationName: 'Snapshot', - query: snapshotQueryString, - variables: { - dateRangeStart: '2019-01-28T17:40:08.078Z', - dateRangeEnd: '2025-01-28T19:00:16.078Z', - filters: `{"bool":{"must":[{"match":{"monitor.status":{"query":"up","operator":"and"}}}]}}`, - statusFilter: 'up', - }, - }; - const { - body: { data }, - } = await supertest - .post('/api/uptime/graphql') - .set('kbn-xsrf', 'foo') - .send({ ...getSnapshotQuery }); - - - expectFixtureEql(data, 'snapshot_filtered_by_up'); - }); - - it('returns null histogram data when no data present', async () => { - const getSnapshotQuery = { - operationName: 'Snapshot', - query: snapshotQueryString, - variables: { - dateRangeStart: '2019-01-25T04:30:54.740Z', - dateRangeEnd: '2025-01-28T04:50:54.740Z', - filters: `{"bool":{"must":[{"match":{"monitor.status":{"query":"down","operator":"and"}}}]}}`, - }, - }; - const { - body: { data }, - } = await supertest - .post('/api/uptime/graphql') - .set('kbn-xsrf', 'foo') - .send({ ...getSnapshotQuery }); - - - expectFixtureEql(data, 'snapshot_empty'); - }); - // TODO: test for host, port, etc. - }); -} diff --git a/x-pack/test/api_integration/apis/uptime/index.js b/x-pack/test/api_integration/apis/uptime/index.js index 6eb77fb584133d..9175658783fb57 100644 --- a/x-pack/test/api_integration/apis/uptime/index.js +++ b/x-pack/test/api_integration/apis/uptime/index.js @@ -17,5 +17,6 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./get_all_pings')); loadTestFile(require.resolve('./graphql')); + loadTestFile(require.resolve('./rest')); }); } diff --git a/x-pack/test/api_integration/apis/uptime/rest/index.ts b/x-pack/test/api_integration/apis/uptime/rest/index.ts new file mode 100644 index 00000000000000..b76d3f7c2e44ab --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function({ getService, loadTestFile }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + describe('uptime REST endpoints', () => { + before('load heartbeat data', () => esArchiver.load('uptime/full_heartbeat')); + after('unload', () => esArchiver.unload('uptime/full_heartbeat')); + loadTestFile(require.resolve('./snapshot')); + }); +} diff --git a/x-pack/test/api_integration/apis/uptime/rest/snapshot.ts b/x-pack/test/api_integration/apis/uptime/rest/snapshot.ts new file mode 100644 index 00000000000000..0175dc649b4958 --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/snapshot.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { expectFixtureEql } from '../graphql/helpers/expect_fixture_eql'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('snapshot count', () => { + let dateRangeStart = '2019-01-28T17:40:08.078Z'; + let dateRangeEnd = '2025-01-28T19:00:16.078Z'; + + it('will fetch the full set of snapshot counts', async () => { + const apiResponse = await supertest.get( + `/api/uptime/snapshot/count?dateRangeStart=${dateRangeStart}&dateRangeEnd=${dateRangeEnd}` + ); + expectFixtureEql(apiResponse.body, 'snapshot'); + }); + + it('will fetch a monitor snapshot filtered by down status', async () => { + const filters = `{"bool":{"must":[{"match":{"monitor.status":{"query":"down","operator":"and"}}}]}}`; + const statusFilter = 'down'; + const apiResponse = await supertest.get( + `/api/uptime/snapshot/count?dateRangeStart=${dateRangeStart}&dateRangeEnd=${dateRangeEnd}&filters=${filters}&statusFilter=${statusFilter}` + ); + expectFixtureEql(apiResponse.body, 'snapshot_filtered_by_down'); + }); + + it('will fetch a monitor snapshot filtered by up status', async () => { + const filters = `{"bool":{"must":[{"match":{"monitor.status":{"query":"up","operator":"and"}}}]}}`; + const statusFilter = 'up'; + const apiResponse = await supertest.get( + `/api/uptime/snapshot/count?dateRangeStart=${dateRangeStart}&dateRangeEnd=${dateRangeEnd}&filters=${filters}&statusFilter=${statusFilter}` + ); + expectFixtureEql(apiResponse.body, 'snapshot_filtered_by_up'); + }); + + it('returns a null snapshot when no data is present', async () => { + dateRangeStart = '2019-01-25T04:30:54.740Z'; + dateRangeEnd = '2025-01-28T04:50:54.740Z'; + const filters = `{"bool":{"must":[{"match":{"monitor.status":{"query":"down","operator":"and"}}}]}}`; + const apiResponse = await supertest.get( + `/api/uptime/snapshot/count?dateRangeStart=${dateRangeStart}&dateRangeEnd=${dateRangeEnd}&filters=${filters}` + ); + expectFixtureEql(apiResponse.body, 'snapshot_empty'); + }); + }); +} diff --git a/x-pack/test/functional/apps/machine_learning/anomaly_detection/index.ts b/x-pack/test/functional/apps/machine_learning/anomaly_detection/index.ts index 2b76bce544f6d6..d5d617587fc3bb 100644 --- a/x-pack/test/functional/apps/machine_learning/anomaly_detection/index.ts +++ b/x-pack/test/functional/apps/machine_learning/anomaly_detection/index.ts @@ -6,8 +6,9 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ loadTestFile }: FtrProviderContext) { - // FLAKY: https://github.com/elastic/kibana/issues/51669 - describe.skip('anomaly detection', function() { + describe('anomaly detection', function() { + this.tags(['skipFirefox']); + loadTestFile(require.resolve('./single_metric_job')); loadTestFile(require.resolve('./multi_metric_job')); loadTestFile(require.resolve('./population_job')); diff --git a/x-pack/test/functional/services/machine_learning/job_management.ts b/x-pack/test/functional/services/machine_learning/job_management.ts index ddab5fd68f13c3..5ffb235a828d6b 100644 --- a/x-pack/test/functional/services/machine_learning/job_management.ts +++ b/x-pack/test/functional/services/machine_learning/job_management.ts @@ -15,7 +15,6 @@ export function MachineLearningJobManagementProvider( mlApi: ProvidedType ) { const testSubjects = getService('testSubjects'); - const retry = getService('retry'); return { async navigateToNewJobSourceSelection() { @@ -36,10 +35,7 @@ export function MachineLearningJobManagementProvider( }, async assertStartDatafeedModalExists() { - // this retry can be removed as soon as #48734 is merged - await retry.tryForTime(5000, async () => { - await testSubjects.existOrFail('mlStartDatafeedModal'); - }); + await testSubjects.existOrFail('mlStartDatafeedModal', { timeout: 5000 }); }, async confirmStartDatafeedModal() { diff --git a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts index 71b76a6885592d..3e7dacb23d61bc 100644 --- a/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts +++ b/x-pack/test/functional/services/machine_learning/job_wizard_advanced.ts @@ -146,10 +146,7 @@ export function MachineLearningJobWizardAdvancedProvider({ }, async assertCreateDetectorModalExists() { - // this retry can be removed as soon as #48734 is merged - await retry.tryForTime(5000, async () => { - await testSubjects.existOrFail('mlCreateDetectorModal'); - }); + await testSubjects.existOrFail('mlCreateDetectorModal', { timeout: 5000 }); }, async assertDetectorFunctionInputExists() { @@ -298,18 +295,17 @@ export function MachineLearningJobWizardAdvancedProvider({ }, async clickEditDetector(detectorIndex: number) { - await testSubjects.click( - `mlAdvancedDetector ${detectorIndex} > mlAdvancedDetectorEditButton` - ); - await this.assertCreateDetectorModalExists(); + await retry.tryForTime(20 * 1000, async () => { + await testSubjects.click( + `mlAdvancedDetector ${detectorIndex} > mlAdvancedDetectorEditButton` + ); + await this.assertCreateDetectorModalExists(); + }); }, async createJob() { await testSubjects.clickWhenNotDisabled('mlJobWizardButtonCreateJob'); - // this retry can be removed as soon as #48734 is merged - await retry.tryForTime(5000, async () => { - await testSubjects.existOrFail('mlStartDatafeedModal'); - }); + await testSubjects.existOrFail('mlStartDatafeedModal', { timeout: 10 * 1000 }); }, }; } diff --git a/yarn.lock b/yarn.lock index 7e965979fd46ff..e12a0eb46c6cc1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -982,6 +982,14 @@ core-js "^2.6.5" regenerator-runtime "^0.13.2" +"@babel/runtime-corejs2@^7.6.3": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.7.4.tgz#b9c2b1b5882762005785bc47740195a0ac780888" + integrity sha512-hKNcmHQbBSJFnZ82ewYtWDZ3fXkP/l1XcfRtm7c8gHPM/DMecJtFFBEp7KMLZTuHwwb7RfemHdsEnd7L916Z6A== + dependencies: + core-js "^2.6.5" + regenerator-runtime "^0.13.2" + "@babel/runtime@7.0.0-beta.54": version "7.0.0-beta.54" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0-beta.54.tgz#39ebb42723fe7ca4b3e1b00e967e80138d47cadf" @@ -3735,13 +3743,6 @@ "@types/history" "*" "@types/react" "*" -"@types/react-beautiful-dnd@^10.0.1": - version "10.1.2" - resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-10.1.2.tgz#74069f7b1d0cb67b7af99a2584b30e496e545d8b" - integrity sha512-76M5VRbhduUarM9wyMWQm3tLKCVMKTlhG0+W67dteg/HBE+kueIwuyLWzE0m5fmuilvrDXoM5NL890KLnHETZw== - dependencies: - "@types/react" "*" - "@types/react-beautiful-dnd@^10.1.0": version "10.1.1" resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-10.1.1.tgz#7afae39a4247f30c13b8bbb726ccd1b8cda9d4a5" @@ -3749,6 +3750,13 @@ dependencies: "@types/react" "*" +"@types/react-beautiful-dnd@^11.0.3": + version "11.0.3" + resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-11.0.3.tgz#51d9f37942dd18cc4aa10da98a5c883664e7ee46" + integrity sha512-7ZbT/7mNJu+uRrUGdTQ1hAINtqg909L4NHrXyspV42fvVgBgda6ysiBzoDUMENmQ/RlRJdpyrcp8Dtd/77bp9Q== + dependencies: + "@types/react" "*" + "@types/react-color@^3.0.1": version "3.0.1" resolved "https://registry.yarnpkg.com/@types/react-color/-/react-color-3.0.1.tgz#5433e2f503ea0e0831cbc6fd0c20f8157d93add0" @@ -3829,10 +3837,10 @@ dependencies: "@types/react" "*" -"@types/react-test-renderer@^16.8.0": - version "16.8.1" - resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.8.1.tgz#96f3ce45a3a41c94eca532a99103dd3042c9d055" - integrity sha512-8gU69ELfJGxzVWVYj4MTtuHxz9nO+d175XeQ1XrXXxesUBsB4KK6OCfzVhEX6leZWWBDVtMJXp/rUjhClzL7gw== +"@types/react-test-renderer@^16.8.3": + version "16.9.1" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.1.tgz#9d432c46c515ebe50c45fa92c6fb5acdc22e39c4" + integrity sha512-nCXQokZN1jp+QkoDNmDZwoWpKY8HDczqevIDO4Uv9/s9rbGPbSpy8Uaxa5ixHKkcm/Wt0Y9C3wCxZivh4Al+rQ== dependencies: "@types/react" "*" @@ -4154,24 +4162,24 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.8.0.tgz#eca584d46094ebebc3cb3e9fb625bfbc904a534d" - integrity sha512-ohqul5s6XEB0AzPWZCuJF5Fd6qC0b4+l5BGEnrlpmvXxvyymb8yw8Bs4YMF8usNAeuCJK87eFIHy8g8GFvOtGA== +"@typescript-eslint/eslint-plugin@^2.9.0": + version "2.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.9.0.tgz#fa810282c0e45f6c2310b9c0dfcd25bff97ab7e9" + integrity sha512-98rfOt3NYn5Gr9wekTB8TexxN6oM8ZRvYuphPs1Atfsy419SDLYCaE30aJkRiiTCwGEY98vOhFsEVm7Zs4toQQ== dependencies: - "@typescript-eslint/experimental-utils" "2.8.0" + "@typescript-eslint/experimental-utils" "2.9.0" eslint-utils "^1.4.3" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.8.0.tgz#208b4164d175587e9b03ce6fea97d55f19c30ca9" - integrity sha512-jZ05E4SxCbbXseQGXOKf3ESKcsGxT8Ucpkp1jiVp55MGhOvZB2twmWKf894PAuVQTCgbPbJz9ZbRDqtUWzP8xA== +"@typescript-eslint/experimental-utils@2.9.0": + version "2.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.9.0.tgz#bbe99a8d9510240c055fc4b17230dd0192ba3c7f" + integrity sha512-0lOLFdpdJsCMqMSZT7l7W2ta0+GX8A3iefG3FovJjrX+QR8y6htFlFdU7aOVPL6pDvt6XcsOb8fxk5sq+girTw== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.8.0" + "@typescript-eslint/typescript-estree" "2.9.0" eslint-scope "^5.0.0" "@typescript-eslint/experimental-utils@^1.13.0": @@ -4183,14 +4191,14 @@ "@typescript-eslint/typescript-estree" "1.13.0" eslint-scope "^4.0.0" -"@typescript-eslint/parser@^2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.8.0.tgz#e10f7c40c8cf2fb19920c879311e6c46ad17bacb" - integrity sha512-NseXWzhkucq+JM2HgqAAoKEzGQMb5LuTRjFPLQzGIdLthXMNUfuiskbl7QSykvWW6mvzCtYbw1fYWGa2EIaekw== +"@typescript-eslint/parser@^2.9.0": + version "2.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.9.0.tgz#2e9cf438de119b143f642a3a406e1e27eb70b7cd" + integrity sha512-fJ+dNs3CCvEsJK2/Vg5c2ZjuQ860ySOAsodDPwBaVlrGvRN+iCNC8kUfLFL8cT49W4GSiLPa/bHiMjYXA7EhKQ== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.8.0" - "@typescript-eslint/typescript-estree" "2.8.0" + "@typescript-eslint/experimental-utils" "2.9.0" + "@typescript-eslint/typescript-estree" "2.9.0" eslint-visitor-keys "^1.1.0" "@typescript-eslint/typescript-estree@1.13.0": @@ -4201,10 +4209,10 @@ lodash.unescape "4.0.1" semver "5.5.0" -"@typescript-eslint/typescript-estree@2.8.0": - version "2.8.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.8.0.tgz#fcc3fe6532840085d29b75432c8a59895876aeca" - integrity sha512-ksvjBDTdbAQ04cR5JyFSDX113k66FxH1tAXmi+dj6hufsl/G0eMc/f1GgLjEVPkYClDbRKv+rnBFuE5EusomUw== +"@typescript-eslint/typescript-estree@2.9.0": + version "2.9.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.9.0.tgz#09138daf8f47d0e494ba7db9e77394e928803017" + integrity sha512-v6btSPXEWCP594eZbM+JCXuFoXWXyF/z8kaSBSdCb83DF+Y7+xItW29SsKtSULgLemqJBT+LpT+0ZqdfH7QVmA== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" @@ -4533,7 +4541,7 @@ acorn-walk@^7.0.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.0.0.tgz#c8ba6f0f1aac4b0a9e32d1f0af12be769528f36b" integrity sha512-7Bv1We7ZGuU79zZbb6rRqcpxo3OY+zrdtloZWoyD8fmGX+FeXRjE+iuGkZjSXLVovLzrsvMGMy0EkwA0E0umxg== -acorn@5.X, acorn@^5.0.0, acorn@^5.0.3, acorn@^5.1.2, acorn@^5.2.1, acorn@^5.5.0: +acorn@5.X, acorn@^5.0.0, acorn@^5.0.3, acorn@^5.1.2, acorn@^5.5.0: version "5.7.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== @@ -6366,11 +6374,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base62@^1.1.0: - version "1.2.8" - resolved "https://registry.yarnpkg.com/base62/-/base62-1.2.8.tgz#1264cb0fb848d875792877479dbe8bae6bae3428" - integrity sha512-V6YHUbjLxN1ymqNLb1DPHoU1CpfdL7d2YTIp5W3U4hhoG4hhxNmsFDs66M9EXxBiSEke5Bt5dwdfMwwZF70iLA== - base64-arraybuffer@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" @@ -8224,11 +8227,6 @@ commander@^2.13.0, commander@^2.15.1, commander@^2.16.0, commander@^2.19.0, comm resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== -commander@^2.5.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - commander@^2.8.1: version "2.18.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" @@ -8258,21 +8256,6 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -commoner@^0.10.1: - version "0.10.8" - resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5" - integrity sha1-NPw2cs0kOT6LtH5wyqApOBH08sU= - dependencies: - commander "^2.5.0" - detective "^4.3.1" - glob "^5.0.15" - graceful-fs "^4.1.2" - iconv-lite "^0.4.5" - mkdirp "^0.5.0" - private "^0.1.6" - q "^1.1.2" - recast "^0.11.17" - compare-versions@3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.5.1.tgz#26e1f5cf0d48a77eced5046b9f67b6b61075a393" @@ -8976,6 +8959,13 @@ css-box-model@^1.1.1: dependencies: tiny-invariant "^1.0.3" +css-box-model@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.0.tgz#3a26377b4162b3200d2ede4b064ec5b6a75186d0" + integrity sha512-lri0br+jSNV0kkkiGEp9y9y3Njq2PmpqbeGWRFQJuZteZzY9iC9GZhQ8Y4WpPwM/2YocjHePxy14igJY7YKzkA== + dependencies: + tiny-invariant "^1.0.6" + css-color-keywords@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05" @@ -9805,7 +9795,7 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -defined@^1.0.0, defined@~1.0.0: +defined@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= @@ -10054,14 +10044,6 @@ detective-typescript@^5.1.1: node-source-walk "^4.2.0" typescript "^3.4.5" -detective@^4.3.1: - version "4.7.1" - resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e" - integrity sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig== - dependencies: - acorn "^5.2.1" - defined "^1.0.0" - dezalgo@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" @@ -10740,14 +10722,6 @@ env-variable@0.0.x: resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.5.tgz#913dd830bef11e96a039c038d4130604eba37f88" integrity sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA== -envify@^3.0.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/envify/-/envify-3.4.1.tgz#d7122329e8df1688ba771b12501917c9ce5cbce8" - integrity sha1-1xIjKejfFoi6dxsSUBkXyc5cvOg= - dependencies: - jstransform "^11.0.3" - through "~2.3.4" - enzyme-adapter-react-16@^1.15.1: version "1.15.1" resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.1.tgz#8ad55332be7091dc53a25d7d38b3485fc2ba50d5" @@ -11439,11 +11413,6 @@ espree@^6.1.1: acorn-jsx "^5.0.2" eslint-visitor-keys "^1.1.0" -esprima-fb@^15001.1.0-dev-harmony-fb: - version "15001.1.0-dev-harmony-fb" - resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901" - integrity sha1-MKlHMDxrjV6VW+4rmbHSMyBqaQE= - esprima@2.7.x, esprima@^2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" @@ -12118,17 +12087,6 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" -fbjs@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.6.1.tgz#9636b7705f5ba9684d44b72f78321254afc860f7" - integrity sha1-lja3cF9bqWhNRLcveDISVK/IYPc= - dependencies: - core-js "^1.0.0" - loose-envify "^1.0.0" - promise "^7.0.3" - ua-parser-js "^0.7.9" - whatwg-fetch "^0.9.0" - fbjs@^0.8.0, fbjs@^0.8.1, fbjs@^0.8.16: version "0.8.17" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" @@ -14999,7 +14957,7 @@ icalendar@0.7.1: resolved "https://registry.yarnpkg.com/icalendar/-/icalendar-0.7.1.tgz#d0d3486795f8f1c5cf4f8cafac081b4b4e7a32ae" integrity sha1-0NNIZ5X48cXPT4yvrAgbS056Mq4= -iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@^0.4.5, iconv-lite@~0.4.13: +iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -17246,17 +17204,6 @@ jssha@^2.1.0: resolved "https://registry.yarnpkg.com/jssha/-/jssha-2.3.1.tgz#147b2125369035ca4b2f7d210dc539f009b3de9a" integrity sha1-FHshJTaQNcpLL30hDcU58Amz3po= -jstransform@^11.0.3: - version "11.0.3" - resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-11.0.3.tgz#09a78993e0ae4d4ef4487f6155a91f6190cb4223" - integrity sha1-CaeJk+CuTU70SH9hVakfYZDLQiM= - dependencies: - base62 "^1.1.0" - commoner "^0.10.1" - esprima-fb "^15001.1.0-dev-harmony-fb" - object-assign "^2.0.0" - source-map "^0.4.2" - jstransformer-ejs@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/jstransformer-ejs/-/jstransformer-ejs-0.0.3.tgz#04d9201469274fcf260f1e7efd732d487fa234b6" @@ -18844,6 +18791,11 @@ memoize-one@^5.0.1: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.2.tgz#6aba5276856d72fb44ead3efab86432f94ba203d" integrity sha512-o7lldN4fs/axqctc03NF+PMhd2veRrWeJ2n2GjEzUPBD4F9rmNg4A+bQCACIzwjHJEXuYv4aFFMaH35KZfHUrw== +memoize-one@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" + integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA== + memoizee@0.4.X: version "0.4.14" resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57" @@ -21954,7 +21906,7 @@ promise.prototype.finally@^3.1.0: es-abstract "^1.9.0" function-bind "^1.1.1" -promise@^7.0.1, promise@^7.0.3, promise@^7.1.1: +promise@^7.0.1, promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== @@ -22405,6 +22357,11 @@ raf-schd@^4.0.0: resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.0.tgz#9855756c5045ff4ed4516e14a47719387c3c907b" integrity sha512-m7zq0JkIrECzw9mO5Zcq6jN4KayE34yoIS9hJoiZNXyOAT06PPA8PrR+WtJIeFW09YjUfNkMMN9lrmAt6BURCA== +raf-schd@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.2.tgz#bd44c708188f2e84c810bf55fcea9231bcaed8a0" + integrity sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ== + raf@^3.1.0, raf@^3.3.0: version "3.4.0" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" @@ -22600,7 +22557,7 @@ react-apollo@^2.1.4: lodash "^4.17.10" prop-types "^15.6.0" -react-beautiful-dnd@^10.0.1, react-beautiful-dnd@^10.1.0: +react-beautiful-dnd@^10.1.0: version "10.1.1" resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-10.1.1.tgz#d753088d77d7632e77cf8a8935fafcffa38f574b" integrity sha512-TdE06Shfp56wm28EzjgC56EEMgGI5PDHejJ2bxuAZvZr8CVsbksklsJC06Hxf0MSL7FHbflL/RpkJck9isuxHg== @@ -22614,6 +22571,19 @@ react-beautiful-dnd@^10.0.1, react-beautiful-dnd@^10.1.0: redux "^4.0.1" tiny-invariant "^1.0.4" +react-beautiful-dnd@^12.1.1: + version "12.1.1" + resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-12.1.1.tgz#810f9b9d94f667b15b253793e853d016a0f3f07c" + integrity sha512-w/mpIXMEXowc53PCEnMoFyAEYFgxMfygMK5msLo5ifJ2/CiSACLov9A79EomnPF7zno3N207QGXsraBxAJnyrw== + dependencies: + "@babel/runtime-corejs2" "^7.6.3" + css-box-model "^1.2.0" + memoize-one "^5.1.1" + raf-schd "^4.0.2" + react-redux "^7.1.1" + redux "^4.0.4" + use-memo-one "^1.1.1" + react-beautiful-dnd@^8.0.7: version "8.0.7" resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-8.0.7.tgz#2cc7ba62bffe08d3dad862fd8f48204440901b43" @@ -22738,17 +22708,7 @@ react-docgen@^4.1.0: node-dir "^0.1.10" recast "^0.17.3" -react-dom@^16.8.0: - version "16.8.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.2.tgz#7c8a69545dd554d45d66442230ba04a6a0a3c3d3" - integrity sha512-cPGfgFfwi+VCZjk73buu14pYkYBR1b/SRMSYqkLDdhSEHnSwcuYTPu6/Bh6ZphJFIk80XLvbSe2azfcRzNF+Xg== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.13.2" - -react-dom@^16.8.3, react-dom@^16.8.5: +react-dom@16.8.6, react-dom@^16.8.3, react-dom@^16.8.5, react-dom@^16.8.6: version "16.8.6" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.6.tgz#71d6303f631e8b0097f56165ef608f051ff6e10f" integrity sha512-1nL7PIq9LTL3fthPqwkvr2zY7phIPjYrT0jp4HjyEQrEROnw4dG41VVwi/wfoCneoleqrNX7iAD+pXebJZwrwA== @@ -22911,7 +22871,7 @@ react-is@^16.10.2, react-is@^16.9.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.11.0.tgz#b85dfecd48ad1ce469ff558a882ca8e8313928fa" integrity sha512-gbBVYR2p8mnriqAwWx9LbuUrShnAuSCNnuPGyc7GJrMVQtPDAh8iLpv7FRuMPFb56KkaVZIYSz1PrjI9q0QPCw== -react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.2, react-is@^16.8.6: +react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6: version "16.8.6" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== @@ -23088,6 +23048,18 @@ react-redux@^5.1.1: react-is "^16.6.0" react-lifecycles-compat "^3.0.0" +react-redux@^7.1.1: + version "7.1.3" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.1.3.tgz#717a3d7bbe3a1b2d535c94885ce04cdc5a33fc79" + integrity sha512-uI1wca+ECG9RoVkWQFF4jDMqmaw0/qnvaSvOoL/GA4dNxf6LoV8sUAcNDvE5NWKs4hFpn0t6wswNQnY3f7HT3w== + dependencies: + "@babel/runtime" "^7.5.5" + hoist-non-react-statics "^3.3.0" + invariant "^2.2.4" + loose-envify "^1.4.0" + prop-types "^15.7.2" + react-is "^16.9.0" + react-resizable@1.x: version "1.7.5" resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-1.7.5.tgz#83eb75bb3684da6989bbbf4f826e1470f0af902e" @@ -23236,7 +23208,7 @@ react-syntax-highlighter@^8.0.1: prismjs "^1.8.4" refractor "^2.4.1" -react-test-renderer@^16.0.0-0: +react-test-renderer@16.8.6, react-test-renderer@^16.0.0-0, react-test-renderer@^16.8.6: version "16.8.6" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.8.6.tgz#188d8029b8c39c786f998aa3efd3ffe7642d5ba1" integrity sha512-H2srzU5IWYT6cZXof6AhUcx/wEyJddQ8l7cLM/F7gDXYyPr4oq+vCIxJYXVGhId1J706sqziAjuOEjyNkfgoEw== @@ -23246,16 +23218,6 @@ react-test-renderer@^16.0.0-0: react-is "^16.8.6" scheduler "^0.13.6" -react-test-renderer@^16.8.0: - version "16.8.2" - resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.8.2.tgz#3ce0bf12aa211116612fda01a886d6163c9c459b" - integrity sha512-gsd4NoOaYrZD2R8zi+CBV9wTGMsGhE2bRe4wvenGy0WcLJgdPscRZDDz+kmLjY+/5XpYC8yRR/v4CScgYfGyoQ== - dependencies: - object-assign "^4.1.1" - prop-types "^15.6.2" - react-is "^16.8.2" - scheduler "^0.13.2" - react-testing-library@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/react-testing-library/-/react-testing-library-6.0.0.tgz#81edfcfae8a795525f48685be9bf561df45bb35d" @@ -23343,25 +23305,7 @@ react-visibility-sensor@^5.1.1: dependencies: prop-types "^15.7.2" -react@^0.14.0: - version "0.14.9" - resolved "https://registry.yarnpkg.com/react/-/react-0.14.9.tgz#9110a6497c49d44ba1c0edd317aec29c2e0d91d1" - integrity sha1-kRCmSXxJ1EuhwO3TF67CnC4NkdE= - dependencies: - envify "^3.0.0" - fbjs "^0.6.1" - -react@^16.8.0: - version "16.8.2" - resolved "https://registry.yarnpkg.com/react/-/react-16.8.2.tgz#83064596feaa98d9c2857c4deae1848b542c9c0c" - integrity sha512-aB2ctx9uQ9vo09HVknqv3DGRpI7OIGJhCx3Bt0QqoRluEjHSaObJl+nG12GDdYH6sTgE7YiPJ6ZUyMx9kICdXw== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.13.2" - -react@^16.8.3, react@^16.8.5: +react@16.8.6, react@^0.14.0, react@^16.8.3, react@^16.8.5, react@^16.8.6: version "16.8.6" resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw== @@ -23594,16 +23538,6 @@ realpath-native@^1.1.0: dependencies: util.promisify "^1.0.0" -recast@^0.11.17, recast@~0.11.12: - version "0.11.23" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" - integrity sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM= - dependencies: - ast-types "0.9.6" - esprima "~3.1.0" - private "~0.1.5" - source-map "~0.5.0" - recast@^0.14.7: version "0.14.7" resolved "https://registry.yarnpkg.com/recast/-/recast-0.14.7.tgz#4f1497c2b5826d42a66e8e3c9d80c512983ff61d" @@ -23624,6 +23558,16 @@ recast@^0.17.3: private "^0.1.8" source-map "~0.6.1" +recast@~0.11.12: + version "0.11.23" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" + integrity sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM= + dependencies: + ast-types "0.9.6" + esprima "~3.1.0" + private "~0.1.5" + source-map "~0.5.0" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -23749,6 +23693,14 @@ redux@^4.0.1: loose-envify "^1.4.0" symbol-observable "^1.2.0" +redux@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796" + integrity sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q== + dependencies: + loose-envify "^1.4.0" + symbol-observable "^1.2.0" + reflect.ownkeys@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" @@ -24809,7 +24761,7 @@ saxes@^3.1.3: dependencies: xmlchars "^2.1.1" -scheduler@^0.13.2, scheduler@^0.13.3, scheduler@^0.13.6: +scheduler@^0.13.3, scheduler@^0.13.6: version "0.13.6" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" integrity sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ== @@ -26987,6 +26939,11 @@ tiny-invariant@^1.0.2, tiny-invariant@^1.0.3, tiny-invariant@^1.0.4: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.4.tgz#346b5415fd93cb696b0c4e8a96697ff590f92463" integrity sha512-lMhRd/djQJ3MoaHEBrw8e2/uM4rs9YMNk0iOr8rHQ0QdbM7D4l0gFl3szKdeixrlyfm9Zqi4dxHCM2qVG8ND5g== +tiny-invariant@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.6.tgz#b3f9b38835e36a41c843a3b0907a5a7b3755de73" + integrity sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA== + tiny-lr@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.1.1.tgz#9fa547412f238fedb068ee295af8b682c98b2aab" @@ -28407,6 +28364,11 @@ url@0.11.0, url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +use-memo-one@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.1.tgz#39e6f08fe27e422a7d7b234b5f9056af313bd22c" + integrity sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ== + use@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/use/-/use-2.0.2.tgz#ae28a0d72f93bf22422a18a2e379993112dec8e8" @@ -29570,11 +29532,6 @@ whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== -whatwg-fetch@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz#0e3684c6cb9995b43efc9df03e4c365d95fd9cc0" - integrity sha1-DjaExsuZlbQ+/J3wPkw2XZX9nMA= - whatwg-mimetype@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz#a3d58ef10b76009b042d03e25591ece89b88d171"