diff --git a/.eslintignore b/.eslintignore index cf13fc28467d9..90155ca9cb681 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 e208dc73c7b4b..d567f267afa9d 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 2cdf7d2b039c6..e5ba6762da154 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 67ec15892afe4..a00fedf7e7ac4 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 b967dabf0684f..02b8233a9a3df 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 866755e78648a..cecceb04240e6 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 50451b813a61c..c4ceb47f66e1b 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.basepath.get.md b/docs/development/core/server/kibana-plugin-server.basepath.get.md index 2b3b6c899e8de..6ef7022f10e62 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.get.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.get.md @@ -9,5 +9,5 @@ returns `basePath` value, specific for an incoming request. Signature: ```typescript -get: (request: KibanaRequest | LegacyRequest) => string; +get: (request: KibanaRequest | LegacyRequest) => string; ``` diff --git a/docs/development/core/server/kibana-plugin-server.basepath.md b/docs/development/core/server/kibana-plugin-server.basepath.md index 478e29696966c..77f50abc60369 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.md @@ -16,11 +16,11 @@ export declare class BasePath | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [get](./kibana-plugin-server.basepath.get.md) | | (request: KibanaRequest<unknown, unknown, unknown> | LegacyRequest) => string | returns basePath value, specific for an incoming request. | +| [get](./kibana-plugin-server.basepath.get.md) | | (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest) => string | returns basePath value, specific for an incoming request. | | [prepend](./kibana-plugin-server.basepath.prepend.md) | | (path: string) => string | Prepends path with the basePath. | | [remove](./kibana-plugin-server.basepath.remove.md) | | (path: string) => string | Removes the prepended basePath from the path. | | [serverBasePath](./kibana-plugin-server.basepath.serverbasepath.md) | | string | returns the server's basePathSee [BasePath.get](./kibana-plugin-server.basepath.get.md) for getting the basePath value for a specific request | -| [set](./kibana-plugin-server.basepath.set.md) | | (request: KibanaRequest<unknown, unknown, unknown> | LegacyRequest, requestSpecificBasePath: string) => void | sets basePath value, specific for an incoming request. | +| [set](./kibana-plugin-server.basepath.set.md) | | (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest, requestSpecificBasePath: string) => void | sets basePath value, specific for an incoming request. | ## Remarks diff --git a/docs/development/core/server/kibana-plugin-server.basepath.set.md b/docs/development/core/server/kibana-plugin-server.basepath.set.md index 1272a134ef5c4..56a7f644d34cc 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.set.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.set.md @@ -9,5 +9,5 @@ sets `basePath` value, specific for an incoming request. Signature: ```typescript -set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; +set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; ``` diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.md b/docs/development/core/server/kibana-plugin-server.coresetup.md index c51459bc41a43..1ad1641beb83b 100644 --- a/docs/development/core/server/kibana-plugin-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-server.coresetup.md @@ -19,5 +19,6 @@ export interface CoreSetup | [context](./kibana-plugin-server.coresetup.context.md) | ContextSetup | [ContextSetup](./kibana-plugin-server.contextsetup.md) | | [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | | [http](./kibana-plugin-server.coresetup.http.md) | HttpServiceSetup | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | +| [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) | SavedObjectsServiceSetup | [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | | [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | UiSettingsServiceSetup | [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.savedobjects.md b/docs/development/core/server/kibana-plugin-server.coresetup.savedobjects.md new file mode 100644 index 0000000000000..96acc1ffce194 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.coresetup.savedobjects.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) > [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) + +## CoreSetup.savedObjects property + +[SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) + +Signature: + +```typescript +savedObjects: SavedObjectsServiceSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-server.corestart.md b/docs/development/core/server/kibana-plugin-server.corestart.md index da80ae8be93af..a675c45a29820 100644 --- a/docs/development/core/server/kibana-plugin-server.corestart.md +++ b/docs/development/core/server/kibana-plugin-server.corestart.md @@ -11,3 +11,10 @@ Context passed to the plugins `start` method. ```typescript export interface CoreStart ``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [savedObjects](./kibana-plugin-server.corestart.savedobjects.md) | SavedObjectsServiceStart | [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | + diff --git a/docs/development/core/server/kibana-plugin-server.corestart.savedobjects.md b/docs/development/core/server/kibana-plugin-server.corestart.savedobjects.md new file mode 100644 index 0000000000000..531b04e9eed07 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.corestart.savedobjects.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreStart](./kibana-plugin-server.corestart.md) > [savedObjects](./kibana-plugin-server.corestart.savedobjects.md) + +## CoreStart.savedObjects property + +[SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) + +Signature: + +```typescript +savedObjects: SavedObjectsServiceStart; +``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.delete.md b/docs/development/core/server/kibana-plugin-server.irouter.delete.md index 5202e0cfd5ebb..a479c03ecede3 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.delete.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.delete.md @@ -9,5 +9,5 @@ Register a route handler for `DELETE` request. Signature: ```typescript -delete: RouteRegistrar; +delete: RouteRegistrar<'delete'>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.get.md b/docs/development/core/server/kibana-plugin-server.irouter.get.md index 32552a49cb999..0d52ef26f008c 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.get.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.get.md @@ -9,5 +9,5 @@ Register a route handler for `GET` request. Signature: ```typescript -get: RouteRegistrar; +get: RouteRegistrar<'get'>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.md b/docs/development/core/server/kibana-plugin-server.irouter.md index b5d3c893d745d..73e96191e02e7 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.md @@ -16,10 +16,11 @@ export interface IRouter | Property | Type | Description | | --- | --- | --- | -| [delete](./kibana-plugin-server.irouter.delete.md) | RouteRegistrar | Register a route handler for DELETE request. | -| [get](./kibana-plugin-server.irouter.get.md) | RouteRegistrar | Register a route handler for GET request. | +| [delete](./kibana-plugin-server.irouter.delete.md) | RouteRegistrar<'delete'> | Register a route handler for DELETE request. | +| [get](./kibana-plugin-server.irouter.get.md) | RouteRegistrar<'get'> | Register a route handler for GET request. | | [handleLegacyErrors](./kibana-plugin-server.irouter.handlelegacyerrors.md) | <P extends ObjectType, Q extends ObjectType, B extends ObjectType>(handler: RequestHandler<P, Q, B>) => RequestHandler<P, Q, B> | Wrap a router handler to catch and converts legacy boom errors to proper custom errors. | -| [post](./kibana-plugin-server.irouter.post.md) | RouteRegistrar | Register a route handler for POST request. | -| [put](./kibana-plugin-server.irouter.put.md) | RouteRegistrar | Register a route handler for PUT request. | +| [patch](./kibana-plugin-server.irouter.patch.md) | RouteRegistrar<'patch'> | Register a route handler for PATCH request. | +| [post](./kibana-plugin-server.irouter.post.md) | RouteRegistrar<'post'> | Register a route handler for POST request. | +| [put](./kibana-plugin-server.irouter.put.md) | RouteRegistrar<'put'> | Register a route handler for PUT request. | | [routerPath](./kibana-plugin-server.irouter.routerpath.md) | string | Resulted path | diff --git a/docs/development/core/server/kibana-plugin-server.irouter.patch.md b/docs/development/core/server/kibana-plugin-server.irouter.patch.md new file mode 100644 index 0000000000000..460f1b9d23640 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.irouter.patch.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [IRouter](./kibana-plugin-server.irouter.md) > [patch](./kibana-plugin-server.irouter.patch.md) + +## IRouter.patch property + +Register a route handler for `PATCH` request. + +Signature: + +```typescript +patch: RouteRegistrar<'patch'>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.post.md b/docs/development/core/server/kibana-plugin-server.irouter.post.md index cd655c9ce0dc8..a2ac27ebc731a 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.post.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.post.md @@ -9,5 +9,5 @@ Register a route handler for `POST` request. Signature: ```typescript -post: RouteRegistrar; +post: RouteRegistrar<'post'>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.irouter.put.md b/docs/development/core/server/kibana-plugin-server.irouter.put.md index e553d4b79dd2b..219c5d8805661 100644 --- a/docs/development/core/server/kibana-plugin-server.irouter.put.md +++ b/docs/development/core/server/kibana-plugin-server.irouter.put.md @@ -9,5 +9,5 @@ Register a route handler for `PUT` request. Signature: ```typescript -put: RouteRegistrar; +put: RouteRegistrar<'put'>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.isavedobjectsrepository.md b/docs/development/core/server/kibana-plugin-server.isavedobjectsrepository.md new file mode 100644 index 0000000000000..7863d1b0ca49d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.isavedobjectsrepository.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) + +## ISavedObjectsRepository type + +See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) + +Signature: + +```typescript +export declare type ISavedObjectsRepository = Pick; +``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.md index b2460cd58f7a7..bc805fdc0b86f 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequest.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.md @@ -9,7 +9,7 @@ Kibana specific abstraction for an incoming request. Signature: ```typescript -export declare class KibanaRequest +export declare class KibanaRequest ``` ## Constructors @@ -26,7 +26,7 @@ export declare class KibanaRequestHeaders | Readonly copy of incoming request headers. | | [params](./kibana-plugin-server.kibanarequest.params.md) | | Params | | | [query](./kibana-plugin-server.kibanarequest.query.md) | | Query | | -| [route](./kibana-plugin-server.kibanarequest.route.md) | | RecursiveReadonly<KibanaRequestRoute> | matched route details | +| [route](./kibana-plugin-server.kibanarequest.route.md) | | RecursiveReadonly<KibanaRequestRoute<Method>> | matched route details | | [socket](./kibana-plugin-server.kibanarequest.socket.md) | | IKibanaSocket | | | [url](./kibana-plugin-server.kibanarequest.url.md) | | Url | a WHATWG URL standard object. | diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md index 88954eedf4cfb..1905070a99068 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequest.route.md @@ -9,5 +9,5 @@ matched route details Signature: ```typescript -readonly route: RecursiveReadonly; +readonly route: RecursiveReadonly>; ``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.md b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.md index b92fe45d19edb..2983639458200 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.md @@ -9,14 +9,14 @@ Request specific route information exposed to a handler. Signature: ```typescript -export interface KibanaRequestRoute +export interface KibanaRequestRoute ``` ## Properties | Property | Type | Description | | --- | --- | --- | -| [method](./kibana-plugin-server.kibanarequestroute.method.md) | RouteMethod | 'patch' | 'options' | | -| [options](./kibana-plugin-server.kibanarequestroute.options.md) | Required<RouteConfigOptions> | | +| [method](./kibana-plugin-server.kibanarequestroute.method.md) | Method | | +| [options](./kibana-plugin-server.kibanarequestroute.options.md) | KibanaRequestRouteOptions<Method> | | | [path](./kibana-plugin-server.kibanarequestroute.path.md) | string | | diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.method.md b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.method.md index c003b06e128e4..5775d28b1e053 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.method.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.method.md @@ -7,5 +7,5 @@ Signature: ```typescript -method: RouteMethod | 'patch' | 'options'; +method: Method; ``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.options.md b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.options.md index 98c898449a5b1..438263f61eb20 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanarequestroute.options.md +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestroute.options.md @@ -7,5 +7,5 @@ Signature: ```typescript -options: Required; +options: KibanaRequestRouteOptions; ``` diff --git a/docs/development/core/server/kibana-plugin-server.kibanarequestrouteoptions.md b/docs/development/core/server/kibana-plugin-server.kibanarequestrouteoptions.md new file mode 100644 index 0000000000000..f48711ac11f92 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.kibanarequestrouteoptions.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequestRouteOptions](./kibana-plugin-server.kibanarequestrouteoptions.md) + +## KibanaRequestRouteOptions type + +Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. + +Signature: + +```typescript +export declare type KibanaRequestRouteOptions = Method extends 'get' | 'options' ? Required, 'body'>> : Required>; +``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 360675b3490c2..17c5136fdc318 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -22,6 +22,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. | | [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) | | | [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | | +| [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | | [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | ## Enumerations @@ -83,6 +84,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md) | Plugin specific context passed to a route handler.Provides the following clients: - [savedObjects.client](./kibana-plugin-server.savedobjectsclient.md) - Saved Objects client which uses the credentials of the incoming request - [elasticsearch.dataClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch data client which uses the credentials of the incoming request - [elasticsearch.adminClient](./kibana-plugin-server.scopedclusterclient.md) - Elasticsearch admin client which uses the credentials of the incoming request | | [RouteConfig](./kibana-plugin-server.routeconfig.md) | Route specific configuration. | | [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) | Additional route options. | +| [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) | Additional body options for a route | +| [RouteSchemas](./kibana-plugin-server.routeschemas.md) | RouteSchemas contains the schemas for validating the different parts of a request. | | [SavedObject](./kibana-plugin-server.savedobject.md) | | | [SavedObjectAttributes](./kibana-plugin-server.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | | [SavedObjectReference](./kibana-plugin-server.savedobjectreference.md) | A reference to another saved object. | @@ -96,6 +99,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsClientProviderOptions](./kibana-plugin-server.savedobjectsclientprovideroptions.md) | Options to control the creation of the Saved Objects Client. | | [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) | Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance. | | [SavedObjectsCreateOptions](./kibana-plugin-server.savedobjectscreateoptions.md) | | +| [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md) | | | [SavedObjectsDeleteOptions](./kibana-plugin-server.savedobjectsdeleteoptions.md) | | | [SavedObjectsExportOptions](./kibana-plugin-server.savedobjectsexportoptions.md) | Options controlling the export operation. | | [SavedObjectsExportResultDetails](./kibana-plugin-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry | @@ -109,12 +113,16 @@ The plugin integrates with the core system via lifecycle events: `setup` | [SavedObjectsImportRetry](./kibana-plugin-server.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. | | [SavedObjectsImportUnknownError](./kibana-plugin-server.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. | | [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-server.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. | +| [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) | | | [SavedObjectsMigrationLogger](./kibana-plugin-server.savedobjectsmigrationlogger.md) | | | [SavedObjectsMigrationVersion](./kibana-plugin-server.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | | [SavedObjectsRawDoc](./kibana-plugin-server.savedobjectsrawdoc.md) | A raw document as represented directly in the saved object index. | | [SavedObjectsResolveImportErrorsOptions](./kibana-plugin-server.savedobjectsresolveimporterrorsoptions.md) | Options to control the "resolve import" operation. | +| [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. | +| [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. | | [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 | @@ -127,6 +135,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | Variable | Description | | --- | --- | | [kibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Set of helpers used to create KibanaResponse to form HTTP response on an incoming request. Should be returned as a result of [RequestHandler](./kibana-plugin-server.requesthandler.md) execution. | +| [validBodyOutput](./kibana-plugin-server.validbodyoutput.md) | The set of valid body.output | ## Type Aliases @@ -148,7 +157,9 @@ The plugin integrates with the core system via lifecycle events: `setup` | [IClusterClient](./kibana-plugin-server.iclusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)).See [ClusterClient](./kibana-plugin-server.clusterclient.md). | | [IContextProvider](./kibana-plugin-server.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | | [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Return authentication status for a request. | +| [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) | See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | | [IScopedClusterClient](./kibana-plugin-server.iscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient but exposes additional callAsCurrentUser method that doesn't use credentials of the Kibana internal user (as callAsInternalUser does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). | +| [KibanaRequestRouteOptions](./kibana-plugin-server.kibanarequestrouteoptions.md) | Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. | | [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. | | [KnownHeaders](./kibana-plugin-server.knownheaders.md) | Set of well-known HTTP headers. | | [LifecycleResponseFactory](./kibana-plugin-server.lifecycleresponsefactory.md) | Creates an object containing redirection or error response with error details, HTTP headers, and other data transmitted to the client. | @@ -169,11 +180,13 @@ The plugin integrates with the core system via lifecycle events: `setup` | [ResponseError](./kibana-plugin-server.responseerror.md) | Error message and optional data send to the client in case of error. | | [ResponseErrorAttributes](./kibana-plugin-server.responseerrorattributes.md) | Additional data to provide error details. | | [ResponseHeaders](./kibana-plugin-server.responseheaders.md) | Http response headers to set. | +| [RouteContentType](./kibana-plugin-server.routecontenttype.md) | The set of supported parseable Content-Types | | [RouteMethod](./kibana-plugin-server.routemethod.md) | The set of common HTTP methods supported by Kibana routing. | -| [RouteRegistrar](./kibana-plugin-server.routeregistrar.md) | Handler to declare a route. | +| [RouteRegistrar](./kibana-plugin-server.routeregistrar.md) | Route handler common definition | | [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value | | [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | | [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError() helpers exposed at SavedObjectsErrorHelpers should be used to understand and manage error responses from the SavedObjectsClient.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type or doing substring checks on error.body.error.reason, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | +| [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. | | [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. | | [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | UI element type to represent the settings. | diff --git a/docs/development/core/server/kibana-plugin-server.requesthandler.md b/docs/development/core/server/kibana-plugin-server.requesthandler.md index 035d16c9fca3c..79abfd4293e9f 100644 --- a/docs/development/core/server/kibana-plugin-server.requesthandler.md +++ b/docs/development/core/server/kibana-plugin-server.requesthandler.md @@ -9,7 +9,7 @@ A function executed when route path matched requested resource path. Request han Signature: ```typescript -export declare type RequestHandler

= (context: RequestHandlerContext, request: KibanaRequest, TypeOf, TypeOf>, response: KibanaResponseFactory) => IKibanaResponse | Promise>; +export declare type RequestHandler

| Type, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest, TypeOf, TypeOf, Method>, response: KibanaResponseFactory) => IKibanaResponse | Promise>; ``` ## Example diff --git a/docs/development/core/server/kibana-plugin-server.routeconfig.md b/docs/development/core/server/kibana-plugin-server.routeconfig.md index 769d0dda42644..1970b23c7ec09 100644 --- a/docs/development/core/server/kibana-plugin-server.routeconfig.md +++ b/docs/development/core/server/kibana-plugin-server.routeconfig.md @@ -9,14 +9,14 @@ Route specific configuration. Signature: ```typescript -export interface RouteConfig

+export interface RouteConfig

| Type, Method extends RouteMethod> ``` ## Properties | Property | Type | Description | | --- | --- | --- | -| [options](./kibana-plugin-server.routeconfig.options.md) | RouteConfigOptions | Additional route options [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md). | +| [options](./kibana-plugin-server.routeconfig.options.md) | RouteConfigOptions<Method> | Additional route options [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md). | | [path](./kibana-plugin-server.routeconfig.path.md) | string | The endpoint \_within\_ the router path to register the route. | | [validate](./kibana-plugin-server.routeconfig.validate.md) | RouteSchemas<P, Q, B> | false | A schema created with @kbn/config-schema that every request will be validated against. | diff --git a/docs/development/core/server/kibana-plugin-server.routeconfig.options.md b/docs/development/core/server/kibana-plugin-server.routeconfig.options.md index 12ca36da6de7c..90ad294457101 100644 --- a/docs/development/core/server/kibana-plugin-server.routeconfig.options.md +++ b/docs/development/core/server/kibana-plugin-server.routeconfig.options.md @@ -9,5 +9,5 @@ Additional route options [RouteConfigOptions](./kibana-plugin-server.routeconfig Signature: ```typescript -options?: RouteConfigOptions; +options?: RouteConfigOptions; ``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptions.body.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.body.md new file mode 100644 index 0000000000000..fee5528ce3378 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.body.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptions](./kibana-plugin-server.routeconfigoptions.md) > [body](./kibana-plugin-server.routeconfigoptions.body.md) + +## RouteConfigOptions.body property + +Additional body options [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md). + +Signature: + +```typescript +body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md index b4d210ac0b711..99339db81065c 100644 --- a/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptions.md @@ -9,7 +9,7 @@ Additional route options. Signature: ```typescript -export interface RouteConfigOptions +export interface RouteConfigOptions ``` ## Properties @@ -17,5 +17,6 @@ export interface RouteConfigOptions | Property | Type | Description | | --- | --- | --- | | [authRequired](./kibana-plugin-server.routeconfigoptions.authrequired.md) | boolean | A flag shows that authentication for a route: enabled when true disabled when falseEnabled by default. | +| [body](./kibana-plugin-server.routeconfigoptions.body.md) | Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody | Additional body options [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md). | | [tags](./kibana-plugin-server.routeconfigoptions.tags.md) | readonly string[] | Additional metadata tag strings to attach to the route. | diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.accepts.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.accepts.md new file mode 100644 index 0000000000000..f48c9a1d73b11 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.accepts.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [accepts](./kibana-plugin-server.routeconfigoptionsbody.accepts.md) + +## RouteConfigOptionsBody.accepts property + +A string or an array of strings with the allowed mime types for the endpoint. Use this settings to limit the set of allowed mime types. Note that allowing additional mime types not listed above will not enable them to be parsed, and if parse is true, the request will result in an error response. + +Default value: allows parsing of the following mime types: \* application/json \* application/\*+json \* application/octet-stream \* application/x-www-form-urlencoded \* multipart/form-data \* text/\* + +Signature: + +```typescript +accepts?: RouteContentType | RouteContentType[] | string | string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.maxbytes.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.maxbytes.md new file mode 100644 index 0000000000000..3d22dc07d5bae --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.maxbytes.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [maxBytes](./kibana-plugin-server.routeconfigoptionsbody.maxbytes.md) + +## RouteConfigOptionsBody.maxBytes property + +Limits the size of incoming payloads to the specified byte count. Allowing very large payloads may cause the server to run out of memory. + +Default value: The one set in the kibana.yml config file under the parameter `server.maxPayloadBytes`. + +Signature: + +```typescript +maxBytes?: number; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.md new file mode 100644 index 0000000000000..6ef04de459fcf --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) + +## RouteConfigOptionsBody interface + +Additional body options for a route + +Signature: + +```typescript +export interface RouteConfigOptionsBody +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [accepts](./kibana-plugin-server.routeconfigoptionsbody.accepts.md) | RouteContentType | RouteContentType[] | string | string[] | A string or an array of strings with the allowed mime types for the endpoint. Use this settings to limit the set of allowed mime types. Note that allowing additional mime types not listed above will not enable them to be parsed, and if parse is true, the request will result in an error response.Default value: allows parsing of the following mime types: \* application/json \* application/\*+json \* application/octet-stream \* application/x-www-form-urlencoded \* multipart/form-data \* text/\* | +| [maxBytes](./kibana-plugin-server.routeconfigoptionsbody.maxbytes.md) | number | Limits the size of incoming payloads to the specified byte count. Allowing very large payloads may cause the server to run out of memory.Default value: The one set in the kibana.yml config file under the parameter server.maxPayloadBytes. | +| [output](./kibana-plugin-server.routeconfigoptionsbody.output.md) | typeof validBodyOutput[number] | The processed payload format. The value must be one of: \* 'data' - the incoming payload is read fully into memory. If parse is true, the payload is parsed (JSON, form-decoded, multipart) based on the 'Content-Type' header. If parse is false, a raw Buffer is returned. \* 'stream' - the incoming payload is made available via a Stream.Readable interface. If the payload is 'multipart/form-data' and parse is true, field values are presented as text while files are provided as streams. File streams from a 'multipart/form-data' upload will also have a hapi property containing the filename and headers properties. Note that payload streams for multipart payloads are a synthetic interface created on top of the entire multipart content loaded into memory. To avoid loading large multipart payloads into memory, set parse to false and handle the multipart payload in the handler using a streaming parser (e.g. pez).Default value: 'data', unless no validation.body is provided in the route definition. In that case the default is 'stream' to alleviate memory pressure. | +| [parse](./kibana-plugin-server.routeconfigoptionsbody.parse.md) | boolean | 'gunzip' | Determines if the incoming payload is processed or presented raw. Available values: \* true - if the request 'Content-Type' matches the allowed mime types set by allow (for the whole payload as well as parts), the payload is converted into an object when possible. If the format is unknown, a Bad Request (400) error response is sent. Any known content encoding is decoded. \* false - the raw payload is returned unmodified. \* 'gunzip' - the raw payload is returned unmodified after any known content encoding is decoded.Default value: true, unless no validation.body is provided in the route definition. In that case the default is false to alleviate memory pressure. | + diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.output.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.output.md new file mode 100644 index 0000000000000..b84bc709df3ec --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.output.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [output](./kibana-plugin-server.routeconfigoptionsbody.output.md) + +## RouteConfigOptionsBody.output property + +The processed payload format. The value must be one of: \* 'data' - the incoming payload is read fully into memory. If parse is true, the payload is parsed (JSON, form-decoded, multipart) based on the 'Content-Type' header. If parse is false, a raw Buffer is returned. \* 'stream' - the incoming payload is made available via a Stream.Readable interface. If the payload is 'multipart/form-data' and parse is true, field values are presented as text while files are provided as streams. File streams from a 'multipart/form-data' upload will also have a hapi property containing the filename and headers properties. Note that payload streams for multipart payloads are a synthetic interface created on top of the entire multipart content loaded into memory. To avoid loading large multipart payloads into memory, set parse to false and handle the multipart payload in the handler using a streaming parser (e.g. pez). + +Default value: 'data', unless no validation.body is provided in the route definition. In that case the default is 'stream' to alleviate memory pressure. + +Signature: + +```typescript +output?: typeof validBodyOutput[number]; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.parse.md b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.parse.md new file mode 100644 index 0000000000000..d395f67c69669 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeconfigoptionsbody.parse.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteConfigOptionsBody](./kibana-plugin-server.routeconfigoptionsbody.md) > [parse](./kibana-plugin-server.routeconfigoptionsbody.parse.md) + +## RouteConfigOptionsBody.parse property + +Determines if the incoming payload is processed or presented raw. Available values: \* true - if the request 'Content-Type' matches the allowed mime types set by allow (for the whole payload as well as parts), the payload is converted into an object when possible. If the format is unknown, a Bad Request (400) error response is sent. Any known content encoding is decoded. \* false - the raw payload is returned unmodified. \* 'gunzip' - the raw payload is returned unmodified after any known content encoding is decoded. + +Default value: true, unless no validation.body is provided in the route definition. In that case the default is false to alleviate memory pressure. + +Signature: + +```typescript +parse?: boolean | 'gunzip'; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routecontenttype.md b/docs/development/core/server/kibana-plugin-server.routecontenttype.md new file mode 100644 index 0000000000000..010388c7b8f17 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routecontenttype.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteContentType](./kibana-plugin-server.routecontenttype.md) + +## RouteContentType type + +The set of supported parseable Content-Types + +Signature: + +```typescript +export declare type RouteContentType = 'application/json' | 'application/*+json' | 'application/octet-stream' | 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/*'; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routemethod.md b/docs/development/core/server/kibana-plugin-server.routemethod.md index dd1a050708bb3..4f83344f842b3 100644 --- a/docs/development/core/server/kibana-plugin-server.routemethod.md +++ b/docs/development/core/server/kibana-plugin-server.routemethod.md @@ -9,5 +9,5 @@ The set of common HTTP methods supported by Kibana routing. Signature: ```typescript -export declare type RouteMethod = 'get' | 'post' | 'put' | 'delete'; +export declare type RouteMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'; ``` diff --git a/docs/development/core/server/kibana-plugin-server.routeregistrar.md b/docs/development/core/server/kibana-plugin-server.routeregistrar.md index 535927dc73743..0f5f49636fdd5 100644 --- a/docs/development/core/server/kibana-plugin-server.routeregistrar.md +++ b/docs/development/core/server/kibana-plugin-server.routeregistrar.md @@ -4,10 +4,10 @@ ## RouteRegistrar type -Handler to declare a route. +Route handler common definition Signature: ```typescript -export declare type RouteRegistrar =

(route: RouteConfig, handler: RequestHandler) => void; +export declare type RouteRegistrar =

| Type>(route: RouteConfig, handler: RequestHandler) => void; ``` diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.body.md b/docs/development/core/server/kibana-plugin-server.routeschemas.body.md new file mode 100644 index 0000000000000..78a9d25c25d9d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeschemas.body.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) > [body](./kibana-plugin-server.routeschemas.body.md) + +## RouteSchemas.body property + +Signature: + +```typescript +body?: B; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.md b/docs/development/core/server/kibana-plugin-server.routeschemas.md new file mode 100644 index 0000000000000..77b980551a8ff --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeschemas.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) + +## RouteSchemas interface + +RouteSchemas contains the schemas for validating the different parts of a request. + +Signature: + +```typescript +export interface RouteSchemas

| Type> +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [body](./kibana-plugin-server.routeschemas.body.md) | B | | +| [params](./kibana-plugin-server.routeschemas.params.md) | P | | +| [query](./kibana-plugin-server.routeschemas.query.md) | Q | | + diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.params.md b/docs/development/core/server/kibana-plugin-server.routeschemas.params.md new file mode 100644 index 0000000000000..3dbf9fed94dc0 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeschemas.params.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) > [params](./kibana-plugin-server.routeschemas.params.md) + +## RouteSchemas.params property + +Signature: + +```typescript +params?: P; +``` diff --git a/docs/development/core/server/kibana-plugin-server.routeschemas.query.md b/docs/development/core/server/kibana-plugin-server.routeschemas.query.md new file mode 100644 index 0000000000000..5be5830cb4bc8 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.routeschemas.query.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [RouteSchemas](./kibana-plugin-server.routeschemas.md) > [query](./kibana-plugin-server.routeschemas.query.md) + +## RouteSchemas.query property + +Signature: + +```typescript +query?: Q; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md deleted file mode 100644 index 0bcca3ec57b54..0000000000000 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md +++ /dev/null @@ -1,20 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) > [(constructor)](./kibana-plugin-server.savedobjectsclient._constructor_.md) - -## SavedObjectsClient.(constructor) - -Constructs a new instance of the `SavedObjectsClient` class - -Signature: - -```typescript -constructor(repository: SavedObjectsRepository); -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| repository | SavedObjectsRepository | | - diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md index cc00934a1e1fd..17d29bb912c83 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md @@ -4,19 +4,12 @@ ## SavedObjectsClient class - Signature: ```typescript export declare class SavedObjectsClient ``` -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)(repository)](./kibana-plugin-server.savedobjectsclient._constructor_.md) | | Constructs a new instance of the SavedObjectsClient class | - ## Properties | Property | Modifiers | Type | Description | @@ -37,3 +30,7 @@ export declare class SavedObjectsClient | [get(type, id, options)](./kibana-plugin-server.savedobjectsclient.get.md) | | Retrieves a single object | | [update(type, id, attributes, options)](./kibana-plugin-server.savedobjectsclient.update.md) | | Updates an SavedObject | +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsClient` class. + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md new file mode 100644 index 0000000000000..9e30759720680 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) + +## SavedObjectsClientFactory type + +Describes the factory used to create instances of the Saved Objects Client. + +Signature: + +```typescript +export declare type SavedObjectsClientFactory = ({ request, }: { + request: Request; +}) => SavedObjectsClientContract; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md new file mode 100644 index 0000000000000..df4ce1b4b8428 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md @@ -0,0 +1,19 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md) + +## SavedObjectsDeleteByNamespaceOptions interface + + +Signature: + +```typescript +export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [refresh](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md new file mode 100644 index 0000000000000..2332520ac388f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md) > [refresh](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md) + +## SavedObjectsDeleteByNamespaceOptions.refresh property + +The Elasticsearch Refresh setting for this operation + +Signature: + +```typescript +refresh?: MutatingOperationRefreshSetting; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.md new file mode 100644 index 0000000000000..38ee40157888f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) + +## SavedObjectsIncrementCounterOptions interface + + +Signature: + +```typescript +export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [migrationVersion](./kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md) | SavedObjectsMigrationVersion | | +| [refresh](./kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md) | MutatingOperationRefreshSetting | The Elasticsearch Refresh setting for this operation | + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md new file mode 100644 index 0000000000000..3b80dea4fecde --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) > [migrationVersion](./kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md) + +## SavedObjectsIncrementCounterOptions.migrationVersion property + +Signature: + +```typescript +migrationVersion?: SavedObjectsMigrationVersion; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md new file mode 100644 index 0000000000000..acd8d6f0916f9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) > [refresh](./kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md) + +## SavedObjectsIncrementCounterOptions.refresh property + +The Elasticsearch Refresh setting for this operation + +Signature: + +```typescript +refresh?: MutatingOperationRefreshSetting; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md new file mode 100644 index 0000000000000..003bc6ac72466 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [bulkCreate](./kibana-plugin-server.savedobjectsrepository.bulkcreate.md) + +## SavedObjectsRepository.bulkCreate() method + +Creates multiple documents at once + +Signature: + +```typescript +bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| objects | Array<SavedObjectsBulkCreateObject<T>> | | +| options | SavedObjectsCreateOptions | | + +Returns: + +`Promise>` + +{promise} - {saved\_objects: \[\[{ id, type, version, references, attributes, error: { message } }\]} + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md new file mode 100644 index 0000000000000..605984d5dea30 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md @@ -0,0 +1,31 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [bulkGet](./kibana-plugin-server.savedobjectsrepository.bulkget.md) + +## SavedObjectsRepository.bulkGet() method + +Returns an array of objects by id + +Signature: + +```typescript +bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| objects | SavedObjectsBulkGetObject[] | | +| options | SavedObjectsBaseOptions | | + +Returns: + +`Promise>` + +{promise} - { saved\_objects: \[{ id, type, version, attributes }\] } + +## Example + +bulkGet(\[ { id: 'one', type: 'config' }, { id: 'foo', type: 'index-pattern' } \]) + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md new file mode 100644 index 0000000000000..52a73c83b4c3a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [bulkUpdate](./kibana-plugin-server.savedobjectsrepository.bulkupdate.md) + +## SavedObjectsRepository.bulkUpdate() method + +Updates multiple objects in bulk + +Signature: + +```typescript +bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| objects | Array<SavedObjectsBulkUpdateObject<T>> | | +| options | SavedObjectsBulkUpdateOptions | | + +Returns: + +`Promise>` + +{promise} - {saved\_objects: \[\[{ id, type, version, references, attributes, error: { message } }\]} + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md new file mode 100644 index 0000000000000..3a731629156e2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [create](./kibana-plugin-server.savedobjectsrepository.create.md) + +## SavedObjectsRepository.create() method + +Persists an object + +Signature: + +```typescript +create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| attributes | T | | +| options | SavedObjectsCreateOptions | | + +Returns: + +`Promise>` + +{promise} - { id, type, version, attributes } + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.delete.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.delete.md new file mode 100644 index 0000000000000..52c36d2da162d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.delete.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [delete](./kibana-plugin-server.savedobjectsrepository.delete.md) + +## SavedObjectsRepository.delete() method + +Deletes an object + +Signature: + +```typescript +delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | +| options | SavedObjectsDeleteOptions | | + +Returns: + +`Promise<{}>` + +{promise} + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.deletebynamespace.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.deletebynamespace.md new file mode 100644 index 0000000000000..ab6eb30e664f1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.deletebynamespace.md @@ -0,0 +1,27 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [deleteByNamespace](./kibana-plugin-server.savedobjectsrepository.deletebynamespace.md) + +## SavedObjectsRepository.deleteByNamespace() method + +Deletes all objects from the provided namespace. + +Signature: + +```typescript +deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| namespace | string | | +| options | SavedObjectsDeleteByNamespaceOptions | | + +Returns: + +`Promise` + +{promise} - { took, timed\_out, total, deleted, batches, version\_conflicts, noops, retries, failures } + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md new file mode 100644 index 0000000000000..3c2855ed9a50c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [find](./kibana-plugin-server.savedobjectsrepository.find.md) + +## SavedObjectsRepository.find() method + +Signature: + +```typescript +find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, }: SavedObjectsFindOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| { search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, } | SavedObjectsFindOptions | | + +Returns: + +`Promise>` + +{promise} - { saved\_objects: \[{ id, type, version, attributes }\], total, per\_page, page } + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md new file mode 100644 index 0000000000000..dd1d81f225937 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md @@ -0,0 +1,28 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [get](./kibana-plugin-server.savedobjectsrepository.get.md) + +## SavedObjectsRepository.get() method + +Gets a single object + +Signature: + +```typescript +get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | +| options | SavedObjectsBaseOptions | | + +Returns: + +`Promise>` + +{promise} - { id, type, version, attributes } + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.incrementcounter.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.incrementcounter.md new file mode 100644 index 0000000000000..f20e9a73d99a1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.incrementcounter.md @@ -0,0 +1,43 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [incrementCounter](./kibana-plugin-server.savedobjectsrepository.incrementcounter.md) + +## SavedObjectsRepository.incrementCounter() method + +Increases a counter field by one. Creates the document if one doesn't exist for the given id. + +Signature: + +```typescript +incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise<{ + id: string; + type: string; + updated_at: string; + references: any; + version: string; + attributes: any; + }>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | +| counterFieldName | string | | +| options | SavedObjectsIncrementCounterOptions | | + +Returns: + +`Promise<{ + id: string; + type: string; + updated_at: string; + references: any; + version: string; + attributes: any; + }>` + +{promise} + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.md new file mode 100644 index 0000000000000..019363776590c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.md @@ -0,0 +1,31 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) + +## SavedObjectsRepository class + +Signature: + +```typescript +export declare class SavedObjectsRepository +``` + +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [bulkCreate(objects, options)](./kibana-plugin-server.savedobjectsrepository.bulkcreate.md) | | Creates multiple documents at once | +| [bulkGet(objects, options)](./kibana-plugin-server.savedobjectsrepository.bulkget.md) | | Returns an array of objects by id | +| [bulkUpdate(objects, options)](./kibana-plugin-server.savedobjectsrepository.bulkupdate.md) | | Updates multiple objects in bulk | +| [create(type, attributes, options)](./kibana-plugin-server.savedobjectsrepository.create.md) | | Persists an object | +| [delete(type, id, options)](./kibana-plugin-server.savedobjectsrepository.delete.md) | | Deletes an object | +| [deleteByNamespace(namespace, options)](./kibana-plugin-server.savedobjectsrepository.deletebynamespace.md) | | Deletes all objects from the provided namespace. | +| [find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, })](./kibana-plugin-server.savedobjectsrepository.find.md) | | | +| [get(type, id, options)](./kibana-plugin-server.savedobjectsrepository.get.md) | | Gets a single object | +| [incrementCounter(type, id, counterFieldName, options)](./kibana-plugin-server.savedobjectsrepository.incrementcounter.md) | | Increases a counter field by one. Creates the document if one doesn't exist for the given id. | +| [update(type, id, attributes, options)](./kibana-plugin-server.savedobjectsrepository.update.md) | | Updates an object | + +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsRepository` class. + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md new file mode 100644 index 0000000000000..15890ab9211aa --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md @@ -0,0 +1,29 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [update](./kibana-plugin-server.savedobjectsrepository.update.md) + +## SavedObjectsRepository.update() method + +Updates an object + +Signature: + +```typescript +update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| type | string | | +| id | string | | +| attributes | Partial<T> | | +| options | SavedObjectsUpdateOptions | | + +Returns: + +`Promise>` + +{promise} + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md new file mode 100644 index 0000000000000..e787d737ada17 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) + +## SavedObjectsServiceSetup.addClientWrapper property + +Add a client wrapper with the given priority. + +Signature: + +```typescript +addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md new file mode 100644 index 0000000000000..492aa1a2453a1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [createInternalRepository](./kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) + +## SavedObjectsServiceSetup.createInternalRepository property + +Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. + +Signature: + +```typescript +createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; +``` + +## Remarks + +The repository should only be used for creating and registering a client factory or client wrapper. Using the repository directly for interacting with Saved Objects is an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md new file mode 100644 index 0000000000000..fc5aa40c21a20 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [createScopedRepository](./kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) + +## SavedObjectsServiceSetup.createScopedRepository property + +Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. + +Signature: + +```typescript +createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; +``` + +## Remarks + +The repository should only be used for creating and registering a client factory or client wrapper. Using the repository directly for interacting with Saved Objects is an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md new file mode 100644 index 0000000000000..dd97b45f590e2 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md @@ -0,0 +1,35 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) + +## SavedObjectsServiceSetup interface + +Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. + +Signature: + +```typescript +export interface SavedObjectsServiceSetup +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | (priority: number, id: string, factory: SavedObjectsClientWrapperFactory<KibanaRequest>) => void | Add a client wrapper with the given priority. | +| [createInternalRepository](./kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) | (extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. | +| [createScopedRepository](./kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. | +| [setClientFactory](./kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | (customClientFactory: SavedObjectsClientFactory<KibanaRequest>) => void | Set a default factory for creating Saved Objects clients. Only one client factory can be set, subsequent calls to this method will fail. | + +## Remarks + +Note: The Saved Object setup API's should only be used for creating and registering client wrappers. Constructing a Saved Objects client or repository for use within your own plugin won't have any of the registered wrappers applied and is considered an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. + +When plugins access the Saved Objects client, a new client is created using the factory provided to `setClientFactory` and wrapped by all wrappers registered through `addClientWrapper`. To create a factory or wrapper, plugins will have to construct a Saved Objects client. First create a repository by calling `scopedRepository` or `internalRepository` and then use this repository as the argument to the [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) constructor. + +## Example + +import {SavedObjectsClient, CoreSetup} from 'src/core/server'; + +export class Plugin() { setup: (core: CoreSetup) => { core.savedObjects.setClientFactory(({request: KibanaRequest}) => { return new SavedObjectsClient(core.savedObjects.scopedRepository(request)); }) } } + diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md new file mode 100644 index 0000000000000..544e0b9d5fa73 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [setClientFactory](./kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) + +## SavedObjectsServiceSetup.setClientFactory property + +Set a default factory for creating Saved Objects clients. Only one client factory can be set, subsequent calls to this method will fail. + +Signature: + +```typescript +setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md new file mode 100644 index 0000000000000..e87979a124bdc --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) > [getScopedClient](./kibana-plugin-server.savedobjectsservicestart.getscopedclient.md) + +## SavedObjectsServiceStart.getScopedClient property + +Creates a [Saved Objects client](./kibana-plugin-server.savedobjectsclientcontract.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. If other plugins have registered Saved Objects client wrappers, these will be applied to extend the functionality of the client. + +A client that is already scoped to the incoming request is also exposed from the route handler context see [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md). + +Signature: + +```typescript +getScopedClient: (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract; +``` diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md new file mode 100644 index 0000000000000..5a869b3b6c1cb --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md @@ -0,0 +1,20 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) + +## SavedObjectsServiceStart interface + +Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. + +Signature: + +```typescript +export interface SavedObjectsServiceStart +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [getScopedClient](./kibana-plugin-server.savedobjectsservicestart.getscopedclient.md) | (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract | Creates a [Saved Objects client](./kibana-plugin-server.savedobjectsclientcontract.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. If other plugins have registered Saved Objects client wrappers, these will be applied to extend the functionality of the client.A client that is already scoped to the incoming request is also exposed from the route handler context see [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md). | + 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 0000000000000..6e5f6acca2eb9 --- /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 0000000000000..6d32c4cca3dd6 --- /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 0000000000000..8ca6d452213aa --- /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 167ab03d7567f..ef65735e7bdba 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 de412818142f2..778dc27a190d9 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 f3cbfc0d84e18..effa4b6bbc077 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/development/core/server/kibana-plugin-server.validbodyoutput.md b/docs/development/core/server/kibana-plugin-server.validbodyoutput.md new file mode 100644 index 0000000000000..ea866abf887fb --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.validbodyoutput.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [validBodyOutput](./kibana-plugin-server.validbodyoutput.md) + +## validBodyOutput variable + +The set of valid body.output + +Signature: + +```typescript +validBodyOutput: readonly ["data", "stream"] +``` diff --git a/docs/getting-started/tutorial-full-experience.asciidoc b/docs/getting-started/tutorial-full-experience.asciidoc index eafbb7d8f7c91..a05205fceab4a 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 24cc176d5daf9..f41c648a3d492 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 4a736e3ddab59..4c7f6c2aee6e6 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/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index 805d991a9a0f3..a2c05e4d87325 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -49,10 +49,16 @@ is set to `true` if `server.ssl.certificate` and `server.ssl.key` are set. Set this to `true` if SSL is configured outside of {kib} (for example, you are routing requests through a load balancer or proxy). -`xpack.security.sessionTimeout`:: +`xpack.security.session.idleTimeout`:: Sets the session duration (in milliseconds). By default, sessions stay active -until the browser is closed. When this is set to an explicit timeout, closing the -browser still requires the user to log back in to {kib}. +until the browser is closed. When this is set to an explicit idle timeout, closing +the browser still requires the user to log back in to {kib}. + +`xpack.security.session.lifespan`:: +Sets the maximum duration (in milliseconds), also known as "absolute timeout". By +default, a session can be renewed indefinitely. When this value is set, a session +will end once its lifespan is exceeded, even if the user is not idle. NOTE: if +`idleTimeout` is not set, this setting will still cause sessions to expire. `xpack.security.loginAssistanceMessage`:: Adds a message to the login screen. Useful for displaying information about maintenance windows, links to corporate sign up pages etc. diff --git a/docs/setup/install.asciidoc b/docs/setup/install.asciidoc index b0893a6e78945..286fed34f64c5 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 c2b1adc5e1b92..32f341a9c1b7c 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,18 +180,19 @@ 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 -for every request that requires authentication. It also means that the {kib} session depends on the `xpack.security.sessionTimeout` -setting and the user is automatically logged out if the session expires. An access token that is stored in the session cookie -can expire, in which case {kib} will automatically renew it with a one-time-use refresh token and store it in the same cookie. +for every request that requires authentication. It also means that the {kib} session depends on the <> settings, and the user is automatically logged +out if the session expires. An access token that is stored in the session cookie can expire, in which case {kib} will +automatically renew it with a one-time-use refresh token and store it in the same cookie. {kib} can only determine if an access token has expired if it receives a request that requires authentication. If both access and refresh tokens have already expired (for example, after 24 hours of inactivity), {kib} initiates a new "handshake" and @@ -201,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 fb40dc17c0abd..aaba60ca4b3ca 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 1c74bd98642a7..60f5473f43b9d 100644 --- a/docs/user/security/securing-kibana.asciidoc +++ b/docs/user/security/securing-kibana.asciidoc @@ -56,16 +56,31 @@ xpack.security.encryptionKey: "something_at_least_32_characters" For more information, see <>. -- -. Optional: Change the default session duration. By default, sessions stay -active until the browser is closed. To change the duration, set the -`xpack.security.sessionTimeout` property in the `kibana.yml` configuration file. -The timeout is specified in milliseconds. For example, set the timeout to 600000 -to expire sessions after 10 minutes: +. Optional: Set a timeout to expire idle sessions. By default, a session stays +active until the browser is closed. To define a sliding session expiration, set +the `xpack.security.session.idleTimeout` property in the `kibana.yml` +configuration file. The idle timeout is specified in milliseconds. For example, +set the idle timeout to 600000 to expire idle sessions after 10 minutes: + -- [source,yaml] -------------------------------------------------------------------------------- -xpack.security.sessionTimeout: 600000 +xpack.security.session.idleTimeout: 600000 +-------------------------------------------------------------------------------- +-- + +. Optional: Change the maximum session duration or "lifespan" -- also known as +the "absolute timeout". By default, a session stays active until the browser is +closed. If an idle timeout is defined, a session can still be extended +indefinitely. To define a maximum session lifespan, set the +`xpack.security.session.lifespan` property in the `kibana.yml` configuration +file. The lifespan is specified in milliseconds. For example, set the lifespan +to 28800000 to expire sessions after 8 hours: ++ +-- +[source,yaml] +-------------------------------------------------------------------------------- +xpack.security.session.lifespan: 28800000 -------------------------------------------------------------------------------- -- @@ -106,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 a4f7b869aef6f..4b48731bd9a88 100644 --- a/package.json +++ b/package.json @@ -79,13 +79,15 @@ }, "resolutions": { "**/@types/node": "10.12.27", - "**/@types/react": "16.8.3", + "**/@types/react": "^16.9.13", "**/@types/hapi": "^17.0.18", "**/@types/angular": "^1.6.56", "**/typescript": "3.7.2", "**/graphql-toolkit/lodash": "^4.17.13", "**/isomorphic-git/**/base64-js": "^1.2.1", "**/image-diff/gm/debug": "^2.6.9", + "**/react-dom": "^16.12.0", + "**/react-test-renderer": "^16.12.0", "**/deepmerge": "^4.2.2" }, "workspaces": { @@ -121,7 +123,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,15 +215,13 @@ "pug": "^2.0.3", "querystring-browser": "1.0.4", "raw-loader": "3.1.0", - "react": "^16.8.0", - "react-addons-shallow-compare": "15.6.2", + "react": "^16.12.0", "react-color": "^2.13.8", - "react-dom": "^16.8.0", + "react-dom": "^16.12.0", "react-grid-layout": "^0.16.2", - "react-hooks-testing-library": "^0.5.0", "react-input-range": "^1.3.0", "react-markdown": "^3.4.1", - "react-redux": "^5.1.1", + "react-redux": "^5.1.2", "react-router-dom": "^4.3.1", "react-sizeme": "^2.3.6", "reactcss": "1.2.3", @@ -285,6 +284,8 @@ "@microsoft/api-documenter": "7.4.3", "@microsoft/api-extractor": "7.4.2", "@percy/agent": "^0.11.0", + "@testing-library/react": "^9.3.2", + "@testing-library/react-hooks": "^3.2.1", "@types/angular": "^1.6.56", "@types/angular-mocks": "^1.7.0", "@types/babel__core": "^7.1.2", @@ -310,7 +311,7 @@ "@types/has-ansi": "^3.0.0", "@types/history": "^4.7.3", "@types/hoek": "^4.1.3", - "@types/jest": "^24.0.18", + "@types/jest": "24.0.19", "@types/joi": "^13.4.2", "@types/jquery": "^3.3.31", "@types/js-yaml": "^3.11.1", @@ -330,8 +331,8 @@ "@types/podium": "^1.0.0", "@types/prop-types": "^15.5.3", "@types/reach__router": "^1.2.6", - "@types/react": "^16.8.0", - "@types/react-dom": "^16.8.0", + "@types/react": "^16.9.11", + "@types/react-dom": "^16.9.4", "@types/react-redux": "^6.0.6", "@types/react-router-dom": "^4.3.1", "@types/react-virtualized": "^9.18.7", @@ -345,12 +346,14 @@ "@types/styled-components": "^4.4.0", "@types/supertest": "^2.0.5", "@types/supertest-as-promised": "^2.0.38", + "@types/testing-library__react": "^9.1.2", + "@types/testing-library__react-hooks": "^3.1.0", "@types/type-detect": "^4.0.1", "@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", @@ -408,7 +411,6 @@ "istanbul-instrumenter-loader": "3.0.1", "jest": "^24.9.0", "jest-cli": "^24.9.0", - "jest-dom": "^3.5.0", "jest-raw-loader": "^1.0.1", "jimp": "0.8.4", "json5": "^1.0.1", diff --git a/packages/eslint-config-kibana/javascript.js b/packages/eslint-config-kibana/javascript.js index 0c79669c15e73..7afd3da3a7b93 100644 --- a/packages/eslint-config-kibana/javascript.js +++ b/packages/eslint-config-kibana/javascript.js @@ -40,7 +40,7 @@ module.exports = { rules: { 'block-scoped-var': 'error', - camelcase: [ 'error', { properties: 'never' } ], + camelcase: [ 'error', { properties: 'never', allow: ['^UNSAFE_'] } ], 'comma-dangle': 'off', 'comma-spacing': ['error', { before: false, after: true }], 'comma-style': [ 'error', 'last' ], diff --git a/packages/eslint-config-kibana/jest.js b/packages/eslint-config-kibana/jest.js index 2aa9627404a6c..d682277ff905a 100644 --- a/packages/eslint-config-kibana/jest.js +++ b/packages/eslint-config-kibana/jest.js @@ -16,7 +16,7 @@ module.exports = { rules: { 'jest/no-focused-tests': 'error', 'jest/no-identical-title': 'error', - 'import/order': 'off' + 'import/order': 'off', }, } ] diff --git a/packages/eslint-config-kibana/package.json b/packages/eslint-config-kibana/package.json index 71517bc10404d..ee65a1cf79148 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/eslint-config-kibana/typescript.js b/packages/eslint-config-kibana/typescript.js index 8ffae5edc88eb..3337bfc8eb101 100644 --- a/packages/eslint-config-kibana/typescript.js +++ b/packages/eslint-config-kibana/typescript.js @@ -94,7 +94,7 @@ module.exports = { '@typescript-eslint/camelcase': ['error', { 'properties': 'never', 'ignoreDestructuring': true, - 'allow': ['^[A-Z0-9_]+$'] + 'allow': ['^[A-Z0-9_]+$', '^UNSAFE_'] }], '@typescript-eslint/class-name-casing': 'error', '@typescript-eslint/explicit-member-accessibility': ['error', diff --git a/packages/kbn-config-schema/README.md b/packages/kbn-config-schema/README.md index 8ba2c43b5e1fe..fd62f1b3c03b2 100644 --- a/packages/kbn-config-schema/README.md +++ b/packages/kbn-config-schema/README.md @@ -12,6 +12,8 @@ Kibana configuration entries providing developers with a fully typed model of th - [`schema.number()`](#schemanumber) - [`schema.boolean()`](#schemaboolean) - [`schema.literal()`](#schemaliteral) + - [`schema.buffer()`](#schemabuffer) + - [`schema.stream()`](#schemastream) - [Composite types](#composite-types) - [`schema.arrayOf()`](#schemaarrayof) - [`schema.object()`](#schemaobject) @@ -173,6 +175,36 @@ const valueSchema = [ ]; ``` +#### `schema.buffer()` + +Validates input data as a NodeJS `Buffer`. + +__Output type:__ `Buffer` + +__Options:__ + * `defaultValue: TBuffer | Reference | (() => TBuffer)` - defines a default value, see [Default values](#default-values) section for more details. + * `validate: (value: TBuffer) => Buffer | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details. + +__Usage:__ +```typescript +const valueSchema = schema.buffer({ defaultValue: Buffer.from('Hi, there!') }); +``` + +#### `schema.stream()` + +Validates input data as a NodeJS `stream`. + +__Output type:__ `Stream`, `Readable` or `Writtable` + +__Options:__ + * `defaultValue: TStream | Reference | (() => TStream)` - defines a default value, see [Default values](#default-values) section for more details. + * `validate: (value: TStream) => string | void` - defines a custom validator function, see [Custom validation](#custom-validation) section for more details. + +__Usage:__ +```typescript +const valueSchema = schema.stream({ defaultValue: new Stream() }); +``` + ### Composite types #### `schema.arrayOf()` diff --git a/packages/kbn-config-schema/src/index.ts b/packages/kbn-config-schema/src/index.ts index 210b044421e7e..56b3096433c24 100644 --- a/packages/kbn-config-schema/src/index.ts +++ b/packages/kbn-config-schema/src/index.ts @@ -18,6 +18,7 @@ */ import { Duration } from 'moment'; +import { Stream } from 'stream'; import { ByteSizeValue } from './byte_size_value'; import { ContextReference, Reference, SiblingReference } from './references'; @@ -26,6 +27,7 @@ import { ArrayOptions, ArrayType, BooleanType, + BufferType, ByteSizeOptions, ByteSizeType, ConditionalType, @@ -52,6 +54,7 @@ import { UnionType, URIOptions, URIType, + StreamType, } from './types'; export { ObjectType, TypeOf, Type }; @@ -65,6 +68,14 @@ function boolean(options?: TypeOptions): Type { return new BooleanType(options); } +function buffer(options?: TypeOptions): Type { + return new BufferType(options); +} + +function stream(options?: TypeOptions): Type { + return new StreamType(options); +} + function string(options?: StringOptions): Type { return new StringType(options); } @@ -188,6 +199,7 @@ export const schema = { any, arrayOf, boolean, + buffer, byteSize, conditional, contextRef, @@ -201,6 +213,7 @@ export const schema = { object, oneOf, recordOf, + stream, siblingRef, string, uri, diff --git a/packages/kbn-config-schema/src/internals/index.ts b/packages/kbn-config-schema/src/internals/index.ts index e5a5b446de4f5..4d5091eaa09b1 100644 --- a/packages/kbn-config-schema/src/internals/index.ts +++ b/packages/kbn-config-schema/src/internals/index.ts @@ -29,6 +29,7 @@ import { } from 'joi'; import { isPlainObject } from 'lodash'; import { isDuration } from 'moment'; +import { Stream } from 'stream'; import { ByteSizeValue, ensureByteSizeValue } from '../byte_size_value'; import { ensureDuration } from '../duration'; @@ -89,6 +90,33 @@ export const internals = Joi.extend([ }, rules: [anyCustomRule], }, + { + name: 'binary', + + base: Joi.binary(), + coerce(value: any, state: State, options: ValidationOptions) { + // If value isn't defined, let Joi handle default value if it's defined. + if (value !== undefined && !(typeof value === 'object' && Buffer.isBuffer(value))) { + return this.createError('binary.base', { value }, state, options); + } + + return value; + }, + rules: [anyCustomRule], + }, + { + name: 'stream', + + pre(value: any, state: State, options: ValidationOptions) { + // If value isn't defined, let Joi handle default value if it's defined. + if (value instanceof Stream) { + return value as any; + } + + return this.createError('stream.base', { value }, state, options); + }, + rules: [anyCustomRule], + }, { name: 'string', diff --git a/packages/kbn-config-schema/src/types/__snapshots__/buffer_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/buffer_type.test.ts.snap new file mode 100644 index 0000000000000..96a7ab34dac26 --- /dev/null +++ b/packages/kbn-config-schema/src/types/__snapshots__/buffer_type.test.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`includes namespace in failure 1`] = `"[foo-namespace]: expected value of type [Buffer] but got [undefined]"`; + +exports[`is required by default 1`] = `"expected value of type [Buffer] but got [undefined]"`; + +exports[`returns error when not a buffer 1`] = `"expected value of type [Buffer] but got [number]"`; + +exports[`returns error when not a buffer 2`] = `"expected value of type [Buffer] but got [Array]"`; + +exports[`returns error when not a buffer 3`] = `"expected value of type [Buffer] but got [string]"`; diff --git a/packages/kbn-config-schema/src/types/__snapshots__/stream_type.test.ts.snap b/packages/kbn-config-schema/src/types/__snapshots__/stream_type.test.ts.snap new file mode 100644 index 0000000000000..e813b4f68a09e --- /dev/null +++ b/packages/kbn-config-schema/src/types/__snapshots__/stream_type.test.ts.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`includes namespace in failure 1`] = `"[foo-namespace]: expected value of type [Stream] but got [undefined]"`; + +exports[`is required by default 1`] = `"expected value of type [Buffer] but got [undefined]"`; + +exports[`returns error when not a stream 1`] = `"expected value of type [Stream] but got [number]"`; + +exports[`returns error when not a stream 2`] = `"expected value of type [Stream] but got [Array]"`; + +exports[`returns error when not a stream 3`] = `"expected value of type [Stream] but got [string]"`; diff --git a/packages/kbn-config-schema/src/types/buffer_type.test.ts b/packages/kbn-config-schema/src/types/buffer_type.test.ts new file mode 100644 index 0000000000000..63d59296aec84 --- /dev/null +++ b/packages/kbn-config-schema/src/types/buffer_type.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 { schema } from '..'; + +test('returns value by default', () => { + const value = Buffer.from('Hi!'); + expect(schema.buffer().validate(value)).toStrictEqual(value); +}); + +test('is required by default', () => { + expect(() => schema.buffer().validate(undefined)).toThrowErrorMatchingSnapshot(); +}); + +test('includes namespace in failure', () => { + expect(() => + schema.buffer().validate(undefined, {}, 'foo-namespace') + ).toThrowErrorMatchingSnapshot(); +}); + +describe('#defaultValue', () => { + test('returns default when undefined', () => { + const value = Buffer.from('Hi!'); + expect(schema.buffer({ defaultValue: value }).validate(undefined)).toStrictEqual(value); + }); + + test('returns value when specified', () => { + const value = Buffer.from('Hi!'); + expect(schema.buffer({ defaultValue: Buffer.from('Bye!') }).validate(value)).toStrictEqual( + value + ); + }); +}); + +test('returns error when not a buffer', () => { + expect(() => schema.buffer().validate(123)).toThrowErrorMatchingSnapshot(); + + expect(() => schema.buffer().validate([1, 2, 3])).toThrowErrorMatchingSnapshot(); + + expect(() => schema.buffer().validate('abc')).toThrowErrorMatchingSnapshot(); +}); diff --git a/packages/kbn-config-schema/src/types/buffer_type.ts b/packages/kbn-config-schema/src/types/buffer_type.ts new file mode 100644 index 0000000000000..194163e5096f0 --- /dev/null +++ b/packages/kbn-config-schema/src/types/buffer_type.ts @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import typeDetect from 'type-detect'; +import { internals } from '../internals'; +import { Type, TypeOptions } from './type'; + +export class BufferType extends Type { + constructor(options?: TypeOptions) { + super(internals.binary(), options); + } + + protected handleError(type: string, { value }: Record) { + if (type === 'any.required' || type === 'binary.base') { + return `expected value of type [Buffer] but got [${typeDetect(value)}]`; + } + } +} diff --git a/packages/kbn-config-schema/src/types/index.ts b/packages/kbn-config-schema/src/types/index.ts index cfa8cc4b7553d..9db79b8bf9e00 100644 --- a/packages/kbn-config-schema/src/types/index.ts +++ b/packages/kbn-config-schema/src/types/index.ts @@ -21,6 +21,7 @@ export { Type, TypeOptions } from './type'; export { AnyType } from './any_type'; export { ArrayOptions, ArrayType } from './array_type'; export { BooleanType } from './boolean_type'; +export { BufferType } from './buffer_type'; export { ByteSizeOptions, ByteSizeType } from './byte_size_type'; export { ConditionalType, ConditionalTypeValue } from './conditional_type'; export { DurationOptions, DurationType } from './duration_type'; @@ -30,6 +31,7 @@ export { MapOfOptions, MapOfType } from './map_type'; export { NumberOptions, NumberType } from './number_type'; export { ObjectType, ObjectTypeOptions, Props, TypeOf } from './object_type'; export { RecordOfOptions, RecordOfType } from './record_type'; +export { StreamType } from './stream_type'; export { StringOptions, StringType } from './string_type'; export { UnionType } from './union_type'; export { URIOptions, URIType } from './uri_type'; diff --git a/packages/kbn-config-schema/src/types/stream_type.test.ts b/packages/kbn-config-schema/src/types/stream_type.test.ts new file mode 100644 index 0000000000000..011fa6373df33 --- /dev/null +++ b/packages/kbn-config-schema/src/types/stream_type.test.ts @@ -0,0 +1,71 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema } from '..'; +import { Stream, Readable, Writable, PassThrough } from 'stream'; + +test('returns value by default', () => { + const value = new Stream(); + expect(schema.stream().validate(value)).toStrictEqual(value); +}); + +test('Readable is valid', () => { + const value = new Readable(); + expect(schema.stream().validate(value)).toStrictEqual(value); +}); + +test('Writable is valid', () => { + const value = new Writable(); + expect(schema.stream().validate(value)).toStrictEqual(value); +}); + +test('Passthrough is valid', () => { + const value = new PassThrough(); + expect(schema.stream().validate(value)).toStrictEqual(value); +}); + +test('is required by default', () => { + expect(() => schema.buffer().validate(undefined)).toThrowErrorMatchingSnapshot(); +}); + +test('includes namespace in failure', () => { + expect(() => + schema.stream().validate(undefined, {}, 'foo-namespace') + ).toThrowErrorMatchingSnapshot(); +}); + +describe('#defaultValue', () => { + test('returns default when undefined', () => { + const value = new Stream(); + expect(schema.stream({ defaultValue: value }).validate(undefined)).toStrictEqual(value); + }); + + test('returns value when specified', () => { + const value = new Stream(); + expect(schema.stream({ defaultValue: new PassThrough() }).validate(value)).toStrictEqual(value); + }); +}); + +test('returns error when not a stream', () => { + expect(() => schema.stream().validate(123)).toThrowErrorMatchingSnapshot(); + + expect(() => schema.stream().validate([1, 2, 3])).toThrowErrorMatchingSnapshot(); + + expect(() => schema.stream().validate('abc')).toThrowErrorMatchingSnapshot(); +}); diff --git a/packages/kbn-config-schema/src/types/stream_type.ts b/packages/kbn-config-schema/src/types/stream_type.ts new file mode 100644 index 0000000000000..db1559f537490 --- /dev/null +++ b/packages/kbn-config-schema/src/types/stream_type.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import typeDetect from 'type-detect'; +import { Stream } from 'stream'; +import { internals } from '../internals'; +import { Type, TypeOptions } from './type'; + +export class StreamType extends Type { + constructor(options?: TypeOptions) { + super(internals.stream(), options); + } + + protected handleError(type: string, { value }: Record) { + if (type === 'any.required' || type === 'stream.base') { + return `expected value of type [Stream] but got [${typeDetect(value)}]`; + } + } +} diff --git a/packages/kbn-config-schema/types/joi.d.ts b/packages/kbn-config-schema/types/joi.d.ts index 5c7e42d0d6f5f..770314faa8ebd 100644 --- a/packages/kbn-config-schema/types/joi.d.ts +++ b/packages/kbn-config-schema/types/joi.d.ts @@ -38,6 +38,7 @@ declare module 'joi' { duration: () => AnySchema; map: () => MapSchema; record: () => RecordSchema; + stream: () => AnySchema; }; interface AnySchema { diff --git a/packages/kbn-es-query/README.md b/packages/kbn-es-query/README.md deleted file mode 100644 index fc403447877d8..0000000000000 --- 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 2cd2a8f53d2ee..0000000000000 --- 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 1799d04a0fbd8..0000000000000 --- 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 588e6ada69cfe..0000000000000 --- 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/kuery/ast/__tests__/ast.js b/packages/kbn-es-query/src/kuery/ast/__tests__/ast.js deleted file mode 100644 index 3cbe1203bc533..0000000000000 --- 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/index.d.ts b/packages/kbn-es-query/src/kuery/ast/index.d.ts deleted file mode 100644 index 9e68d01d046cc..0000000000000 --- 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 7afa0fcce1bfe..0000000000000 --- 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 c1f2fae0bb3e1..0000000000000 --- 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 b2f3d7ec16a65..0000000000000 --- 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 dae15979a161c..0000000000000 --- 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 de00c083fc830..0000000000000 --- 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 cfb8f6d5274db..0000000000000 --- 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 0c4379378c6d6..0000000000000 --- 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 6e4f5c342688c..0000000000000 --- 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 27f51c1f44cf2..0000000000000 --- 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 2a43c4d10e007..0000000000000 --- 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 4a91407471266..0000000000000 --- 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 05f51bbccd2ff..0000000000000 --- 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 591faff64711d..bbc5126da1dce 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.12.0", "react-intl": "^2.8.0" } } diff --git a/packages/kbn-ui-framework/doc_site/src/components/guide_nav/guide_nav.js b/packages/kbn-ui-framework/doc_site/src/components/guide_nav/guide_nav.js index 53bc42ce33276..cb4654522e4e3 100644 --- a/packages/kbn-ui-framework/doc_site/src/components/guide_nav/guide_nav.js +++ b/packages/kbn-ui-framework/doc_site/src/components/guide_nav/guide_nav.js @@ -47,7 +47,7 @@ export class GuideNav extends Component { }); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { const currentRoute = nextProps.routes[1]; const nextRoute = this.props.getNextRoute(currentRoute.name); const previousRoute = this.props.getPreviousRoute(currentRoute.name); diff --git a/packages/kbn-ui-framework/doc_site/src/components/guide_sandbox/guide_sandbox.js b/packages/kbn-ui-framework/doc_site/src/components/guide_sandbox/guide_sandbox.js index 6d4c9cddae0be..f5a71bfb1b296 100644 --- a/packages/kbn-ui-framework/doc_site/src/components/guide_sandbox/guide_sandbox.js +++ b/packages/kbn-ui-framework/doc_site/src/components/guide_sandbox/guide_sandbox.js @@ -49,7 +49,7 @@ function mapDispatchToProps(dispatch) { } class GuideSandboxComponent extends Component { - componentWillMount() { + UNSAFE_componentWillMount() { this.props.openSandbox(); } diff --git a/packages/kbn-ui-framework/doc_site/src/components/guide_section/guide_section.js b/packages/kbn-ui-framework/doc_site/src/components/guide_section/guide_section.js index 99a68e9575114..dbad59ffb3bd5 100644 --- a/packages/kbn-ui-framework/doc_site/src/components/guide_section/guide_section.js +++ b/packages/kbn-ui-framework/doc_site/src/components/guide_section/guide_section.js @@ -36,7 +36,7 @@ export class GuideSection extends Component { this.props.openCodeViewer(this.props.source, this.props.title); } - componentWillMount() { + UNSAFE_componentWillMount() { this.props.registerSection(this.getId(), this.props.title); } diff --git a/packages/kbn-ui-framework/doc_site/src/views/app_view.js b/packages/kbn-ui-framework/doc_site/src/views/app_view.js index 7a9d7a01b820a..fc14417564d72 100644 --- a/packages/kbn-ui-framework/doc_site/src/views/app_view.js +++ b/packages/kbn-ui-framework/doc_site/src/views/app_view.js @@ -81,7 +81,7 @@ export class AppView extends Component { }); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { // Only force the chrome to be hidden if we're navigating from a non-sandbox to a sandbox. if (!this.props.isSandbox && nextProps.isSandbox) { this.setState({ diff --git a/packages/kbn-ui-framework/package.json b/packages/kbn-ui-framework/package.json index 472c801721ecf..b4d9d3dfee03f 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.12.0", "react-ace": "^5.9.0", "react-color": "^2.13.8", "tabbable": "1.1.3", @@ -57,8 +57,8 @@ "postcss": "^7.0.5", "postcss-loader": "^3.0.0", "raw-loader": "^3.1.0", - "react-dom": "^16.8.0", - "react-redux": "^5.0.6", + "react-dom": "^16.12.0", + "react-redux": "^5.1.2", "react-router": "^3.2.0", "react-router-redux": "^4.0.8", "redux": "3.7.2", diff --git a/scripts/functional_tests.js b/scripts/functional_tests.js index 9f4e678c6adf5..b65cd3835cc0a 100644 --- a/scripts/functional_tests.js +++ b/scripts/functional_tests.js @@ -22,6 +22,6 @@ require('@kbn/test').runTestsCli([ require.resolve('../test/functional/config.js'), require.resolve('../test/api_integration/config.js'), require.resolve('../test/plugin_functional/config.js'), - require.resolve('../test/interpreter_functional/config.js'), + require.resolve('../test/interpreter_functional/config.ts'), require.resolve('../test/ui_capabilities/newsfeed_err/config.ts'), ]); diff --git a/src/core/CONVENTIONS.md b/src/core/CONVENTIONS.md index 11a5f33a1b2d8..fbe2740b96108 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 c5e04c3cfb53a..e88f1675114bc 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 16a634b2d3287..30a98c9046ff5 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 8a1b56d87fb4c..25b463140bfbc 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 5cd2fbaa1ebe8..bf0585ad280d5 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.mocks.ts b/src/core/server/http/http_server.mocks.ts index 0ac2f59525c32..8469a1d23a44b 100644 --- a/src/core/server/http/http_server.mocks.ts +++ b/src/core/server/http/http_server.mocks.ts @@ -54,7 +54,7 @@ function createKibanaRequestMock({ }: RequestFixtureOptions = {}) { const queryString = querystring.stringify(query); return KibanaRequest.from( - { + createRawRequestMock({ headers, params, query, @@ -71,13 +71,13 @@ function createKibanaRequestMock({ raw: { req: { socket }, }, - } as any, + }), { params: schema.object({}, { allowUnknowns: true }), body: schema.object({}, { allowUnknowns: true }), query: schema.object({}, { allowUnknowns: true }), } - ); + ) as KibanaRequest, Readonly<{}>, Readonly<{}>>; } type DeepPartial = T extends any[] diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index acae9d8ff0e70..df47ffdc1176b 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -30,11 +30,12 @@ import { HttpConfig } from './http_config'; import { Router } from './router'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { HttpServer } from './http_server'; +import { Readable } from 'stream'; const cookieOptions = { name: 'sid', encryptionKey: 'something_at_least_32_characters', - validate: () => true, + validate: () => ({ isValid: true }), isSecure: false, }; @@ -577,6 +578,157 @@ test('exposes route details of incoming request to a route handler', async () => }); }); +test('exposes route details of incoming request to a route handler (POST + payload options)', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.post( + { + path: '/', + validate: { body: schema.object({ test: schema.number() }) }, + options: { body: { accepts: 'application/json' } }, + }, + (context, req, res) => res.ok({ body: req.route }) + ); + registerRouter(router); + + await server.start(); + await supertest(innerServer.listener) + .post('/') + .send({ test: 1 }) + .expect(200, { + method: 'post', + path: '/', + options: { + authRequired: true, + tags: [], + body: { + parse: true, // hapi populates the default + maxBytes: 1024, // hapi populates the default + accepts: ['application/json'], + output: 'data', + }, + }, + }); +}); + +describe('body options', () => { + test('should reject the request because the Content-Type in the request is not valid', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.post( + { + path: '/', + validate: { body: schema.object({ test: schema.number() }) }, + options: { body: { accepts: 'multipart/form-data' } }, // supertest sends 'application/json' + }, + (context, req, res) => res.ok({ body: req.route }) + ); + registerRouter(router); + + await server.start(); + await supertest(innerServer.listener) + .post('/') + .send({ test: 1 }) + .expect(415, { + statusCode: 415, + error: 'Unsupported Media Type', + message: 'Unsupported Media Type', + }); + }); + + test('should reject the request because the payload is too large', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.post( + { + path: '/', + validate: { body: schema.object({ test: schema.number() }) }, + options: { body: { maxBytes: 1 } }, + }, + (context, req, res) => res.ok({ body: req.route }) + ); + registerRouter(router); + + await server.start(); + await supertest(innerServer.listener) + .post('/') + .send({ test: 1 }) + .expect(413, { + statusCode: 413, + error: 'Request Entity Too Large', + message: 'Payload content length greater than maximum allowed: 1', + }); + }); + + test('should not parse the content in the request', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.post( + { + path: '/', + validate: { body: schema.buffer() }, + options: { body: { parse: false } }, + }, + (context, req, res) => { + try { + expect(req.body).toBeInstanceOf(Buffer); + expect(req.body.toString()).toBe(JSON.stringify({ test: 1 })); + return res.ok({ body: req.route.options.body }); + } catch (err) { + return res.internalError({ body: err.message }); + } + } + ); + registerRouter(router); + + await server.start(); + await supertest(innerServer.listener) + .post('/') + .send({ test: 1 }) + .expect(200, { + parse: false, + maxBytes: 1024, // hapi populates the default + output: 'data', + }); + }); +}); + +test('should return a stream in the body', async () => { + const { registerRouter, server: innerServer } = await server.setup(config); + + const router = new Router('', logger, enhanceWithContext); + router.put( + { + path: '/', + validate: { body: schema.stream() }, + options: { body: { output: 'stream' } }, + }, + (context, req, res) => { + try { + expect(req.body).toBeInstanceOf(Readable); + return res.ok({ body: req.route.options.body }); + } catch (err) { + return res.internalError({ body: err.message }); + } + } + ); + registerRouter(router); + + await server.start(); + await supertest(innerServer.listener) + .put('/') + .send({ test: 1 }) + .expect(200, { + parse: true, + maxBytes: 1024, // hapi populates the default + output: 'stream', + }); +}); + describe('setup contract', () => { describe('#createSessionStorage', () => { it('creates session storage factory', async () => { diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index da97ab535516c..a587eed1f54ec 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -127,21 +127,26 @@ export class HttpServer { for (const router of this.registeredRouters) { for (const route of router.getRoutes()) { this.log.debug(`registering route handler for [${route.path}]`); - const { authRequired = true, tags } = route.options; // Hapi does not allow payload validation to be specified for 'head' or 'get' requests const validate = ['head', 'get'].includes(route.method) ? undefined : { payload: true }; + const { authRequired = true, tags, body = {} } = route.options; + const { accepts: allow, maxBytes, output, parse } = body; this.server.route({ handler: route.handler, method: route.method, path: route.path, options: { - auth: authRequired ? undefined : false, + // Enforcing the comparison with true because plugins could overwrite the auth strategy by doing `options: { authRequired: authStrategy as any }` + auth: authRequired === true ? undefined : false, tags: tags ? Array.from(tags) : undefined, // TODO: This 'validate' section can be removed once the legacy platform is completely removed. // We are telling Hapi that NP routes can accept any payload, so that it can bypass the default // validation applied in ./http_tools#getServerOptions // (All NP routes are already required to specify their own validation in order to access the payload) validate, + payload: [allow, maxBytes, output, parse].some(v => typeof v !== 'undefined') + ? { allow, maxBytes, output, parse } + : undefined, }, }); } diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index e9a2571382edc..6dab120b20e50 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -43,6 +43,7 @@ const createRouterMock = (): jest.Mocked => ({ get: jest.fn(), post: jest.fn(), put: jest.fn(), + patch: jest.fn(), delete: jest.fn(), getRoutes: jest.fn(), handleLegacyErrors: jest.fn().mockImplementation(handler => handler), diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts index 2fa67750f6406..f9a3a91ec18ad 100644 --- a/src/core/server/http/index.ts +++ b/src/core/server/http/index.ts @@ -30,6 +30,7 @@ export { ErrorHttpResponseOptions, KibanaRequest, KibanaRequestRoute, + KibanaRequestRouteOptions, IKibanaResponse, KnownHeaders, LegacyRequest, @@ -44,8 +45,12 @@ export { RouteConfig, IRouter, RouteMethod, - RouteConfigOptions, RouteRegistrar, + RouteConfigOptions, + RouteSchemas, + RouteConfigOptionsBody, + RouteContentType, + validBodyOutput, } from './router'; export { BasePathProxyServer } from './base_path_proxy_server'; export { OnPreAuthHandler, OnPreAuthToolkit } from './lifecycle/on_pre_auth'; @@ -60,6 +65,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 00629b811b28f..f3867faa2ae75 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 4592a646b7f04..7c4a0097456ca 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/http/router/error_wrapper.ts b/src/core/server/http/router/error_wrapper.ts index 706a9fe3b8887..c4b4d3840d1b9 100644 --- a/src/core/server/http/router/error_wrapper.ts +++ b/src/core/server/http/router/error_wrapper.ts @@ -23,13 +23,14 @@ import { KibanaRequest } from './request'; import { KibanaResponseFactory } from './response'; import { RequestHandler } from './router'; import { RequestHandlerContext } from '../../../server'; +import { RouteMethod } from './route'; export const wrapErrors =

( - handler: RequestHandler -): RequestHandler => { + handler: RequestHandler +): RequestHandler => { return async ( context: RequestHandlerContext, - request: KibanaRequest, TypeOf, TypeOf>, + request: KibanaRequest, TypeOf, TypeOf, RouteMethod>, response: KibanaResponseFactory ) => { try { diff --git a/src/core/server/http/router/index.ts b/src/core/server/http/router/index.ts index f07ad3cfe85c0..35bfb3ba9c33a 100644 --- a/src/core/server/http/router/index.ts +++ b/src/core/server/http/router/index.ts @@ -22,11 +22,20 @@ export { Router, RequestHandler, IRouter, RouteRegistrar } from './router'; export { KibanaRequest, KibanaRequestRoute, + KibanaRequestRouteOptions, isRealRequest, LegacyRequest, ensureRawRequest, } from './request'; -export { RouteMethod, RouteConfig, RouteConfigOptions } from './route'; +export { + RouteMethod, + RouteConfig, + RouteConfigOptions, + RouteSchemas, + RouteContentType, + RouteConfigOptionsBody, + validBodyOutput, +} from './route'; export { HapiResponseAdapter } from './response_adapter'; export { CustomHttpResponseOptions, diff --git a/src/core/server/http/router/request.ts b/src/core/server/http/router/request.ts index 5d3b70ba27eee..b132899910569 100644 --- a/src/core/server/http/router/request.ts +++ b/src/core/server/http/router/request.ts @@ -20,23 +20,32 @@ import { Url } from 'url'; import { Request } from 'hapi'; -import { ObjectType, TypeOf } from '@kbn/config-schema'; +import { ObjectType, Type, TypeOf } from '@kbn/config-schema'; +import { Stream } from 'stream'; import { deepFreeze, RecursiveReadonly } from '../../../utils'; import { Headers } from './headers'; -import { RouteMethod, RouteSchemas, RouteConfigOptions } from './route'; +import { RouteMethod, RouteSchemas, RouteConfigOptions, validBodyOutput } from './route'; import { KibanaSocket, IKibanaSocket } from './socket'; const requestSymbol = Symbol('request'); +/** + * Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. + * @public + */ +export type KibanaRequestRouteOptions = Method extends 'get' | 'options' + ? Required, 'body'>> + : Required>; + /** * Request specific route information exposed to a handler. * @public * */ -export interface KibanaRequestRoute { +export interface KibanaRequestRoute { path: string; - method: RouteMethod | 'patch' | 'options'; - options: Required; + method: Method; + options: KibanaRequestRouteOptions; } /** @@ -50,17 +59,22 @@ export interface LegacyRequest extends Request {} // eslint-disable-line @typesc * Kibana specific abstraction for an incoming request. * @public */ -export class KibanaRequest { +export class KibanaRequest< + Params = unknown, + Query = unknown, + Body = unknown, + Method extends RouteMethod = any +> { /** * Factory for creating requests. Validates the request before creating an * instance of a KibanaRequest. * @internal */ - public static from

( - req: Request, - routeSchemas?: RouteSchemas, - withoutSecretHeaders: boolean = true - ) { + public static from< + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType | Type | Type + >(req: Request, routeSchemas?: RouteSchemas, withoutSecretHeaders: boolean = true) { const requestParts = KibanaRequest.validate(req, routeSchemas); return new KibanaRequest( req, @@ -77,7 +91,11 @@ export class KibanaRequest { * received in the route handler. * @internal */ - private static validate

( + private static validate< + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType | Type | Type + >( req: Request, routeSchemas: RouteSchemas | undefined ): { @@ -113,7 +131,7 @@ export class KibanaRequest { /** a WHATWG URL standard object. */ public readonly url: Url; /** matched route details */ - public readonly route: RecursiveReadonly; + public readonly route: RecursiveReadonly>; /** * Readonly copy of incoming request headers. * @remarks @@ -148,15 +166,28 @@ export class KibanaRequest { this.socket = new KibanaSocket(request.raw.req.socket); } - private getRouteInfo() { + private getRouteInfo(): KibanaRequestRoute { const request = this[requestSymbol]; + const method = request.method as Method; + const { parse, maxBytes, allow, output } = request.route.settings.payload || {}; + + const options = ({ + authRequired: request.route.settings.auth !== false, + tags: request.route.settings.tags || [], + body: ['get', 'options'].includes(method) + ? undefined + : { + parse, + maxBytes, + accepts: allow, + output: output as typeof validBodyOutput[number], // We do not support all the HAPI-supported outputs and TS complains + }, + } as unknown) as KibanaRequestRouteOptions; // TS does not understand this is OK so I'm enforced to do this enforced casting + return { path: request.path, - method: request.method, - options: { - authRequired: request.route.settings.auth !== false, - tags: request.route.settings.tags || [], - }, + method, + options, }; } } diff --git a/src/core/server/http/router/route.ts b/src/core/server/http/router/route.ts index bffa23551dd52..129cf4c922ffd 100644 --- a/src/core/server/http/router/route.ts +++ b/src/core/server/http/router/route.ts @@ -17,18 +17,89 @@ * under the License. */ -import { ObjectType } from '@kbn/config-schema'; +import { ObjectType, Type } from '@kbn/config-schema'; +import { Stream } from 'stream'; + /** * The set of common HTTP methods supported by Kibana routing. * @public */ -export type RouteMethod = 'get' | 'post' | 'put' | 'delete'; +export type RouteMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'; + +/** + * The set of valid body.output + * @public + */ +export const validBodyOutput = ['data', 'stream'] as const; + +/** + * The set of supported parseable Content-Types + * @public + */ +export type RouteContentType = + | 'application/json' + | 'application/*+json' + | 'application/octet-stream' + | 'application/x-www-form-urlencoded' + | 'multipart/form-data' + | 'text/*'; + +/** + * Additional body options for a route + * @public + */ +export interface RouteConfigOptionsBody { + /** + * A string or an array of strings with the allowed mime types for the endpoint. Use this settings to limit the set of allowed mime types. Note that allowing additional mime types not listed + * above will not enable them to be parsed, and if parse is true, the request will result in an error response. + * + * Default value: allows parsing of the following mime types: + * * application/json + * * application/*+json + * * application/octet-stream + * * application/x-www-form-urlencoded + * * multipart/form-data + * * text/* + */ + accepts?: RouteContentType | RouteContentType[] | string | string[]; + + /** + * Limits the size of incoming payloads to the specified byte count. Allowing very large payloads may cause the server to run out of memory. + * + * Default value: The one set in the kibana.yml config file under the parameter `server.maxPayloadBytes`. + */ + maxBytes?: number; + + /** + * The processed payload format. The value must be one of: + * * 'data' - the incoming payload is read fully into memory. If parse is true, the payload is parsed (JSON, form-decoded, multipart) based on the 'Content-Type' header. If parse is false, a raw + * Buffer is returned. + * * 'stream' - the incoming payload is made available via a Stream.Readable interface. If the payload is 'multipart/form-data' and parse is true, field values are presented as text while files + * are provided as streams. File streams from a 'multipart/form-data' upload will also have a hapi property containing the filename and headers properties. Note that payload streams for multipart + * payloads are a synthetic interface created on top of the entire multipart content loaded into memory. To avoid loading large multipart payloads into memory, set parse to false and handle the + * multipart payload in the handler using a streaming parser (e.g. pez). + * + * Default value: 'data', unless no validation.body is provided in the route definition. In that case the default is 'stream' to alleviate memory pressure. + */ + output?: typeof validBodyOutput[number]; + + /** + * Determines if the incoming payload is processed or presented raw. Available values: + * * true - if the request 'Content-Type' matches the allowed mime types set by allow (for the whole payload as well as parts), the payload is converted into an object when possible. If the + * format is unknown, a Bad Request (400) error response is sent. Any known content encoding is decoded. + * * false - the raw payload is returned unmodified. + * * 'gunzip' - the raw payload is returned unmodified after any known content encoding is decoded. + * + * Default value: true, unless no validation.body is provided in the route definition. In that case the default is false to alleviate memory pressure. + */ + parse?: boolean | 'gunzip'; +} /** * Additional route options. * @public */ -export interface RouteConfigOptions { +export interface RouteConfigOptions { /** * A flag shows that authentication for a route: * `enabled` when true @@ -42,13 +113,23 @@ export interface RouteConfigOptions { * Additional metadata tag strings to attach to the route. */ tags?: readonly string[]; + + /** + * Additional body options {@link RouteConfigOptionsBody}. + */ + body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody; } /** * Route specific configuration. * @public */ -export interface RouteConfig

{ +export interface RouteConfig< + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType | Type | Type, + Method extends RouteMethod +> { /** * The endpoint _within_ the router path to register the route. * @@ -125,7 +206,7 @@ export interface RouteConfig

; } /** @@ -133,7 +214,11 @@ export interface RouteConfig

{ +export interface RouteSchemas< + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType | Type | Type +> { params?: P; query?: Q; body?: B; diff --git a/src/core/server/http/router/router.test.ts b/src/core/server/http/router/router.test.ts index 9fdf7297ed775..f5469a95b5106 100644 --- a/src/core/server/http/router/router.test.ts +++ b/src/core/server/http/router/router.test.ts @@ -19,6 +19,7 @@ import { Router } from './router'; import { loggingServiceMock } from '../../logging/logging_service.mock'; +import { schema } from '@kbn/config-schema'; const logger = loggingServiceMock.create().get(); const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {}); @@ -45,5 +46,46 @@ describe('Router', () => { `"Expected a valid schema declared with '@kbn/config-schema' package at key: [params]."` ); }); + + it('throws if options.body.output is not a valid value', () => { + const router = new Router('', logger, enhanceWithContext); + expect(() => + router.post( + // we use 'any' because TS already checks we cannot provide this body.output + { + path: '/', + options: { body: { output: 'file' } } as any, // We explicitly don't support 'file' + validate: { body: schema.object({}, { allowUnknowns: true }) }, + }, + (context, req, res) => res.ok({}) + ) + ).toThrowErrorMatchingInlineSnapshot( + `"[options.body.output: 'file'] in route POST / is not valid. Only 'data' or 'stream' are valid."` + ); + }); + + it('should default `output: "stream" and parse: false` when no body validation is required but not a GET', () => { + const router = new Router('', logger, enhanceWithContext); + router.post({ path: '/', validate: {} }, (context, req, res) => res.ok({})); + const [route] = router.getRoutes(); + expect(route.options).toEqual({ body: { output: 'stream', parse: false } }); + }); + + it('should NOT default `output: "stream" and parse: false` when the user has specified body options (he cares about it)', () => { + const router = new Router('', logger, enhanceWithContext); + router.post( + { path: '/', options: { body: { maxBytes: 1 } }, validate: {} }, + (context, req, res) => res.ok({}) + ); + const [route] = router.getRoutes(); + expect(route.options).toEqual({ body: { maxBytes: 1 } }); + }); + + it('should NOT default `output: "stream" and parse: false` when no body validation is required and GET', () => { + const router = new Router('', logger, enhanceWithContext); + router.get({ path: '/', validate: {} }, (context, req, res) => res.ok({})); + const [route] = router.getRoutes(); + expect(route.options).toEqual({}); + }); }); }); diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index a13eae51a19a6..3bed8fe4186ac 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -21,10 +21,17 @@ import { ObjectType, TypeOf, Type } from '@kbn/config-schema'; import { Request, ResponseObject, ResponseToolkit } from 'hapi'; import Boom from 'boom'; +import { Stream } from 'stream'; import { Logger } from '../../logging'; import { KibanaRequest } from './request'; import { KibanaResponseFactory, kibanaResponseFactory, IKibanaResponse } from './response'; -import { RouteConfig, RouteConfigOptions, RouteMethod, RouteSchemas } from './route'; +import { + RouteConfig, + RouteConfigOptions, + RouteMethod, + RouteSchemas, + validBodyOutput, +} from './route'; import { HapiResponseAdapter } from './response_adapter'; import { RequestHandlerContext } from '../../../server'; import { wrapErrors } from './error_wrapper'; @@ -32,17 +39,22 @@ import { wrapErrors } from './error_wrapper'; interface RouterRoute { method: RouteMethod; path: string; - options: RouteConfigOptions; + options: RouteConfigOptions; handler: (req: Request, responseToolkit: ResponseToolkit) => Promise>; } /** - * Handler to declare a route. + * Route handler common definition + * * @public */ -export type RouteRegistrar =

( - route: RouteConfig, - handler: RequestHandler +export type RouteRegistrar = < + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType | Type | Type +>( + route: RouteConfig, + handler: RequestHandler ) => void; /** @@ -62,28 +74,35 @@ export interface IRouter { * @param route {@link RouteConfig} - a route configuration. * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ - get: RouteRegistrar; + get: RouteRegistrar<'get'>; /** * Register a route handler for `POST` request. * @param route {@link RouteConfig} - a route configuration. * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ - post: RouteRegistrar; + post: RouteRegistrar<'post'>; /** * Register a route handler for `PUT` request. * @param route {@link RouteConfig} - a route configuration. * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ - put: RouteRegistrar; + put: RouteRegistrar<'put'>; + + /** + * Register a route handler for `PATCH` request. + * @param route {@link RouteConfig} - a route configuration. + * @param handler {@link RequestHandler} - a function to call to respond to an incoming request + */ + patch: RouteRegistrar<'patch'>; /** * Register a route handler for `DELETE` request. * @param route {@link RouteConfig} - a route configuration. * @param handler {@link RequestHandler} - a function to call to respond to an incoming request */ - delete: RouteRegistrar; + delete: RouteRegistrar<'delete'>; /** * Wrap a router handler to catch and converts legacy boom errors to proper custom errors. @@ -94,16 +113,19 @@ export interface IRouter { ) => RequestHandler; /** - * Returns all routes registered with the this router. + * Returns all routes registered with this router. * @returns List of registered routes. * @internal */ getRoutes: () => RouterRoute[]; } -export type ContextEnhancer

= ( - handler: RequestHandler -) => RequestHandlerEnhanced; +export type ContextEnhancer< + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType, + Method extends RouteMethod +> = (handler: RequestHandler) => RequestHandlerEnhanced; function getRouteFullPath(routerPath: string, routePath: string) { // If router's path ends with slash and route's path starts with slash, @@ -121,8 +143,8 @@ function getRouteFullPath(routerPath: string, routePath: string) { function routeSchemasFromRouteConfig< P extends ObjectType, Q extends ObjectType, - B extends ObjectType ->(route: RouteConfig, routeMethod: RouteMethod) { + B extends ObjectType | Type | Type +>(route: RouteConfig, routeMethod: RouteMethod) { // The type doesn't allow `validate` to be undefined, but it can still // happen when it's used from JavaScript. if (route.validate === undefined) { @@ -144,6 +166,49 @@ function routeSchemasFromRouteConfig< return route.validate ? route.validate : undefined; } +/** + * Create a valid options object with "sensible" defaults + adding some validation to the options fields + * + * @param method HTTP verb for these options + * @param routeConfig The route config definition + */ +function validOptions( + method: RouteMethod, + routeConfig: RouteConfig< + ObjectType, + ObjectType, + ObjectType | Type | Type, + typeof method + > +) { + const shouldNotHavePayload = ['head', 'get'].includes(method); + const { options = {}, validate } = routeConfig; + const shouldValidateBody = (validate && !!validate.body) || !!options.body; + + const { output } = options.body || {}; + if (typeof output === 'string' && !validBodyOutput.includes(output)) { + throw new Error( + `[options.body.output: '${output}'] in route ${method.toUpperCase()} ${ + routeConfig.path + } is not valid. Only '${validBodyOutput.join("' or '")}' are valid.` + ); + } + + const body = shouldNotHavePayload + ? undefined + : { + // If it's not a GET (requires payload) but no body validation is required (or no body options are specified), + // We assume the route does not care about the body => use the memory-cheapest approach (stream and no parsing) + output: !shouldValidateBody ? ('stream' as const) : undefined, + parse: !shouldValidateBody ? false : undefined, + + // User's settings should overwrite any of the "desired" values + ...options.body, + }; + + return { ...options, body }; +} + /** * @internal */ @@ -153,21 +218,21 @@ export class Router implements IRouter { public post: IRouter['post']; public delete: IRouter['delete']; public put: IRouter['put']; + public patch: IRouter['patch']; constructor( public readonly routerPath: string, private readonly log: Logger, - private readonly enhanceWithContext: ContextEnhancer + private readonly enhanceWithContext: ContextEnhancer ) { - const buildMethod = (method: RouteMethod) => < + const buildMethod = (method: Method) => < P extends ObjectType, Q extends ObjectType, - B extends ObjectType + B extends ObjectType | Type | Type >( - route: RouteConfig, - handler: RequestHandler + route: RouteConfig, + handler: RequestHandler ) => { - const { path, options = {} } = route; const routeSchemas = routeSchemasFromRouteConfig(route, method); this.routes.push({ @@ -179,8 +244,8 @@ export class Router implements IRouter { handler: this.enhanceWithContext(handler), }), method, - path: getRouteFullPath(this.routerPath, path), - options, + path: getRouteFullPath(this.routerPath, route.path), + options: validOptions(method, route), }); }; @@ -188,6 +253,7 @@ export class Router implements IRouter { this.post = buildMethod('post'); this.delete = buildMethod('delete'); this.put = buildMethod('put'); + this.patch = buildMethod('patch'); } public getRoutes() { @@ -200,7 +266,11 @@ export class Router implements IRouter { return wrapErrors(handler); } - private async handle

({ + private async handle< + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType | Type | Type + >({ routeSchemas, request, responseToolkit, @@ -208,10 +278,10 @@ export class Router implements IRouter { }: { request: Request; responseToolkit: ResponseToolkit; - handler: RequestHandlerEnhanced; + handler: RequestHandlerEnhanced; routeSchemas?: RouteSchemas; }) { - let kibanaRequest: KibanaRequest, TypeOf, TypeOf>; + let kibanaRequest: KibanaRequest, TypeOf, TypeOf, typeof request.method>; const hapiResponseAdapter = new HapiResponseAdapter(responseToolkit); try { kibanaRequest = KibanaRequest.from(request, routeSchemas); @@ -236,8 +306,9 @@ type WithoutHeadArgument = T extends (first: any, ...rest: infer Params) => i type RequestHandlerEnhanced< P extends ObjectType, Q extends ObjectType, - B extends ObjectType -> = WithoutHeadArgument>; + B extends ObjectType | Type | Type, + Method extends RouteMethod +> = WithoutHeadArgument>; /** * A function executed when route path matched requested resource path. @@ -272,8 +343,13 @@ type RequestHandlerEnhanced< * ``` * @public */ -export type RequestHandler

= ( +export type RequestHandler< + P extends ObjectType, + Q extends ObjectType, + B extends ObjectType | Type | Type, + Method extends RouteMethod = any +> = ( context: RequestHandlerContext, - request: KibanaRequest, TypeOf, TypeOf>, + request: KibanaRequest, TypeOf, TypeOf, Method>, response: KibanaResponseFactory ) => IKibanaResponse | Promise>; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 31dec2c9b96ff..a54ada233bbc9 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -45,6 +45,7 @@ import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plug import { ContextSetup } from './context'; import { IUiSettingsClient, UiSettingsServiceSetup } from './ui_settings'; import { SavedObjectsClientContract } from './saved_objects/types'; +import { SavedObjectsServiceSetup, SavedObjectsServiceStart } from './saved_objects'; export { bootstrap } from './bootstrap'; export { ConfigPath, ConfigService, EnvironmentMode, PackageInfo } from './config'; @@ -93,6 +94,7 @@ export { IsAuthenticated, KibanaRequest, KibanaRequestRoute, + KibanaRequestRouteOptions, IKibanaResponse, LifecycleResponseFactory, KnownHeaders, @@ -112,11 +114,16 @@ export { KibanaResponseFactory, RouteConfig, IRouter, + RouteRegistrar, RouteMethod, RouteConfigOptions, - RouteRegistrar, + RouteSchemas, + RouteConfigOptionsBody, + RouteContentType, + validBodyOutput, SessionStorage, SessionStorageCookieOptions, + SessionCookieValidationResult, SessionStorageFactory, } from './http'; export { Logger, LoggerFactory, LogMeta, LogRecord, LogLevel } from './logging'; @@ -143,6 +150,7 @@ export { SavedObjectsClientProviderOptions, SavedObjectsClientWrapperFactory, SavedObjectsClientWrapperOptions, + SavedObjectsClientFactory, SavedObjectsCreateOptions, SavedObjectsErrorHelpers, SavedObjectsExportOptions, @@ -164,7 +172,13 @@ export { SavedObjectsLegacyService, SavedObjectsUpdateOptions, SavedObjectsUpdateResponse, + SavedObjectsServiceStart, + SavedObjectsServiceSetup, SavedObjectsDeleteOptions, + ISavedObjectsRepository, + SavedObjectsRepository, + SavedObjectsDeleteByNamespaceOptions, + SavedObjectsIncrementCounterOptions, } from './saved_objects'; export { @@ -232,6 +246,8 @@ export interface CoreSetup { elasticsearch: ElasticsearchServiceSetup; /** {@link HttpServiceSetup} */ http: HttpServiceSetup; + /** {@link SavedObjectsServiceSetup} */ + savedObjects: SavedObjectsServiceSetup; /** {@link UiSettingsServiceSetup} */ uiSettings: UiSettingsServiceSetup; } @@ -241,6 +257,9 @@ export interface CoreSetup { * * @public */ -export interface CoreStart {} // eslint-disable-line @typescript-eslint/no-empty-interface +export interface CoreStart { + /** {@link SavedObjectsServiceStart} */ + savedObjects: SavedObjectsServiceStart; +} export { ContextSetup, PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId }; diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index 1330c5aee64fd..d1a65c6f3437e 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -21,7 +21,10 @@ import { InternalElasticsearchServiceSetup } from './elasticsearch'; import { InternalHttpServiceSetup } from './http'; import { InternalUiSettingsServiceSetup } from './ui_settings'; import { ContextSetup } from './context'; -import { SavedObjectsServiceStart } from './saved_objects'; +import { + InternalSavedObjectsServiceStart, + InternalSavedObjectsServiceSetup, +} from './saved_objects'; /** @internal */ export interface InternalCoreSetup { @@ -29,11 +32,12 @@ export interface InternalCoreSetup { http: InternalHttpServiceSetup; elasticsearch: InternalElasticsearchServiceSetup; uiSettings: InternalUiSettingsServiceSetup; + savedObjects: InternalSavedObjectsServiceSetup; } /** * @internal */ export interface InternalCoreStart { - savedObjects: SavedObjectsServiceStart; + savedObjects: InternalSavedObjectsServiceStart; } diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index 030caa8324521..286e1a0612c94 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -43,10 +43,9 @@ import { BasePathProxyServer } from '../http'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { DiscoveredPlugin } from '../plugins'; -import { KibanaMigrator } from '../saved_objects/migrations'; -import { ISavedObjectsClientProvider } from '../saved_objects'; import { httpServiceMock } from '../http/http_service.mock'; import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; +import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock'; const MockKbnServer: jest.Mock = KbnServer as any; @@ -79,7 +78,7 @@ beforeEach(() => { getAuthHeaders: () => undefined, } as any, }, - + savedObjects: savedObjectsServiceMock.createSetupContract(), plugins: { contracts: new Map([['plugin-id', 'plugin-value']]), uiPlugins: { @@ -94,10 +93,7 @@ beforeEach(() => { startDeps = { core: { - savedObjects: { - migrator: {} as KibanaMigrator, - clientProvider: {} as ISavedObjectsClientProvider, - }, + savedObjects: savedObjectsServiceMock.createStartContract(), plugins: { contracts: new Map() }, }, plugins: {}, @@ -128,6 +124,7 @@ describe('once LegacyService is set up with connection info', () => { configService: configService as any, }); + await legacyService.discoverPlugins(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -153,6 +150,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); + await legacyService.discoverPlugins(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -180,6 +178,7 @@ describe('once LegacyService is set up with connection info', () => { configService: configService as any, }); + await legacyService.discoverPlugins(); await legacyService.setup(setupDeps); await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot( `"something failed"` @@ -199,9 +198,12 @@ describe('once LegacyService is set up with connection info', () => { configService: configService as any, }); - await expect(legacyService.setup(setupDeps)).rejects.toThrowErrorMatchingInlineSnapshot( + await expect(legacyService.discoverPlugins()).rejects.toThrowErrorMatchingInlineSnapshot( `"something failed"` ); + await expect(legacyService.setup(setupDeps)).rejects.toThrowErrorMatchingInlineSnapshot( + `"Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()"` + ); await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot( `"Legacy service is not setup yet."` ); @@ -217,6 +219,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); + await legacyService.discoverPlugins(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -237,6 +240,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); + await legacyService.discoverPlugins(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -261,6 +265,7 @@ describe('once LegacyService is set up with connection info', () => { logger, configService: configService as any, }); + await legacyService.discoverPlugins(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); @@ -280,7 +285,7 @@ describe('once LegacyService is set up without connection info', () => { let legacyService: LegacyService; beforeEach(async () => { legacyService = new LegacyService({ coreId, env, logger, configService: configService as any }); - + await legacyService.discoverPlugins(); await legacyService.setup(setupDeps); await legacyService.start(startDeps); }); @@ -329,6 +334,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => { configService: configService as any, }); + await devClusterLegacyService.discoverPlugins(); await devClusterLegacyService.setup(setupDeps); await devClusterLegacyService.start(startDeps); @@ -350,6 +356,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => { configService: configService as any, }); + await devClusterLegacyService.discoverPlugins(); await devClusterLegacyService.setup(setupDeps); await devClusterLegacyService.start(startDeps); diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 99963ad9ce3e8..fd081b23a0ef2 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -74,14 +74,17 @@ export interface LegacyServiceStartDeps { } /** @internal */ -export interface LegacyServiceSetup { +export interface LegacyServiceDiscoverPlugins { pluginSpecs: LegacyPluginSpec[]; uiExports: SavedObjectsLegacyUiExports; pluginExtendedConfig: Config; } /** @internal */ -export class LegacyService implements CoreService { +export type ILegacyService = Pick; + +/** @internal */ +export class LegacyService implements CoreService { /** Symbol to represent the legacy platform as a fake "plugin". Used by the ContextService */ public readonly legacyId = Symbol(); private readonly log: Logger; @@ -111,9 +114,7 @@ export class LegacyService implements CoreService { .pipe(map(rawConfig => new HttpConfig(rawConfig, coreContext.env))); } - public async setup(setupDeps: LegacyServiceSetupDeps) { - this.setupDeps = setupDeps; - + public async discoverPlugins(): Promise { this.update$ = this.coreContext.configService.getConfig$().pipe( tap(config => { if (this.kbnServer !== undefined) { @@ -164,6 +165,16 @@ export class LegacyService implements CoreService { }; } + public async setup(setupDeps: LegacyServiceSetupDeps) { + this.log.debug('setting up legacy service'); + if (!this.legacyRawConfig || !this.legacyPlugins || !this.settings) { + throw new Error( + 'Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()' + ); + } + this.setupDeps = setupDeps; + } + public async start(startDeps: LegacyServiceStartDeps) { const { setupDeps } = this; if (!setupDeps || !this.legacyRawConfig || !this.legacyPlugins || !this.settings) { @@ -249,11 +260,19 @@ export class LegacyService implements CoreService { basePath: setupDeps.core.http.basePath, isTlsEnabled: setupDeps.core.http.isTlsEnabled, }, + savedObjects: { + setClientFactory: setupDeps.core.savedObjects.setClientFactory, + addClientWrapper: setupDeps.core.savedObjects.addClientWrapper, + createInternalRepository: setupDeps.core.savedObjects.createInternalRepository, + createScopedRepository: setupDeps.core.savedObjects.createScopedRepository, + }, uiSettings: { register: setupDeps.core.uiSettings.register, }, }; - const coreStart: CoreStart = {}; + const coreStart: CoreStart = { + savedObjects: { getScopedClient: startDeps.core.savedObjects.getScopedClient }, + }; // eslint-disable-next-line @typescript-eslint/no-var-requires const KbnServer = require('../../../legacy/server/kbn_server'); diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index b51d5302e3274..a811efdf4b1b9 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -22,7 +22,9 @@ import { loggingServiceMock } from './logging/logging_service.mock'; import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock'; import { httpServiceMock } from './http/http_service.mock'; import { contextServiceMock } from './context/context_service.mock'; +import { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock'; import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; +import { InternalCoreSetup, InternalCoreStart } from './internal_types'; export { httpServerMock } from './http/http_server.mocks'; export { sessionStorageMock } from './http/cookie_session_storage.mocks'; @@ -87,6 +89,7 @@ function createCoreSetupMock() { context: contextServiceMock.createSetupContract(), elasticsearch: elasticsearchServiceMock.createSetupContract(), http: httpMock, + savedObjects: savedObjectsServiceMock.createSetupContract(), uiSettings: uiSettingsMock, }; @@ -94,24 +97,35 @@ function createCoreSetupMock() { } function createCoreStartMock() { - const mock: MockedKeys = {}; + const mock: MockedKeys = { + savedObjects: savedObjectsServiceMock.createStartContract(), + }; return mock; } function createInternalCoreSetupMock() { - const setupDeps = { + const setupDeps: InternalCoreSetup = { context: contextServiceMock.createSetupContract(), elasticsearch: elasticsearchServiceMock.createSetupContract(), http: httpServiceMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), + savedObjects: savedObjectsServiceMock.createSetupContract(), }; return setupDeps; } +function createInternalCoreStartMock() { + const startDeps: InternalCoreStart = { + savedObjects: savedObjectsServiceMock.createStartContract(), + }; + return startDeps; +} + export const coreMock = { createSetup: createCoreSetupMock, createStart: createCoreStartMock, createInternalSetup: createInternalCoreSetupMock, + createInternalStart: createInternalCoreStartMock, createPluginInitializerContext: pluginInitializerContextMock, }; diff --git a/src/core/server/plugins/plugin.test.ts b/src/core/server/plugins/plugin.test.ts index 6aab03a01675d..10259b718577c 100644 --- a/src/core/server/plugins/plugin.test.ts +++ b/src/core/server/plugins/plugin.test.ts @@ -66,7 +66,9 @@ configService.atPath.mockReturnValue(new BehaviorSubject({ initialize: true })); let coreId: symbol; let env: Env; let coreContext: CoreContext; + const setupDeps = coreMock.createInternalSetup(); + beforeEach(() => { coreId = Symbol('core'); env = Env.createDefault(getEnvOptions()); diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 9885a572ad8c0..6edce1b2533cb 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -123,6 +123,12 @@ export function createPluginSetupContext( basePath: deps.http.basePath, isTlsEnabled: deps.http.isTlsEnabled, }, + savedObjects: { + setClientFactory: deps.savedObjects.setClientFactory, + addClientWrapper: deps.savedObjects.addClientWrapper, + createInternalRepository: deps.savedObjects.createInternalRepository, + createScopedRepository: deps.savedObjects.createScopedRepository, + }, uiSettings: { register: deps.uiSettings.register, }, @@ -146,5 +152,7 @@ export function createPluginStartContext( deps: PluginsServiceStartDeps, plugin: PluginWrapper ): CoreStart { - return {}; + return { + savedObjects: { getScopedClient: deps.savedObjects.getScopedClient }, + }; } diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts index 7e55faa43360e..df5473bc97d99 100644 --- a/src/core/server/plugins/plugins_service.test.ts +++ b/src/core/server/plugins/plugins_service.test.ts @@ -43,6 +43,7 @@ let configService: ConfigService; let coreId: symbol; let env: Env; let mockPluginSystem: jest.Mocked; + const setupDeps = coreMock.createInternalSetup(); const logger = loggingServiceMock.create(); diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index 4c73c2a304dc4..3f9999aad4ab9 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -28,7 +28,7 @@ import { PluginWrapper } from './plugin'; import { DiscoveredPlugin, PluginConfigDescriptor, PluginName, InternalPluginInfo } from './types'; import { PluginsConfig, PluginsConfigType } from './plugins_config'; import { PluginsSystem } from './plugins_system'; -import { InternalCoreSetup } from '../internal_types'; +import { InternalCoreSetup, InternalCoreStart } from '../internal_types'; import { IConfigService } from '../config'; import { pick } from '../../utils'; @@ -63,7 +63,7 @@ export interface PluginsServiceStart { export type PluginsServiceSetupDeps = InternalCoreSetup; /** @internal */ -export interface PluginsServiceStartDeps {} // eslint-disable-line @typescript-eslint/no-empty-interface +export type PluginsServiceStartDeps = InternalCoreStart; /** @internal */ export class PluginsService implements CoreService { diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts index 6f1788f717f61..18c04af3bb641 100644 --- a/src/core/server/plugins/plugins_system.test.ts +++ b/src/core/server/plugins/plugins_system.test.ts @@ -33,7 +33,6 @@ import { loggingServiceMock } from '../logging/logging_service.mock'; import { PluginWrapper } from './plugin'; import { PluginName } from './types'; import { PluginsSystem } from './plugins_system'; - import { coreMock } from '../mocks'; const logger = loggingServiceMock.create(); @@ -68,7 +67,9 @@ const configService = configServiceMock.create(); configService.atPath.mockReturnValue(new BehaviorSubject({ initialize: true })); let env: Env; let coreContext: CoreContext; + const setupDeps = coreMock.createInternalSetup(); +const startDeps = coreMock.createInternalStart(); beforeEach(() => { env = Env.createDefault(getEnvOptions()); @@ -249,7 +250,6 @@ test('correctly orders plugins and returns exposed values for "setup" and "start expect(plugin.setup).toHaveBeenCalledWith(setupContextMap.get(plugin.name), deps.setup); } - const startDeps = {}; expect([...(await pluginsSystem.startPlugins(startDeps))]).toMatchInlineSnapshot(` Array [ Array [ @@ -382,7 +382,7 @@ test('`uiPlugins` returns only ui plugin dependencies', async () => { test('can start without plugins', async () => { await pluginsSystem.setupPlugins(setupDeps); - const pluginsStart = await pluginsSystem.startPlugins({}); + const pluginsStart = await pluginsSystem.startPlugins(startDeps); expect(pluginsStart).toBeInstanceOf(Map); expect(pluginsStart.size).toBe(0); @@ -400,7 +400,7 @@ test('`startPlugins` only starts plugins that were setup', async () => { pluginsSystem.addPlugin(plugin); }); await pluginsSystem.setupPlugins(setupDeps); - const result = await pluginsSystem.startPlugins({}); + const result = await pluginsSystem.startPlugins(startDeps); expect([...result]).toMatchInlineSnapshot(` Array [ Array [ diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts index 76c62e0841bff..1100c18bcc72f 100644 --- a/src/core/server/saved_objects/index.ts +++ b/src/core/server/saved_objects/index.ts @@ -35,6 +35,18 @@ export { SavedObjectsSerializer, RawDoc as SavedObjectsRawDoc } from './serializ export { SavedObjectsMigrationLogger } from './migrations/core/migration_logger'; -export { SavedObjectsService, SavedObjectsServiceStart } from './saved_objects_service'; +export { + SavedObjectsService, + InternalSavedObjectsServiceStart, + SavedObjectsServiceStart, + SavedObjectsServiceSetup, + InternalSavedObjectsServiceSetup, +} from './saved_objects_service'; + +export { + ISavedObjectsRepository, + SavedObjectsIncrementCounterOptions, + SavedObjectsDeleteByNamespaceOptions, +} from './service/lib/repository'; export { config } from './saved_objects_config'; diff --git a/src/core/server/saved_objects/migrations/core/build_index_map.ts b/src/core/server/saved_objects/migrations/core/build_index_map.ts index d7a26b7728f44..3b7ed20d94646 100644 --- a/src/core/server/saved_objects/migrations/core/build_index_map.ts +++ b/src/core/server/saved_objects/migrations/core/build_index_map.ts @@ -39,6 +39,7 @@ export interface IndexMap { * This file contains logic to convert savedObjectSchemas into a dictonary of indexes and documents */ export function createIndexMap({ + /** @deprecated Remove once savedObjectsSchemas are exposed from Core */ config, kibanaIndexName, schema, diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts index 51551ae4887b5..b89abc596ad18 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts @@ -20,6 +20,7 @@ import _ from 'lodash'; import { KibanaMigratorOptions, KibanaMigrator } from './kibana_migrator'; import { loggingServiceMock } from '../../../logging/logging_service.mock'; +import { SavedObjectsSchema } from '../../schema'; describe('KibanaMigrator', () => { describe('getActiveMappings', () => { @@ -112,12 +113,12 @@ function mockOptions({ configValues }: { configValues?: any } = {}): KibanaMigra }, }, ], - savedObjectSchemas: { + savedObjectSchemas: new SavedObjectsSchema({ testtype2: { isNamespaceAgnostic: false, indexPattern: 'other-index', }, - }, + }), kibanaConfig: { enabled: true, index: '.my-index', diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts index 5bde5deec9382..1b01680c427ee 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts @@ -25,7 +25,7 @@ import { Logger } from 'src/core/server/logging'; import { KibanaConfigType } from 'src/core/server/kibana_config'; import { MappingProperties, SavedObjectsMapping, IndexMapping } from '../../mappings'; -import { SavedObjectsSchema, SavedObjectsSchemaDefinition } from '../../schema'; +import { SavedObjectsSchema } from '../../schema'; import { RawSavedObjectDoc, SavedObjectsSerializer } from '../../serialization'; import { docValidator, PropertyValidators } from '../../validation'; import { buildActiveMappings, CallCluster, IndexMigrator } from '../core'; @@ -47,7 +47,7 @@ export interface KibanaMigratorOptions { logger: Logger; savedObjectMappings: SavedObjectsMapping[]; savedObjectMigrations: MigrationDefinition; - savedObjectSchemas: SavedObjectsSchemaDefinition; + savedObjectSchemas: SavedObjectsSchema; savedObjectValidations: PropertyValidators; } @@ -87,7 +87,7 @@ export class KibanaMigrator { this.callCluster = callCluster; this.kibanaConfig = kibanaConfig; this.savedObjectsConfig = savedObjectsConfig; - this.schema = new SavedObjectsSchema(savedObjectSchemas); + this.schema = savedObjectSchemas; this.serializer = new SavedObjectsSerializer(this.schema); this.mappingProperties = mergeProperties(savedObjectMappings || []); this.log = logger; diff --git a/src/core/server/saved_objects/saved_objects_service.mock.ts b/src/core/server/saved_objects/saved_objects_service.mock.ts index 0a021ee97e26a..b2596146a02d4 100644 --- a/src/core/server/saved_objects/saved_objects_service.mock.ts +++ b/src/core/server/saved_objects/saved_objects_service.mock.ts @@ -17,21 +17,44 @@ * under the License. */ -import { SavedObjectsService, SavedObjectsServiceStart } from './saved_objects_service'; +import { + SavedObjectsService, + InternalSavedObjectsServiceSetup, + InternalSavedObjectsServiceStart, +} from './saved_objects_service'; import { mockKibanaMigrator } from './migrations/kibana/kibana_migrator.mock'; import { savedObjectsClientProviderMock } from './service/lib/scoped_client_provider.mock'; +import { savedObjectsRepositoryMock } from './service/lib/repository.mock'; +import { savedObjectsClientMock } from './service/saved_objects_client.mock'; type SavedObjectsServiceContract = PublicMethodsOf; const createStartContractMock = () => { - const startContract: jest.Mocked = { + const startContract: jest.Mocked = { clientProvider: savedObjectsClientProviderMock.create(), + getScopedClient: jest.fn(), migrator: mockKibanaMigrator.create(), }; return startContract; }; +const createSetupContractMock = () => { + const setupContract: jest.Mocked = { + getScopedClient: jest.fn(), + setClientFactory: jest.fn(), + addClientWrapper: jest.fn(), + createInternalRepository: jest.fn(), + createScopedRepository: jest.fn(), + }; + + setupContract.getScopedClient.mockReturnValue(savedObjectsClientMock.create()); + setupContract.createInternalRepository.mockReturnValue(savedObjectsRepositoryMock.create()); + setupContract.createScopedRepository.mockReturnValue(savedObjectsRepositoryMock.create()); + + return setupContract; +}; + const createsavedObjectsServiceMock = () => { const mocked: jest.Mocked = { setup: jest.fn(), @@ -39,7 +62,7 @@ const createsavedObjectsServiceMock = () => { stop: jest.fn(), }; - mocked.setup.mockResolvedValue({ clientProvider: savedObjectsClientProviderMock.create() }); + mocked.setup.mockResolvedValue(createSetupContractMock()); mocked.start.mockResolvedValue(createStartContractMock()); mocked.stop.mockResolvedValue(); return mocked; @@ -47,5 +70,6 @@ const createsavedObjectsServiceMock = () => { export const savedObjectsServiceMock = { create: createsavedObjectsServiceMock, + createSetupContract: createSetupContractMock, createStartContract: createStartContractMock, }; diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts index c31ad90011865..f58939c58e85e 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -27,7 +27,6 @@ import { of } from 'rxjs'; import * as legacyElasticsearch from 'elasticsearch'; import { Env } from '../config'; import { configServiceMock } from '../mocks'; -import { SavedObjectsClientProvider } from '.'; afterEach(() => { jest.clearAllMocks(); @@ -51,7 +50,7 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); const coreSetup = ({ elasticsearch: { adminClient$: of(clusterClient) }, - legacy: { uiExports: { savedObjectMappings: [] }, pluginExtendedConfig: {} }, + legacyPlugins: { uiExports: { savedObjectMappings: [] }, pluginExtendedConfig: {} }, } as unknown) as SavedObjectsSetupDeps; await soService.setup(coreSetup, 1); @@ -60,18 +59,6 @@ describe('SavedObjectsService', () => { 'success' ); }); - - it('resolves with clientProvider', async () => { - const coreContext = mockCoreContext.create(); - const soService = new SavedObjectsService(coreContext); - const coreSetup = ({ - elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) }, - legacy: { uiExports: {}, pluginExtendedConfig: {} }, - } as unknown) as SavedObjectsSetupDeps; - - const savedObjectsSetup = await soService.setup(coreSetup); - expect(savedObjectsSetup.clientProvider).toBeInstanceOf(SavedObjectsClientProvider); - }); }); describe('#start()', () => { @@ -82,7 +69,7 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); const coreSetup = ({ elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) }, - legacy: { uiExports: {}, pluginExtendedConfig: {} }, + legacyPlugins: { uiExports: {}, pluginExtendedConfig: {} }, } as unknown) as SavedObjectsSetupDeps; await soService.setup(coreSetup); @@ -96,7 +83,7 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); const coreSetup = ({ elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) }, - legacy: { uiExports: {}, pluginExtendedConfig: {} }, + legacyPlugins: { uiExports: {}, pluginExtendedConfig: {} }, } as unknown) as SavedObjectsSetupDeps; await soService.setup(coreSetup); @@ -110,7 +97,7 @@ describe('SavedObjectsService', () => { const soService = new SavedObjectsService(coreContext); const coreSetup = ({ elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) }, - legacy: { uiExports: {}, pluginExtendedConfig: {} }, + legacyPlugins: { uiExports: {}, pluginExtendedConfig: {} }, } as unknown) as SavedObjectsSetupDeps; await soService.setup(coreSetup); diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index 43c3afa3ed639..589cd9cce400f 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -22,40 +22,159 @@ import { first } from 'rxjs/operators'; import { SavedObjectsClient, SavedObjectsSchema, - SavedObjectsRepository, - SavedObjectsSerializer, SavedObjectsClientProvider, ISavedObjectsClientProvider, + SavedObjectsClientProviderOptions, } from './'; -import { getRootPropertiesObjects } from './mappings'; import { KibanaMigrator, IKibanaMigrator } from './migrations'; import { CoreContext } from '../core_context'; -import { LegacyServiceSetup } from '../legacy/legacy_service'; -import { ElasticsearchServiceSetup } from '../elasticsearch'; +import { LegacyServiceDiscoverPlugins } from '../legacy/legacy_service'; +import { ElasticsearchServiceSetup, APICaller } from '../elasticsearch'; import { KibanaConfigType } from '../kibana_config'; -import { retryCallCluster, migrationsRetryCallCluster } from '../elasticsearch/retry_call_cluster'; +import { migrationsRetryCallCluster } from '../elasticsearch/retry_call_cluster'; import { SavedObjectsConfigType } from './saved_objects_config'; import { KibanaRequest } from '../http'; +import { SavedObjectsClientContract } from './types'; +import { ISavedObjectsRepository } from './service/lib/repository'; +import { + SavedObjectsClientFactory, + SavedObjectsClientWrapperFactory, +} from './service/lib/scoped_client_provider'; +import { createRepository } from './service/lib/create_repository'; import { Logger } from '..'; /** + * Saved Objects is Kibana's data persisentence mechanism allowing plugins to + * use Elasticsearch for storing and querying state. The + * SavedObjectsServiceSetup API exposes methods for creating and registering + * Saved Object client wrappers. + * + * @remarks + * Note: The Saved Object setup API's should only be used for creating and + * registering client wrappers. Constructing a Saved Objects client or + * repository for use within your own plugin won't have any of the registered + * wrappers applied and is considered an anti-pattern. Use the Saved Objects + * client from the + * {@link SavedObjectsServiceStart | SavedObjectsServiceStart#getScopedClient } + * method or the {@link RequestHandlerContext | route handler context} instead. + * + * When plugins access the Saved Objects client, a new client is created using + * the factory provided to `setClientFactory` and wrapped by all wrappers + * registered through `addClientWrapper`. To create a factory or wrapper, + * plugins will have to construct a Saved Objects client. First create a + * repository by calling `scopedRepository` or `internalRepository` and then + * use this repository as the argument to the {@link SavedObjectsClient} + * constructor. + * + * @example + * import {SavedObjectsClient, CoreSetup} from 'src/core/server'; + * + * export class Plugin() { + * setup: (core: CoreSetup) => { + * core.savedObjects.setClientFactory(({request: KibanaRequest}) => { + * return new SavedObjectsClient(core.savedObjects.scopedRepository(request)); + * }) + * } + * } + * * @public */ export interface SavedObjectsServiceSetup { - clientProvider: ISavedObjectsClientProvider; + /** + * Set a default factory for creating Saved Objects clients. Only one client + * factory can be set, subsequent calls to this method will fail. + */ + setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void; + + /** + * Add a client wrapper with the given priority. + */ + addClientWrapper: ( + priority: number, + id: string, + factory: SavedObjectsClientWrapperFactory + ) => void; + + /** + * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that + * uses the credentials from the passed in request to authenticate with + * Elasticsearch. + * + * @remarks + * The repository should only be used for creating and registering a client + * factory or client wrapper. Using the repository directly for interacting + * with Saved Objects is an anti-pattern. Use the Saved Objects client from + * the + * {@link SavedObjectsServiceStart | SavedObjectsServiceStart#getScopedClient } + * method or the {@link RequestHandlerContext | route handler context} + * instead. + */ + createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; + + /** + * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that + * uses the internal Kibana user for authenticating with Elasticsearch. + * + * @remarks + * The repository should only be used for creating and registering a client + * factory or client wrapper. Using the repository directly for interacting + * with Saved Objects is an anti-pattern. Use the Saved Objects client from + * the + * {@link SavedObjectsServiceStart | SavedObjectsServiceStart#getScopedClient } + * method or the {@link RequestHandlerContext | route handler context} + * instead. + */ + createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; +} + +/** + * @internal + */ +export interface InternalSavedObjectsServiceSetup extends SavedObjectsServiceSetup { + getScopedClient: ( + req: KibanaRequest, + options?: SavedObjectsClientProviderOptions + ) => SavedObjectsClientContract; } /** + * Saved Objects is Kibana's data persisentence mechanism allowing plugins to + * use Elasticsearch for storing and querying state. The + * SavedObjectsServiceStart API provides a scoped Saved Objects client for + * interacting with Saved Objects. + * * @public */ export interface SavedObjectsServiceStart { + /** + * Creates a {@link SavedObjectsClientContract | Saved Objects client} that + * uses the credentials from the passed in request to authenticate with + * Elasticsearch. If other plugins have registered Saved Objects client + * wrappers, these will be applied to extend the functionality of the client. + * + * A client that is already scoped to the incoming request is also exposed + * from the route handler context see {@link RequestHandlerContext}. + */ + getScopedClient: ( + req: KibanaRequest, + options?: SavedObjectsClientProviderOptions + ) => SavedObjectsClientContract; +} + +export interface InternalSavedObjectsServiceStart extends SavedObjectsServiceStart { + /** + * @deprecated Exposed only for injecting into Legacy + */ migrator: IKibanaMigrator; + /** + * @deprecated Exposed only for injecting into Legacy + */ clientProvider: ISavedObjectsClientProvider; } /** @internal */ export interface SavedObjectsSetupDeps { - legacy: LegacyServiceSetup; + legacyPlugins: LegacyServiceDiscoverPlugins; elasticsearch: ElasticsearchServiceSetup; } @@ -64,7 +183,7 @@ export interface SavedObjectsSetupDeps { export interface SavedObjectsStartDeps {} export class SavedObjectsService - implements CoreService { + implements CoreService { private migrator: KibanaMigrator | undefined; private logger: Logger; private clientProvider: ISavedObjectsClientProvider | undefined; @@ -74,19 +193,21 @@ export class SavedObjectsService } public async setup( - coreSetup: SavedObjectsSetupDeps, + setupDeps: SavedObjectsSetupDeps, migrationsRetryDelay?: number - ): Promise { + ): Promise { this.logger.debug('Setting up SavedObjects service'); const { - savedObjectSchemas, + savedObjectSchemas: savedObjectsSchemasDefinition, savedObjectMappings, savedObjectMigrations, savedObjectValidations, - } = await coreSetup.legacy.uiExports; + } = setupDeps.legacyPlugins.uiExports; + + const savedObjectSchemas = new SavedObjectsSchema(savedObjectsSchemasDefinition); - const adminClient = await coreSetup.elasticsearch.adminClient$.pipe(first()).toPromise(); + const adminClient = await setupDeps.elasticsearch.adminClient$.pipe(first()).toPromise(); const kibanaConfig = await this.coreContext.configService .atPath('kibana') @@ -105,7 +226,7 @@ export class SavedObjectsService savedObjectValidations, logger: this.coreContext.logger.get('migrations'), kibanaVersion: this.coreContext.env.packageInfo.version, - config: coreSetup.legacy.pluginExtendedConfig, + config: setupDeps.legacyPlugins.pluginExtendedConfig, savedObjectsConfig, kibanaConfig, callCluster: migrationsRetryCallCluster( @@ -115,35 +236,36 @@ export class SavedObjectsService ), })); - const mappings = this.migrator.getActiveMappings(); - const allTypes = Object.keys(getRootPropertiesObjects(mappings)); - const schema = new SavedObjectsSchema(savedObjectSchemas); - const serializer = new SavedObjectsSerializer(schema); - const visibleTypes = allTypes.filter(type => !schema.isHiddenType(type)); + const createSORepository = (callCluster: APICaller, extraTypes: string[] = []) => { + return createRepository( + migrator, + savedObjectSchemas, + setupDeps.legacyPlugins.pluginExtendedConfig, + kibanaConfig.index, + callCluster, + extraTypes + ); + }; this.clientProvider = new SavedObjectsClientProvider({ defaultClientFactory({ request }) { - const repository = new SavedObjectsRepository({ - index: kibanaConfig.index, - config: coreSetup.legacy.pluginExtendedConfig, - migrator, - mappings, - schema, - serializer, - allowedTypes: visibleTypes, - callCluster: retryCallCluster(adminClient.asScoped(request).callAsCurrentUser), - }); - + const repository = createSORepository(adminClient.asScoped(request).callAsCurrentUser); return new SavedObjectsClient(repository); }, }); return { - clientProvider: this.clientProvider, + getScopedClient: this.clientProvider.getClient.bind(this.clientProvider), + setClientFactory: this.clientProvider.setClientFactory.bind(this.clientProvider), + addClientWrapper: this.clientProvider.addClientWrapperFactory.bind(this.clientProvider), + createInternalRepository: (extraTypes?: string[]) => + createSORepository(adminClient.callAsInternalUser, extraTypes), + createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => + createSORepository(adminClient.asScoped(req).callAsCurrentUser, extraTypes), }; } - public async start(core: SavedObjectsStartDeps): Promise { + public async start(core: SavedObjectsStartDeps): Promise { if (!this.clientProvider) { throw new Error('#setup() needs to be run first'); } @@ -171,6 +293,7 @@ export class SavedObjectsService return { migrator: this.migrator!, clientProvider: this.clientProvider, + getScopedClient: this.clientProvider.getClient.bind(this.clientProvider), }; } diff --git a/src/core/server/saved_objects/schema/schema.ts b/src/core/server/saved_objects/schema/schema.ts index 06d29bf7dcf32..6be5ca9bfce60 100644 --- a/src/core/server/saved_objects/schema/schema.ts +++ b/src/core/server/saved_objects/schema/schema.ts @@ -46,6 +46,7 @@ export class SavedObjectsSchema { return false; } + // TODO: Remove dependency on config when we move SavedObjectsSchema to NP public getIndexForType(config: Config, type: string): string | undefined { if (this.definition != null && this.definition.hasOwnProperty(type)) { const { indexPattern } = this.definition[type]; diff --git a/src/core/server/saved_objects/service/index.ts b/src/core/server/saved_objects/service/index.ts index cf0769fced460..f50ee1759dad7 100644 --- a/src/core/server/saved_objects/service/index.ts +++ b/src/core/server/saved_objects/service/index.ts @@ -58,6 +58,7 @@ export { SavedObjectsClientWrapperFactory, SavedObjectsClientWrapperOptions, SavedObjectsErrorHelpers, + SavedObjectsClientFactory, } from './lib'; export * from './saved_objects_client'; diff --git a/src/core/server/saved_objects/service/lib/create_repository.test.ts b/src/core/server/saved_objects/service/lib/create_repository.test.ts new file mode 100644 index 0000000000000..d40a5d04dcd8a --- /dev/null +++ b/src/core/server/saved_objects/service/lib/create_repository.test.ts @@ -0,0 +1,119 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { SavedObjectsRepository } from './repository'; +import { mockKibanaMigrator } from '../../migrations/kibana/kibana_migrator.mock'; +import { SavedObjectsSchema } from '../../schema'; +import { KibanaMigrator } from '../../migrations'; +import { Config } from 'src/core/server/config'; +import { createRepository } from './create_repository'; +jest.mock('./repository'); + +describe('#createRepository', () => { + const callAdminCluster = jest.fn(); + const schema = new SavedObjectsSchema({ + nsAgnosticType: { isNamespaceAgnostic: true }, + nsType: { indexPattern: 'beats', isNamespaceAgnostic: false }, + hiddenType: { isNamespaceAgnostic: true, hidden: true }, + }); + const mappings = [ + { + pluginId: 'testplugin', + properties: { + nsAgnosticType: { + properties: { + name: { type: 'keyword' }, + }, + }, + nsType: { + properties: { + name: { type: 'keyword' }, + }, + }, + hiddenType: { + properties: { + name: { type: 'keyword' }, + }, + }, + }, + }, + ]; + const migrator = mockKibanaMigrator.create({ savedObjectMappings: mappings }); + const RepositoryConstructor = (SavedObjectsRepository as unknown) as jest.Mock< + SavedObjectsRepository + >; + + beforeEach(() => { + RepositoryConstructor.mockClear(); + }); + + it('should not allow a repository with an undefined type', () => { + try { + createRepository( + (migrator as unknown) as KibanaMigrator, + schema, + {} as Config, + '.kibana-test', + callAdminCluster, + ['unMappedType1', 'unmappedType2'] + ); + } catch (e) { + expect(e).toMatchInlineSnapshot( + `[Error: Missing mappings for saved objects types: 'unMappedType1, unmappedType2']` + ); + } + }); + + it('should create a repository without hidden types', () => { + const repository = createRepository( + (migrator as unknown) as KibanaMigrator, + schema, + {} as Config, + '.kibana-test', + callAdminCluster + ); + expect(repository).toBeDefined(); + expect(RepositoryConstructor.mock.calls[0][0].allowedTypes).toMatchInlineSnapshot(` + Array [ + "config", + "nsAgnosticType", + "nsType", + ] + `); + }); + + it('should create a repository with a unique list of hidden types', () => { + const repository = createRepository( + (migrator as unknown) as KibanaMigrator, + schema, + {} as Config, + '.kibana-test', + callAdminCluster, + ['hiddenType', 'hiddenType', 'hiddenType'] + ); + expect(repository).toBeDefined(); + expect(RepositoryConstructor.mock.calls[0][0].allowedTypes).toMatchInlineSnapshot(` + Array [ + "config", + "nsAgnosticType", + "nsType", + "hiddenType", + ] + `); + }); +}); diff --git a/src/core/server/saved_objects/service/lib/create_repository.ts b/src/core/server/saved_objects/service/lib/create_repository.ts new file mode 100644 index 0000000000000..a0e920a95c2c6 --- /dev/null +++ b/src/core/server/saved_objects/service/lib/create_repository.ts @@ -0,0 +1,61 @@ +/* + * 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 { APICaller } from 'src/core/server/elasticsearch'; +import { Config } from 'src/core/server/config'; +import { retryCallCluster } from '../../../elasticsearch/retry_call_cluster'; +import { KibanaMigrator } from '../../migrations'; +import { SavedObjectsSchema } from '../../schema'; +import { getRootPropertiesObjects } from '../../mappings'; +import { SavedObjectsSerializer } from '../../serialization'; +import { SavedObjectsRepository } from '.'; + +export const createRepository = ( + migrator: KibanaMigrator, + schema: SavedObjectsSchema, + config: Config, + indexName: string, + callCluster: APICaller, + extraTypes: string[] = [] +) => { + const mappings = migrator.getActiveMappings(); + const allTypes = Object.keys(getRootPropertiesObjects(mappings)); + const serializer = new SavedObjectsSerializer(schema); + const visibleTypes = allTypes.filter(type => !schema.isHiddenType(type)); + + const missingTypeMappings = extraTypes.filter(type => !allTypes.includes(type)); + if (missingTypeMappings.length > 0) { + throw new Error( + `Missing mappings for saved objects types: '${missingTypeMappings.join(', ')}'` + ); + } + + const allowedTypes = [...new Set(visibleTypes.concat(extraTypes))]; + + return new SavedObjectsRepository({ + index: indexName, + config, + migrator, + mappings, + schema, + serializer, + allowedTypes, + callCluster: retryCallCluster(callCluster), + }); +}; 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 4c4f321695d70..13a132ab9dd67 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 e331d3eff990f..3cf499de541ee 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/index.ts b/src/core/server/saved_objects/service/lib/index.ts index 4bc159e17ec0f..afac50a680ed5 100644 --- a/src/core/server/saved_objects/service/lib/index.ts +++ b/src/core/server/saved_objects/service/lib/index.ts @@ -17,13 +17,19 @@ * under the License. */ -export { SavedObjectsRepository, SavedObjectsRepositoryOptions } from './repository'; +export { + ISavedObjectsRepository, + SavedObjectsRepository, + SavedObjectsRepositoryOptions, +} from './repository'; + export { SavedObjectsClientWrapperFactory, SavedObjectsClientWrapperOptions, ISavedObjectsClientProvider, SavedObjectsClientProvider, SavedObjectsClientProviderOptions, + SavedObjectsClientFactory, } from './scoped_client_provider'; export { SavedObjectsErrorHelpers } from './errors'; diff --git a/src/core/server/saved_objects/service/lib/repository.mock.ts b/src/core/server/saved_objects/service/lib/repository.mock.ts new file mode 100644 index 0000000000000..e69c0ff37d1be --- /dev/null +++ b/src/core/server/saved_objects/service/lib/repository.mock.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ISavedObjectsRepository } from './repository'; + +const create = (): jest.Mocked => ({ + create: jest.fn(), + bulkCreate: jest.fn(), + bulkUpdate: jest.fn(), + delete: jest.fn(), + bulkGet: jest.fn(), + find: jest.fn(), + get: jest.fn(), + update: jest.fn(), + deleteByNamespace: jest.fn(), + incrementCounter: jest.fn(), +}); + +export const savedObjectsRepositoryMock = { create }; 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 79a3e573ab98c..07ad3494ab78c 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -16,14 +16,11 @@ * specific language governing permissions and limitations * under the License. */ - -import { delay } from 'bluebird'; import _ from 'lodash'; import { SavedObjectsRepository } from './repository'; import * as getSearchDslNS from './search_dsl/search_dsl'; import { SavedObjectsErrorHelpers } from './errors'; -import * as legacyElasticsearch from 'elasticsearch'; import { SavedObjectsSchema } from '../../schema'; import { SavedObjectsSerializer } from '../../serialization'; import { getRootPropertiesObjects } from '../../mappings/lib/get_root_properties_objects'; @@ -36,7 +33,6 @@ jest.mock('./search_dsl/search_dsl', () => ({ getSearchDsl: jest.fn() })); describe('SavedObjectsRepository', () => { let callAdminCluster; - let onBeforeWrite; let savedObjectsRepository; let migrator; const mockTimestamp = '2017-08-14T15:49:14.886Z'; @@ -254,7 +250,6 @@ describe('SavedObjectsRepository', () => { beforeEach(() => { callAdminCluster = jest.fn(); - onBeforeWrite = jest.fn(); migrator = { migrateDocument: jest.fn(doc => doc), runMigrations: async () => ({ status: 'skipped' }), @@ -272,7 +267,6 @@ describe('SavedObjectsRepository', () => { schema, serializer, allowedTypes, - onBeforeWrite, }); savedObjectsRepository._getCurrentTime = jest.fn(() => mockTimestamp); @@ -350,7 +344,6 @@ describe('SavedObjectsRepository', () => { expect(callAdminCluster).toHaveBeenCalledTimes(1); expect(callAdminCluster).toHaveBeenCalledWith('index', expect.any(Object)); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('should use default index', async () => { @@ -359,8 +352,8 @@ describe('SavedObjectsRepository', () => { title: 'Logstash', }); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); - expect(onBeforeWrite).toHaveBeenCalledWith( + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster).toHaveBeenCalledWith( 'index', expect.objectContaining({ index: '.kibana-test', @@ -374,8 +367,8 @@ describe('SavedObjectsRepository', () => { title: 'Logstash', }); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); - expect(onBeforeWrite).toHaveBeenCalledWith( + expect(callAdminCluster).toHaveBeenCalledTimes(1); + expect(callAdminCluster).toHaveBeenCalledWith( 'index', expect.objectContaining({ index: 'beats', @@ -447,7 +440,6 @@ describe('SavedObjectsRepository', () => { expect(callAdminCluster).toHaveBeenCalledTimes(1); expect(callAdminCluster).toHaveBeenCalledWith('create', expect.any(Object)); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('allows for id to be provided', async () => { @@ -466,8 +458,6 @@ describe('SavedObjectsRepository', () => { id: 'index-pattern:logstash-*', }) ); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('self-generates an ID', async () => { @@ -482,8 +472,6 @@ describe('SavedObjectsRepository', () => { id: expect.objectContaining(/index-pattern:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/), }) ); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('prepends namespace to the id and adds namespace to body when providing namespace for namespaced type', async () => { @@ -510,7 +498,6 @@ describe('SavedObjectsRepository', () => { }), }) ); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => { @@ -535,7 +522,6 @@ describe('SavedObjectsRepository', () => { }), }) ); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => { @@ -561,7 +547,6 @@ describe('SavedObjectsRepository', () => { }), }) ); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('defaults to empty references array if none are provided', async () => { @@ -658,8 +643,6 @@ describe('SavedObjectsRepository', () => { references: [{ name: 'ref_0', type: 'test', id: '2' }], }, ]); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('defaults to a refresh setting of `wait_for`', async () => { @@ -821,10 +804,7 @@ describe('SavedObjectsRepository', () => { }) ); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); - callAdminCluster.mockReset(); - onBeforeWrite.mockReset(); callAdminCluster.mockReturnValue({ items: [{ create: { type: 'foo', id: 'bar', _primary_term: 1, _seq_no: 1 } }], @@ -844,8 +824,6 @@ describe('SavedObjectsRepository', () => { ], }) ); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('mockReturnValue document errors', async () => { @@ -997,7 +975,6 @@ describe('SavedObjectsRepository', () => { ], }) ); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => { @@ -1044,7 +1021,6 @@ describe('SavedObjectsRepository', () => { ], }) ); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => { @@ -1072,7 +1048,6 @@ describe('SavedObjectsRepository', () => { ], }) ); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('should return objects in the same order regardless of type', () => { }); @@ -1116,8 +1091,6 @@ describe('SavedObjectsRepository', () => { index: '.kibana-test', ignore: [404], }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id when providing no namespace for namespaced type`, async () => { @@ -1131,8 +1104,6 @@ describe('SavedObjectsRepository', () => { index: '.kibana-test', ignore: [404], }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id when providing namespace for namespace agnostic type`, async () => { @@ -1148,8 +1119,6 @@ describe('SavedObjectsRepository', () => { index: '.kibana-test', ignore: [404], }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('defaults to a refresh setting of `wait_for`', async () => { @@ -1180,7 +1149,6 @@ describe('SavedObjectsRepository', () => { callAdminCluster.mockReturnValue(deleteByQueryResults); expect(savedObjectsRepository.deleteByNamespace()).rejects.toThrowErrorMatchingSnapshot(); expect(callAdminCluster).not.toHaveBeenCalled(); - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('requires namespace to be a string', async () => { @@ -1189,7 +1157,6 @@ describe('SavedObjectsRepository', () => { savedObjectsRepository.deleteByNamespace(['namespace-1', 'namespace-2']) ).rejects.toThrowErrorMatchingSnapshot(); expect(callAdminCluster).not.toHaveBeenCalled(); - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('constructs a deleteByQuery call using all types that are namespace aware', async () => { @@ -1198,7 +1165,6 @@ describe('SavedObjectsRepository', () => { expect(result).toEqual(deleteByQueryResults); expect(callAdminCluster).toHaveBeenCalledTimes(1); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, schema, { namespace: 'my-namespace', @@ -1247,7 +1213,6 @@ describe('SavedObjectsRepository', () => { it('requires type to be defined', async () => { await expect(savedObjectsRepository.find({})).rejects.toThrow(/options\.type must be/); expect(callAdminCluster).not.toHaveBeenCalled(); - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('requires searchFields be an array if defined', async () => { @@ -1257,7 +1222,6 @@ describe('SavedObjectsRepository', () => { throw new Error('expected find() to reject'); } catch (error) { expect(callAdminCluster).not.toHaveBeenCalled(); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(error.message).toMatch('must be an array'); } }); @@ -1269,7 +1233,6 @@ describe('SavedObjectsRepository', () => { throw new Error('expected find() to reject'); } catch (error) { expect(callAdminCluster).not.toHaveBeenCalled(); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(error.message).toMatch('must be an array'); } }); @@ -1289,8 +1252,7 @@ describe('SavedObjectsRepository', () => { type: 'foo', id: '1', }, - indexPattern: undefined, - kueryNode: null, + kueryNode: undefined, }; await savedObjectsRepository.find(relevantOpts); @@ -1372,7 +1334,6 @@ describe('SavedObjectsRepository', () => { getSearchDslNS.getSearchDsl.mockReturnValue({ query: 1, aggregations: 2 }); await savedObjectsRepository.find({ type: 'foo' }); expect(callAdminCluster).toHaveBeenCalledTimes(1); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(callAdminCluster).toHaveBeenCalledWith( 'search', expect.objectContaining({ @@ -1441,8 +1402,6 @@ describe('SavedObjectsRepository', () => { from: 50, }) ); - - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('can filter by fields', async () => { @@ -1464,8 +1423,6 @@ describe('SavedObjectsRepository', () => { ], }) ); - - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('should set rest_total_hits_as_int to true on a request', async () => { @@ -1517,7 +1474,6 @@ describe('SavedObjectsRepository', () => { it('formats Elasticsearch response when there is no namespace', async () => { callAdminCluster.mockResolvedValue(noNamespaceResult); const response = await savedObjectsRepository.get('index-pattern', 'logstash-*'); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(response).toEqual({ id: 'logstash-*', type: 'index-pattern', @@ -1533,7 +1489,6 @@ describe('SavedObjectsRepository', () => { it('formats Elasticsearch response when there are namespaces', async () => { callAdminCluster.mockResolvedValue(namespacedResult); const response = await savedObjectsRepository.get('index-pattern', 'logstash-*'); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(response).toEqual({ id: 'logstash-*', type: 'index-pattern', @@ -1552,7 +1507,6 @@ describe('SavedObjectsRepository', () => { namespace: 'foo-namespace', }); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(callAdminCluster).toHaveBeenCalledTimes(1); expect(callAdminCluster).toHaveBeenCalledWith( expect.any(String), @@ -1566,7 +1520,6 @@ describe('SavedObjectsRepository', () => { callAdminCluster.mockResolvedValue(noNamespaceResult); await savedObjectsRepository.get('index-pattern', 'logstash-*'); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(callAdminCluster).toHaveBeenCalledTimes(1); expect(callAdminCluster).toHaveBeenCalledWith( expect.any(String), @@ -1582,7 +1535,6 @@ describe('SavedObjectsRepository', () => { namespace: 'foo-namespace', }); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(callAdminCluster).toHaveBeenCalledTimes(1); expect(callAdminCluster).toHaveBeenCalledWith( expect.any(String), @@ -1631,8 +1583,6 @@ describe('SavedObjectsRepository', () => { }, }) ); - - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('prepends namespace and type appropriately to id when getting objects when there is a namespace', async () => { @@ -1662,8 +1612,6 @@ describe('SavedObjectsRepository', () => { }, }) ); - - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('mockReturnValue early for empty objects argument', async () => { @@ -1673,7 +1621,6 @@ describe('SavedObjectsRepository', () => { expect(response.saved_objects).toHaveLength(0); expect(callAdminCluster).not.toHaveBeenCalled(); - expect(onBeforeWrite).not.toHaveBeenCalled(); }); it('handles missing ids gracefully', async () => { @@ -1724,7 +1671,6 @@ describe('SavedObjectsRepository', () => { { id: 'bad', type: 'config' }, ]); - expect(onBeforeWrite).not.toHaveBeenCalled(); expect(callAdminCluster).toHaveBeenCalledTimes(1); expect(savedObjects).toHaveLength(2); @@ -1992,8 +1938,6 @@ describe('SavedObjectsRepository', () => { refresh: 'wait_for', index: '.kibana-test', }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => { @@ -2034,8 +1978,6 @@ describe('SavedObjectsRepository', () => { refresh: 'wait_for', index: '.kibana-test', }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => { @@ -2077,8 +2019,6 @@ describe('SavedObjectsRepository', () => { refresh: 'wait_for', index: '.kibana-test', }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('defaults to a refresh setting of `wait_for`', async () => { @@ -2505,8 +2445,6 @@ describe('SavedObjectsRepository', () => { }, ], }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => { @@ -2563,8 +2501,6 @@ describe('SavedObjectsRepository', () => { }, ], }); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => { @@ -2724,8 +2660,6 @@ describe('SavedObjectsRepository', () => { expect(requestDoc.body.script.params.type).toBe('config'); expect(requestDoc.body.upsert.type).toBe('config'); expect(requestDoc).toHaveProperty('body.upsert.config'); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => { @@ -2738,8 +2672,6 @@ describe('SavedObjectsRepository', () => { expect(requestDoc.body.script.params.type).toBe('config'); expect(requestDoc.body.upsert.type).toBe('config'); expect(requestDoc).toHaveProperty('body.upsert.config'); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => { @@ -2770,8 +2702,6 @@ describe('SavedObjectsRepository', () => { expect(requestDoc.body.script.params.type).toBe('globaltype'); expect(requestDoc.body.upsert.type).toBe('globaltype'); expect(requestDoc).toHaveProperty('body.upsert.globaltype'); - - expect(onBeforeWrite).toHaveBeenCalledTimes(1); }); it('should assert that the "type" and "counterFieldName" arguments are strings', () => { @@ -2820,39 +2750,6 @@ describe('SavedObjectsRepository', () => { }); }); - describe('onBeforeWrite', () => { - it('blocks calls to callCluster of requests', async () => { - onBeforeWrite.mockReturnValue(delay(500)); - callAdminCluster.mockReturnValue({ result: 'deleted', found: true }); - - const deletePromise = savedObjectsRepository.delete('foo', 'id'); - await delay(100); - expect(onBeforeWrite).toHaveBeenCalledTimes(1); - expect(callAdminCluster).not.toHaveBeenCalled(); - await deletePromise; - expect(onBeforeWrite).toHaveBeenCalledTimes(1); - expect(callAdminCluster).toHaveBeenCalledTimes(1); - }); - - it('can throw es errors and have them decorated as SavedObjectsClient errors', async () => { - expect.assertions(4); - - const es401 = new legacyElasticsearch.errors[401](); - expect(SavedObjectsErrorHelpers.isNotAuthorizedError(es401)).toBe(false); - onBeforeWrite.mockImplementation(() => { - throw es401; - }); - - try { - await savedObjectsRepository.delete('foo', 'id'); - } catch (error) { - expect(onBeforeWrite).toHaveBeenCalledTimes(1); - expect(error).toBe(es401); - expect(SavedObjectsErrorHelpers.isNotAuthorizedError(error)).toBe(true); - } - }); - }); - describe('types on custom index', () => { it('should error when attempting to \'update\' an unsupported type', async () => { await expect( diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 51d4a8ad50ad6..f9e48aba5a70e 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -19,7 +19,6 @@ import { omit } from 'lodash'; import { CallCluster } from 'src/legacy/core_plugins/elasticsearch'; - import { getRootPropertiesObjects, IndexMapping } from '../../mappings'; import { getSearchDsl } from './search_dsl'; import { includedFields } from './included_fields'; @@ -42,7 +41,6 @@ import { SavedObjectsBulkUpdateObject, SavedObjectsBulkUpdateOptions, SavedObjectsDeleteOptions, - SavedObjectsDeleteByNamespaceOptions, } from '../saved_objects_client'; import { SavedObject, @@ -75,6 +73,7 @@ const isLeft = (either: Either): either is Left => { export interface SavedObjectsRepositoryOptions { index: string; + /** @deprecated Will be removed once SavedObjectsSchema is exposed from Core */ config: Config; mappings: IndexMapping; callCluster: CallCluster; @@ -82,17 +81,38 @@ export interface SavedObjectsRepositoryOptions { serializer: SavedObjectsSerializer; migrator: KibanaMigrator; allowedTypes: string[]; - onBeforeWrite?: (...args: Parameters) => Promise; } -export interface IncrementCounterOptions extends SavedObjectsBaseOptions { +/** + * @public + */ +export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions { migrationVersion?: SavedObjectsMigrationVersion; /** The Elasticsearch Refresh setting for this operation */ refresh?: MutatingOperationRefreshSetting; } +/** + * + * @public + */ +export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions { + /** The Elasticsearch Refresh setting for this operation */ + refresh?: MutatingOperationRefreshSetting; +} + const DEFAULT_REFRESH_SETTING = 'wait_for'; +/** + * See {@link SavedObjectsRepository} + * + * @public + */ +export type ISavedObjectsRepository = Pick; + +/** + * @public + */ export class SavedObjectsRepository { private _migrator: KibanaMigrator; private _index: string; @@ -100,10 +120,10 @@ export class SavedObjectsRepository { private _mappings: IndexMapping; private _schema: SavedObjectsSchema; private _allowedTypes: string[]; - private _onBeforeWrite: (...args: Parameters) => Promise; private _unwrappedCallCluster: CallCluster; private _serializer: SavedObjectsSerializer; + /** @internal */ constructor(options: SavedObjectsRepositoryOptions) { const { index, @@ -114,7 +134,6 @@ export class SavedObjectsRepository { serializer, migrator, allowedTypes = [], - onBeforeWrite = () => Promise.resolve(), } = options; // It's important that we migrate documents / mark them as up-to-date @@ -134,8 +153,6 @@ export class SavedObjectsRepository { } this._allowedTypes = allowedTypes; - this._onBeforeWrite = onBeforeWrite; - this._unwrappedCallCluster = async (...args: Parameters) => { await migrator.runMigrations(); return callCluster(...args); @@ -448,11 +465,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); @@ -805,7 +822,7 @@ export class SavedObjectsRepository { type: string, id: string, counterFieldName: string, - options: IncrementCounterOptions = {} + options: SavedObjectsIncrementCounterOptions = {} ) { if (typeof type !== 'string') { throw new Error('"type" argument must be a string'); @@ -871,7 +888,6 @@ export class SavedObjectsRepository { private async _writeToCluster(...args: Parameters) { try { - await this._onBeforeWrite(...args); return await this._callCluster(...args); } catch (err) { throw decorateEsError(err); diff --git a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts index 87607acd94fc4..0b67727455333 100644 --- a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts +++ b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts @@ -55,8 +55,7 @@ export interface SavedObjectsClientProviderOptions { } /** - * @public - * See {@link SavedObjectsClientProvider} + * @internal */ export type ISavedObjectsClientProvider = Pick< SavedObjectsClientProvider, @@ -66,6 +65,8 @@ export type ISavedObjectsClientProvider = Pick< /** * Provider for the Scoped Saved Objects Client. * + * @internal + * * @internalRemarks Because `getClient` is synchronous the Client Provider does * not support creating factories that react to new ES clients emitted from * elasticsearch.adminClient$. The Client Provider therefore doesn't support 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 bee35b899d83c..cfeb258c2f03b 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 868ca51a76eab..f2bbc3ef564a1 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/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index 550e8a1de0d80..b0b2633646e10 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -17,7 +17,7 @@ * under the License. */ -import { SavedObjectsRepository } from './lib'; +import { ISavedObjectsRepository } from './lib'; import { SavedObject, SavedObjectAttributes, @@ -126,15 +126,6 @@ export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions { refresh?: MutatingOperationRefreshSetting; } -/** - * - * @public - */ -export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions { - /** The Elasticsearch Refresh setting for this operation */ - refresh?: MutatingOperationRefreshSetting; -} - /** * * @public @@ -180,9 +171,10 @@ export class SavedObjectsClient { public static errors = SavedObjectsErrorHelpers; public errors = SavedObjectsErrorHelpers; - private _repository: SavedObjectsRepository; + private _repository: ISavedObjectsRepository; - constructor(repository: SavedObjectsRepository) { + /** @internal */ + constructor(repository: ISavedObjectsRepository) { this._repository = repository; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index d6cfa54397565..25ca8ade77aca 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -449,11 +449,11 @@ export interface AuthToolkit { export class BasePath { // @internal constructor(serverBasePath?: string); - get: (request: KibanaRequest | LegacyRequest) => string; + get: (request: KibanaRequest | LegacyRequest) => string; prepend: (path: string) => string; remove: (path: string) => string; readonly serverBasePath: string; - set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; + set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; } // Warning: (ae-forgotten-export) The symbol "BootstrapArgs" needs to be exported by the entry point index.d.ts @@ -512,11 +512,15 @@ export interface CoreSetup { // (undocumented) http: HttpServiceSetup; // (undocumented) + savedObjects: SavedObjectsServiceSetup; + // (undocumented) uiSettings: UiSettingsServiceSetup; } // @public export interface CoreStart { + // (undocumented) + savedObjects: SavedObjectsServiceStart; } // @public @@ -714,21 +718,25 @@ export interface IndexSettingsDeprecationInfo { // @public export interface IRouter { - delete: RouteRegistrar; - get: RouteRegistrar; + delete: RouteRegistrar<'delete'>; + get: RouteRegistrar<'get'>; // Warning: (ae-forgotten-export) The symbol "RouterRoute" needs to be exported by the entry point index.d.ts // // @internal getRoutes: () => RouterRoute[]; handleLegacyErrors:

(handler: RequestHandler) => RequestHandler; - post: RouteRegistrar; - put: RouteRegistrar; + patch: RouteRegistrar<'patch'>; + post: RouteRegistrar<'post'>; + put: RouteRegistrar<'put'>; routerPath: string; } // @public export type IsAuthenticated = (request: KibanaRequest | LegacyRequest) => boolean; +// @public +export type ISavedObjectsRepository = Pick; + // @public export type IScopedClusterClient = Pick; @@ -746,37 +754,38 @@ export interface IUiSettingsClient { } // @public -export class KibanaRequest { +export class KibanaRequest { // @internal (undocumented) protected readonly [requestSymbol]: Request; constructor(request: Request, params: Params, query: Query, body: Body, withoutSecretHeaders: boolean); // (undocumented) readonly body: Body; - // Warning: (ae-forgotten-export) The symbol "RouteSchemas" needs to be exported by the entry point index.d.ts - // // @internal - static from

(req: Request, routeSchemas?: RouteSchemas, withoutSecretHeaders?: boolean): KibanaRequest; + static from

| Type>(req: Request, routeSchemas?: RouteSchemas, withoutSecretHeaders?: boolean): KibanaRequest; readonly headers: Headers; // (undocumented) readonly params: Params; // (undocumented) readonly query: Query; - readonly route: RecursiveReadonly; + readonly route: RecursiveReadonly>; // (undocumented) readonly socket: IKibanaSocket; readonly url: Url; } // @public -export interface KibanaRequestRoute { +export interface KibanaRequestRoute { // (undocumented) - method: RouteMethod | 'patch' | 'options'; + method: Method; // (undocumented) - options: Required; + options: KibanaRequestRouteOptions; // (undocumented) path: string; } +// @public +export type KibanaRequestRouteOptions = Method extends 'get' | 'options' ? Required, 'body'>> : Required>; + // @public export type KibanaResponseFactory = typeof kibanaResponseFactory; @@ -1043,7 +1052,7 @@ export type RedirectResponseOptions = HttpResponseOptions & { }; // @public -export type RequestHandler

= (context: RequestHandlerContext, request: KibanaRequest, TypeOf, TypeOf>, response: KibanaResponseFactory) => IKibanaResponse | Promise>; +export type RequestHandler

| Type, Method extends RouteMethod = any> = (context: RequestHandlerContext, request: KibanaRequest, TypeOf, TypeOf, Method>, response: KibanaResponseFactory) => IKibanaResponse | Promise>; // @public export interface RequestHandlerContext { @@ -1085,23 +1094,45 @@ export type ResponseHeaders = { }; // @public -export interface RouteConfig

{ - options?: RouteConfigOptions; +export interface RouteConfig

| Type, Method extends RouteMethod> { + options?: RouteConfigOptions; path: string; validate: RouteSchemas | false; } // @public -export interface RouteConfigOptions { +export interface RouteConfigOptions { authRequired?: boolean; + body?: Method extends 'get' | 'options' ? undefined : RouteConfigOptionsBody; tags?: readonly string[]; } // @public -export type RouteMethod = 'get' | 'post' | 'put' | 'delete'; +export interface RouteConfigOptionsBody { + accepts?: RouteContentType | RouteContentType[] | string | string[]; + maxBytes?: number; + output?: typeof validBodyOutput[number]; + parse?: boolean | 'gunzip'; +} // @public -export type RouteRegistrar =

(route: RouteConfig, handler: RequestHandler) => void; +export type RouteContentType = 'application/json' | 'application/*+json' | 'application/octet-stream' | 'application/x-www-form-urlencoded' | 'multipart/form-data' | 'text/*'; + +// @public +export type RouteMethod = 'get' | 'post' | 'put' | 'delete' | 'patch' | 'options'; + +// @public +export type RouteRegistrar =

| Type>(route: RouteConfig, handler: RequestHandler) => void; + +// @public +export interface RouteSchemas

| Type> { + // (undocumented) + body?: B; + // (undocumented) + params?: P; + // (undocumented) + query?: Q; +} // @public (undocumented) export interface SavedObject { @@ -1200,8 +1231,8 @@ export interface SavedObjectsBulkUpdateResponse(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; @@ -1219,6 +1250,11 @@ export class SavedObjectsClient { // @public export type SavedObjectsClientContract = Pick; +// @public +export type SavedObjectsClientFactory = ({ request, }: { + request: Request; +}) => SavedObjectsClientContract; + // @public export interface SavedObjectsClientProviderOptions { // (undocumented) @@ -1246,6 +1282,11 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { refresh?: MutatingOperationRefreshSetting; } +// @public (undocumented) +export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions { + refresh?: MutatingOperationRefreshSetting; +} + // @public (undocumented) export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions { refresh?: MutatingOperationRefreshSetting; @@ -1456,6 +1497,13 @@ export interface SavedObjectsImportUnsupportedTypeError { type: 'unsupported_type'; } +// @public (undocumented) +export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions { + // (undocumented) + migrationVersion?: SavedObjectsMigrationVersion; + refresh?: MutatingOperationRefreshSetting; +} + // @internal @deprecated (undocumented) export interface SavedObjectsLegacyService { // Warning: (ae-forgotten-export) The symbol "SavedObjectsClientProvider" needs to be exported by the entry point index.d.ts @@ -1515,6 +1563,32 @@ export interface SavedObjectsRawDoc { _type?: string; } +// @public (undocumented) +export class SavedObjectsRepository { + // Warning: (ae-forgotten-export) The symbol "SavedObjectsRepositoryOptions" needs to be exported by the entry point index.d.ts + // + // @internal + constructor(options: SavedObjectsRepositoryOptions); + bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>; + bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>; + bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>; + create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>; + delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; + deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; + // (undocumented) + find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, }: SavedObjectsFindOptions): Promise>; + get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>; + incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise<{ + id: string; + type: string; + updated_at: string; + references: any; + version: string; + attributes: any; + }>; + update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>; + } + // @public export interface SavedObjectsResolveImportErrorsOptions { // (undocumented) @@ -1555,6 +1629,19 @@ export class SavedObjectsSerializer { savedObjectToRaw(savedObj: SanitizedSavedObjectDoc): SavedObjectsRawDoc; } +// @public +export interface SavedObjectsServiceSetup { + addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void; + createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; + createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; + setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void; +} + +// @public +export interface SavedObjectsServiceStart { + getScopedClient: (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract; +} + // @public (undocumented) export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions { references?: SavedObjectReference[]; @@ -1577,6 +1664,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 +1682,7 @@ export interface SessionStorageCookieOptions { encryptionKey: string; isSecure: boolean; name: string; - validate: (sessionValue: T) => boolean | Promise; + validate: (sessionValue: T | T[]) => SessionCookieValidationResult; } // @public @@ -1627,6 +1720,9 @@ export interface UserProvidedValues { userValue?: T; } +// @public +export const validBodyOutput: readonly ["data", "stream"]; + // Warnings were encountered during analysis: // diff --git a/src/core/server/server.test.mocks.ts b/src/core/server/server.test.mocks.ts index f8eb5e32f4c5a..b378273a7075a 100644 --- a/src/core/server/server.test.mocks.ts +++ b/src/core/server/server.test.mocks.ts @@ -35,9 +35,11 @@ jest.doMock('./elasticsearch/elasticsearch_service', () => ({ ElasticsearchService: jest.fn(() => mockElasticsearchService), })); -export const mockLegacyService = { +import { ILegacyService } from './legacy/legacy_service'; +export const mockLegacyService: ILegacyService = { legacyId: Symbol(), - setup: jest.fn().mockReturnValue({ uiExports: {} }), + discoverPlugins: jest.fn().mockReturnValue({ uiExports: {} }), + setup: jest.fn(), start: jest.fn(), stop: jest.fn(), }; diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 6c38de03f0f2d..b36468b85d7a1 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -38,7 +38,6 @@ import { config as savedObjectsConfig } from './saved_objects'; import { config as uiSettingsConfig } from './ui_settings'; import { mapToObject } from '../utils/'; import { ContextService } from './context'; -import { SavedObjectsServiceSetup } from './saved_objects/saved_objects_service'; import { RequestHandlerContext } from '.'; import { InternalCoreSetup } from './internal_types'; @@ -78,6 +77,7 @@ export class Server { // Discover any plugins before continuing. This allows other systems to utilize the plugin dependency graph. const pluginDependencies = await this.plugins.discover(); + const legacyPlugins = await this.legacy.discoverPlugins(); const contextServiceSetup = this.context.setup({ // We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins: // 1) Can access context from any NP plugin @@ -103,40 +103,41 @@ export class Server { http: httpSetup, }); + const savedObjectsSetup = await this.savedObjects.setup({ + elasticsearch: elasticsearchServiceSetup, + legacyPlugins, + }); + const coreSetup: InternalCoreSetup = { context: contextServiceSetup, elasticsearch: elasticsearchServiceSetup, http: httpSetup, uiSettings: uiSettingsSetup, + savedObjects: savedObjectsSetup, }; const pluginsSetup = await this.plugins.setup(coreSetup); - const legacySetup = await this.legacy.setup({ + await this.legacy.setup({ core: { ...coreSetup, plugins: pluginsSetup }, plugins: mapToObject(pluginsSetup.contracts), }); - const savedObjectsSetup = await this.savedObjects.setup({ - elasticsearch: elasticsearchServiceSetup, - legacy: legacySetup, - }); - - this.registerCoreContext(coreSetup, savedObjectsSetup); + this.registerCoreContext(coreSetup); return coreSetup; } public async start() { this.log.debug('starting server'); - const pluginsStart = await this.plugins.start({}); const savedObjectsStart = await this.savedObjects.start({}); + const pluginsStart = await this.plugins.start({ savedObjects: savedObjectsStart }); + const coreStart = { savedObjects: savedObjectsStart, plugins: pluginsStart, }; - await this.legacy.start({ core: coreStart, plugins: mapToObject(pluginsStart.contracts), @@ -164,17 +165,14 @@ export class Server { ); } - private registerCoreContext( - coreSetup: InternalCoreSetup, - savedObjects: SavedObjectsServiceSetup - ) { + private registerCoreContext(coreSetup: InternalCoreSetup) { coreSetup.http.registerRouteHandlerContext( coreId, 'core', async (context, req): Promise => { const adminClient = await coreSetup.elasticsearch.adminClient$.pipe(take(1)).toPromise(); const dataClient = await coreSetup.elasticsearch.dataClient$.pipe(take(1)).toPromise(); - const savedObjectsClient = savedObjects.clientProvider.getClient(req); + const savedObjectsClient = coreSetup.savedObjects.getScopedClient(req); return { savedObjects: { diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker index 497307fa4124b..0c8faf47411d4 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker @@ -180,6 +180,8 @@ kibana_vars=( xpack.security.encryptionKey xpack.security.secureCookies xpack.security.sessionTimeout + xpack.security.session.idleTimeout + xpack.security.session.lifespan xpack.security.loginAssistanceMessage telemetry.enabled telemetry.sendUsageFrom diff --git a/src/es_archiver/lib/indices/kibana_index.js b/src/es_archiver/lib/indices/kibana_index.js index 6f491783829a8..1d88b7dc4e634 100644 --- a/src/es_archiver/lib/indices/kibana_index.js +++ b/src/es_archiver/lib/indices/kibana_index.js @@ -26,6 +26,7 @@ import { toArray } from 'rxjs/operators'; import { deleteIndex } from './delete_index'; import { collectUiExports } from '../../../legacy/ui/ui_exports'; import { KibanaMigrator } from '../../../core/server/saved_objects/migrations'; +import { SavedObjectsSchema } from '../../../core/server/saved_objects'; import { findPluginSpecs } from '../../../legacy/plugin_discovery'; /** @@ -101,7 +102,7 @@ export async function migrateKibanaIndex({ client, log, kibanaPluginIds }) { error: log.error.bind(log), }, version: kibanaVersion, - savedObjectSchemas: uiExports.savedObjectSchemas, + savedObjectSchemas: new SavedObjectsSchema(uiExports.savedObjectSchemas), savedObjectMappings: uiExports.savedObjectMappings, savedObjectMigrations: uiExports.savedObjectMigrations, savedObjectValidations: uiExports.savedObjectValidations, diff --git a/src/legacy/core_plugins/console/index.ts b/src/legacy/core_plugins/console/index.ts index caef3ff6f99f3..c4e6a77b7d859 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/public/quarantined/_app.scss b/src/legacy/core_plugins/console/public/quarantined/_app.scss index 1e13b6b483981..b19fd438f8ee3 100644 --- a/src/legacy/core_plugins/console/public/quarantined/_app.scss +++ b/src/legacy/core_plugins/console/public/quarantined/_app.scss @@ -1,5 +1,8 @@ // TODO: Move all of the styles here (should be modularised by, e.g., CSS-in-JS or CSS modules). +@import '@elastic/eui/src/components/header/variables'; + #consoleRoot { + height: calc(100vh - calc(#{$euiHeaderChildSize} * 2)); display: flex; flex: 1 1 auto; // Make sure the editor actions don't create scrollbars on this container 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 c9ad09cb017c4..a7fd8df1b10f4 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 2e78201f9990e..347b8dae80e29 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 aa7b764f84fc7..2cf09f96e7b72 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 f20adb897be65..6b98702131d91 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 856128f3d4c03..f67c97443ba07 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 d5504c0f3a3c2..2cbde5b3b39b8 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 0082f3591a132..0f6b78b484adf 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/index.ts b/src/legacy/core_plugins/data/index.ts index 71f2fa5ffec7c..c91500cd545d4 100644 --- a/src/legacy/core_plugins/data/index.ts +++ b/src/legacy/core_plugins/data/index.ts @@ -20,7 +20,7 @@ import { resolve } from 'path'; import { Legacy } from '../../../../kibana'; import { mappings } from './mappings'; -import { SavedQuery } from './public'; +import { SavedQuery } from '../../../plugins/data/public'; // eslint-disable-next-line import/no-default-export export default function DataPlugin(kibana: any) { diff --git a/src/legacy/core_plugins/data/public/index.scss b/src/legacy/core_plugins/data/public/index.scss index 913141666c7b9..94f02fe2d6049 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 c1b4226e6e49f..833d8c248f46a 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -37,23 +37,15 @@ export { IndexPatterns, StaticIndexPattern, } from './index_patterns'; -export { QueryBarInput } from './query'; -export { SearchBar, SearchBarProps, SavedQueryAttributes, SavedQuery } from './search'; +export { QueryStringInput } from './query'; +export { SearchBar, SearchBarProps } from './search'; +export { + SavedQueryAttributes, + SavedQuery, + SavedQueryTimeFilter, +} from '../../../../plugins/data/public'; /** @public static code */ export * from '../common'; export { FilterStateManager } from './filter/filter_manager'; -export { - CONTAINS_SPACES, - getFromSavedObject, - getRoutes, - IndexPatternSelect, - validateIndexPattern, - ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - IndexPatternAlreadyExists, - IndexPatternMissingIndices, - NoDefaultIndexPattern, - NoDefinedIndexPatterns, -} from './index_patterns'; +export { getFromSavedObject, getRoutes } from './index_patterns'; diff --git a/src/legacy/core_plugins/data/public/index_patterns/errors.ts b/src/legacy/core_plugins/data/public/index_patterns/errors.ts deleted file mode 100644 index c64da47b8c785..0000000000000 --- a/src/legacy/core_plugins/data/public/index_patterns/errors.ts +++ /dev/null @@ -1,63 +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. - */ - -/* eslint-disable */ - -import { KbnError } from '../../../../../plugins/kibana_utils/public'; - -/** - * when a mapping already exists for a field the user is attempting to add - * @param {String} name - the field name - */ -export class IndexPatternAlreadyExists extends KbnError { - constructor(name: string) { - super(`An index pattern of "${name}" already exists`); - } -} - -/** - * Tried to call a method that relies on SearchSource having an indexPattern assigned - */ -export class IndexPatternMissingIndices extends KbnError { - constructor(message: string) { - const defaultMessage = "IndexPattern's configured pattern does not match any indices"; - - super( - message && message.length ? `No matching indices found: ${message}` : defaultMessage - ); - } -} - -/** - * Tried to call a method that relies on SearchSource having an indexPattern assigned - */ -export class NoDefinedIndexPatterns extends KbnError { - constructor() { - super('Define at least one index pattern to continue'); - } -} - -/** - * Tried to load a route besides management/kibana/index but you don't have a default index pattern! - */ -export class NoDefaultIndexPattern extends KbnError { - constructor() { - super('Please specify a default index pattern'); - } -} diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts index f77342c7bc274..de364b6c217dd 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -32,10 +32,10 @@ import { ES_FIELD_TYPES, KBN_FIELD_TYPES, IIndexPattern, + indexPatterns, } from '../../../../../../plugins/data/public'; import { findIndexPatternByTitle, getRoutes } from '../utils'; -import { IndexPatternMissingIndices } from '../errors'; import { Field, FieldList, FieldListInterface, FieldType } from '../fields'; import { createFieldsFetcher } from './_fields_fetcher'; import { formatHitProvider } from './format_hit'; @@ -499,7 +499,7 @@ export class IndexPattern implements IIndexPattern { // so do not rethrow the error here const { toasts } = getNotifications(); - if (err instanceof IndexPatternMissingIndices) { + if (err instanceof indexPatterns.IndexPatternMissingIndices) { toasts.addDanger((err as any).message); return []; diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts index 0a5d1bfcae21f..2ad0a1f1394e5 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns.test.ts @@ -25,10 +25,6 @@ import { HttpServiceBase, } from 'kibana/public'; -jest.mock('../errors', () => ({ - IndexPatternMissingIndices: jest.fn(), -})); - jest.mock('./index_pattern', () => { class IndexPattern { init = async () => { diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts index c0e8516a75bb3..87dd7a68e3061 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts @@ -18,7 +18,7 @@ */ import { HttpServiceBase } from 'src/core/public'; -import { IndexPatternMissingIndices } from '../errors'; +import { indexPatterns } from '../../../../../../plugins/data/public'; const API_BASE_URL: string = `/api/index_patterns/`; @@ -46,7 +46,7 @@ export class IndexPatternsApiClient { }) .catch((resp: any) => { if (resp.body.statusCode === 404 && resp.body.statuscode === 'no_matching_indices') { - throw new IndexPatternMissingIndices(resp.body.message); + throw new indexPatterns.IndexPatternMissingIndices(resp.body.message); } throw new Error(resp.body.message || resp.body.error || `${resp.body.statusCode} Response`); diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.mock.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.mock.ts index 5dcf4005ef4e8..db1ece78e7b4d 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.mock.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.mock.ts @@ -33,7 +33,6 @@ const createSetupContractMock = () => { flattenHitWrapper: jest.fn().mockImplementation(flattenHitWrapper), formatHitProvider: jest.fn(), indexPatterns: jest.fn() as any, - IndexPatternSelect: jest.fn(), __LEGACY: { // For BWC we must temporarily export the class implementation of Field, // which is only used externally by the Index Pattern UI. diff --git a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts index f97246bc5a9bf..9973a7081443d 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/index_patterns_service.ts @@ -25,7 +25,6 @@ import { } from 'src/core/public'; import { FieldFormatsStart } from '../../../../../plugins/data/public'; import { Field, FieldList, FieldListInterface, FieldType } from './fields'; -import { createIndexPatternSelect } from './components'; import { setNotifications, setFieldFormats } from './services'; import { @@ -79,7 +78,6 @@ export class IndexPatternsService { return { ...this.setupApi, indexPatterns: new IndexPatterns(uiSettings, savedObjectsClient, http), - IndexPatternSelect: createIndexPatternSelect(savedObjectsClient), }; } @@ -91,24 +89,7 @@ export class IndexPatternsService { // static code /** @public */ -export { IndexPatternSelect } from './components'; -export { - CONTAINS_SPACES, - getFromSavedObject, - getRoutes, - ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - validateIndexPattern, -} from './utils'; - -/** @public */ -export { - IndexPatternAlreadyExists, - IndexPatternMissingIndices, - NoDefaultIndexPattern, - NoDefinedIndexPatterns, -} from './errors'; +export { getFromSavedObject, getRoutes } from './utils'; // types @@ -120,4 +101,4 @@ export type IndexPatternsStart = ReturnType; export { IndexPattern, IndexPatterns, StaticIndexPattern, Field, FieldType, FieldListInterface }; /** @public */ -export { getIndexPatternTitle, findIndexPatternByTitle } from './utils'; +export { findIndexPatternByTitle } from './utils'; diff --git a/src/legacy/core_plugins/data/public/index_patterns/utils.ts b/src/legacy/core_plugins/data/public/index_patterns/utils.ts index 8542c1dcce24d..0d0d5705a0ccc 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/utils.ts +++ b/src/legacy/core_plugins/data/public/index_patterns/utils.ts @@ -21,27 +21,6 @@ import { find, get } from 'lodash'; import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public'; -export const ILLEGAL_CHARACTERS = 'ILLEGAL_CHARACTERS'; -export const CONTAINS_SPACES = 'CONTAINS_SPACES'; -export const INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE = ['\\', '/', '?', '"', '<', '>', '|']; -export const INDEX_PATTERN_ILLEGAL_CHARACTERS = INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE.concat( - ' ' -); - -function findIllegalCharacters(indexPattern: string): string[] { - const illegalCharacters = INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE.reduce( - (chars: string[], char: string) => { - if (indexPattern.includes(char)) { - chars.push(char); - } - return chars; - }, - [] - ); - - return illegalCharacters; -} - /** * Returns an object matching a given title * @@ -71,39 +50,6 @@ export async function findIndexPatternByTitle( ); } -export async function getIndexPatternTitle( - client: SavedObjectsClientContract, - indexPatternId: string -): Promise> { - const savedObject = (await client.get('index-pattern', indexPatternId)) as SimpleSavedObject; - - if (savedObject.error) { - throw new Error(`Unable to get index-pattern title: ${savedObject.error.message}`); - } - - return savedObject.attributes.title; -} - -function indexPatternContainsSpaces(indexPattern: string): boolean { - return indexPattern.includes(' '); -} - -export function validateIndexPattern(indexPattern: string) { - const errors: Record = {}; - - const illegalCharacters = findIllegalCharacters(indexPattern); - - if (illegalCharacters.length) { - errors[ILLEGAL_CHARACTERS] = illegalCharacters; - } - - if (indexPatternContainsSpaces(indexPattern)) { - errors[CONTAINS_SPACES] = true; - } - - return errors; -} - export function getFromSavedObject(savedObject: any) { if (get(savedObject, 'attributes.fields') === undefined) { return; diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index da24576655d2b..6cce91a5a25b5 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -18,7 +18,7 @@ */ import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import { SearchService, SearchStart, createSearchBar, StatetfulSearchBarProps } from './search'; +import { createSearchBar, StatetfulSearchBarProps } from './search'; import { IndexPatternsService, IndexPatternsSetup, IndexPatternsStart } from './index_patterns'; import { Storage, IStorageWrapper } from '../../../../../src/plugins/kibana_utils/public'; import { DataPublicPluginStart } from '../../../../plugins/data/public'; @@ -51,7 +51,6 @@ export interface DataSetup { */ export interface DataStart { indexPatterns: IndexPatternsStart; - search: SearchStart; ui: { SearchBar: React.ComponentType; }; @@ -71,7 +70,6 @@ export interface DataStart { export class DataPlugin implements Plugin { private readonly indexPatterns: IndexPatternsService = new IndexPatternsService(); - private readonly search: SearchService = new SearchService(); private setupApi!: DataSetup; private storage!: IStorageWrapper; @@ -119,7 +117,6 @@ export class DataPlugin implements Plugin - - + @@ -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 e17c416c13546..1d955920b8e13 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 ae08083f82af3..ea01347e38865 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 1bf8ac086d341..824e8cf1e2a7c 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 79% 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 3edb689ca2bfe..04347773ff293 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 { QueryLanguageSwitcher } from '../../../../../../../plugins/data/public'; +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 dce245e0ccb24..c51bfb705c9f0 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,27 +38,27 @@ import { AutocompleteSuggestion, AutocompleteSuggestionType, IDataPluginServices, + IIndexPattern, PersistedLog, + SuggestionsComponent, toUser, fromUser, matchPairs, getQueryLog, Query, + QueryLanguageSwitcher, } from '../../../../../../../plugins/data/public'; import { withKibana, 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 f0ad0707c699a..47b0ca5eae1bf 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 7165de026920d..61b9b7bf83c03 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/index.ts b/src/legacy/core_plugins/data/public/search/index.ts index bbced035e3d5f..24ffa939a746e 100644 --- a/src/legacy/core_plugins/data/public/search/index.ts +++ b/src/legacy/core_plugins/data/public/search/index.ts @@ -17,5 +17,4 @@ * under the License. */ -export * from './search_service'; export * from './search_bar'; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx index 7a9786f5f9ce8..4515c71baa267 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/save_query_form.tsx @@ -35,8 +35,11 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { sortBy, isEqual } from 'lodash'; -import { SavedQuery, SavedQueryAttributes } from '../../index'; -import { SavedQueryService } from '../../lib/saved_query_service'; +import { + SavedQuery, + SavedQueryAttributes, + SavedQueryService, +} from '../../../../../../../../plugins/data/public'; interface Props { savedQuery?: SavedQueryAttributes; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_list_item.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_list_item.tsx index e8cf8a3d61ecd..bee32c347e6f4 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_list_item.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_list_item.tsx @@ -22,7 +22,7 @@ import { EuiListGroupItem, EuiConfirmModal, EuiOverlayMask, EuiIconTip } from '@ import React, { Fragment, useState } from 'react'; import classNames from 'classnames'; import { i18n } from '@kbn/i18n'; -import { SavedQuery } from '../../index'; +import { SavedQuery } from '../../../../../../../../plugins/data/public'; interface Props { savedQuery: SavedQuery; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx index b73b8edb39e54..65ed92c40741b 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/saved_query_management/saved_query_management_component.tsx @@ -35,8 +35,7 @@ import { import { i18n } from '@kbn/i18n'; import React, { FunctionComponent, useEffect, useState, Fragment } from 'react'; import { sortBy } from 'lodash'; -import { SavedQuery } from '../../index'; -import { SavedQueryService } from '../../lib/saved_query_service'; +import { SavedQuery, SavedQueryService } from '../../../../../../../../plugins/data/public'; import { SavedQueryListItem } from './saved_query_list_item'; const perPage = 50; 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 0ca9482fefa30..0605b156a9669 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 @@ -38,12 +38,13 @@ const mockTimeHistory = { jest.mock('../../../../../../../plugins/data/public', () => { return { FilterBar: () =>

, + createSavedQueryService: () => {}, }; }); jest.mock('../../../../../data/public', () => { return { - QueryBarInput: () =>
, + QueryStringInput: () =>
, }; }); diff --git a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx index 6a1ef77a56653..da08165289afc 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/components/search_bar.tsx @@ -26,11 +26,9 @@ import { get, isEqual } from 'lodash'; import { IndexPattern } from '../../../../../data/public'; import { QueryBarTopRow } from '../../../query'; -import { SavedQuery, SavedQueryAttributes } from '../index'; import { SavedQueryMeta, SaveQueryForm } from './saved_query_management/save_query_form'; import { SavedQueryManagementComponent } from './saved_query_management/saved_query_management_component'; -import { SavedQueryService } from '../lib/saved_query_service'; -import { createSavedQueryService } from '../lib/saved_query_service'; + import { withKibana, KibanaReactContextValue, @@ -42,6 +40,10 @@ import { esFilters, TimeHistoryContract, FilterBar, + SavedQueryService, + createSavedQueryService, + SavedQuery, + SavedQueryAttributes, } from '../../../../../../../plugins/data/public'; interface SearchBarInjectedDeps { diff --git a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx b/src/legacy/core_plugins/data/public/search/search_bar/index.tsx index f369bf997c1a9..faf6e24aa6ed5 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/index.tsx +++ b/src/legacy/core_plugins/data/public/search/search_bar/index.tsx @@ -17,23 +17,4 @@ * under the License. */ -import { RefreshInterval, TimeRange, Query, esFilters } from 'src/plugins/data/public'; - export * from './components'; - -export type SavedQueryTimeFilter = TimeRange & { - refreshInterval: RefreshInterval; -}; - -export interface SavedQuery { - id: string; - attributes: SavedQueryAttributes; -} - -export interface SavedQueryAttributes { - title: string; - description: string; - query: Query; - filters?: esFilters.Filter[]; - timefilter?: SavedQueryTimeFilter; -} diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.js index 27f37421b0e25..45981adf9af45 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/controls_tab.test.js @@ -17,8 +17,27 @@ * under the License. */ -jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); +jest.mock('../../../../../core_plugins/data/public/legacy', () => ({ + indexPatterns: { + indexPatterns: { + get: jest.fn(), + } + } +})); + +jest.mock('ui/new_platform', () => ({ + npStart: { + plugins: { + data: { + ui: { + IndexPatternSelect: () => { + return
; + } + } + } + }, + }, +})); import React from 'react'; import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js index 11c6f27af38c5..ce7d48a3f1376 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js @@ -49,7 +49,7 @@ class FieldSelectUi extends Component { this.loadFields(this.state.indexPatternId); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (this.props.indexPatternId !== nextProps.indexPatternId) { this.loadFields(nextProps.indexPatternId); } diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.js index 663a36ab69f46..c48123f3db714 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/index_pattern_select_form_row.js @@ -20,12 +20,13 @@ import PropTypes from 'prop-types'; import React from 'react'; import { injectI18n } from '@kbn/i18n/react'; -import { IndexPatternSelect } from 'ui/index_patterns'; - import { EuiFormRow, } from '@elastic/eui'; +import { npStart } from 'ui/new_platform'; +const { IndexPatternSelect } = npStart.plugins.data.ui; + function IndexPatternSelectFormRowUi(props) { const { controlIndex, diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js index ea029af9e4890..b37e8af0895fe 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/list_control_editor.test.js @@ -17,12 +17,24 @@ * under the License. */ -jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); +jest.mock('ui/new_platform', () => ({ + npStart: { + plugins: { + data: { + ui: { + IndexPatternSelect: () => { + return
; + } + } + } + }, + }, +})); import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; + import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.js index 5a698d65286ac..8d601f5a727d1 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.js +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/range_control_editor.test.js @@ -17,19 +17,30 @@ * under the License. */ -jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; + +jest.mock('ui/new_platform', () => ({ + npStart: { + plugins: { + data: { + ui: { + IndexPatternSelect: () => { + return
; + } + } + } + }, + }, +})); + import { findTestSubject } from '@elastic/eui/lib/test'; import { getIndexPatternMock } from './__tests__/get_index_pattern_mock'; -import { - RangeControlEditor, -} from './range_control_editor'; +import { RangeControlEditor } from './range_control_editor'; const controlParams = { id: '1', 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 731cf2dac9dd2..12e7291fea7a1 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 ca9115b729da8..a03e8affe319b 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/controller.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js index 319f7d9b9fa9f..014606fb375ab 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js @@ -19,9 +19,12 @@ import $ from 'jquery'; -import { CUSTOM_LEGEND_VIS_TYPES } from '../../../ui/public/vis/vis_types/vislib_vis_legend'; +import React from 'react'; + +import { CUSTOM_LEGEND_VIS_TYPES, VisLegend } from '../../../ui/public/vis/vis_types/vislib_vis_legend'; import { VislibVisProvider } from '../../../ui/public/vislib/vis'; import chrome from '../../../ui/public/chrome'; +import { mountReactNode } from '../../../../core/public/utils'; const legendClassName = { top: 'visLib--legend-top', @@ -30,24 +33,30 @@ const legendClassName = { right: 'visLib--legend-right', }; - export class vislibVisController { constructor(el, vis) { this.el = el; this.vis = vis; - this.$scope = null; + this.unmount = null; + this.legendRef = React.createRef(); + // vis mount point this.container = document.createElement('div'); this.container.className = 'visLib'; this.el.appendChild(this.container); + // chart mount point this.chartEl = document.createElement('div'); this.chartEl.className = 'visLib__chart'; this.container.appendChild(this.chartEl); + // legend mount point + this.legendEl = document.createElement('div'); + this.legendEl.className = 'visLib__legend'; + this.container.appendChild(this.legendEl); } render(esResponse, visParams) { - if (this.vis.vislibVis) { + if (this.vislibVis) { this.destroy(); } @@ -56,62 +65,69 @@ export class vislibVisController { const $injector = await chrome.dangerouslyGetActiveInjector(); const Private = $injector.get('Private'); this.Vislib = Private(VislibVisProvider); - this.$compile = $injector.get('$compile'); - this.$rootScope = $injector.get('$rootScope'); } if (this.el.clientWidth === 0 || this.el.clientHeight === 0) { return resolve(); } - this.vis.vislibVis = new this.Vislib(this.chartEl, visParams); - this.vis.vislibVis.on('brush', this.vis.API.events.brush); - this.vis.vislibVis.on('click', this.vis.API.events.filter); - this.vis.vislibVis.on('renderComplete', resolve); + this.vislibVis = new this.Vislib(this.chartEl, visParams); + this.vislibVis.on('brush', this.vis.API.events.brush); + this.vislibVis.on('click', this.vis.API.events.filter); + this.vislibVis.on('renderComplete', resolve); - this.vis.vislibVis.initVisConfig(esResponse, this.vis.getUiState()); + this.vislibVis.initVisConfig(esResponse, this.vis.getUiState()); if (visParams.addLegend) { $(this.container).attr('class', (i, cls) => { return cls.replace(/visLib--legend-\S+/g, ''); }).addClass(legendClassName[visParams.legendPosition]); - this.$scope = this.$rootScope.$new(); - this.$scope.refreshLegend = 0; - this.$scope.vis = this.vis; - this.$scope.visData = esResponse; - this.$scope.visParams = visParams; - this.$scope.uiState = this.$scope.vis.getUiState(); - const legendHtml = this.$compile('')(this.$scope); - this.container.appendChild(legendHtml[0]); - this.$scope.$digest(); + this.mountLegend(esResponse, visParams.legendPosition); } - this.vis.vislibVis.render(esResponse, this.vis.getUiState()); + this.vislibVis.render(esResponse, this.vis.getUiState()); // refreshing the legend after the chart is rendered. // this is necessary because some visualizations // provide data necessary for the legend only after a render cycle. - if (visParams.addLegend && CUSTOM_LEGEND_VIS_TYPES.includes(this.vis.vislibVis.visConfigArgs.type)) { - this.$scope.refreshLegend++; - this.$scope.$digest(); - - this.vis.vislibVis.render(esResponse, this.vis.getUiState()); + if (visParams.addLegend && CUSTOM_LEGEND_VIS_TYPES.includes(this.vislibVis.visConfigArgs.type)) { + this.unmountLegend(); + this.mountLegend(esResponse, visParams.legendPosition); + this.vislibVis.render(esResponse, this.vis.getUiState()); } }); } + mountLegend(visData, position) { + this.unmount = mountReactNode( + + )(this.legendEl); + } + + unmountLegend() { + if (this.unmount) { + this.unmount(); + } + } + destroy() { - if (this.vis.vislibVis) { - this.vis.vislibVis.off('brush', this.vis.API.events.brush); - this.vis.vislibVis.off('click', this.vis.API.events.filter); - this.vis.vislibVis.destroy(); - delete this.vis.vislibVis; + if (this.unmount) { + this.unmount(); } - $(this.container).find('vislib-legend').remove(); - if (this.$scope) { - this.$scope.$destroy(); - this.$scope = null; + + if (this.vislibVis) { + this.vislibVis.off('brush', this.vis.API.events.brush); + this.vislibVis.off('click', this.vis.API.events.filter); + this.vislibVis.destroy(); + delete this.vislibVis; } } } 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 75907618eb859..6d0d997604e01 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 3a6b9f873aa87..dedd2e3885876 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 207f80996b5a7..e3212037ecf2f 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 87e690fa6457e..15ede19e21c22 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 2f8107580f0f7..0369e8d8c27b5 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 fe2cca3b80064..c82073ff582b8 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 fedbf48541451..74c7e2fa2af89 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 8a374f21dcb09..691f2e6349f72 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 dcb7d7998ff1a..91364071579ab 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/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts index 9c50adeeefccb..f98a4ca53f467 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/application.ts @@ -67,7 +67,7 @@ export interface RenderDeps { uiSettings: UiSettingsClientContract; chrome: ChromeStart; addBasePath: (path: string) => string; - savedQueryService: DataStart['search']['services']['savedQueryService']; + savedQueryService: NpDataStart['query']['savedQueries']; embeddables: IEmbeddableStart; localStorage: Storage; share: SharePluginStart; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index 0ce8f2ef59fc0..26b86204b03db 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -17,7 +17,7 @@ * under the License. */ -import { StaticIndexPattern, SavedQuery } from 'plugins/data'; +import { StaticIndexPattern } from 'plugins/data'; import moment from 'moment'; import { Subscription } from 'rxjs'; @@ -31,7 +31,7 @@ import { import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types'; -import { TimeRange, Query, esFilters } from '../../../../../../src/plugins/data/public'; +import { TimeRange, Query, esFilters, SavedQuery } from '../../../../../../src/plugins/data/public'; import { DashboardAppController } from './dashboard_app_controller'; import { RenderDeps } from './application'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 1a0e13417d1e1..dcd25033e9d15 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -38,8 +38,8 @@ import { SavedObjectFinder, unhashUrl, } from './legacy_imports'; -import { FilterStateManager, IndexPattern, SavedQuery } from '../../../data/public'; -import { Query } from '../../../../../plugins/data/public'; +import { FilterStateManager, IndexPattern } from '../../../data/public'; +import { Query, SavedQuery } from '../../../../../plugins/data/public'; import './dashboard_empty_screen_directive'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index 609bd717f3c48..cb0980c914983 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -104,7 +104,7 @@ export class DashboardPlugin implements Plugin { chrome: contextCore.chrome, addBasePath: contextCore.http.basePath.prepend, uiSettings: contextCore.uiSettings, - savedQueryService: dataStart.search.services.savedQueryService, + savedQueryService: npDataStart.query.savedQueries, embeddables, dashboardCapabilities: contextCore.application.capabilities.dashboard, localStorage: new Storage(localStorage), diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index ba74ea069c4ab..0e92d048a65a9 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -68,16 +68,17 @@ const { share, StateProvider, timefilter, + npData, toastNotifications, uiModules, uiRoutes, } = getServices(); import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../breadcrumbs'; -import { start as data } from '../../../../data/public/legacy'; import { generateFilters } from '../../../../../../plugins/data/public'; +import { start as data } from '../../../../data/public/legacy'; -const { savedQueryService } = data.search.services; +const savedQueryService = npData.query.savedQueries; const fetchStatuses = { UNINITIALIZED: 'uninitialized', diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx b/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx index b3efd23ea48d0..ee80f29c053dc 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc/doc.test.tsx @@ -45,16 +45,6 @@ beforeEach(() => { jest.clearAllMocks(); }); -// Suppress warnings about "act" until we use React 16.9 -/* eslint-disable no-console */ -const originalError = console.error; -beforeAll(() => { - console.error = jest.fn(); -}); -afterAll(() => { - console.error = originalError; -}); - export const waitForPromises = () => new Promise(resolve => setTimeout(resolve, 0)); /** diff --git a/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.test.tsx b/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.test.tsx index 083a5997ac5dd..2420eb2cd22bb 100644 --- a/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.test.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/doc/use_es_doc_search.test.tsx @@ -16,20 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -import { renderHook, act } from 'react-hooks-testing-library'; +import { renderHook, act } from '@testing-library/react-hooks'; import { buildSearchBody, useEsDocSearch, ElasticRequestState } from './use_es_doc_search'; import { DocProps } from './doc'; -// Suppress warnings about "act" until we use React 16.9 -/* eslint-disable no-console */ -const originalError = console.error; -beforeAll(() => { - console.error = jest.fn(); -}); -afterAll(() => { - console.error = originalError; -}); - describe('Test of helper / hook', () => { test('buildSearchBody', () => { const indexPattern = { 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 fc5f34fab7564..822bf69e52e0b 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -34,7 +34,6 @@ import { StateProvider } from 'ui/state_management/state'; import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import { timefilter } from 'ui/timefilter'; // @ts-ignore import { IndexPattern, IndexPatterns } from 'ui/index_patterns'; import { wrapInI18nContext } from 'ui/i18n'; @@ -58,7 +57,9 @@ const services = { uiSettings: npStart.core.uiSettings, uiActions: npStart.plugins.uiActions, embeddable: npStart.plugins.embeddable, + npData: npStart.plugins.data, share: npStart.plugins.share, + timefilter: npStart.plugins.data.query.timefilter.timefilter, // legacy docTitle, docViewsRegistry, @@ -70,7 +71,6 @@ const services = { SavedObjectProvider, SearchSource, StateProvider, - timefilter, uiModules, uiRoutes, wrapInI18nContext, @@ -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/home/components/tutorial/tutorial.js b/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js index 086fa5a059121..567bb3f83f22c 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js +++ b/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js @@ -67,7 +67,7 @@ class TutorialUi extends React.Component { } } - componentWillMount() { + UNSAFE_componentWillMount() { this._isMounted = true; } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.js index df90abce00e48..d0441ce3ceb8b 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/indices_list/indices_list.js @@ -61,7 +61,7 @@ export class IndicesList extends Component { this.pager = new Pager(props.indices.length, this.state.perPage, this.state.page); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.indices.length !== this.props.indices.length) { this.pager.setTotalItems(nextProps.indices.length); this.resetPageTo0(); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js index 617262c13b034..4764b516dffec 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js @@ -75,7 +75,7 @@ export class StepIndexPattern extends Component { this.lastQuery = null; } - async componentWillMount() { + async UNSAFE_componentWillMount() { this.fetchExistingIndexPatterns(); if (this.state.query) { this.lastQuery = this.state.query; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js index a1f302dc87f6c..566277802174a 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/create_index_pattern_wizard.js @@ -65,7 +65,7 @@ export class CreateIndexPatternWizard extends Component { }; } - async componentWillMount() { + async UNSAFE_componentWillMount() { this.fetchData(); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js index cb1c316c8af9b..b32c325e93653 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/indexed_fields_table/indexed_fields_table.js @@ -47,7 +47,7 @@ export class IndexedFieldsTable extends Component { }; } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.fields !== this.props.fields) { this.setState({ fields: this.mapFields(nextProps.fields) diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js index d91f4836ee1d8..c7677955a8069 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/scripted_fields_table.js @@ -59,7 +59,7 @@ export class ScriptedFieldsTable extends Component { }; } - componentWillMount() { + UNSAFE_componentWillMount() { this.fetchFields(); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js index ba93485a1739a..1201e23c48e44 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/source_filters_table.js @@ -58,7 +58,7 @@ export class SourceFiltersTable extends Component { }; } - componentWillMount() { + UNSAFE_componentWillMount() { this.updateFilters(); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js index 278f7de38b19d..ee9fb70e31fb2 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/relationships.js @@ -58,11 +58,11 @@ export class Relationships extends Component { }; } - componentWillMount() { + UNSAFE_componentWillMount() { this.getRelationshipData(); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (nextProps.savedObject.id !== this.props.savedObject.id) { this.getRelationshipData(); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.js index c2dabdffb2cd2..525f8495027c1 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.js @@ -81,7 +81,7 @@ export class AdvancedSettings extends Component { }, {}); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { const { config } = nextProps; const { query } = this.state; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js index a953d09906ed1..c790930e32aa0 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js @@ -76,7 +76,7 @@ export class Field extends PureComponent { this.changeImageForm = null; } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { const { unsavedValue } = this.state; const { type, value, defVal } = nextProps.setting; const editableValue = this.getEditableValue(type, value, defVal); diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 6840c1386bee2..5410289bfc2d7 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -54,19 +54,20 @@ const { capabilities, chrome, chromeLegacy, + npData, data, docTitle, FilterBarQueryFilterProvider, getBasePath, toastNotifications, - timefilter, uiModules, uiRoutes, visualizations, share, } = getServices(); -const { savedQueryService } = data.search.services; +const savedQueryService = npData.query.savedQueries; +const { timefilter } = npData.query.timefilter; uiRoutes .when(VisualizeConstants.CREATE_PATH, { diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index e2201cdca9a57..61b1cde0dcaf9 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -36,7 +36,6 @@ import { wrapInI18nContext } from 'ui/i18n'; // @ts-ignore import { uiModules } from 'ui/modules'; import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; -import { timefilter } from 'ui/timefilter'; // Saved objects import { SavedObjectsClientProvider } from 'ui/saved_objects'; @@ -46,8 +45,8 @@ import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_regis import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; -import { start as data } from '../../../data/public/legacy'; import { start as embeddables } from '../../../../core_plugins/embeddable_api/public/np_ready/public/legacy'; +import { start as data } from '../../../data/public/legacy'; const services = { // new platform @@ -63,6 +62,7 @@ const services = { core: npStart.core, share: npStart.plugins.share, + npData: npStart.plugins.data, data, embeddables, visualizations, @@ -78,7 +78,7 @@ const services = { SavedObjectProvider, SavedObjectRegistryProvider, SavedObjectsClientProvider, - timefilter, + timefilter: npStart.plugins.data.query.timefilter.timefilter, uiModules, uiRoutes, wrapInI18nContext, 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 aec80b8d13551..ae4b4d1c779df 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 245b4270c6aea..fcc612ab49bd2 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 3ff39c1a4eb8c..9890aaf187a13 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 19fb64b7ecc74..6d751a9e9ff45 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 24f336043d0d1..7737a0fbc2a71 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 b57fbd637f0b7..7571c616093ba 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 aaaf4c866c521..a41d638986ae5 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 3a28277f9f4c7..03e0de728ca85 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/status_page/index.js b/src/legacy/core_plugins/status_page/index.js index 34de58048b887..9f0ad632fd5b1 100644 --- a/src/legacy/core_plugins/status_page/index.js +++ b/src/legacy/core_plugins/status_page/index.js @@ -26,6 +26,11 @@ export default function (kibana) { hidden: true, url: '/status', }, + injectDefaultVars(server) { + return { + isStatusPageAnonymous: server.config().get('status.allowAnonymous'), + }; + } } }); } diff --git a/src/legacy/core_plugins/telemetry/README.md b/src/legacy/core_plugins/telemetry/README.md new file mode 100644 index 0000000000000..830c08f8e8bed --- /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 5ae0d5f127eed..9f850fc0fe719 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/public/components/telemetry_form.js b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js index 6c6ace71af4d0..d4bbe1029b40d 100644 --- a/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js +++ b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js @@ -52,7 +52,7 @@ export class TelemetryForm extends Component { queryMatches: null, } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { const { query } = nextProps; diff --git a/src/legacy/core_plugins/telemetry/server/collection_manager.ts b/src/legacy/core_plugins/telemetry/server/collection_manager.ts index 799d9f4ee9c8b..933c249cd7279 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 f963ecec0477c..2f2a53278117b 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 3b289752ce39f..71026b026263f 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 74c93931096b2..191565187be14 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 e96c47741f79c..631a37e674c4e 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 a172ba7dc6955..5e25538cbad80 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 e1ac7a1f5af12..013db526211e1 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 fa3159669c33c..73157abce8629 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 a1b3d5a7b1982..3ef9eed3c1265 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 3806dfc77120f..2b2e946198e0a 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 c927453641193..99090cb2fb7ef 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 02752ca773488..6c62d03adf25c 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 f2628090c08af..06a974f473498 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 4cbdf18df4a74..140204ac5ab49 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 051ef370fcde5..236dd046148f6 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 e11c6b1277d5b..a4ea2eb534226 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 0e3c4fdd9d355..57469625ea4a6 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 602f6c266b5f6..14a348f624002 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 243b4c2bf7765..a976fb5c77ef0 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 6291948f75077..b0123cd34b49e 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 a586fd71e6cf8..7b82553a24e5b 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 74111bf794877..14cd3d0083e6a 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 7b2f8f6c236b2..524bbeed1b552 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 85d8c27ed970d..f131664756202 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 384beb3764e2e..de126087b36be 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 a05df6f4d1564..ceab5dafe1f06 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 e3e0ffaab2116..f5c152ce888c0 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 54f1f36e19c99..ce0e78140a86a 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 4153ce2da36a7..e22dd4caa6d01 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 13e8a4fd9535a..2978856a3511d 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 28e812701c2d3..ce8d349d8dd7a 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 1de72c0b33a4c..7e8537a1fee54 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 4a16bb72bd2e3..39023d1305cb6 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 bcb210e9ed081..865229ce0e4c1 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 0b4d90522cc44..9c673b1122573 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/aggs/calculation.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js index 2eefeb35c26ff..76306ecf28994 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js @@ -42,7 +42,7 @@ import { } from '@elastic/eui'; export class CalculationAgg extends Component { - componentWillMount() { + UNSAFE_componentWillMount() { if (!this.props.model.variables) { this.props.onChange( _.assign({}, this.props.model, { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js index a73460798ebd8..c62012927f951 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/math.js @@ -42,7 +42,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; export class MathAgg extends Component { - componentWillMount() { + UNSAFE_componentWillMount() { if (!this.props.model.variables) { this.props.onChange( _.assign({}, this.props.model, { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js index ec16a0f2eb3ee..3ce5be5b6875a 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/percentile.js @@ -42,7 +42,7 @@ const RESTRICT_FIELDS = [KBN_FIELD_TYPES.NUMBER]; export class PercentileAgg extends Component { // eslint-disable-line react/no-multi-comp - componentWillMount() { + UNSAFE_componentWillMount() { if (!this.props.model.percentiles) { this.props.onChange( _.assign({}, this.props.model, { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js index 9fe237d9cb671..835628368efab 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/custom_color_picker.js @@ -18,7 +18,7 @@ */ import PropTypes from 'prop-types'; -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import { ColorWrap as colorWrap, @@ -32,18 +32,13 @@ import ChromePointer from 'react-color/lib/components/chrome/ChromePointer'; import ChromePointerCircle from 'react-color/lib/components/chrome/ChromePointerCircle'; import CompactColor from 'react-color/lib/components/compact/CompactColor'; import color from 'react-color/lib/helpers/color'; -import shallowCompare from 'react-addons-shallow-compare'; -class CustomColorPickerUI extends Component { +class CustomColorPickerUI extends PureComponent { constructor(props) { super(props); this.handleChange = this.handleChange.bind(this); } - shouldComponentUpdate(nextProps, nextState) { - return shallowCompare(nextProps, nextState); - } - handleChange(data) { this.props.onChange(data); } diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js index 6da10f6a80816..19770519ef010 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/gauge.js @@ -52,7 +52,7 @@ class GaugePanelConfigUi extends Component { this.state = { selectedTab: 'data' }; } - componentWillMount() { + UNSAFE_componentWillMount() { const { model } = this.props; const parts = {}; if ( 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 7b8a7061e8bb1..9ec8184dbaebb 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/panel_config/metric.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js index 705adc07e7314..526649766a008 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js @@ -48,7 +48,7 @@ export class MetricPanelConfig extends Component { this.state = { selectedTab: 'data' }; } - componentWillMount() { + UNSAFE_componentWillMount() { const { model } = this.props; if ( !model.background_color_rules || diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js index 029962f189aff..3acaa728bb50f 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/table.js @@ -51,7 +51,7 @@ export class TablePanelConfig extends Component { this.state = { selectedTab: 'data' }; } - componentWillMount() { + UNSAFE_componentWillMount() { const { model } = this.props; const parts = {}; if (!model.bar_color_rules || (model.bar_color_rules && model.bar_color_rules.length === 0)) { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js index 8f5619909754e..7dbecb9e674b0 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/top_n.js @@ -51,7 +51,7 @@ export class TopNPanelConfig extends Component { this.state = { selectedTab: 'data' }; } - componentWillMount() { + UNSAFE_componentWillMount() { const { model } = this.props; const parts = {}; if (!model.bar_color_rules || (model.bar_color_rules && model.bar_color_rules.length === 0)) { 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 2eb9a7b03ac5f..dc976beeca0d1 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/split.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/split.js index fe91cb39f4ace..d1c53899db879 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/split.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/split.js @@ -37,7 +37,7 @@ const SPLIT_MODES = { }; export class Split extends Component { - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { const { model } = nextProps; if (model.split_mode === 'filters' && !model.split_filters) { this.props.onChange({ 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 2b42c22ad7c43..1d42b77336933 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 edbeba5d176ae..4efd5bb65451c 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 cfcac5d4908a0..299e7c12f931a 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/components/vis_types/table/config.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js index d0e3acb2ef2fa..a917a93ffbf5e 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js @@ -44,7 +44,7 @@ import { getDefaultQueryLanguage } from '../../lib/get_default_query_language'; import { QueryBarWrapper } from '../../query_bar_wrapper'; class TableSeriesConfigUI extends Component { - componentWillMount() { + UNSAFE_componentWillMount() { const { model } = this.props; if (!model.color_rules || (model.color_rules && model.color_rules.length === 0)) { this.props.onChange({ 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 464cec744eed7..4d029553145da 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 12f14ea3cb816..8740f84dab3b9 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 42ff653e9bfe9..75a65e131797d 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 7bd400e8bed15..f4032af1838c1 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/server/usage/index.js b/src/legacy/core_plugins/vis_type_timeseries/public/services.ts similarity index 62% rename from src/legacy/server/usage/index.js rename to src/legacy/core_plugins/vis_type_timeseries/public/services.ts index 2a02070a55f95..dcc7de4098bdd 100644 --- a/src/legacy/server/usage/index.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/services.ts @@ -17,15 +17,15 @@ * under the License. */ -import { CollectorSet } from './classes'; +import { I18nStart, SavedObjectsStart, UiSettingsClientContract } from 'src/core/public'; +import { createGetterSetter } from '../../../../plugins/kibana_utils/public'; -export function usageMixin(kbnServer, server, config) { - const collectorSet = new CollectorSet(server, undefined, config); +export const [getUISettings, setUISettings] = createGetterSetter( + 'UISettings' +); - /* - * 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 }); -} +export const [getSavedObjectsClient, setSavedObjectsClient] = createGetterSetter( + 'SavedObjectsClient' +); + +export const [getI18n, setI18n] = createGetterSetter('I18n'); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js index 68b738503c9b3..3be2e9daed58c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js @@ -42,7 +42,7 @@ export class Gauge extends Component { this.handleResize = this.handleResize.bind(this); } - componentWillMount() { + UNSAFE_componentWillMount() { const check = () => { this.timeout = setTimeout(() => { const newState = calculateCoordinates(this.inner, this.resize, this.state); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js index aa4ac99243397..d66eddae253a1 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge_vis.js @@ -37,7 +37,7 @@ export class GaugeVis extends Component { this.handleResize = this.handleResize.bind(this); } - componentWillMount() { + UNSAFE_componentWillMount() { const check = () => { this.timeout = setTimeout(() => { const newState = calculateCoordinates(this.inner, this.resize, this.state); diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js index d6831769c6a6a..004d59efca333 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/metric.js @@ -37,7 +37,7 @@ export class Metric extends Component { this.handleResize = this.handleResize.bind(this); } - componentWillMount() { + UNSAFE_componentWillMount() { const check = () => { this.timeout = setTimeout(() => { const newState = calculateCoordinates(this.inner, this.resize, this.state); 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 191f35d2e03ea..6a1a5431f2e51 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 67fbba7f161d3..9001164afe820 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 83ae31bf87400..26380bf2b9d94 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 0d5290ddbefc7..9ab5f820cec31 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 f15cdf23fe15b..40648a137c141 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 ca79f547890f9..f38c03c50c307 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/src/legacy/core_plugins/visualizations/public/legacy_imports.ts b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts new file mode 100644 index 0000000000000..92d8ac2c7db3a --- /dev/null +++ b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts @@ -0,0 +1,30 @@ +/* + * 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 { 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 79e6903b18644..e6ca678db563d 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 0000000000000..532e8106b023f --- /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 26c894f914910..5a9a1830ebdf3 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 8aec7adeaec9a..95fd31049d233 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 8ba3f66ec4d86..5a964caa46b4b 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 480796c377175..4558621dc6615 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 18d633e1b5fb2..9e72cb3402a5a 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 ceb0ca5316354..2e9d055858a48 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 16cec5d2d9e91..1e86aa64d1fa8 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 1d5e2de6dafe3..bd8ce8381608c 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 d2e545bde8241..5e03b205e76e4 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 b2478655d1cfe..bc16c6acbc20c 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 608a8b9ce8aa7..e733bad2c0127 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 ca9540b4d3737..0f9e9c11a9dbc 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 f5e162f50dcf6..6d32a6df5f1ec 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 5d7ab12a677cf..88c5768a0b4e4 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 0e77c3ce88385..ccf6aaf152ea4 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/services.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts new file mode 100644 index 0000000000000..63afbca71a280 --- /dev/null +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/services.ts @@ -0,0 +1,30 @@ +/* + * 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 { I18nStart, UiSettingsClientContract } from 'src/core/public'; +import { TypesStart } from './types'; +import { createGetterSetter } from '../../../../../../plugins/kibana_utils/public'; + +export const [getUISettings, setUISettings] = createGetterSetter( + 'UISettings' +); + +export const [getTypes, setTypes] = createGetterSetter('Types'); + +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 30d806bc305af..2dc657ecde05b 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 29f809a65edda..2566e25c17343 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 e16562641801e..6e6a2174d6ad1 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 304289a5cfa07..558fff7d0076e 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 9cc4e30d4252d..6f2730476956e 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 f7ed56b10c267..e5f182c931d80 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 8561a6c3f1007..bcb5e7be2597a 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 aded85384fd85..116e588c5ade6 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 4310dff7359ef..92d9e601bbb35 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 dda20878605e5..ba2f835599bc9 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 91272ead1d2c1..366d36860731c 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 5c4bcc05bbc38..0000000000000 --- 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 5a86992f0af71..0000000000000 --- 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/lib/get_collector_logger.js b/src/legacy/server/usage/lib/get_collector_logger.js deleted file mode 100644 index 023bf6bf635a8..0000000000000 --- 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 7db3cd4506503..0000000000000 --- 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 2f6951891f84d..b4ea0ec8bc465 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 03e358af5f1f0..6a87b2e88ac4c 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 { - public componentWillMount() { + public UNSAFE_componentWillMount() { chrome.setVisible(false); } diff --git a/src/legacy/ui/public/index_patterns/__mocks__/index.ts b/src/legacy/ui/public/index_patterns/__mocks__/index.ts index f51ae86b5c9a7..9ff09835e48da 100644 --- a/src/legacy/ui/public/index_patterns/__mocks__/index.ts +++ b/src/legacy/ui/public/index_patterns/__mocks__/index.ts @@ -31,17 +31,4 @@ export const { } = dataSetup.indexPatterns!; // static code -export { - CONTAINS_SPACES, - getFromSavedObject, - getRoutes, - IndexPatternSelect, - validateIndexPattern, - ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - IndexPatternAlreadyExists, - IndexPatternMissingIndices, - NoDefaultIndexPattern, - NoDefinedIndexPatterns, -} from '../../../../core_plugins/data/public'; +export { getFromSavedObject, getRoutes } from '../../../../core_plugins/data/public'; diff --git a/src/legacy/ui/public/index_patterns/index.ts b/src/legacy/ui/public/index_patterns/index.ts index 690a9cffaa138..06001667c9e53 100644 --- a/src/legacy/ui/public/index_patterns/index.ts +++ b/src/legacy/ui/public/index_patterns/index.ts @@ -30,23 +30,18 @@ export const { FieldList, // only used in Discover and StubIndexPattern flattenHitWrapper, formatHitProvider, - IndexPatternSelect, // only used in x-pack/plugin/maps and input control vis } = data.indexPatterns; +import { indexPatterns } from '../../../../plugins/data/public'; + // static code -export { - CONTAINS_SPACES, - getFromSavedObject, - getRoutes, - validateIndexPattern, - ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - IndexPatternAlreadyExists, - IndexPatternMissingIndices, - NoDefaultIndexPattern, - NoDefinedIndexPatterns, -} from '../../../core_plugins/data/public'; +export { getFromSavedObject, getRoutes } from '../../../core_plugins/data/public'; + +export const INDEX_PATTERN_ILLEGAL_CHARACTERS = indexPatterns.ILLEGAL_CHARACTERS; +export const INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE = indexPatterns.ILLEGAL_CHARACTERS_VISIBLE; +export const ILLEGAL_CHARACTERS = indexPatterns.ILLEGAL_CHARACTERS_KEY; +export const CONTAINS_SPACES = indexPatterns.CONTAINS_SPACES_KEY; +export const validateIndexPattern = indexPatterns.validate; // types export { diff --git a/src/legacy/ui/public/indices/validate/validate_index.test.js b/src/legacy/ui/public/indices/validate/validate_index.test.js index 62a6c8610fd40..f81ba9d4bcab5 100644 --- a/src/legacy/ui/public/indices/validate/validate_index.test.js +++ b/src/legacy/ui/public/indices/validate/validate_index.test.js @@ -18,7 +18,6 @@ */ jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); import { INDEX_ILLEGAL_CHARACTERS_VISIBLE } from '../constants'; diff --git a/src/legacy/ui/public/inspector/build_tabular_inspector_data.ts b/src/legacy/ui/public/inspector/build_tabular_inspector_data.ts index ceeed4e9e1d67..b09ed60e7186f 100644 --- a/src/legacy/ui/public/inspector/build_tabular_inspector_data.ts +++ b/src/legacy/ui/public/inspector/build_tabular_inspector_data.ts @@ -19,7 +19,7 @@ import { set } from 'lodash'; // @ts-ignore -import { createFilter } from '../vis/vis_filters'; +import { createFilter } from '../../../core_plugins/visualizations/public'; import { FormattedData } from './adapters'; interface Column { diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 773d4283cad88..e816b1858f21e 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -26,6 +26,10 @@ const mockObservable = () => { }; }; +const mockComponent = () => { + return null; +}; + export const mockUiSettings = { get: (item) => { return mockUiSettings[item]; @@ -76,6 +80,14 @@ export const npSetup = { timefilter: sinon.fake(), history: sinon.fake(), }, + savedQueries: { + saveQuery: sinon.fake(), + getAllSavedQueries: sinon.fake(), + findSavedQueries: sinon.fake(), + getSavedQuery: sinon.fake(), + deleteSavedQuery: sinon.fake(), + getSavedQueryCount: sinon.fake(), + } }, fieldFormats: getFieldFormatsRegistry(mockUiSettings), }, @@ -139,6 +151,9 @@ export const npStart = { getProvider: sinon.fake(), }, getSuggestions: sinon.fake(), + ui: { + IndexPatternSelect: mockComponent, + }, query: { filterManager: { getFetches$: sinon.fake(), diff --git a/src/legacy/ui/public/time_buckets/time_buckets.js b/src/legacy/ui/public/time_buckets/time_buckets.js index c875d7820373e..b40bc754bcd79 100644 --- a/src/legacy/ui/public/time_buckets/time_buckets.js +++ b/src/legacy/ui/public/time_buckets/time_buckets.js @@ -19,7 +19,6 @@ import _ from 'lodash'; import moment from 'moment'; -import chrome from '../chrome'; import { npStart } from 'ui/new_platform'; import { parseInterval } from '../utils/parse_interval'; import { calcAutoIntervalLessThan, calcAutoIntervalNear } from './calc_auto_interval'; @@ -29,9 +28,7 @@ import { } from './calc_es_interval'; import { FIELD_FORMAT_IDS } from '../../../../plugins/data/public'; -const config = chrome.getUiSettingsClient(); - -const getConfig = (...args) => 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 93a0bf026ae5d..46074f2c5197b 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 e847a95ead478..664a0b3e02a00 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 791ce2563e0f1..85798549691a5 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 05cd030f7d100..aaee86c378984 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/vis_filters/index.js b/src/legacy/ui/public/vis/vis_filters/index.js deleted file mode 100644 index 1236e88a52803..0000000000000 --- 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 deleted file mode 100644 index afb3fea15a430..0000000000000 --- a/src/legacy/ui/public/vis/vis_types/__tests__/vislib_vis_legend.js +++ /dev/null @@ -1,159 +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 'jquery'; -import _ from 'lodash'; -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; -import { Vis } from '../../vis'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -describe('visualize_legend directive', function () { - let $rootScope; - let $compile; - let $timeout; - let $el; - let indexPattern; - let fixtures; - - beforeEach(ngMock.module('kibana', 'kibana/table_vis')); - beforeEach(ngMock.inject(function (Private, $injector) { - $rootScope = $injector.get('$rootScope'); - $compile = $injector.get('$compile'); - $timeout = $injector.get('$timeout'); - fixtures = require('fixtures/fake_hierarchical_data'); - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - })); - - // basically a parameterized beforeEach - function init(vis, esResponse) { - vis.aggs.aggs.forEach(function (agg, i) { agg.id = 'agg_' + (i + 1); }); - - $rootScope.vis = vis; - $rootScope.visData = esResponse; - $rootScope.uiState = require('fixtures/mock_ui_state'); - $el = $(''); - $compile($el)($rootScope); - $rootScope.$apply(); - } - - function CreateVis(params, requiresSearch) { - const vis = new Vis(indexPattern, { - type: 'line', - params: params || {}, - aggs: [ - { type: 'count', schema: 'metric' }, - { - type: 'range', - schema: 'bucket', - params: { - field: 'bytes', - ranges: [ - { from: 0, to: 1000 }, - { from: 1000, to: 2000 } - ] - } - } - ] - }); - - vis.type.requestHandler = requiresSearch ? 'default' : 'none'; - vis.type.responseHandler = 'none'; - vis.type.requiresSearch = false; - return vis; - } - - it('calls highlight handler when highlight function is called', () => { - const requiresSearch = false; - const vis = new CreateVis(null, requiresSearch); - init(vis, fixtures.oneRangeBucket); - let highlight = 0; - _.set(vis, 'vislibVis.handler.highlight', () => { highlight++; }); - $rootScope.highlight({ currentTarget: null }); - expect(highlight).to.equal(1); - }); - - it('calls unhighlight handler when unhighlight function is called', () => { - const requiresSearch = false; - const vis = new CreateVis(null, requiresSearch); - init(vis, fixtures.oneRangeBucket); - let unhighlight = 0; - _.set(vis, 'vislibVis.handler.unHighlight', () => { unhighlight++; }); - $rootScope.unhighlight({ currentTarget: null }); - expect(unhighlight).to.equal(1); - }); - - describe('setColor function', () => { - beforeEach(() => { - const requiresSearch = false; - const vis = new CreateVis(null, requiresSearch); - init(vis, fixtures.oneRangeBucket); - }); - - it('sets the color in the UI state', () => { - $rootScope.setColor('test', '#ffffff'); - const colors = $rootScope.uiState.get('vis.colors'); - expect(colors.test).to.equal('#ffffff'); - }); - }); - - describe('toggleLegend function', () => { - let vis; - - beforeEach(() => { - const requiresSearch = false; - vis = new CreateVis(null, requiresSearch); - init(vis, fixtures.oneRangeBucket); - }); - - it('sets the color in the UI state', () => { - $rootScope.open = true; - $rootScope.toggleLegend(); - $rootScope.$digest(); - $timeout.flush(); - $timeout.verifyNoPendingTasks(); - let legendOpen = $rootScope.uiState.get('vis.legendOpen'); - expect(legendOpen).to.equal(false); - - $rootScope.toggleLegend(); - $rootScope.$digest(); - $timeout.flush(); - $timeout.verifyNoPendingTasks(); - legendOpen = $rootScope.uiState.get('vis.legendOpen'); - expect(legendOpen).to.equal(true); - }); - }); - - it('does not update scope.data if visData is null', () => { - $rootScope.visData = null; - $rootScope.$digest(); - expect($rootScope.data).to.not.equal(null); - }); - - it('works without handler set', () => { - const requiresSearch = false; - const vis = new CreateVis(null, requiresSearch); - vis.vislibVis = {}; - init(vis, fixtures.oneRangeBucket); - expect(() => { - $rootScope.highlight({ currentTarget: null }); - $rootScope.unhighlight({ currentTarget: null }); - }).to.not.throwError(); - }); -}); diff --git a/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss b/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss index 8de88959cfb59..4d7c0e2bdcadb 100644 --- a/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss +++ b/src/legacy/ui/public/vis/vis_types/_vislib_vis_legend.scss @@ -11,9 +11,11 @@ $visLegendLineHeight: $euiSize; position: absolute; bottom: 0; left: 0; + display: flex; + padding: $euiSizeXS; background-color: $euiColorEmptyShade; transition: opacity $euiAnimSpeedFast $euiAnimSlightResistance, - background-color $euiAnimSpeedFast $euiAnimSlightResistance $euiAnimSpeedExtraSlow; + background-color $euiAnimSpeedFast $euiAnimSlightResistance $euiAnimSpeedExtraSlow; &:focus { box-shadow: none; @@ -22,13 +24,11 @@ $visLegendLineHeight: $euiSize; } .visLegend__toggle--isOpen { - background-color: transparentize($euiColorDarkestShade, .9); + background-color: transparentize($euiColorDarkestShade, 0.9); opacity: 1; } - .visLegend { - @include euiFontSizeXS; display: flex; min-height: 0; height: 100%; @@ -46,27 +46,30 @@ $visLegendLineHeight: $euiSize; } } -/** - * 1. Position the .visLegend__valueDetails absolutely against the legend item - * 2. Make sure the .visLegend__valueDetails is visible outside the list bounds - * 3. Make sure the currently selected item is top most in z level - */ .visLegend__list { @include euiScrollBar; display: flex; - line-height: $visLegendLineHeight; width: $visLegendWidth; // Must be a hard-coded width for the chart to get its correct dimensions flex: 1 1 auto; flex-direction: column; overflow-x: hidden; overflow-y: auto; + .visLegend__button { + font-size: $euiFontSizeXS; + text-align: left; + overflow: hidden; // Ensures scrollbars don't appear because EuiButton__text has a high line-height + + .visLegend__valueTitle { + vertical-align: middle; + } + } + .visLib--legend-top &, .visLib--legend-bottom & { width: auto; flex-direction: row; flex-wrap: wrap; - overflow: visible; /* 2 */ .visLegend__value { flex-grow: 0; @@ -79,74 +82,19 @@ $visLegendLineHeight: $euiSize; } } -.visLegend__value { - cursor: pointer; - padding: $euiSizeXS; - display: flex; - flex-shrink: 0; - position: relative; /* 1 */ - - > * { - width: 100%; - } - - &.disabled { - opacity: 0.5; - } +.visLegend__valueColorPicker { + width: ($euiSizeL * 8); // 8 columns } -.visLegend__valueTitle { - @include euiTextTruncate; // ALWAYS truncate - color: $visTextColor; +.visLegend__valueColorPickerDot { + cursor: pointer; &:hover { - text-decoration: underline; - } -} - -.visLegend__valueTitle--full ~ .visLegend__valueDetails { - z-index: 2; /* 3 */ -} - -.visLegend__valueDetails { - background-color: $euiColorEmptyShade; - - .visLib--legend-left &, - .visLib--legend-right & { - margin-top: $euiSizeXS; - border-bottom: $euiBorderThin; - } - - .visLib--legend-top &, - .visLib--legend-bottom & { - @include euiBottomShadowMedium; - position: absolute; /* 1 */ - border-radius: $euiBorderRadius; + transform: scale(1.4); } - .visLib--legend-bottom & { - bottom: $visLegendLineHeight + 2 * $euiSizeXS; - } - - .visLib--legend-top & { - margin-top: $euiSizeXS; - } -} - -.visLegend__valueColorPicker { - width: $visColorPickerWidth; - margin: auto; - - .visLegend__valueColorPickerDot { - $colorPickerDotsPerRow: 8; - $colorPickerDotMargin: $euiSizeXS / 2; - $colorPickerDotWidth: $visColorPickerWidth / $colorPickerDotsPerRow - 2 * $colorPickerDotMargin; - - margin: $colorPickerDotMargin; - width: $colorPickerDotWidth; - - &:hover { - transform: scale(1.4); - } + &-isSelected { + border: $euiSizeXS solid; + border-radius: 100%; } } diff --git a/src/legacy/ui/public/vis/vis_types/index.js b/src/legacy/ui/public/vis/vis_types/index.js index 9c4ae82d58e6f..113aa903df52f 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 9d06409fda622..0000000000000 --- 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.html b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.html deleted file mode 100644 index 70d2a796658f2..0000000000000 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.html +++ /dev/null @@ -1,99 +0,0 @@ -
- -
    - -
  • - -
    -
    - - {{legendData.label}} -
    - -
    -
    - - - -
    - -
    - - - - -
    - -
    -
    - -
  • -
-
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 deleted file mode 100644 index ce94c3a5f68ab..0000000000000 --- a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.js +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { 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 { htmlIdGenerator, keyCodes } from '@elastic/eui'; -import { getTableAggs } from '../../visualize/loader/pipeline_helpers/utilities'; - -export const CUSTOM_LEGEND_VIS_TYPES = ['heatmap', 'gauge']; - -uiModules.get('kibana') - .directive('vislibLegend', function ($timeout) { - - return { - restrict: 'E', - template: html, - link: function ($scope) { - $scope.legendId = htmlIdGenerator()('legend'); - $scope.open = $scope.uiState.get('vis.legendOpen', true); - - $scope.$watch('visData', function (data) { - if (!data) return; - $scope.data = data; - }); - - $scope.$watch('refreshLegend', () => { - refresh(); - }); - - $scope.highlight = function (event) { - const el = event.currentTarget; - const handler = $scope.vis.vislibVis.handler; - - //there is no guarantee that a Chart will set the highlight-function on its handler - if (!handler || typeof handler.highlight !== 'function') { - return; - } - handler.highlight.call(el, handler.el); - }; - - $scope.unhighlight = function (event) { - const el = event.currentTarget; - const handler = $scope.vis.vislibVis.handler; - //there is no guarantee that a Chart will set the unhighlight-function on its handler - if (!handler || typeof handler.unHighlight !== 'function') { - return; - } - handler.unHighlight.call(el, handler.el); - }; - - $scope.setColor = function (label, color) { - const colors = $scope.uiState.get('vis.colors') || {}; - if (colors[label] === color) delete colors[label]; - else colors[label] = color; - $scope.uiState.setSilent('vis.colors', null); - $scope.uiState.set('vis.colors', colors); - $scope.uiState.emit('colorChanged'); - refresh(); - }; - - $scope.toggleLegend = function () { - const bwcAddLegend = $scope.vis.params.addLegend; - const bwcLegendStateDefault = bwcAddLegend == null ? true : bwcAddLegend; - $scope.open = !$scope.uiState.get('vis.legendOpen', bwcLegendStateDefault); - // open should be applied on template before we update uiState - $timeout(() => { - $scope.uiState.set('vis.legendOpen', $scope.open); - }); - }; - - $scope.filter = function (legendData, negate) { - $scope.vis.API.events.filter({ data: legendData.values, negate: negate }); - }; - - $scope.canFilter = function (legendData) { - if (CUSTOM_LEGEND_VIS_TYPES.includes($scope.vis.vislibVis.visConfigArgs.type)) { - return false; - } - const filters = createFiltersFromEvent({ aggConfigs: $scope.tableAggs, data: legendData.values }); - return filters.length; - }; - - /** - * Keydown listener for a legend entry. - * This will close the details panel of this legend entry when pressing Escape. - */ - $scope.onLegendEntryKeydown = function (event) { - if (event.keyCode === keyCodes.ESCAPE) { - event.preventDefault(); - event.stopPropagation(); - $scope.shownDetails = undefined; - } - }; - - $scope.toggleDetails = function (label) { - $scope.shownDetails = $scope.shownDetails === label ? undefined : label; - }; - - $scope.areDetailsVisible = function (label) { - return $scope.shownDetails === label; - }; - - $scope.colors = [ - '#3F6833', '#967302', '#2F575E', '#99440A', '#58140C', '#052B51', '#511749', '#3F2B5B', //6 - '#508642', '#CCA300', '#447EBC', '#C15C17', '#890F02', '#0A437C', '#6D1F62', '#584477', //2 - '#629E51', '#E5AC0E', '#64B0C8', '#E0752D', '#BF1B00', '#0A50A1', '#962D82', '#614D93', //4 - '#7EB26D', '#EAB839', '#6ED0E0', '#EF843C', '#E24D42', '#1F78C1', '#BA43A9', '#705DA0', // Normal - '#9AC48A', '#F2C96D', '#65C5DB', '#F9934E', '#EA6460', '#5195CE', '#D683CE', '#806EB7', //5 - '#B7DBAB', '#F4D598', '#70DBED', '#F9BA8F', '#F29191', '#82B5D8', '#E5A8E2', '#AEA2E0', //3 - '#E0F9D7', '#FCEACA', '#CFFAFF', '#F9E2D2', '#FCE2DE', '#BADFF4', '#F9D9F9', '#DEDAF7' //7 - ]; - - function refresh() { - const vislibVis = $scope.vis.vislibVis; - if (!vislibVis || !vislibVis.visConfig) { - $scope.labels = [{ label: i18n.translate('common.ui.vis.visTypes.legend.loadingLabel', { defaultMessage: 'loading…' }) }]; - return; - } // make sure vislib is defined at this point - - if ($scope.uiState.get('vis.legendOpen') == null && $scope.vis.params.addLegend != null) { - $scope.open = $scope.vis.params.addLegend; - } - - if (CUSTOM_LEGEND_VIS_TYPES.includes(vislibVis.visConfigArgs.type)) { - const labels = vislibVis.getLegendLabels(); - if (labels) { - $scope.labels = _.map(labels, label => { - return { label: label }; - }); - } - } else { - $scope.labels = getLabels($scope.data, vislibVis.visConfigArgs.type); - } - - if (vislibVis.visConfig) { - $scope.getColor = vislibVis.visConfig.data.getColorFunc(); - } - - $scope.tableAggs = getTableAggs($scope.vis); - } - - // Most of these functions were moved directly from the old Legend class. Not a fan of this. - function getLabels(data, type) { - if (!data) return []; - data = data.columns || data.rows || [data]; - if (type === 'pie') return Data.prototype.pieNames(data); - return getSeriesLabels(data); - } - - function getSeriesLabels(data) { - const values = data.map(function (chart) { - return chart.series; - }) - .reduce(function (a, b) { - return a.concat(b); - }, []); - return _.compact(_.uniq(values, 'label')).map(label => { - return { - ...label, - values: [label.values[0].seriesRaw], - }; - }); - } - } - }; - }); diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/__snapshots__/vislib_vis_legend.test.tsx.snap b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/__snapshots__/vislib_vis_legend.test.tsx.snap new file mode 100644 index 0000000000000..f2c9f4e1b53ec --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/__snapshots__/vislib_vis_legend.test.tsx.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`VisLegend Component Legend closed should match the snapshot 1`] = `"
"`; + +exports[`VisLegend Component Legend open should match the snapshot 1`] = `"
"`; diff --git a/packages/kbn-es-query/index.d.ts b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts similarity index 88% rename from packages/kbn-es-query/index.d.ts rename to src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts index 9bbd0a193dfed..ebf132f0ab697 100644 --- a/packages/kbn-es-query/index.d.ts +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts @@ -17,4 +17,5 @@ * under the License. */ -export * from './src'; +export { VisLegend } from './vislib_vis_legend'; +export { CUSTOM_LEGEND_VIS_TYPES } from './models'; diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/models.ts b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/models.ts new file mode 100644 index 0000000000000..1c8d5baf011a3 --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/models.ts @@ -0,0 +1,84 @@ +/* + * 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 interface LegendItem { + label: string; + values: any[]; +} + +export const CUSTOM_LEGEND_VIS_TYPES = ['heatmap', 'gauge']; + +export const legendColors: string[] = [ + '#3F6833', + '#967302', + '#2F575E', + '#99440A', + '#58140C', + '#052B51', + '#511749', + '#3F2B5B', // 6 + '#508642', + '#CCA300', + '#447EBC', + '#C15C17', + '#890F02', + '#0A437C', + '#6D1F62', + '#584477', // 2 + '#629E51', + '#E5AC0E', + '#64B0C8', + '#E0752D', + '#BF1B00', + '#0A50A1', + '#962D82', + '#614D93', // 4 + '#7EB26D', + '#EAB839', + '#6ED0E0', + '#EF843C', + '#E24D42', + '#1F78C1', + '#BA43A9', + '#705DA0', // Normal + '#9AC48A', + '#F2C96D', + '#65C5DB', + '#F9934E', + '#EA6460', + '#5195CE', + '#D683CE', + '#806EB7', // 5 + '#B7DBAB', + '#F4D598', + '#70DBED', + '#F9BA8F', + '#F29191', + '#82B5D8', + '#E5A8E2', + '#AEA2E0', // 3 + '#E0F9D7', + '#FCEACA', + '#CFFAFF', + '#F9E2D2', + '#FCE2DE', + '#BADFF4', + '#F9D9F9', + '#DEDAF7', // 7 +]; diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx new file mode 100644 index 0000000000000..66acc9e247e63 --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.test.tsx @@ -0,0 +1,279 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { mount, ReactWrapper } from 'enzyme'; +import { act } from '@testing-library/react-hooks'; + +import { I18nProvider } from '@kbn/i18n/react'; +import { EuiButtonGroup } from '@elastic/eui'; + +import { VisLegend, VisLegendProps } from '../vislib_vis_legend/vislib_vis_legend'; +import { legendColors } from './models'; + +jest.mock('@elastic/eui', () => ({ + ...jest.requireActual('@elastic/eui'), + htmlIdGenerator: jest.fn().mockReturnValue(() => 'legendId'), +})); + +jest.mock('../../../visualize/loader/pipeline_helpers/utilities', () => ({ + getTableAggs: jest.fn(), +})); +jest.mock('../../../../../core_plugins/visualizations/public', () => ({ + createFiltersFromEvent: jest.fn().mockReturnValue(['yes']), +})); + +const vis = { + params: { + addLegend: true, + }, + API: { + events: { + filter: jest.fn(), + }, + }, +}; +const vislibVis = { + handler: { + highlight: jest.fn(), + unHighlight: jest.fn(), + }, + getLegendLabels: jest.fn(), + visConfigArgs: { + type: 'area', + }, + visConfig: { + data: { + getColorFunc: jest.fn().mockReturnValue(() => 'red'), + }, + }, +}; + +const visData = { + series: [ + { + label: 'A', + values: [ + { + seriesRaw: 'valuesA', + }, + ], + }, + { + label: 'B', + values: [ + { + seriesRaw: 'valuesB', + }, + ], + }, + ], +}; + +const mockState = new Map(); +const uiState = { + get: jest + .fn() + .mockImplementation((key, fallback) => (mockState.has(key) ? mockState.get(key) : fallback)), + set: jest.fn().mockImplementation((key, value) => mockState.set(key, value)), + emit: jest.fn(), + setSilent: jest.fn(), +}; + +const getWrapper = (props?: Partial) => + mount( + + + + ); + +const getLegendItems = (wrapper: ReactWrapper) => wrapper.find('.visLegend__button'); + +describe('VisLegend Component', () => { + let wrapper: ReactWrapper; + + afterEach(() => { + mockState.clear(); + jest.clearAllMocks(); + }); + + describe('Legend open', () => { + beforeEach(() => { + mockState.set('vis.legendOpen', true); + wrapper = getWrapper(); + }); + + it('should match the snapshot', () => { + expect(wrapper.html()).toMatchSnapshot(); + }); + }); + + describe('Legend closed', () => { + beforeEach(() => { + mockState.set('vis.legendOpen', false); + wrapper = getWrapper(); + }); + + it('should match the snapshot', () => { + expect(wrapper.html()).toMatchSnapshot(); + }); + }); + + describe('Highlighting', () => { + beforeEach(() => { + wrapper = getWrapper(); + }); + + it('should call highlight handler when legend item is focused', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('focus'); + + expect(vislibVis.handler.highlight).toHaveBeenCalledTimes(1); + }); + + it('should call highlight handler when legend item is hovered', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('mouseEnter'); + + expect(vislibVis.handler.highlight).toHaveBeenCalledTimes(1); + }); + + it('should call unHighlight handler when legend item is blurred', () => { + let first = getLegendItems(wrapper).first(); + first.simulate('focus'); + first = getLegendItems(wrapper).first(); + first.simulate('blur'); + + expect(vislibVis.handler.unHighlight).toHaveBeenCalledTimes(1); + }); + + it('should call unHighlight handler when legend item is unhovered', () => { + const first = getLegendItems(wrapper).first(); + + act(() => { + first.simulate('mouseEnter'); + first.simulate('mouseLeave'); + }); + + expect(vislibVis.handler.unHighlight).toHaveBeenCalledTimes(1); + }); + + it('should work with no handlers set', () => { + const newVis = { + ...vis, + vislibVis: { + ...vislibVis, + handler: null, + }, + }; + + expect(() => { + wrapper = getWrapper({ vis: newVis }); + const first = getLegendItems(wrapper).first(); + first.simulate('focus'); + first.simulate('blur'); + }).not.toThrow(); + }); + }); + + describe('Filtering', () => { + beforeEach(() => { + wrapper = getWrapper(); + }); + + it('should filter out when clicked', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('click'); + const filterGroup = wrapper.find(EuiButtonGroup).first(); + filterGroup.getElement().props.onChange('filterIn'); + + expect(vis.API.events.filter).toHaveBeenCalledWith({ data: ['valuesA'], negate: false }); + expect(vis.API.events.filter).toHaveBeenCalledTimes(1); + }); + + it('should filter in when clicked', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('click'); + const filterGroup = wrapper.find(EuiButtonGroup).first(); + filterGroup.getElement().props.onChange('filterOut'); + + expect(vis.API.events.filter).toHaveBeenCalledWith({ data: ['valuesA'], negate: true }); + expect(vis.API.events.filter).toHaveBeenCalledTimes(1); + }); + }); + + describe('Toggles details', () => { + beforeEach(() => { + wrapper = getWrapper(); + }); + + it('should show details when clicked', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('click'); + + expect(wrapper.exists('.visLegend__valueDetails')).toBe(true); + }); + }); + + describe('setColor', () => { + beforeEach(() => { + wrapper = getWrapper(); + }); + + it('sets the color in the UI state', () => { + const first = getLegendItems(wrapper).first(); + first.simulate('click'); + + const popover = wrapper.find('.visLegend__valueDetails').first(); + const firstColor = popover.find('.visLegend__valueColorPickerDot').first(); + firstColor.simulate('click'); + + const colors = mockState.get('vis.colors'); + + expect(colors.A).toBe(legendColors[0]); + }); + }); + + describe('toggleLegend function', () => { + it('click should show legend once toggled from hidden', () => { + mockState.set('vis.legendOpen', false); + wrapper = getWrapper(); + const toggleButton = wrapper.find('.visLegend__toggle').first(); + toggleButton.simulate('click'); + + expect(wrapper.exists('.visLegend__list')).toBe(true); + }); + + it('click should hide legend once toggled from shown', () => { + mockState.set('vis.legendOpen', true); + wrapper = getWrapper(); + const toggleButton = wrapper.find('.visLegend__toggle').first(); + toggleButton.simulate('click'); + + expect(wrapper.exists('.visLegend__list')).toBe(false); + }); + }); +}); diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx new file mode 100644 index 0000000000000..f0100e369f050 --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend.tsx @@ -0,0 +1,264 @@ +/* + * 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, { BaseSyntheticEvent, KeyboardEvent, PureComponent } from 'react'; +import classNames from 'classnames'; +import { compact, uniq, map } from 'lodash'; + +import { i18n } from '@kbn/i18n'; +import { EuiPopoverProps, EuiIcon, keyCodes, htmlIdGenerator } from '@elastic/eui'; + +// @ts-ignore +import { Data } from '../../../vislib/lib/data'; +// @ts-ignore +import { createFiltersFromEvent } from '../../../../../core_plugins/visualizations/public'; +import { CUSTOM_LEGEND_VIS_TYPES, LegendItem } from './models'; +import { VisLegendItem } from './vislib_vis_legend_item'; +import { getTableAggs } from '../../../visualize/loader/pipeline_helpers/utilities'; + +export interface VisLegendProps { + vis: any; + vislibVis: any; + visData: any; + uiState: any; + position: 'top' | 'bottom' | 'left' | 'right'; +} + +export interface VisLegendState { + open: boolean; + labels: any[]; + tableAggs: any[]; + selectedLabel: string | null; +} + +export class VisLegend extends PureComponent { + legendId = htmlIdGenerator()('legend'); + getColor: (label: string) => string = () => ''; + + constructor(props: VisLegendProps) { + super(props); + const open = props.uiState.get('vis.legendOpen', true); + + this.state = { + open, + labels: [], + tableAggs: [], + selectedLabel: null, + }; + } + + componentDidMount() { + this.refresh(); + } + + toggleLegend = () => { + const bwcAddLegend = this.props.vis.params.addLegend; + const bwcLegendStateDefault = bwcAddLegend == null ? true : bwcAddLegend; + const newOpen = !this.props.uiState.get('vis.legendOpen', bwcLegendStateDefault); + this.setState({ open: newOpen }); + // open should be applied on template before we update uiState + setTimeout(() => { + this.props.uiState.set('vis.legendOpen', newOpen); + }); + }; + + setColor = (label: string, color: string) => (event: BaseSyntheticEvent) => { + if ((event as KeyboardEvent).keyCode && (event as KeyboardEvent).keyCode !== keyCodes.ENTER) { + return; + } + + const colors = this.props.uiState.get('vis.colors') || {}; + if (colors[label] === color) delete colors[label]; + else colors[label] = color; + this.props.uiState.setSilent('vis.colors', null); + this.props.uiState.set('vis.colors', colors); + this.props.uiState.emit('colorChanged'); + this.refresh(); + }; + + filter = ({ values: data }: LegendItem, negate: boolean) => { + this.props.vis.API.events.filter({ data, negate }); + }; + + canFilter = (item: LegendItem): boolean => { + if (CUSTOM_LEGEND_VIS_TYPES.includes(this.props.vislibVis.visConfigArgs.type)) { + return false; + } + const filters = createFiltersFromEvent({ aggConfigs: this.state.tableAggs, data: item.values }); + return Boolean(filters.length); + }; + + toggleDetails = (label: string | null) => (event?: BaseSyntheticEvent) => { + if ( + event && + (event as KeyboardEvent).keyCode && + (event as KeyboardEvent).keyCode !== keyCodes.ENTER + ) { + return; + } + this.setState({ selectedLabel: this.state.selectedLabel === label ? null : label }); + }; + + getSeriesLabels = (data: any[]) => { + const values = data.map(chart => chart.series).reduce((a, b) => a.concat(b), []); + + return compact(uniq(values, 'label')).map((label: any) => ({ + ...label, + values: [label.values[0].seriesRaw], + })); + }; + + // Most of these functions were moved directly from the old Legend class. Not a fan of this. + getLabels = (data: any, type: string) => { + if (!data) return []; + data = data.columns || data.rows || [data]; + + if (type === 'pie') return Data.prototype.pieNames(data); + + return this.getSeriesLabels(data); + }; + + refresh = () => { + const vislibVis = this.props.vislibVis; + if (!vislibVis || !vislibVis.visConfig) { + this.setState({ + labels: [ + { + label: i18n.translate('common.ui.vis.visTypes.legend.loadingLabel', { + defaultMessage: 'loading…', + }), + }, + ], + }); + return; + } // make sure vislib is defined at this point + + if ( + this.props.uiState.get('vis.legendOpen') == null && + this.props.vis.params.addLegend != null + ) { + this.setState({ open: this.props.vis.params.addLegend }); + } + + if (CUSTOM_LEGEND_VIS_TYPES.includes(vislibVis.visConfigArgs.type)) { + const legendLabels = this.props.vislibVis.getLegendLabels(); + if (legendLabels) { + this.setState({ + labels: map(legendLabels, label => { + return { label }; + }), + }); + } + } else { + this.setState({ labels: this.getLabels(this.props.visData, vislibVis.visConfigArgs.type) }); + } + + if (vislibVis.visConfig) { + this.getColor = this.props.vislibVis.visConfig.data.getColorFunc(); + } + + this.setState({ tableAggs: getTableAggs(this.props.vis) }); + }; + + highlight = (event: BaseSyntheticEvent) => { + const el = event.currentTarget; + const handler = this.props.vislibVis && this.props.vislibVis.handler; + + // there is no guarantee that a Chart will set the highlight-function on its handler + if (!handler || typeof handler.highlight !== 'function') { + return; + } + handler.highlight.call(el, handler.el); + }; + + unhighlight = (event: BaseSyntheticEvent) => { + const el = event.currentTarget; + const handler = this.props.vislibVis && this.props.vislibVis.handler; + + // there is no guarantee that a Chart will set the unhighlight-function on its handler + if (!handler || typeof handler.unHighlight !== 'function') { + return; + } + handler.unHighlight.call(el, handler.el); + }; + + getAnchorPosition = () => { + const { position } = this.props; + + switch (position) { + case 'bottom': + return 'upCenter'; + case 'left': + return 'rightUp'; + case 'right': + return 'leftUp'; + default: + return 'downCenter'; + } + }; + + renderLegend = (anchorPosition: EuiPopoverProps['anchorPosition']) => ( +
    + {this.state.labels.map(item => ( + + ))} +
+ ); + + render() { + const { open } = this.state; + const anchorPosition = this.getAnchorPosition(); + + return ( +
+ + {open && this.renderLegend(anchorPosition)} +
+ ); + } +} diff --git a/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx new file mode 100644 index 0000000000000..7376fabfe738b --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/vislib_vis_legend_item.tsx @@ -0,0 +1,203 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { memo, BaseSyntheticEvent, KeyboardEvent } from 'react'; +import classNames from 'classnames'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiPopover, + keyCodes, + EuiIcon, + EuiSpacer, + EuiButtonEmpty, + EuiPopoverProps, + EuiButtonGroup, + EuiButtonGroupOption, +} from '@elastic/eui'; + +import { legendColors, LegendItem } from './models'; + +interface Props { + item: LegendItem; + legendId: string; + selected: boolean; + canFilter: boolean; + anchorPosition: EuiPopoverProps['anchorPosition']; + onFilter: (item: LegendItem, negate: boolean) => void; + onSelect: (label: string | null) => (event?: BaseSyntheticEvent) => void; + onHighlight: (event: BaseSyntheticEvent) => void; + onUnhighlight: (event: BaseSyntheticEvent) => void; + setColor: (label: string, color: string) => (event: BaseSyntheticEvent) => void; + getColor: (label: string) => string; +} + +const VisLegendItemComponent = ({ + item, + legendId, + selected, + canFilter, + anchorPosition, + onFilter, + onSelect, + onHighlight, + onUnhighlight, + setColor, + getColor, +}: Props) => { + /** + * Keydown listener for a legend entry. + * This will close the details panel of this legend entry when pressing Escape. + */ + const onLegendEntryKeydown = (event: KeyboardEvent) => { + if (event.keyCode === keyCodes.ESCAPE) { + event.preventDefault(); + event.stopPropagation(); + onSelect(null)(); + } + }; + + const filterOptions: EuiButtonGroupOption[] = [ + { + id: 'filterIn', + label: i18n.translate('common.ui.vis.visTypes.legend.filterForValueButtonAriaLabel', { + defaultMessage: 'Filter for value {legendDataLabel}', + values: { legendDataLabel: item.label }, + }), + iconType: 'plusInCircle', + 'data-test-subj': `legend-${item.label}-filterIn`, + }, + { + id: 'filterOut', + label: i18n.translate('common.ui.vis.visTypes.legend.filterOutValueButtonAriaLabel', { + defaultMessage: 'Filter out value {legendDataLabel}', + values: { legendDataLabel: item.label }, + }), + iconType: 'minusInCircle', + 'data-test-subj': `legend-${item.label}-filterOut`, + }, + ]; + + const handleFilterChange = (id: string) => { + onFilter(item, id !== 'filterIn'); + }; + + const renderFilterBar = () => ( + <> + + + + ); + + const button = ( + + + {item.label} + + ); + + const renderDetails = () => ( + +
+ {canFilter && renderFilterBar()} + +
+ + + + {legendColors.map(color => ( + + ))} +
+
+
+ ); + + return ( +
  • + {renderDetails()} +
  • + ); +}; + +export const VisLegendItem = memo(VisLegendItemComponent); diff --git a/src/legacy/ui/public/visualize/_index.scss b/src/legacy/ui/public/visualize/_index.scss index 192091fb04e3c..c528c1e37b412 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 99c357b53952f..0000000000000 --- 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 a1292c59ac61d..f19940726ef2d 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 f49e0f08e8732..377e2cd97b72e 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 3db23051b6ced..405754ffcb572 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 b754496793660..e4f5f1f9e216c 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 6e03c665290ae..669c5a62af726 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 9b68f5088c447..a9cd3d8b7ba26 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 1e0957d816590..e33040485bf47 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 000815b51f620..4574cd5ffd0cb 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 f91c3d97b95b4..f4ec0fe0b34c5 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 4617ee1a1c43d..e01240da87543 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 258ab9e703131..fdc40768ebe41 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 56eb45c4b1dca..937fe09903b6b 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 0000000000000..e441420760475 --- /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 1688995d46f80..253f432617972 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 07289a878e8c1..133e691b27dba 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 ee4cfab94e614..8443436cf4cfb 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 0000000000000..cf287ff2c437a --- /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 0000000000000..84500cb4ade7e --- /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 63ade9e8793a7..4f2f298c4707d 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 0000000000000..df147bad54a34 --- /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 5ba73e485ddf1..945a36d304a05 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 7a2d7fa39c152..01c1976b939ea 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 f24f24b98e7fb..a6590546e5fc5 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 f7719998ad524..80181cfc003f1 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 2361e8bb66769..ed8e40830df02 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 7718479130a8a..d48f0943082c9 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 0000000000000..e138e22b76ad3 --- /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 e0cacada7f274..4184dea62ef2c 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 d8040e464b696..cfe2f86e813ca 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 82e1aee7b775a..7c90119fcc1bc 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 0000000000000..ca9798eb6e74f --- /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 daf8032f9fe0e..720d64e11a0f8 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 25fe2bcc45a45..60fe2d6d1013c 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 0000000000000..36c40d28e55c2 --- /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 0000000000000..7e221d96b49e9 --- /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 b01a8914f68ef..86cb7e08a767c 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 687870306c873..1c17e231cace8 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 6b5f665c6e20e..dd445a33f21c5 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.ts b/src/plugins/data/public/index.ts index ace0b44378b45..eca6258099141 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -30,6 +30,7 @@ export * from '../common'; export * from './autocomplete_provider'; export * from './field_formats_provider'; +export * from './index_patterns'; export * from './types'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/filters_service.ts b/src/plugins/data/public/index_patterns/errors.ts similarity index 64% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/filters/filters_service.ts rename to src/plugins/data/public/index_patterns/errors.ts index 51709f365dbbd..3eb43eaf460cc 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/filters_service.ts +++ b/src/plugins/data/public/index_patterns/errors.ts @@ -17,28 +17,19 @@ * under the License. */ -interface SetupDependecies { - VisFiltersProvider: any; - createFilter: any; -} +/* eslint-disable */ + +import { KbnError } from '../../../kibana_utils/public'; /** - * Vis Filters Service - * - * @internal + * Tried to call a method that relies on SearchSource having an indexPattern assigned */ -export class FiltersService { - public setup({ VisFiltersProvider, createFilter }: SetupDependecies) { - return { - VisFiltersProvider, - createFilter, - }; - } +export class IndexPatternMissingIndices extends KbnError { + constructor(message: string) { + const defaultMessage = "IndexPattern's configured pattern does not match any indices"; - public stop() { - // nothing to do here yet + super( + message && message.length ? `No matching indices found: ${message}` : defaultMessage + ); } } - -/** @public */ -export type FiltersSetup = ReturnType; diff --git a/packages/kbn-es-query/babel.config.js b/src/plugins/data/public/index_patterns/index.ts similarity index 66% rename from packages/kbn-es-query/babel.config.js rename to src/plugins/data/public/index_patterns/index.ts index 68783433fc711..aedfc18db3ade 100644 --- a/packages/kbn-es-query/babel.config.js +++ b/src/plugins/data/public/index_patterns/index.ts @@ -17,19 +17,20 @@ * 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'], +import { IndexPatternMissingIndices } from './errors'; +import { + ILLEGAL_CHARACTERS_KEY, + CONTAINS_SPACES_KEY, + ILLEGAL_CHARACTERS_VISIBLE, + ILLEGAL_CHARACTERS, + validateIndexPattern, +} from './lib'; + +export const indexPatterns = { + ILLEGAL_CHARACTERS_KEY, + CONTAINS_SPACES_KEY, + ILLEGAL_CHARACTERS_VISIBLE, + ILLEGAL_CHARACTERS, + IndexPatternMissingIndices, + validate: validateIndexPattern, }; 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 3d5151752a080..4f8108575aa15 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/index_patterns/lib/get_index_pattern_title.ts b/src/plugins/data/public/index_patterns/lib/get_index_pattern_title.ts new file mode 100644 index 0000000000000..777a12c7e2884 --- /dev/null +++ b/src/plugins/data/public/index_patterns/lib/get_index_pattern_title.ts @@ -0,0 +1,33 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public'; + +export async function getIndexPatternTitle( + client: SavedObjectsClientContract, + indexPatternId: string +): Promise> { + const savedObject = (await client.get('index-pattern', indexPatternId)) as SimpleSavedObject; + + if (savedObject.error) { + throw new Error(`Unable to get index-pattern title: ${savedObject.error.message}`); + } + + return savedObject.attributes.title; +} diff --git a/src/plugins/data/public/index_patterns/lib/index.ts b/src/plugins/data/public/index_patterns/lib/index.ts new file mode 100644 index 0000000000000..3b87d91bb9fff --- /dev/null +++ b/src/plugins/data/public/index_patterns/lib/index.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { getIndexPatternTitle } from './get_index_pattern_title'; +export * from './types'; +export { validateIndexPattern } from './validate_index_pattern'; diff --git a/src/plugins/data/public/index_patterns/lib/types.ts b/src/plugins/data/public/index_patterns/lib/types.ts new file mode 100644 index 0000000000000..5eb309a1e5a9c --- /dev/null +++ b/src/plugins/data/public/index_patterns/lib/types.ts @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const ILLEGAL_CHARACTERS_KEY = 'ILLEGAL_CHARACTERS'; +export const CONTAINS_SPACES_KEY = 'CONTAINS_SPACES'; +export const ILLEGAL_CHARACTERS_VISIBLE = ['\\', '/', '?', '"', '<', '>', '|']; +export const ILLEGAL_CHARACTERS = ILLEGAL_CHARACTERS_VISIBLE.concat(' '); diff --git a/src/legacy/core_plugins/data/public/index_patterns/utils.test.ts b/src/plugins/data/public/index_patterns/lib/validate_index_pattern.test.ts similarity index 79% rename from src/legacy/core_plugins/data/public/index_patterns/utils.test.ts rename to src/plugins/data/public/index_patterns/lib/validate_index_pattern.test.ts index cff48144489f0..74e420ffeb5c0 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/utils.test.ts +++ b/src/plugins/data/public/index_patterns/lib/validate_index_pattern.test.ts @@ -17,24 +17,21 @@ * under the License. */ -import { - CONTAINS_SPACES, - ILLEGAL_CHARACTERS, - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, - validateIndexPattern, -} from './utils'; +import { CONTAINS_SPACES_KEY, ILLEGAL_CHARACTERS_KEY, ILLEGAL_CHARACTERS_VISIBLE } from './types'; + +import { validateIndexPattern } from './validate_index_pattern'; describe('Index Pattern Utils', () => { describe('Validation', () => { it('should not allow space in the pattern', () => { const errors = validateIndexPattern('my pattern'); - expect(errors[CONTAINS_SPACES]).toBe(true); + expect(errors[CONTAINS_SPACES_KEY]).toBe(true); }); it('should not allow illegal characters', () => { - INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE.forEach(char => { + ILLEGAL_CHARACTERS_VISIBLE.forEach(char => { const errors = validateIndexPattern(`pattern${char}`); - expect(errors[ILLEGAL_CHARACTERS]).toEqual([char]); + expect(errors[ILLEGAL_CHARACTERS_KEY]).toEqual([char]); }); }); diff --git a/src/plugins/data/public/index_patterns/lib/validate_index_pattern.ts b/src/plugins/data/public/index_patterns/lib/validate_index_pattern.ts new file mode 100644 index 0000000000000..70f5971c91bd5 --- /dev/null +++ b/src/plugins/data/public/index_patterns/lib/validate_index_pattern.ts @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ILLEGAL_CHARACTERS_VISIBLE, CONTAINS_SPACES_KEY, ILLEGAL_CHARACTERS_KEY } from './types'; + +function indexPatternContainsSpaces(indexPattern: string): boolean { + return indexPattern.includes(' '); +} + +function findIllegalCharacters(indexPattern: string): string[] { + const illegalCharacters = ILLEGAL_CHARACTERS_VISIBLE.reduce((chars: string[], char: string) => { + if (indexPattern.includes(char)) { + chars.push(char); + } + return chars; + }, []); + + return illegalCharacters; +} + +export function validateIndexPattern(indexPattern: string) { + const errors: Record = {}; + + const illegalCharacters = findIllegalCharacters(indexPattern); + + if (illegalCharacters.length) { + errors[ILLEGAL_CHARACTERS_KEY] = illegalCharacters; + } + + if (indexPatternContainsSpaces(indexPattern)) { + errors[CONTAINS_SPACES_KEY] = true; + } + + return errors; +} diff --git a/src/plugins/data/public/mocks.ts b/src/plugins/data/public/mocks.ts index ff5c96c2d89ed..ceb57b4a3a564 100644 --- a/src/plugins/data/public/mocks.ts +++ b/src/plugins/data/public/mocks.ts @@ -66,6 +66,9 @@ const createStartContract = (): Start => { search: { search: jest.fn() }, fieldFormats: fieldFormatsMock as FieldFormatsStart, query: queryStartMock, + ui: { + IndexPatternSelect: jest.fn(), + }, }; return startContract; }; diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index 3aa9cd9a0bcb4..35d8edc488467 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -25,6 +25,7 @@ import { getSuggestionsProvider } from './suggestions_provider'; import { SearchService } from './search/search_service'; import { FieldFormatsService } from './field_formats_provider'; import { QueryService } from './query'; +import { createIndexPatternSelect } from './ui/index_pattern_select'; export class DataPublicPlugin implements Plugin { private readonly autocomplete = new AutocompleteProviderRegister(); @@ -58,7 +59,10 @@ export class DataPublicPlugin implements Plugin { const startContract = { filterManager: jest.fn() as any, timefilter: timefilterServiceMock.createStartContract(), + savedQueries: jest.fn() as any, }; return startContract; diff --git a/src/plugins/data/public/query/query_service.ts b/src/plugins/data/public/query/query_service.ts index 206f8ac284ec3..ebef8b8d45050 100644 --- a/src/plugins/data/public/query/query_service.ts +++ b/src/plugins/data/public/query/query_service.ts @@ -17,10 +17,11 @@ * under the License. */ -import { UiSettingsClientContract } from 'src/core/public'; +import { CoreStart } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { FilterManager } from './filter_manager'; import { TimefilterService, TimefilterSetup } from './timefilter'; +import { createSavedQueryService } from './saved_query/saved_query_service'; /** * Query Service @@ -29,9 +30,8 @@ import { TimefilterService, TimefilterSetup } from './timefilter'; export interface QueryServiceDependencies { storage: IStorageWrapper; - uiSettings: UiSettingsClientContract; + uiSettings: CoreStart['uiSettings']; } - export class QueryService { filterManager!: FilterManager; timefilter!: TimefilterSetup; @@ -51,10 +51,11 @@ export class QueryService { }; } - public start() { + public start(savedObjects: CoreStart['savedObjects']) { return { filterManager: this.filterManager, timefilter: this.timefilter, + savedQueries: createSavedQueryService(savedObjects.client), }; } diff --git a/src/plugins/data/public/query/saved_query/index.ts b/src/plugins/data/public/query/saved_query/index.ts new file mode 100644 index 0000000000000..f9b58e137b276 --- /dev/null +++ b/src/plugins/data/public/query/saved_query/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { SavedQuery, SavedQueryAttributes, SavedQueryService, SavedQueryTimeFilter } from './types'; +export { createSavedQueryService } from './saved_query_service'; diff --git a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts similarity index 98% rename from src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts rename to src/plugins/data/public/query/saved_query/saved_query_service.test.ts index 415da8a2c32cc..ecb3fc2d606ec 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.test.ts +++ b/src/plugins/data/public/query/saved_query/saved_query_service.test.ts @@ -17,9 +17,8 @@ * under the License. */ -import { SavedQueryAttributes } from '../index'; import { createSavedQueryService } from './saved_query_service'; -import { esFilters } from '../../../../../../../plugins/data/public'; +import { esFilters, SavedQueryAttributes } from '../..'; const savedQueryAttributes: SavedQueryAttributes = { title: 'foo', diff --git a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.ts b/src/plugins/data/public/query/saved_query/saved_query_service.ts similarity index 88% rename from src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.ts rename to src/plugins/data/public/query/saved_query/saved_query_service.ts index 2668ce911c371..434efe80ecd8c 100644 --- a/src/legacy/core_plugins/data/public/search/search_bar/lib/saved_query_service.ts +++ b/src/plugins/data/public/query/saved_query/saved_query_service.ts @@ -17,9 +17,8 @@ * under the License. */ -import { SavedObjectAttributes } from 'src/core/server'; -import { SavedObjectsClientContract } from 'src/core/public'; -import { SavedQueryAttributes, SavedQuery } from '../index'; +import { SavedObjectsClientContract, SavedObjectAttributes } from 'src/core/public'; +import { SavedQueryAttributes, SavedQuery, SavedQueryService } from './types'; type SerializedSavedQueryAttributes = SavedObjectAttributes & SavedQueryAttributes & { @@ -29,22 +28,6 @@ type SerializedSavedQueryAttributes = SavedObjectAttributes & }; }; -export interface SavedQueryService { - saveQuery: ( - attributes: SavedQueryAttributes, - config?: { overwrite: boolean } - ) => Promise; - getAllSavedQueries: () => Promise; - findSavedQueries: ( - searchText?: string, - perPage?: number, - activePage?: number - ) => Promise; - getSavedQuery: (id: string) => Promise; - deleteSavedQuery: (id: string) => Promise<{}>; - getSavedQueryCount: () => Promise; -} - export const createSavedQueryService = ( savedObjectsClient: SavedObjectsClientContract ): SavedQueryService => { diff --git a/src/plugins/data/public/query/saved_query/types.ts b/src/plugins/data/public/query/saved_query/types.ts new file mode 100644 index 0000000000000..c278c2476c2e7 --- /dev/null +++ b/src/plugins/data/public/query/saved_query/types.ts @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { RefreshInterval, TimeRange, Query, esFilters } from '../..'; + +export type SavedQueryTimeFilter = TimeRange & { + refreshInterval: RefreshInterval; +}; + +export interface SavedQuery { + id: string; + attributes: SavedQueryAttributes; +} + +export interface SavedQueryAttributes { + title: string; + description: string; + query: Query; + filters?: esFilters.Filter[]; + timefilter?: SavedQueryTimeFilter; +} + +export interface SavedQueryService { + saveQuery: ( + attributes: SavedQueryAttributes, + config?: { overwrite: boolean } + ) => Promise; + getAllSavedQueries: () => Promise; + findSavedQueries: ( + searchText?: string, + perPage?: number, + activePage?: number + ) => Promise; + getSavedQuery: (id: string) => Promise; + deleteSavedQuery: (id: string) => Promise<{}>; + getSavedQueryCount: () => Promise; +} diff --git a/src/plugins/data/public/query/timefilter/time_history.ts b/src/plugins/data/public/query/timefilter/time_history.ts index 4dabbb557e9db..fe73fd85b164d 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 01e68288bd655..d2519716dd83e 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/types.ts b/src/plugins/data/public/types.ts index a59e7f3de3588..c0c96372f9f59 100644 --- a/src/plugins/data/public/types.ts +++ b/src/plugins/data/public/types.ts @@ -24,6 +24,7 @@ import { FieldFormatsSetup, FieldFormatsStart } from './field_formats_provider'; import { ISearchSetup, ISearchStart } from './search'; import { IGetSuggestions } from './suggestions_provider/types'; import { QuerySetup, QueryStart } from './query'; +import { IndexPatternSelectProps } from './ui/index_pattern_select'; export interface DataPublicPluginSetup { autocomplete: AutocompletePublicPluginSetup; @@ -38,6 +39,9 @@ export interface DataPublicPluginStart { search: ISearchStart; fieldFormats: FieldFormatsStart; query: QueryStart; + ui: { + IndexPatternSelect: React.ComponentType; + }; } export * from './autocomplete_provider/types'; diff --git a/src/plugins/data/public/ui/index.ts b/src/plugins/data/public/ui/index.ts index 79107d1ede676..6fb8e260dd720 100644 --- a/src/plugins/data/public/ui/index.ts +++ b/src/plugins/data/public/ui/index.ts @@ -17,5 +17,9 @@ * 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'; +// temp export +export { QueryLanguageSwitcher } from './query_string_input/language_switcher'; diff --git a/src/legacy/core_plugins/data/public/index_patterns/components/index.ts b/src/plugins/data/public/ui/index_pattern_select/index.ts similarity index 100% rename from src/legacy/core_plugins/data/public/index_patterns/components/index.ts rename to src/plugins/data/public/ui/index_pattern_select/index.ts diff --git a/src/legacy/core_plugins/data/public/index_patterns/components/index_pattern_select.tsx b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx similarity index 96% rename from src/legacy/core_plugins/data/public/index_patterns/components/index_pattern_select.tsx rename to src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx index 77692d7bcaa0d..f41024ed16191 100644 --- a/src/legacy/core_plugins/data/public/index_patterns/components/index_pattern_select.tsx +++ b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx @@ -21,10 +21,10 @@ import _ from 'lodash'; import React, { Component } from 'react'; import { EuiComboBox } from '@elastic/eui'; -import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../../core/public'; -import { getIndexPatternTitle } from '../utils'; +import { SavedObjectsClientContract, SimpleSavedObject } from '../../../../../core/public'; +import { getIndexPatternTitle } from '../../index_patterns/lib'; -interface IndexPatternSelectProps { +export interface IndexPatternSelectProps { onChange: (opt: any) => void; indexPatternId: string; placeholder: string; @@ -88,7 +88,7 @@ export class IndexPatternSelect extends Component { this.fetchSelectedIndexPattern(this.props.indexPatternId); } - componentWillReceiveProps(nextProps: IndexPatternSelectProps) { + UNSAFE_componentWillReceiveProps(nextProps: IndexPatternSelectProps) { if (nextProps.indexPatternId !== this.props.indexPatternId) { this.fetchSelectedIndexPattern(nextProps.indexPatternId); } diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/language_switcher.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/data/public/query/query_bar/components/__snapshots__/language_switcher.test.tsx.snap rename to src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.test.tsx b/src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx similarity index 96% rename from src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.test.tsx rename to src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx index ab210071870ca..e3ec5212abfd2 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/language_switcher.test.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { QueryLanguageSwitcher } from './language_switcher'; import { KibanaContextProvider } from 'src/plugins/kibana_react/public'; -import { coreMock } from '../../../../../../../core/public/mocks'; +import { coreMock } from '../../../../../core/public/mocks'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; const startMock = coreMock.createStart(); diff --git a/src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.tsx b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx similarity index 98% rename from src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.tsx rename to src/plugins/data/public/ui/query_string_input/language_switcher.tsx index 31b0e375eaac6..d86a8a970a8e7 100644 --- a/src/legacy/core_plugins/data/public/query/query_bar/components/language_switcher.tsx +++ b/src/plugins/data/public/ui/query_string_input/language_switcher.tsx @@ -31,7 +31,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useState } from 'react'; -import { useKibana } from '../../../../../../../plugins/kibana_react/public'; +import { useKibana } from '../../../../kibana_react/public'; interface Props { language: string; 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 dc7ebfc7b37ea..591176bf133fa 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 27e3eb1eebd1b..fd29de4573ff0 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 ea360fc8fd72e..7fb2fdf25104a 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 32860e7cb390b..e4cccbcde4fb8 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/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index cad095a6b0814..2b48bf237829c 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -102,7 +102,7 @@ export class EmbeddablePanel extends React.Component { } } - public componentWillMount() { + public UNSAFE_componentWillMount() { this.mounted = true; const { embeddable } = this.props; const { parent } = embeddable; diff --git a/src/plugins/eui_utils/public/eui_utils.test.tsx b/src/plugins/eui_utils/public/eui_utils.test.tsx index 019ca4fcbc18d..a42eba838fe23 100644 --- a/src/plugins/eui_utils/public/eui_utils.test.tsx +++ b/src/plugins/eui_utils/public/eui_utils.test.tsx @@ -18,7 +18,7 @@ */ import { BehaviorSubject } from 'rxjs'; -import { renderHook, act } from 'react-hooks-testing-library'; +import { renderHook, act } from '@testing-library/react-hooks'; import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; import { EuiUtils } from './eui_utils'; diff --git a/src/plugins/expressions/public/execute.test.ts b/src/plugins/expressions/public/execute.test.ts index b60c4aed89fcf..6700ec38df940 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 26db8753e6403..217618bc3a177 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 b4f0a509c81b6..3989f4ed7d698 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 200249b60c773..0342713f7627b 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 3a28256d57162..7471326cdd749 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 6b5acc8405fd2..56eb43a9bd133 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 364d5f587bb6f..62bde12490fbe 100644 --- a/src/plugins/expressions/public/render.ts +++ b/src/plugins/expressions/public/render.ts @@ -17,46 +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 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 RenderId | RenderError | 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) => { @@ -80,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 { @@ -115,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); } }; @@ -134,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 6deaccadfdb76..4d6bee1e375e0 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 a1a42aa85e670..75ec4826ea45a 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 d86e042bca15c..66a3da48dbee9 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/exit_full_screen_button/exit_full_screen_button.tsx b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx index a880d3c6cf87c..09e702c55ac78 100644 --- a/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx +++ b/src/plugins/kibana_react/public/exit_full_screen_button/exit_full_screen_button.tsx @@ -35,7 +35,7 @@ class ExitFullScreenButtonUi extends PureComponent { } }; - public componentWillMount() { + public UNSAFE_componentWillMount() { document.addEventListener('keydown', this.onKeyDown, false); } diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts index 2d82f646c827b..46f330ea0a2c5 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/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx index dde8efa7e1106..e3be0b08ab83f 100644 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx +++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx @@ -112,7 +112,7 @@ class TableListView extends React.Component { + 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 0000000000000..dfba7b907f5fb --- /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/status_page/kibana.json b/src/plugins/status_page/kibana.json new file mode 100644 index 0000000000000..edebf8cb12239 --- /dev/null +++ b/src/plugins/status_page/kibana.json @@ -0,0 +1,6 @@ +{ + "id": "status_page", + "version": "kibana", + "server": false, + "ui": true +} diff --git a/src/plugins/status_page/public/index.ts b/src/plugins/status_page/public/index.ts new file mode 100644 index 0000000000000..db1f05cac076f --- /dev/null +++ b/src/plugins/status_page/public/index.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializer } from 'kibana/public'; +import { StatusPagePlugin, StatusPagePluginSetup, StatusPagePluginStart } from './plugin'; + +export const plugin: PluginInitializer = () => + new StatusPagePlugin(); diff --git a/src/legacy/core_plugins/data/public/search/search_service.ts b/src/plugins/status_page/public/plugin.ts similarity index 59% rename from src/legacy/core_plugins/data/public/search/search_service.ts rename to src/plugins/status_page/public/plugin.ts index 90ac288912f64..d072fd4a67c30 100644 --- a/src/legacy/core_plugins/data/public/search/search_service.ts +++ b/src/plugins/status_page/public/plugin.ts @@ -17,30 +17,23 @@ * under the License. */ -import { SavedObjectsClientContract } from 'src/core/public'; -import { createSavedQueryService } from './search_bar/lib/saved_query_service'; +import { Plugin, CoreSetup } from 'kibana/public'; -/** - * Search Service - * @internal - */ +export class StatusPagePlugin implements Plugin { + public setup(core: CoreSetup) { + const isStatusPageAnonymous = core.injectedMetadata.getInjectedVar( + 'isStatusPageAnonymous' + ) as boolean; -export class SearchService { - public setup() { - // Service requires index patterns, which are only available in `start` + if (isStatusPageAnonymous) { + core.http.anonymousPaths.register('/status'); + } } - public start(savedObjectsClient: SavedObjectsClientContract) { - return { - services: { - savedQueryService: createSavedQueryService(savedObjectsClient), - }, - }; - } + public start() {} public stop() {} } -/** @public */ - -export type SearchStart = ReturnType; +export type StatusPagePluginSetup = ReturnType; +export type StatusPagePluginStart = ReturnType; diff --git a/src/plugins/usage_collection/README.md b/src/plugins/usage_collection/README.md new file mode 100644 index 0000000000000..4502e1a6ceacf --- /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 6d53a8469b0e0..edd06b171a72c 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 0000000000000..145cd89ff884d --- /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 5cf18a8a15200..a2e400b876ff7 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 40b004f51e49a..ab723edf5b719 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 0000000000000..a87accc47535e --- /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 0d3939e1dc681..962f61474c250 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 559deaef2ce15..1e2806ea15f3b 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/src/plugins/usage_collection/server/config.ts b/src/plugins/usage_collection/server/config.ts new file mode 100644 index 0000000000000..987db1f2b0ff3 --- /dev/null +++ b/src/plugins/usage_collection/server/config.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { schema } from '@kbn/config-schema'; + +export const ConfigSchema = schema.object({ + maximumWaitTimeForAllCollectorsInS: schema.number({ defaultValue: 60 }), +}); diff --git a/src/legacy/ui/public/vis/push_filters.js b/src/plugins/usage_collection/server/index.ts similarity index 69% rename from src/legacy/ui/public/vis/push_filters.js rename to src/plugins/usage_collection/server/index.ts index 771de14f9446d..33a1a0adc6713 100644 --- a/src/legacy/ui/public/vis/push_filters.js +++ b/src/plugins/usage_collection/server/index.ts @@ -17,11 +17,11 @@ * under the License. */ -import _ from 'lodash'; +import { PluginInitializerContext } from '../../../../src/core/server'; +import { Plugin } from './plugin'; +import { ConfigSchema } from './config'; -// 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; -} +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 0000000000000..e8bbc8e512a41 --- /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 7c3e597ae12d2..a9d066f3cd49f 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/tasks/config/run.js b/tasks/config/run.js index ea5a4b01dc8a5..e4071c8b7d0ab 100644 --- a/tasks/config/run.js +++ b/tasks/config/run.js @@ -254,7 +254,7 @@ module.exports = function (grunt) { cmd: NODE, args: [ 'scripts/functional_tests', - '--config', 'test/interpreter_functional/config.js', + '--config', 'test/interpreter_functional/config.ts', '--bail', '--debug', '--kibana-install-dir', KIBANA_INSTALL_DIR, diff --git a/test/functional/page_objects/visualize_page.js b/test/functional/page_objects/visualize_page.js index 81d26a4b69478..a6792670fdb3f 100644 --- a/test/functional/page_objects/visualize_page.js +++ b/test/functional/page_objects/visualize_page.js @@ -1186,8 +1186,13 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli } async getLegendEntries() { - const legendEntries = await find.allByCssSelector('.visLegend__valueTitle', defaultFindTimeout * 2); - return await Promise.all(legendEntries.map(async chart => await chart.getAttribute('data-label'))); + const legendEntries = await find.allByCssSelector( + '.visLegend__button', + defaultFindTimeout * 2 + ); + return await Promise.all( + legendEntries.map(async chart => await chart.getAttribute('data-label')) + ); } async openLegendOptionColors(name) { @@ -1217,7 +1222,7 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli async toggleLegend(show = true) { await retry.try(async () => { - const isVisible = find.byCssSelector('vislib-legend'); + const isVisible = find.byCssSelector('.visLegend'); if ((show && !isVisible) || (!show && isVisible)) { await testSubjects.click('vislibToggleLegend'); } @@ -1227,7 +1232,9 @@ export function VisualizePageProvider({ getService, getPageObjects, updateBaseli async filterLegend(name) { await this.toggleLegend(); await testSubjects.click(`legend-${name}`); - await testSubjects.click(`legend-${name}-filterIn`); + const filters = await testSubjects.find(`legend-${name}-filters`); + const [filterIn] = await filters.findAllByCssSelector(`input`); + await filterIn.click(); await this.waitForVisualizationRenderingStabilized(); } diff --git a/test/functional/services/browser.ts b/test/functional/services/browser.ts index a8ce4270d4205..ab686f4d5ffec 100644 --- a/test/functional/services/browser.ts +++ b/test/functional/services/browser.ts @@ -470,7 +470,10 @@ export async function BrowserProvider({ getService }: FtrProviderContext) { ); } - public async executeAsync(fn: string | ((...args: any[]) => R), ...args: any[]): Promise { + public async executeAsync( + fn: string | ((...args: any[]) => Promise), + ...args: any[] + ): Promise { return await driver.executeAsyncScript( fn, ...cloneDeep(args, arg => { diff --git a/test/interpreter_functional/README.md b/test/interpreter_functional/README.md index 336bfe3405a01..73df0ce4c9f04 100644 --- a/test/interpreter_functional/README.md +++ b/test/interpreter_functional/README.md @@ -3,7 +3,7 @@ This folder contains interpreter functional tests. Add new test suites into the `test_suites` folder and reference them from the -`config.js` file. These test suites work the same as regular functional test. +`config.ts` file. These test suites work the same as regular functional test. ## Run the test @@ -11,17 +11,17 @@ To run these tests during development you can use the following commands: ``` # Start the test server (can continue running) -node scripts/functional_tests_server.js --config test/interpreter_functional/config.js +node scripts/functional_tests_server.js --config test/interpreter_functional/config.ts # Start a test run -node scripts/functional_test_runner.js --config test/interpreter_functional/config.js +node scripts/functional_test_runner.js --config test/interpreter_functional/config.ts ``` # Writing tests -Look into test_suites/run_pipeline/basic.js for examples +Look into test_suites/run_pipeline/basic.ts for examples to update baseline screenshots and snapshots run with: ``` -node scripts/functional_test_runner.js --config test/interpreter_functional/config.js --updateBaselines +node scripts/functional_test_runner.js --config test/interpreter_functional/config.ts --updateBaselines ``` \ No newline at end of file diff --git a/test/interpreter_functional/config.js b/test/interpreter_functional/config.ts similarity index 76% rename from test/interpreter_functional/config.js rename to test/interpreter_functional/config.ts index e8700262e273a..0fe7df4d50715 100644 --- a/test/interpreter_functional/config.js +++ b/test/interpreter_functional/config.ts @@ -19,25 +19,26 @@ import path from 'path'; import fs from 'fs'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; -export default async function ({ readConfigFile }) { +export default async function({ readConfigFile }: FtrConfigProviderContext) { const functionalConfig = await readConfigFile(require.resolve('../functional/config')); // Find all folders in ./plugins since we treat all them as plugin folder const allFiles = fs.readdirSync(path.resolve(__dirname, 'plugins')); - const plugins = allFiles.filter(file => fs.statSync(path.resolve(__dirname, 'plugins', file)).isDirectory()); + const plugins = allFiles.filter(file => + fs.statSync(path.resolve(__dirname, 'plugins', file)).isDirectory() + ); return { - testFiles: [ - require.resolve('./test_suites/run_pipeline'), - ], + testFiles: [require.resolve('./test_suites/run_pipeline')], services: functionalConfig.get('services'), pageObjects: functionalConfig.get('pageObjects'), servers: functionalConfig.get('servers'), esTestCluster: functionalConfig.get('esTestCluster'), apps: functionalConfig.get('apps'), esArchiver: { - directory: path.resolve(__dirname, '../es_archives') + directory: path.resolve(__dirname, '../es_archives'), }, snapshots: { directory: path.resolve(__dirname, 'snapshots'), @@ -49,7 +50,9 @@ export default async function ({ readConfigFile }) { ...functionalConfig.get('kbnTestServer'), serverArgs: [ ...functionalConfig.get('kbnTestServer.serverArgs'), - ...plugins.map(pluginDir => `--plugin-path=${path.resolve(__dirname, 'plugins', pluginDir)}`), + ...plugins.map( + pluginDir => `--plugin-path=${path.resolve(__dirname, 'plugins', pluginDir)}` + ), ], }, }; diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/index.js b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/index.ts similarity index 68% rename from test/interpreter_functional/plugins/kbn_tp_run_pipeline/index.js rename to test/interpreter_functional/plugins/kbn_tp_run_pipeline/index.ts index 95d6a555ebcf0..1d5564ec06e4e 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/index.js +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/index.ts @@ -16,24 +16,34 @@ * specific language governing permissions and limitations * under the License. */ +import { Legacy } from 'kibana'; +import { + ArrayOrItem, + LegacyPluginApi, + LegacyPluginSpec, + LegacyPluginOptions, +} from 'src/legacy/plugin_discovery/types'; -export default function (kibana) { - return new kibana.Plugin({ +// eslint-disable-next-line import/no-default-export +export default function(kibana: LegacyPluginApi): ArrayOrItem { + const pluginSpec: Partial = { + id: 'kbn_tp_run_pipeline', uiExports: { app: { title: 'Run Pipeline', description: 'This is a sample plugin to test running pipeline expressions', - main: 'plugins/kbn_tp_run_pipeline/app', - } + main: 'plugins/kbn_tp_run_pipeline/legacy', + }, }, - init(server) { + init(server: Legacy.Server) { // The following lines copy over some configuration variables from Kibana // to this plugin. This will be needed when embedding visualizations, so that e.g. // region map is able to get its configuration. server.injectUiAppVars('kbn_tp_run_pipeline', async () => { - return await server.getInjectedUiAppVars('kibana'); + return server.getInjectedUiAppVars('kibana'); }); - } - }); + }, + }; + return new kibana.Plugin(pluginSpec); } 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 769acc52e207b..51b9a5c5f1786 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.12.0", + "react-dom": "^16.12.0" } } diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js deleted file mode 100644 index e9ab2a4169915..0000000000000 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; - -import { uiModules } from 'ui/modules'; -import chrome from 'ui/chrome'; - -import { RequestAdapter, DataAdapter } from 'ui/inspector/adapters'; -import { registries } from 'plugins/interpreter/registries'; -import { npStart } from 'ui/new_platform'; - -// This is required so some default styles and required scripts/Angular modules are loaded, -// or the timezone setting is correctly applied. -import 'ui/autoload/all'; - -// These are all the required uiExports you need to import in case you want to embed visualizations. -import 'uiExports/visTypes'; -import 'uiExports/visResponseHandlers'; -import 'uiExports/visRequestHandlers'; -import 'uiExports/visEditorTypes'; -import 'uiExports/visualize'; -import 'uiExports/savedObjectTypes'; -import 'uiExports/search'; -import 'uiExports/interpreter'; - -import { Main } from './components/main'; - -const app = uiModules.get('apps/kbnRunPipelinePlugin', ['kibana']); - -app.config($locationProvider => { - $locationProvider.html5Mode({ - enabled: false, - requireBase: false, - rewriteLinks: false, - }); -}); -app.config(stateManagementConfigProvider => - stateManagementConfigProvider.disable() -); - -function RootController($scope, $element) { - const domNode = $element[0]; - - // render react to DOM - render(
    , domNode); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - unmountComponentAtNode(domNode); - }); -} - -chrome.setRootController('kbnRunPipelinePlugin', RootController); diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/components/main.js b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/components/main.js deleted file mode 100644 index 3e19d3a4d78ec..0000000000000 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/components/main.js +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import React from 'react'; -import { - EuiPage, - EuiPageBody, - EuiPageContent, - EuiPageContentHeader, -} from '@elastic/eui'; -import { first } from 'rxjs/operators'; - -class Main extends React.Component { - chartDiv = React.createRef(); - - constructor(props) { - super(props); - - this.state = { - expression: '', - }; - - window.runPipeline = async (expression, context = {}, initialContext = {}) => { - this.setState({ expression }); - const adapters = { - requests: new props.RequestAdapter(), - data: new props.DataAdapter(), - }; - return await props.expressions.execute(expression, { - inspectorAdapters: adapters, - context, - searchContext: initialContext, - }).getData(); - }; - - let lastRenderHandler; - window.renderPipelineResponse = async (context = {}) => { - if (lastRenderHandler) { - lastRenderHandler.destroy(); - } - - lastRenderHandler = props.expressions.render(this.chartDiv, context); - const renderResult = await lastRenderHandler.render$.pipe(first()).toPromise(); - - if (typeof renderResult === 'object' && renderResult.type === 'error') { - return this.setState({ expression: 'Render error!\n\n' + JSON.stringify(renderResult.error) }); - } - }; - } - - - render() { - const pStyle = { - display: 'flex', - width: '100%', - height: '300px' - }; - - return ( - - - - - runPipeline tests are running ... - -
    this.chartDiv = ref} style={pStyle}/> -
    {this.state.expression}
    - - - - ); - } -} - -export { Main }; diff --git a/packages/kbn-es-query/src/index.d.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/index.ts similarity index 96% rename from packages/kbn-es-query/src/index.d.ts rename to test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/index.ts index 79e6903b18644..c4cc7175d6157 100644 --- a/packages/kbn-es-query/src/index.d.ts +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from './kuery'; +export * from './np_ready'; diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/legacy.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/legacy.ts new file mode 100644 index 0000000000000..39ce2b3077c96 --- /dev/null +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/legacy.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializerContext } from 'src/core/public'; +import { npSetup, npStart } from 'ui/new_platform'; + +import { plugin } from './np_ready'; + +// This is required so some default styles and required scripts/Angular modules are loaded, +// or the timezone setting is correctly applied. +import 'ui/autoload/all'; +// Used to run esaggs queries +import 'uiExports/fieldFormats'; +import 'uiExports/search'; +import 'uiExports/visRequestHandlers'; +import 'uiExports/visResponseHandlers'; +// Used for kibana_context function + +import 'uiExports/savedObjectTypes'; +import 'uiExports/interpreter'; + +const pluginInstance = plugin({} as PluginInitializerContext); + +export const setup = pluginInstance.setup(npSetup.core, npSetup.plugins); +export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/ui/public/vis/vis_factory.js b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/app.tsx similarity index 68% rename from src/legacy/ui/public/vis/vis_factory.js rename to test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/app.tsx index 136122f097f38..f47a7c3a256f0 100644 --- a/src/legacy/ui/public/vis/vis_factory.js +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/app.tsx @@ -17,19 +17,12 @@ * under the License. */ -import { BaseVisType, ReactVisType } from './vis_types'; +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { AppMountContext, AppMountParameters } from 'kibana/public'; +import { Main } from './components/main'; -export const visFactory = { - createBaseVisualization: (config) => { - return new BaseVisType(config); - }, - createReactVisualization: (config) => { - return new ReactVisType(config); - }, -}; - -export const VisFactoryProvider = () => { - return { - ...visFactory, - }; +export const renderApp = (context: AppMountContext, { element }: AppMountParameters) => { + render(
    , element); + return () => unmountComponentAtNode(element); }; 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 new file mode 100644 index 0000000000000..daa19f22a7023 --- /dev/null +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/app/components/main.tsx @@ -0,0 +1,122 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { EuiPage, EuiPageBody, EuiPageContent, EuiPageContentHeader } from '@elastic/eui'; +import { first } from 'rxjs/operators'; +import { + RequestAdapter, + DataAdapter, +} from '../../../../../../../../src/plugins/inspector/public/adapters'; +import { + Adapters, + Context, + ExpressionRenderHandler, + ExpressionDataHandler, + RenderId, +} from '../../types'; +import { getExpressions } from '../../services'; + +declare global { + interface Window { + runPipeline: ( + expressions: string, + context?: Context, + initialContext?: Context + ) => ReturnType; + renderPipelineResponse: (context?: Context) => Promise; + } +} + +interface State { + expression: string; +} + +class Main extends React.Component<{}, State> { + chartRef = React.createRef(); + + constructor(props: {}) { + super(props); + + this.state = { + expression: '', + }; + + window.runPipeline = async ( + expression: string, + context: Context = {}, + initialContext: Context = {} + ) => { + this.setState({ expression }); + const adapters: Adapters = { + requests: new RequestAdapter(), + data: new DataAdapter(), + }; + return getExpressions() + .execute(expression, { + inspectorAdapters: adapters, + context, + // TODO: naming / typing is confusing and doesn't match here + // searchContext is also a way to set initialContext and Context can't be set to SearchContext + searchContext: initialContext as any, + }) + .getData(); + }; + + let lastRenderHandler: ExpressionRenderHandler; + window.renderPipelineResponse = async (context = {}) => { + if (lastRenderHandler) { + lastRenderHandler.destroy(); + } + + lastRenderHandler = getExpressions().render(this.chartRef.current!, context, { + onRenderError: (el, error, handler) => { + this.setState({ + expression: 'Render error!\n\n' + JSON.stringify(error), + }); + handler.done(); + }, + }); + + return lastRenderHandler.render$.pipe(first()).toPromise(); + }; + } + + render() { + const pStyle = { + display: 'flex', + width: '100%', + height: '300px', + }; + + return ( + + + + runPipeline tests are running ... +
    +
    {this.state.expression}
    + + + + ); + } +} + +export { Main }; diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/index.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/index.ts new file mode 100644 index 0000000000000..d7a764b581c01 --- /dev/null +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/index.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializer, PluginInitializerContext } from 'src/core/public'; +import { Plugin, StartDeps } from './plugin'; +export { StartDeps }; + +export const plugin: PluginInitializer = ( + initializerContext: PluginInitializerContext +) => { + return new Plugin(initializerContext); +}; diff --git a/packages/kbn-es-query/src/kuery/ast/ast.d.ts b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/plugin.ts similarity index 53% rename from packages/kbn-es-query/src/kuery/ast/ast.d.ts rename to test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/plugin.ts index ef3d0ee828874..348ba215930b0 100644 --- a/packages/kbn-es-query/src/kuery/ast/ast.d.ts +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/plugin.ts @@ -17,34 +17,29 @@ * under the License. */ -import { JsonObject } from '..'; +import { CoreSetup, CoreStart, PluginInitializerContext } from 'src/core/public'; +import { ExpressionsStart } from './types'; +import { setExpressions } from './services'; -/** - * 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 interface StartDeps { + expressions: ExpressionsStart; } -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; +export class Plugin { + constructor(initializerContext: PluginInitializerContext) {} + + public setup({ application }: CoreSetup) { + application.register({ + id: 'kbn_tp_run_pipeline', + title: 'Run Pipeline', + async mount(context, params) { + const { renderApp } = await import('./app/app'); + return renderApp(context, params); + }, + }); + } + + public start(start: CoreStart, { expressions }: StartDeps) { + setExpressions(expressions); + } +} diff --git a/packages/kbn-es-query/src/utils/get_time_zone_from_settings.js b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/services.ts similarity index 77% rename from packages/kbn-es-query/src/utils/get_time_zone_from_settings.js rename to test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/services.ts index 1a06941ece127..657d8d5150c3a 100644 --- a/packages/kbn-es-query/src/utils/get_time_zone_from_settings.js +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/services.ts @@ -17,12 +17,7 @@ * under the License. */ -import moment from 'moment-timezone'; -const detectedTimezone = moment.tz.guess(); +import { createGetterSetter } from '../../../../../../src/plugins/kibana_utils/public/core'; +import { ExpressionsStart } from './types'; -export function getTimeZoneFromSettings(dateFormatTZ) { - if (dateFormatTZ === 'Browser') { - return detectedTimezone; - } - return dateFormatTZ; -} +export const [getExpressions, setExpressions] = createGetterSetter('Expressions'); 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 new file mode 100644 index 0000000000000..cc4190bd099fa --- /dev/null +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/np_ready/types.ts @@ -0,0 +1,37 @@ +/* + * 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 { + ExpressionsStart, + Context, + ExpressionRenderHandler, + ExpressionDataHandler, + RenderId, +} from 'src/plugins/expressions/public'; + +import { Adapters } from 'src/plugins/inspector/public'; + +export { + ExpressionsStart, + Context, + ExpressionRenderHandler, + ExpressionDataHandler, + RenderId, + Adapters, +}; diff --git a/test/interpreter_functional/test_suites/run_pipeline/basic.js b/test/interpreter_functional/test_suites/run_pipeline/basic.ts similarity index 69% rename from test/interpreter_functional/test_suites/run_pipeline/basic.js rename to test/interpreter_functional/test_suites/run_pipeline/basic.ts index 893a79956093c..77853b0bcd6a4 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/basic.js +++ b/test/interpreter_functional/test_suites/run_pipeline/basic.ts @@ -18,13 +18,16 @@ */ import expect from '@kbn/expect'; -import { expectExpressionProvider } from './helpers'; +import { ExpectExpression, expectExpressionProvider } from './helpers'; +import { FtrProviderContext } from '../../../functional/ftr_provider_context'; -// this file showcases how to use testing utilities defined in helpers.js together with the kbn_tp_run_pipeline +// this file showcases how to use testing utilities defined in helpers.ts together with the kbn_tp_run_pipeline // test plugin to write autmated tests for interprete -export default function ({ getService, updateBaselines }) { - - let expectExpression; +export default function({ + getService, + updateBaselines, +}: FtrProviderContext & { updateBaselines: boolean }) { + let expectExpression: ExpectExpression; describe('basic visualize loader pipeline expression tests', () => { before(() => { expectExpression = expectExpressionProvider({ getService, updateBaselines }); @@ -39,7 +42,12 @@ export default function ({ getService, updateBaselines }) { }); it('correctly sets timeRange', async () => { - const result = await expectExpression('correctly_sets_timerange', 'kibana', {}, { timeRange: 'test' }).getResponse(); + const result = await expectExpression( + 'correctly_sets_timerange', + 'kibana', + {}, + { timeRange: 'test' } + ).getResponse(); expect(result).to.have.property('timeRange', 'test'); }); }); @@ -60,30 +68,32 @@ export default function ({ getService, updateBaselines }) { // we can also do snapshot comparison of result of our expression // to update the snapshots run the tests with --updateBaselines - it ('runs the expression and compares final output', async () => { + it('runs the expression and compares final output', async () => { await expectExpression('final_output_test', expression).toMatchSnapshot(); }); // its also possible to check snapshot at every step of expression (after execution of each function) - it ('runs the expression and compares output at every step', async () => { + it('runs the expression and compares output at every step', async () => { await expectExpression('step_output_test', expression).steps.toMatchSnapshot(); }); // and we can do screenshot comparison of the rendered output of expression (if expression returns renderable) - it ('runs the expression and compares screenshots', async () => { + it('runs the expression and compares screenshots', async () => { await expectExpression('final_screenshot_test', expression).toMatchScreenshot(); }); // it is also possible to combine different checks - it ('runs the expression and combines different checks', async () => { - await (await expectExpression('combined_test', expression).steps.toMatchSnapshot()).toMatchScreenshot(); + it('runs the expression and combines different checks', async () => { + await ( + await expectExpression('combined_test', expression).steps.toMatchSnapshot() + ).toMatchScreenshot(); }); }); // if we want to do multiple different tests using the same data, or reusing a part of expression its // possible to retrieve the intermediate result and reuse it in later expressions describe('reusing partial results', () => { - it ('does some screenshot comparisons', async () => { + it('does some screenshot comparisons', async () => { const expression = `kibana | kibana_context | esaggs index='logstash-*' aggConfigs='[ {"id":"1","enabled":true,"type":"count","schema":"metric","params":{}}, {"id":"2","enabled":true,"type":"terms","schema":"segment","params": @@ -93,17 +103,20 @@ export default function ({ getService, updateBaselines }) { const context = await expectExpression('partial_test', expression).getResponse(); // we reuse that response to render 3 different charts and compare screenshots with baselines - const tagCloudExpr = - `tagcloud metric={visdimension 1 format="number"} bucket={visdimension 0}`; - await (await expectExpression('partial_test_1', tagCloudExpr, context).toMatchSnapshot()).toMatchScreenshot(); + const tagCloudExpr = `tagcloud metric={visdimension 1 format="number"} bucket={visdimension 0}`; + await ( + await expectExpression('partial_test_1', tagCloudExpr, context).toMatchSnapshot() + ).toMatchScreenshot(); - const metricExpr = - `metricVis metric={visdimension 1 format="number"} bucket={visdimension 0}`; - await (await expectExpression('partial_test_2', metricExpr, context).toMatchSnapshot()).toMatchScreenshot(); + const metricExpr = `metricVis metric={visdimension 1 format="number"} bucket={visdimension 0}`; + await ( + await expectExpression('partial_test_2', metricExpr, context).toMatchSnapshot() + ).toMatchScreenshot(); - const regionMapExpr = - `regionmap visConfig='{"metric":{"accessor":1,"format":{"id":"number"}},"bucket":{"accessor":0}}'`; - await (await expectExpression('partial_test_3', regionMapExpr, context).toMatchSnapshot()).toMatchScreenshot(); + const regionMapExpr = `regionmap visConfig='{"metric":{"accessor":1,"format":{"id":"number"}},"bucket":{"accessor":0}}'`; + await ( + await expectExpression('partial_test_3', regionMapExpr, context).toMatchSnapshot() + ).toMatchScreenshot(); }); }); }); diff --git a/test/interpreter_functional/test_suites/run_pipeline/helpers.js b/test/interpreter_functional/test_suites/run_pipeline/helpers.ts similarity index 55% rename from test/interpreter_functional/test_suites/run_pipeline/helpers.js rename to test/interpreter_functional/test_suites/run_pipeline/helpers.ts index 4df86d3418f1f..7fedf1723908a 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/helpers.js +++ b/test/interpreter_functional/test_suites/run_pipeline/helpers.ts @@ -18,14 +18,45 @@ */ import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../functional/ftr_provider_context'; +import { + ExpressionDataHandler, + Context, + RenderId, +} from '../../plugins/kbn_tp_run_pipeline/public/np_ready/types'; + +type UnWrapPromise = T extends Promise ? U : T; +export type ExpressionResult = UnWrapPromise>; + +export type ExpectExpression = ( + name: string, + expression: string, + context?: Context, + initialContext?: Context +) => ExpectExpressionHandler; + +export interface ExpectExpressionHandler { + toReturn: (expectedResult: ExpressionResult) => Promise; + getResponse: () => Promise; + runExpression: (step?: string, stepContext?: Context) => Promise; + steps: { + toMatchSnapshot: () => Promise; + }; + toMatchSnapshot: () => Promise; + toMatchScreenshot: () => Promise; +} // helper for testing interpreter expressions -export const expectExpressionProvider = ({ getService, updateBaselines }) => { +export function expectExpressionProvider({ + getService, + updateBaselines, +}: Pick & { updateBaselines: boolean }): ExpectExpression { const browser = getService('browser'); const screenshot = getService('screenshots'); const snapshots = getService('snapshots'); const log = getService('log'); const testSubjects = getService('testSubjects'); + /** * returns a handler object to test a given expression * @name: name of the test @@ -34,20 +65,25 @@ export const expectExpressionProvider = ({ getService, updateBaselines }) => { * @initialContext: initialContext provided to the expression * @returns handler object */ - return (name, expression, context = {}, initialContext = {}) => { + return ( + name: string, + expression: string, + context: Context = {}, + initialContext: Context = {} + ): ExpectExpressionHandler => { log.debug(`executing expression ${expression}`); const steps = expression.split('|'); // todo: we should actually use interpreter parser and get the ast - let responsePromise; + let responsePromise: Promise; - const handler = { + const handler: ExpectExpressionHandler = { /** * checks if provided object matches expression result * @param result: expected expression result * @returns {Promise} */ - toReturn: async result => { + toReturn: async (expectedResult: ExpressionResult) => { const pipelineResponse = await handler.getResponse(); - expect(pipelineResponse).to.eql(result); + expect(pipelineResponse).to.eql(expectedResult); }, /** * returns expression response @@ -63,16 +99,31 @@ export const expectExpressionProvider = ({ getService, updateBaselines }) => { * @param stepContext: context to provide to expression * @returns {Promise<*>} result of running expression */ - runExpression: async (step, stepContext) => { + runExpression: async ( + step: string = expression, + stepContext: Context = context + ): Promise => { log.debug(`running expression ${step || expression}`); - const promise = browser.executeAsync((expression, context, initialContext, done) => { - if (!context) context = {}; - if (!context.type) context.type = 'null'; - window.runPipeline(expression, context, initialContext).then(result => { - done(result); - }); - }, step || expression, stepContext || context, initialContext); - return await promise; + return browser.executeAsync( + ( + _expression: string, + _currentContext: Context & { type: string }, + _initialContext: Context, + done: (expressionResult: ExpressionResult) => void + ) => { + if (!_currentContext) _currentContext = { type: 'null' }; + if (!_currentContext.type) _currentContext.type = 'null'; + return window + .runPipeline(_expression, _currentContext, _initialContext) + .then(expressionResult => { + done(expressionResult); + return expressionResult; + }); + }, + step, + stepContext, + initialContext + ); }, steps: { /** @@ -80,17 +131,19 @@ export const expectExpressionProvider = ({ getService, updateBaselines }) => { * @returns {Promise} */ toMatchSnapshot: async () => { - let lastResponse; + let lastResponse: ExpressionResult; for (let i = 0; i < steps.length; i++) { const step = steps[i]; - lastResponse = await handler.runExpression(step, lastResponse); - const diff = await snapshots.compareAgainstBaseline(name + i, toSerializable(lastResponse), updateBaselines); + lastResponse = await handler.runExpression(step, lastResponse!); + const diff = await snapshots.compareAgainstBaseline( + name + i, + toSerializable(lastResponse!), + updateBaselines + ); expect(diff).to.be.lessThan(0.05); } if (!responsePromise) { - responsePromise = new Promise(resolve => { - resolve(lastResponse); - }); + responsePromise = Promise.resolve(lastResponse!); } return handler; }, @@ -101,7 +154,11 @@ export const expectExpressionProvider = ({ getService, updateBaselines }) => { */ toMatchSnapshot: async () => { const pipelineResponse = await handler.getResponse(); - await snapshots.compareAgainstBaseline(name, toSerializable(pipelineResponse), updateBaselines); + await snapshots.compareAgainstBaseline( + name, + toSerializable(pipelineResponse), + updateBaselines + ); return handler; }, /** @@ -111,24 +168,31 @@ export const expectExpressionProvider = ({ getService, updateBaselines }) => { toMatchScreenshot: async () => { const pipelineResponse = await handler.getResponse(); log.debug('starting to render'); - const result = await browser.executeAsync((context, done) => { - window.renderPipelineResponse(context).then(result => { - done(result); - }); - }, pipelineResponse); + const result = await browser.executeAsync( + (_context: ExpressionResult, done: (renderResult: RenderId) => void) => + window.renderPipelineResponse(_context).then(renderResult => { + done(renderResult); + return renderResult; + }), + pipelineResponse + ); log.debug('response of rendering: ', result); const chartEl = await testSubjects.find('pluginChart'); - const percentDifference = await screenshot.compareAgainstBaseline(name, updateBaselines, chartEl); + const percentDifference = await screenshot.compareAgainstBaseline( + name, + updateBaselines, + chartEl + ); expect(percentDifference).to.be.lessThan(0.1); return handler; - } + }, }; return handler; }; - function toSerializable(response) { + function toSerializable(response: ExpressionResult) { if (response.error) { // in case of error, pass through only message to the snapshot // as error could be expected and stack trace shouldn't be part of the snapshot @@ -136,4 +200,4 @@ export const expectExpressionProvider = ({ getService, updateBaselines }) => { } return response; } -}; +} diff --git a/test/interpreter_functional/test_suites/run_pipeline/index.js b/test/interpreter_functional/test_suites/run_pipeline/index.ts similarity index 82% rename from test/interpreter_functional/test_suites/run_pipeline/index.js rename to test/interpreter_functional/test_suites/run_pipeline/index.ts index 3c1ce2314f55f..031a0e3576ccc 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/index.js +++ b/test/interpreter_functional/test_suites/run_pipeline/index.ts @@ -17,7 +17,9 @@ * under the License. */ -export default function ({ getService, getPageObjects, loadTestFile }) { +import { FtrProviderContext } from '../../../functional/ftr_provider_context'; + +export default function({ getService, getPageObjects, loadTestFile }: FtrProviderContext) { const browser = getService('browser'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); @@ -25,13 +27,16 @@ export default function ({ getService, getPageObjects, loadTestFile }) { const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common', 'header']); - describe('runPipeline', function () { + describe('runPipeline', function() { this.tags(['skipFirefox']); before(async () => { await esArchiver.loadIfNeeded('../functional/fixtures/es_archiver/logstash_functional'); await esArchiver.load('../functional/fixtures/es_archiver/visualize_embedding'); - await kibanaServer.uiSettings.replace({ 'dateFormat:tz': 'Australia/North', 'defaultIndex': 'logstash-*' }); + await kibanaServer.uiSettings.replace({ + 'dateFormat:tz': 'Australia/North', + defaultIndex: 'logstash-*', + }); await browser.setWindowSize(1300, 900); await PageObjects.common.navigateToApp('settings'); await appsMenu.clickLink('Run Pipeline'); diff --git a/test/interpreter_functional/test_suites/run_pipeline/metric.js b/test/interpreter_functional/test_suites/run_pipeline/metric.ts similarity index 64% rename from test/interpreter_functional/test_suites/run_pipeline/metric.js rename to test/interpreter_functional/test_suites/run_pipeline/metric.ts index 78d571b3583be..c238bedfa28ce 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/metric.js +++ b/test/interpreter_functional/test_suites/run_pipeline/metric.ts @@ -17,18 +17,21 @@ * under the License. */ -import { expectExpressionProvider } from './helpers'; +import { ExpectExpression, expectExpressionProvider, ExpressionResult } from './helpers'; +import { FtrProviderContext } from '../../../functional/ftr_provider_context'; -export default function ({ getService, updateBaselines }) { - - let expectExpression; +export default function({ + getService, + updateBaselines, +}: FtrProviderContext & { updateBaselines: boolean }) { + let expectExpression: ExpectExpression; describe('metricVis pipeline expression tests', () => { before(() => { expectExpression = expectExpressionProvider({ getService, updateBaselines }); }); describe('correctly renders metric', () => { - let dataContext; + let dataContext: ExpressionResult; before(async () => { const expression = `kibana | kibana_context | esaggs index='logstash-*' aggConfigs='[ {"id":"1","enabled":true,"type":"count","schema":"metric","params":{}}, @@ -44,27 +47,46 @@ export default function ({ getService, updateBaselines }) { it('with invalid data', async () => { const expression = 'metricVis metric={visdimension 0}'; - await (await expectExpression('metric_invalid_data', expression).toMatchSnapshot()).toMatchScreenshot(); + await ( + await expectExpression('metric_invalid_data', expression).toMatchSnapshot() + ).toMatchScreenshot(); }); it('with single metric data', async () => { const expression = 'metricVis metric={visdimension 0}'; - await (await expectExpression('metric_single_metric_data', expression, dataContext).toMatchSnapshot()).toMatchScreenshot(); + await ( + await expectExpression( + 'metric_single_metric_data', + expression, + dataContext + ).toMatchSnapshot() + ).toMatchScreenshot(); }); it('with multiple metric data', async () => { const expression = 'metricVis metric={visdimension 0} metric={visdimension 1}'; - await (await expectExpression('metric_multi_metric_data', expression, dataContext).toMatchSnapshot()).toMatchScreenshot(); + await ( + await expectExpression( + 'metric_multi_metric_data', + expression, + dataContext + ).toMatchSnapshot() + ).toMatchScreenshot(); }); it('with metric and bucket data', async () => { const expression = 'metricVis metric={visdimension 0} bucket={visdimension 2}'; - await (await expectExpression('metric_all_data', expression, dataContext).toMatchSnapshot()).toMatchScreenshot(); + await ( + await expectExpression('metric_all_data', expression, dataContext).toMatchSnapshot() + ).toMatchScreenshot(); }); it('with percentage option', async () => { - const expression = 'metricVis metric={visdimension 0} percentage=true colorRange={range from=0 to=1000}'; - await (await expectExpression('metric_percentage', expression, dataContext).toMatchSnapshot()).toMatchScreenshot(); + const expression = + 'metricVis metric={visdimension 0} percentage=true colorRange={range from=0 to=1000}'; + await ( + await expectExpression('metric_percentage', expression, dataContext).toMatchSnapshot() + ).toMatchScreenshot(); }); }); }); diff --git a/test/interpreter_functional/test_suites/run_pipeline/tag_cloud.js b/test/interpreter_functional/test_suites/run_pipeline/tag_cloud.ts similarity index 61% rename from test/interpreter_functional/test_suites/run_pipeline/tag_cloud.js rename to test/interpreter_functional/test_suites/run_pipeline/tag_cloud.ts index 7c0e2d7190703..2451df4db6310 100644 --- a/test/interpreter_functional/test_suites/run_pipeline/tag_cloud.js +++ b/test/interpreter_functional/test_suites/run_pipeline/tag_cloud.ts @@ -17,18 +17,21 @@ * under the License. */ -import { expectExpressionProvider } from './helpers'; +import { ExpectExpression, expectExpressionProvider, ExpressionResult } from './helpers'; +import { FtrProviderContext } from '../../../functional/ftr_provider_context'; -export default function ({ getService, updateBaselines }) { - - let expectExpression; +export default function({ + getService, + updateBaselines, +}: FtrProviderContext & { updateBaselines: boolean }) { + let expectExpression: ExpectExpression; describe('tag cloud pipeline expression tests', () => { before(() => { expectExpression = expectExpressionProvider({ getService, updateBaselines }); }); describe('correctly renders tagcloud', () => { - let dataContext; + let dataContext: ExpressionResult; before(async () => { const expression = `kibana | kibana_context | esaggs index='logstash-*' aggConfigs='[ {"id":"1","enabled":true,"type":"count","schema":"metric","params":{}}, @@ -41,27 +44,39 @@ export default function ({ getService, updateBaselines }) { it('with invalid data', async () => { const expression = 'tagcloud metric={visdimension 0}'; - await (await expectExpression('tagcloud_invalid_data', expression).toMatchSnapshot()).toMatchScreenshot(); + await ( + await expectExpression('tagcloud_invalid_data', expression).toMatchSnapshot() + ).toMatchScreenshot(); }); it('with just metric data', async () => { const expression = 'tagcloud metric={visdimension 0}'; - await (await expectExpression('tagcloud_metric_data', expression, dataContext).toMatchSnapshot()).toMatchScreenshot(); + await ( + await expectExpression('tagcloud_metric_data', expression, dataContext).toMatchSnapshot() + ).toMatchScreenshot(); }); it('with metric and bucket data', async () => { const expression = 'tagcloud metric={visdimension 0} bucket={visdimension 1}'; - await (await expectExpression('tagcloud_all_data', expression, dataContext).toMatchSnapshot()).toMatchScreenshot(); + await ( + await expectExpression('tagcloud_all_data', expression, dataContext).toMatchSnapshot() + ).toMatchScreenshot(); }); it('with font size options', async () => { - const expression = 'tagcloud metric={visdimension 0} bucket={visdimension 1} minFontSize=20 maxFontSize=40'; - await (await expectExpression('tagcloud_fontsize', expression, dataContext).toMatchSnapshot()).toMatchScreenshot(); + const expression = + 'tagcloud metric={visdimension 0} bucket={visdimension 1} minFontSize=20 maxFontSize=40'; + await ( + await expectExpression('tagcloud_fontsize', expression, dataContext).toMatchSnapshot() + ).toMatchScreenshot(); }); it('with scale and orientation options', async () => { - const expression = 'tagcloud metric={visdimension 0} bucket={visdimension 1} scale="log" orientation="multiple"'; - await (await expectExpression('tagcloud_options', expression, dataContext).toMatchSnapshot()).toMatchScreenshot(); + const expression = + 'tagcloud metric={visdimension 0} bucket={visdimension 1} scale="log" orientation="multiple"'; + await ( + await expectExpression('tagcloud_options', expression, dataContext).toMatchSnapshot() + ).toMatchScreenshot(); }); }); }); 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 41e1e6baca0ec..fd0ce478eb6fb 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.12.0" } } 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 c2d8ed7f5f9c1..c24dd077b447e 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 a0b03e52640fc..98df7f4b246dc 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.12.0" }, "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 952d06c4873d4..32f441ba6ccda 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.12.0" }, "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 c16847dab9dc2..a3c9d9d63e353 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/test/visual_regression/tests/discover/chart_visualization.js b/test/visual_regression/tests/discover/chart_visualization.js index 540d95973b547..c90f29c66acb8 100644 --- a/test/visual_regression/tests/discover/chart_visualization.js +++ b/test/visual_regression/tests/discover/chart_visualization.js @@ -27,6 +27,7 @@ export default function ({ getService, getPageObjects }) { const kibanaServer = getService('kibanaServer'); const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); const visualTesting = getService('visualTesting'); + const find = getService('find'); const defaultSettings = { defaultIndex: 'logstash-*', 'discover:sampleSize': 1 @@ -48,10 +49,12 @@ export default function ({ getService, getPageObjects }) { describe('query', function () { this.tags(['skipFirefox']); + let renderCounter = 0; it('should show bars in the correct time zone', async function () { await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); await PageObjects.discover.waitUntilSearchingHasFinished(); + await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`); await visualTesting.snapshot({ show: ['discoverChart'], }); @@ -61,6 +64,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.discover.setChartInterval('Hourly'); + await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`); await visualTesting.snapshot({ show: ['discoverChart'], }); @@ -70,6 +74,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.discover.setChartInterval('Daily'); + await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`); await visualTesting.snapshot({ show: ['discoverChart'], }); @@ -79,6 +84,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.discover.setChartInterval('Weekly'); + await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`); await visualTesting.snapshot({ show: ['discoverChart'], }); @@ -92,6 +98,7 @@ export default function ({ getService, getPageObjects }) { }); await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); await PageObjects.discover.waitUntilSearchingHasFinished(); + await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`); await visualTesting.snapshot({ show: ['discoverChart'], }); @@ -101,6 +108,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.discover.setChartInterval('Monthly'); + await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`); await visualTesting.snapshot({ show: ['discoverChart'], }); @@ -110,6 +118,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.discover.setChartInterval('Yearly'); + await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`); await visualTesting.snapshot({ show: ['discoverChart'], }); @@ -119,6 +128,7 @@ export default function ({ getService, getPageObjects }) { await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); await PageObjects.discover.waitUntilSearchingHasFinished(); await PageObjects.discover.setChartInterval('Auto'); + await find.byCssSelector(`.echChart[data-ech-render-count="${++renderCounter}"]`); await visualTesting.snapshot({ show: ['discoverChart'], }); diff --git a/x-pack/dev-tools/jest/setup/setup_test.js b/x-pack/dev-tools/jest/setup/setup_test.js index 533ea58a561ac..f54be89f30955 100644 --- a/x-pack/dev-tools/jest/setup/setup_test.js +++ b/x-pack/dev-tools/jest/setup/setup_test.js @@ -10,3 +10,4 @@ */ import 'jest-styled-components'; +import '@testing-library/jest-dom/extend-expect'; diff --git a/x-pack/legacy/plugins/actions/README.md b/x-pack/legacy/plugins/actions/README.md index 150cc4c0472b7..2eec667ce95c4 100644 --- a/x-pack/legacy/plugins/actions/README.md +++ b/x-pack/legacy/plugins/actions/README.md @@ -19,10 +19,9 @@ action types. ## Usage -1. Enable the actions plugin in the `kibana.yml` by setting `xpack.actions.enabled: true`. -2. Develop and register an action type (see action types -> example). -3. Create an action by using the RESTful API (see actions -> create action). -4. Use alerts to execute actions or execute manually (see firing actions). +1. Develop and register an action type (see action types -> example). +2. Create an action by using the RESTful API (see actions -> create action). +3. Use alerts to execute actions or execute manually (see firing actions). ## Kibana Actions Configuration Implemented under the [Actions Config](./server/actions_config.ts). diff --git a/x-pack/legacy/plugins/actions/index.ts b/x-pack/legacy/plugins/actions/index.ts index a58c936c63749..98d4d9f84a729 100644 --- a/x-pack/legacy/plugins/actions/index.ts +++ b/x-pack/legacy/plugins/actions/index.ts @@ -33,7 +33,7 @@ export function actions(kibana: any) { config(Joi: Root) { return Joi.object() .keys({ - enabled: Joi.boolean().default(false), + enabled: Joi.boolean().default(true), whitelistedHosts: Joi.array() .items( Joi.string() diff --git a/x-pack/legacy/plugins/alerting/README.md b/x-pack/legacy/plugins/alerting/README.md index 40f61d11e9ace..85dbd75e14174 100644 --- a/x-pack/legacy/plugins/alerting/README.md +++ b/x-pack/legacy/plugins/alerting/README.md @@ -23,9 +23,8 @@ A Kibana alert detects a condition and executes one or more actions when that co ## Usage -1. Enable the alerting plugin in the `kibana.yml` by setting `xpack.alerting.enabled: true`. -2. Develop and register an alert type (see alert types -> example). -3. Create an alert using the RESTful API (see alerts -> create). +1. Develop and register an alert type (see alert types -> example). +2. Create an alert using the RESTful API (see alerts -> create). ## Limitations diff --git a/x-pack/legacy/plugins/alerting/index.ts b/x-pack/legacy/plugins/alerting/index.ts index b3e33f782688c..5baec07fa1182 100644 --- a/x-pack/legacy/plugins/alerting/index.ts +++ b/x-pack/legacy/plugins/alerting/index.ts @@ -34,7 +34,7 @@ export function alerting(kibana: any) { config(Joi: Root) { return Joi.object() .keys({ - enabled: Joi.boolean().default(false), + enabled: Joi.boolean().default(true), }) .default(); }, diff --git a/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.test.ts b/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.test.ts index 1063e20e4ba3b..a465aebc8bd86 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.test.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.test.ts @@ -93,6 +93,16 @@ test('createAPIKey() returns { created: false } when security is disabled', asyn expect(createAPIKeyResult).toEqual({ created: false }); }); +test('createAPIKey() returns { created: false } when security is enabled but ES security is disabled', async () => { + const factory = new AlertsClientFactory(alertsClientFactoryParams); + factory.create(KibanaRequest.from(fakeRequest), fakeRequest); + const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0]; + + securityPluginSetup.authc.createAPIKey.mockResolvedValueOnce(null); + const createAPIKeyResult = await constructorCall.createAPIKey(); + expect(createAPIKeyResult).toEqual({ created: false }); +}); + test('createAPIKey() returns an API key when security is enabled', async () => { const factory = new AlertsClientFactory({ ...alertsClientFactoryParams, @@ -105,3 +115,17 @@ test('createAPIKey() returns an API key when security is enabled', async () => { const createAPIKeyResult = await constructorCall.createAPIKey(); expect(createAPIKeyResult).toEqual({ created: true, result: { api_key: '123', id: 'abc' } }); }); + +test('createAPIKey() throws when security plugin createAPIKey throws an error', async () => { + const factory = new AlertsClientFactory({ + ...alertsClientFactoryParams, + securityPluginSetup: securityPluginSetup as any, + }); + factory.create(KibanaRequest.from(fakeRequest), fakeRequest); + const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0]; + + securityPluginSetup.authc.createAPIKey.mockRejectedValueOnce(new Error('TLS disabled')); + await expect(constructorCall.createAPIKey()).rejects.toThrowErrorMatchingInlineSnapshot( + `"TLS disabled"` + ); +}); diff --git a/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.ts b/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.ts index bacb346042187..b75d681b6586a 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.ts @@ -53,12 +53,16 @@ export class AlertsClientFactory { if (!securityPluginSetup) { return { created: false }; } + const createAPIKeyResult = await securityPluginSetup.authc.createAPIKey(request, { + name: `source: alerting, generated uuid: "${uuid.v4()}"`, + role_descriptors: {}, + }); + if (!createAPIKeyResult) { + return { created: false }; + } return { created: true, - result: (await securityPluginSetup.authc.createAPIKey(request, { - name: `source: alerting, generated uuid: "${uuid.v4()}"`, - role_descriptors: {}, - }))!, + result: createAPIKeyResult, }; }, }); diff --git a/x-pack/legacy/plugins/apm/index.ts b/x-pack/legacy/plugins/apm/index.ts index 0cac20ef340d2..1784ed22a2b4d 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/app/Main/__test__/UpdateBreadcrumbs.test.js b/x-pack/legacy/plugins/apm/public/components/app/Main/__test__/UpdateBreadcrumbs.test.js index 8ddf48e79f911..41fb12be284ad 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/Main/__test__/UpdateBreadcrumbs.test.js +++ b/x-pack/legacy/plugins/apm/public/components/app/Main/__test__/UpdateBreadcrumbs.test.js @@ -10,7 +10,6 @@ import { MemoryRouter } from 'react-router-dom'; import { UpdateBreadcrumbs } from '../UpdateBreadcrumbs'; import * as kibanaCore from '../../../../../../observability/public/context/kibana_core'; -jest.mock('ui/index_patterns'); jest.mock('ui/new_platform'); const coreMock = { diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx index d52c869b95872..18964531958f7 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/WatcherFlyout.tsx @@ -191,6 +191,7 @@ export class WatcherFlyout extends Component< ) as string; return createErrorGroupWatch({ + http: core.http, emails, schedule, serviceName, diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts index c7860b81a7b1e..f05d343ad7ba5 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/__test__/createErrorGroupWatch.test.ts @@ -7,22 +7,30 @@ import { isArray, isObject, isString } from 'lodash'; import mustache from 'mustache'; import uuid from 'uuid'; -// @ts-ignore import * as rest from '../../../../../services/rest/watcher'; import { createErrorGroupWatch } from '../createErrorGroupWatch'; import { esResponse } from './esResponse'; +import { HttpServiceBase } from 'kibana/public'; // disable html escaping since this is also disabled in watcher\s mustache implementation mustache.escape = value => value; +jest.mock('../../../../../services/rest/callApi', () => ({ + callApi: () => Promise.resolve(null) +})); + describe('createErrorGroupWatch', () => { let createWatchResponse: string; let tmpl: any; + const createWatchSpy = jest + .spyOn(rest, 'createWatch') + .mockResolvedValue(undefined); + beforeEach(async () => { jest.spyOn(uuid, 'v4').mockReturnValue(new Buffer('mocked-uuid')); - jest.spyOn(rest, 'createWatch').mockReturnValue(undefined); createWatchResponse = await createErrorGroupWatch({ + http: {} as HttpServiceBase, emails: ['my@email.dk', 'mySecond@email.dk'], schedule: { daily: { @@ -36,19 +44,19 @@ describe('createErrorGroupWatch', () => { apmIndexPatternTitle: 'myIndexPattern' }); - const watchBody = rest.createWatch.mock.calls[0][1]; + const watchBody = createWatchSpy.mock.calls[0][0].watch; const templateCtx = { payload: esResponse, metadata: watchBody.metadata }; - tmpl = renderMustache(rest.createWatch.mock.calls[0][1], templateCtx); + tmpl = renderMustache(createWatchSpy.mock.calls[0][0].watch, templateCtx); }); afterEach(() => jest.restoreAllMocks()); it('should call createWatch with correct args', () => { - expect(rest.createWatch.mock.calls[0][0]).toBe('apm-mocked-uuid'); + expect(createWatchSpy.mock.calls[0][0].id).toBe('apm-mocked-uuid'); }); it('should format slack message correctly', () => { @@ -78,7 +86,7 @@ describe('createErrorGroupWatch', () => { }); it('should return watch id', async () => { - const id = rest.createWatch.mock.calls[0][0]; + const id = createWatchSpy.mock.calls[0][0].id; expect(createWatchResponse).toEqual(id); }); }); diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/createErrorGroupWatch.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/createErrorGroupWatch.ts index e7d06403b8f8e..1d21e35f122d9 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/createErrorGroupWatch.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceDetails/ServiceIntegrations/createErrorGroupWatch.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import url from 'url'; import uuid from 'uuid'; +import { HttpServiceBase } from 'kibana/public'; import { ERROR_CULPRIT, ERROR_EXC_HANDLED, @@ -17,7 +18,6 @@ import { PROCESSOR_EVENT, SERVICE_NAME } from '../../../../../common/elasticsearch_fieldnames'; -// @ts-ignore import { createWatch } from '../../../../services/rest/watcher'; function getSlackPathUrl(slackUrl?: string) { @@ -35,6 +35,7 @@ export interface Schedule { } interface Arguments { + http: HttpServiceBase; emails: string[]; schedule: Schedule; serviceName: string; @@ -54,6 +55,7 @@ interface Actions { } export async function createErrorGroupWatch({ + http, emails = [], schedule, serviceName, @@ -250,6 +252,10 @@ export async function createErrorGroupWatch({ }; } - await createWatch(id, body); + await createWatch({ + http, + id, + watch: body + }); return id; } diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx index 118473a471561..9f48880090369 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx @@ -5,8 +5,7 @@ */ import React from 'react'; -import { render, wait, waitForElement } from 'react-testing-library'; -import 'react-testing-library/cleanup-after-each'; +import { render, wait, waitForElement } from '@testing-library/react'; import { ServiceOverview } from '..'; import * as urlParamsHooks from '../../../../hooks/useUrlParams'; import * as kibanaCore from '../../../../../../observability/public/context/kibana_core'; @@ -61,16 +60,6 @@ describe('Service Overview -> View', () => { jest.resetAllMocks(); }); - // Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 - /* eslint-disable no-console */ - const originalError = console.error; - beforeAll(() => { - console.error = jest.fn(); - }); - afterAll(() => { - console.error = originalError; - }); - it('should render services, when list is not empty', async () => { // mock rest requests coreMock.http.get.mockResolvedValueOnce({ diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx index 1f3403be70aa0..a5356be72f5e4 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionOverview/__jest__/TransactionOverview.test.tsx @@ -8,12 +8,11 @@ import React from 'react'; import { queryByLabelText, render, - queryBySelectText, getByText, getByDisplayValue, queryByDisplayValue, fireEvent -} from 'react-testing-library'; +} from '@testing-library/react'; import { omit } from 'lodash'; import { history } from '../../../../utils/history'; import { TransactionOverview } from '..'; @@ -32,16 +31,6 @@ const coreMock = ({ notifications: { toasts: { addWarning: () => {} } } } as unknown) as LegacyCoreStart; -// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 -/* eslint-disable no-console */ -const originalError = console.error; -beforeAll(() => { - console.error = jest.fn(); -}); -afterAll(() => { - console.error = originalError; -}); - function setup({ urlParams, serviceTransactionTypes @@ -107,8 +96,8 @@ describe('TransactionOverview', () => { }); // secondType is selected in the dropdown - expect(queryBySelectText(container, 'secondType')).not.toBeNull(); - expect(queryBySelectText(container, 'firstType')).toBeNull(); + expect(queryByDisplayValue(container, 'secondType')).not.toBeNull(); + expect(queryByDisplayValue(container, 'firstType')).toBeNull(); expect(getByText(container, 'firstType')).not.toBeNull(); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx index 2ce8feb08d4ad..20125afb52f48 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/KeyValueTable/__test__/KeyValueTable.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { KeyValueTable } from '..'; -import { cleanup, render } from 'react-testing-library'; +import { render } from '@testing-library/react'; function getKeys(output: ReturnType) { const keys = output.getAllByTestId('dot-key'); @@ -19,8 +19,6 @@ function getValues(output: ReturnType) { } describe('KeyValueTable', () => { - afterEach(cleanup); - it('displays key and value table', () => { const data = [ { key: 'name.first', value: 'First Name' }, 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 24d320505c994..32fbe46ac560c 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,8 +7,6 @@ import React, { useState } from 'react'; import { uniqueId, startsWith } from 'lodash'; import styled from 'styled-components'; -import { StaticIndexPattern } from 'ui/index_patterns'; -import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { fromQuery, toQuery } from '../Links/url_helpers'; // @ts-ignore @@ -17,12 +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 { + AutocompleteProvider, AutocompleteSuggestion, - AutocompleteProvider + 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; @@ -33,18 +33,15 @@ interface State { isLoadingSuggestions: boolean; } -function convertKueryToEsQuery( - kuery: string, - indexPattern: StaticIndexPattern -) { - 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, - indexPattern: StaticIndexPattern, + indexPattern: IIndexPattern, boolFilter: unknown, autocompleteProvider?: AutocompleteProvider ) { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/__test__/ManagedTable.test.js b/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/__test__/ManagedTable.test.js index ff8d54935e9b2..1b63274dd3cf4 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/__test__/ManagedTable.test.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/__test__/ManagedTable.test.js @@ -6,7 +6,7 @@ import { shallow } from 'enzyme'; import React from 'react'; -import { ManagedTable } from '..'; +import { UnoptimizedManagedTable } from '..'; describe('ManagedTable component', () => { let people; @@ -31,14 +31,14 @@ describe('ManagedTable component', () => { it('should render a page-full of items, with defaults', () => { expect( - shallow() + shallow() ).toMatchSnapshot(); }); it('should render when specifying initial values', () => { expect( shallow( - { - afterEach(cleanup); - it('should render a error with all sections', () => { const error = getError(); const output = render(); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx index 8c848722b32b2..3c851252666e0 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx @@ -5,8 +5,7 @@ */ import React from 'react'; -import 'jest-dom/extend-expect'; -import { render, cleanup } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { SpanMetadata } from '..'; import { Span } from '../../../../../../typings/es_schemas/ui/Span'; import { @@ -15,7 +14,6 @@ import { } from '../../../../../utils/testHelpers'; describe('SpanMetadata', () => { - afterEach(cleanup); describe('render', () => { it('renders', () => { const span = ({ diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx index d503929cf04d2..1e06648f21eea 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx @@ -6,9 +6,8 @@ import React from 'react'; import { TransactionMetadata } from '..'; -import { render, cleanup } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { Transaction } from '../../../../../../typings/es_schemas/ui/Transaction'; -import 'jest-dom/extend-expect'; import { expectTextsInDocument, expectTextsNotInDocument @@ -37,8 +36,6 @@ function getTransaction() { } describe('TransactionMetadata', () => { - afterEach(cleanup); - it('should render a transaction with all sections', () => { const transaction = getTransaction(); const output = render(); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx index 4398c129aa7b8..fbdd6bad3457d 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx @@ -5,14 +5,12 @@ */ import React from 'react'; -import 'jest-dom/extend-expect'; -import { render, cleanup } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { MetadataTable } from '..'; import { expectTextsInDocument } from '../../../../utils/testHelpers'; import { SectionsWithRows } from '../helper'; describe('MetadataTable', () => { - afterEach(cleanup); it('shows sections', () => { const sectionsWithRows = ([ { key: 'foo', label: 'Foo', required: true }, diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx index 4378c7fdeee0c..7a150f81580d8 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/__test__/Section.test.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import 'jest-dom/extend-expect'; -import { render } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { Section } from '../Section'; import { expectTextsInDocument } from '../../../../utils/testHelpers'; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx index e9e7466cd81a8..4bb018c760f1f 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx @@ -5,8 +5,7 @@ */ import React from 'react'; -import { render, fireEvent, cleanup } from 'react-testing-library'; -import 'react-testing-library/cleanup-after-each'; +import { render, fireEvent } from '@testing-library/react'; import { TransactionActionMenu } from '../TransactionActionMenu'; import { Transaction } from '../../../../../typings/es_schemas/ui/Transaction'; import * as Transactions from './mockData'; @@ -38,7 +37,6 @@ describe('TransactionActionMenu component', () => { afterEach(() => { jest.clearAllMocks(); - cleanup(); }); it('should always render the discover link', async () => { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx index f55d8d470351c..57e634df22837 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/useDelayedVisibility/index.test.tsx @@ -4,21 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { cleanup, renderHook } from 'react-hooks-testing-library'; +import { renderHook } from '@testing-library/react-hooks'; import { useDelayedVisibility } from '.'; -afterEach(cleanup); - -// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 -/* eslint-disable no-console */ -const originalError = console.error; -beforeAll(() => { - console.error = jest.fn(); -}); -afterAll(() => { - console.error = originalError; -}); - describe('useFetcher', () => { let hook; beforeEach(() => { diff --git a/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByBrowser.test.ts b/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByBrowser.test.ts index 38f26c2ba9fbd..4763a560e0f85 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByBrowser.test.ts +++ b/x-pack/legacy/plugins/apm/public/hooks/useAvgDurationByBrowser.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { renderHook } from 'react-hooks-testing-library'; +import { renderHook } from '@testing-library/react-hooks'; import theme from '@elastic/eui/dist/eui_theme_light.json'; import * as useFetcherModule from './useFetcher'; import { useAvgDurationByBrowser } from './useAvgDurationByBrowser'; diff --git a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx index 94c2ee09b5d17..36a8377c02527 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx +++ b/x-pack/legacy/plugins/apm/public/hooks/useFetcher.integration.test.tsx @@ -5,22 +5,12 @@ */ import React from 'react'; -import { render } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { delay, tick } from '../utils/testHelpers'; import { useFetcher } from './useFetcher'; import { KibanaCoreContext } from '../../../observability/public/context/kibana_core'; import { LegacyCoreStart } from 'kibana/public'; -// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 -/* eslint-disable no-console */ -const originalError = console.error; -beforeAll(() => { - console.error = jest.fn(); -}); -afterAll(() => { - console.error = originalError; -}); - // Wrap the hook with a provider so it can useKibanaCore const wrapper = ({ children }: { children?: React.ReactNode }) => ( { - console.error = jest.fn(); -}); -afterAll(() => { - console.error = originalError; -}); - // Wrap the hook with a provider so it can useKibanaCore const wrapper = ({ children }: { children?: React.ReactNode }) => ( & { pathname: string; forceCache?: boolean; method?: string; + body?: any; }; function fetchOptionsWithDebug(fetchOptions: FetchOptions) { @@ -26,9 +27,7 @@ function fetchOptionsWithDebug(fetchOptions: FetchOptions) { const body = isGet ? {} : { - body: JSON.stringify( - fetchOptions.body || ({} as HttpFetchOptions['body']) - ) + body: JSON.stringify(fetchOptions.body || {}) }; return { diff --git a/x-pack/legacy/plugins/apm/public/services/rest/watcher.js b/x-pack/legacy/plugins/apm/public/services/rest/watcher.ts similarity index 60% rename from x-pack/legacy/plugins/apm/public/services/rest/watcher.js rename to x-pack/legacy/plugins/apm/public/services/rest/watcher.ts index 9d68a1665912c..dfa64b5368ee9 100644 --- a/x-pack/legacy/plugins/apm/public/services/rest/watcher.js +++ b/x-pack/legacy/plugins/apm/public/services/rest/watcher.ts @@ -4,12 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ +import { HttpServiceBase } from 'kibana/public'; import { callApi } from './callApi'; -export async function createWatch(id, watch) { - return callApi({ +export async function createWatch({ + id, + watch, + http +}: { + http: HttpServiceBase; + id: string; + watch: any; +}) { + return callApi(http, { method: 'PUT', pathname: `/api/watcher/watch/${id}`, - body: JSON.stringify({ type: 'json', id, watch }) + body: { type: 'json', id, watch } }); } diff --git a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx index 321ce761422f0..8f609f41b079d 100644 --- a/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/legacy/plugins/apm/public/utils/testHelpers.tsx @@ -12,7 +12,8 @@ import { Location } from 'history'; import moment from 'moment'; import { Moment } from 'moment-timezone'; import React from 'react'; -import { render, waitForElement } from 'react-testing-library'; +import { render, waitForElement } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; import { MemoryRouter } from 'react-router-dom'; import { APMConfig } from '../../../../../plugins/apm/server'; import { LocationProvider } from '../context/LocationContext'; 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 de8846a8f9fb4..ddfb4144d9636 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 cee097d010212..a6f6d36ecfc81 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/helpers/setup_request.ts b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts index 8f19f4baed7ee..a09cdbf91ec6e 100644 --- a/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/legacy/plugins/apm/server/lib/helpers/setup_request.ts @@ -6,7 +6,6 @@ import moment from 'moment'; import { KibanaRequest } from 'src/core/server'; -import { StaticIndexPattern } from 'ui/index_patterns'; import { IIndexPattern } from 'src/plugins/data/common'; import { APMConfig } from '../../../../../../plugins/apm/server'; import { @@ -22,7 +21,7 @@ import { ProcessorEvent } from '../../../common/processor_event'; import { getDynamicIndexPattern } from '../index_pattern/get_dynamic_index_pattern'; function decodeUiFilters( - indexPattern: StaticIndexPattern | undefined, + indexPattern: IIndexPattern | undefined, uiFiltersEncoded?: string ) { if (!uiFiltersEncoded || !indexPattern) { diff --git a/x-pack/legacy/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts b/x-pack/legacy/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts index f113e645ed95f..9eb99b7c21e75 100644 --- a/x-pack/legacy/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts +++ b/x-pack/legacy/plugins/apm/server/lib/index_pattern/get_dynamic_index_pattern.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { StaticIndexPattern } from 'ui/index_patterns'; import { APICaller } from 'src/core/server'; import LRU from 'lru-cache'; import { @@ -51,7 +50,7 @@ export const getDynamicIndexPattern = async ({ pattern: patternIndices }); - const indexPattern: StaticIndexPattern = { + const indexPattern: IIndexPattern = { fields, title: indexPatternTitle }; 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 805f8f192bdb1..5d140155f75e4 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/create_api/index.ts b/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts index 2e97b01d0d108..2bbd8b6ddfb62 100644 --- a/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts +++ b/x-pack/legacy/plugins/apm/server/routes/create_api/index.ts @@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema'; import * as t from 'io-ts'; import { PathReporter } from 'io-ts/lib/PathReporter'; import { isLeft } from 'fp-ts/lib/Either'; -import { KibanaResponseFactory } from 'src/core/server'; +import { KibanaResponseFactory, RouteRegistrar } from 'src/core/server'; import { APMConfig } from '../../../../../../plugins/apm/server'; import { ServerAPI, @@ -65,7 +65,7 @@ export function createApi() { body: bodyRt && 'props' in bodyRt ? t.exact(bodyRt) : fallbackBodyRt }; - router[routerMethod]( + (router[routerMethod] as RouteRegistrar)( { path, options, diff --git a/x-pack/legacy/plugins/apm/server/routes/typings.ts b/x-pack/legacy/plugins/apm/server/routes/typings.ts index 207fe7fe5da33..9b114eba72626 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/components/inputs/code_editor.tsx b/x-pack/legacy/plugins/beats_management/public/components/inputs/code_editor.tsx index 508649d6ad22d..6ec2a7f02f3a3 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/inputs/code_editor.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/inputs/code_editor.tsx @@ -41,7 +41,7 @@ class CodeEditor extends Component< setValue(defaultValue || ''); } - public componentWillReceiveProps(nextProps: ComponentProps) { + public UNSAFE_componentWillReceiveProps(nextProps: ComponentProps) { if (nextProps.isFormSubmitted()) { this.showError(); } diff --git a/x-pack/legacy/plugins/beats_management/public/components/inputs/input.tsx b/x-pack/legacy/plugins/beats_management/public/components/inputs/input.tsx index a96fb493c4ab2..0e07c2b4960b7 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/inputs/input.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/inputs/input.tsx @@ -40,7 +40,7 @@ class FieldText extends Component< } } - public componentWillReceiveProps(nextProps: ComponentProps) { + public UNSAFE_componentWillReceiveProps(nextProps: ComponentProps) { if (nextProps.isFormSubmitted()) { this.showError(); } diff --git a/x-pack/legacy/plugins/beats_management/public/components/inputs/multi_input.tsx b/x-pack/legacy/plugins/beats_management/public/components/inputs/multi_input.tsx index 30e4290c64158..155917c894f42 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/inputs/multi_input.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/inputs/multi_input.tsx @@ -42,7 +42,7 @@ class MultiFieldText extends Component< } } - public componentWillReceiveProps(nextProps: ComponentProps) { + public UNSAFE_componentWillReceiveProps(nextProps: ComponentProps) { if (nextProps.isFormSubmitted()) { this.showError(); } diff --git a/x-pack/legacy/plugins/beats_management/public/components/inputs/select.tsx b/x-pack/legacy/plugins/beats_management/public/components/inputs/select.tsx index 65b1f8677e384..f7bf197395ccb 100644 --- a/x-pack/legacy/plugins/beats_management/public/components/inputs/select.tsx +++ b/x-pack/legacy/plugins/beats_management/public/components/inputs/select.tsx @@ -49,7 +49,7 @@ class FieldSelect extends Component< } } - public componentWillReceiveProps(nextProps: ComponentProps) { + public UNSAFE_componentWillReceiveProps(nextProps: ComponentProps) { if (nextProps.isFormSubmitted()) { this.showError(); } 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 526728bd77cac..83c610800b89b 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/beats_management/public/pages/beat/details.tsx b/x-pack/legacy/plugins/beats_management/public/pages/beat/details.tsx index 9b3707b1661f4..3eaf550cb8c77 100644 --- a/x-pack/legacy/plugins/beats_management/public/pages/beat/details.tsx +++ b/x-pack/legacy/plugins/beats_management/public/pages/beat/details.tsx @@ -52,7 +52,7 @@ class BeatDetailPageUi extends React.PureComponent { }; } - public async componentWillMount() { + public async UNSAFE_componentWillMount() { const tags = await this.props.libs.tags.getTagsWithIds(this.props.beat.tags); const blocksResult = await this.props.libs.configBlocks.getForTags( this.props.beat.tags, diff --git a/x-pack/legacy/plugins/beats_management/public/pages/beat/tags.tsx b/x-pack/legacy/plugins/beats_management/public/pages/beat/tags.tsx index 3cbb84dfb954b..672c0d89bb002 100644 --- a/x-pack/legacy/plugins/beats_management/public/pages/beat/tags.tsx +++ b/x-pack/legacy/plugins/beats_management/public/pages/beat/tags.tsx @@ -34,7 +34,7 @@ export class BeatTagsPage extends React.PureComponent { }; } - public async componentWillMount() { + public async UNSAFE_componentWillMount() { if (this.state.loading === true) { try { await this.props.beatsContainer.reload(); diff --git a/x-pack/legacy/plugins/canvas/__tests__/fixtures/kibana.js b/x-pack/legacy/plugins/canvas/__tests__/fixtures/kibana.js index ed83dbfcb75b7..141beb3d34d78 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 d7ebbd87c97e6..271fc7a979057 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/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx index 66a7aa0639dba..fb0baa22c16f4 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_picker/time_picker.tsx @@ -48,7 +48,7 @@ export class TimePicker extends Component { }; // TODO: Refactor to no longer use componentWillReceiveProps since it is being deprecated - componentWillReceiveProps({ from, to }: Props) { + UNSAFE_componentWillReceiveProps({ from, to }: Props) { if (from !== this.props.from || to !== this.props.to) { this.setState({ range: { from, to }, diff --git a/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/__tests__/workpad_telemetry.test.tsx b/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/__tests__/workpad_telemetry.test.tsx index b8779e7d44fcf..d486440c1fd7d 100644 --- a/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/__tests__/workpad_telemetry.test.tsx +++ b/x-pack/legacy/plugins/canvas/public/apps/workpad/workpad_app/__tests__/workpad_telemetry.test.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { render, cleanup } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { withUnconnectedElementsLoadedTelemetry, WorkpadLoadedMetric, @@ -63,8 +63,6 @@ describe('Elements Loaded Telemetry', () => { trackMetric.mockReset(); }); - afterEach(cleanup); - it('tracks when all resolvedArgs are completed', () => { const { rerender } = render( 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 380fe97ca9ef1..0000000000000 --- 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 c043f268af8ea..7641e51f14e56 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 7e6ef31d93ba5..ae009f9265722 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/canvas/shareable_runtime/components/__tests__/app.test.tsx b/x-pack/legacy/plugins/canvas/shareable_runtime/components/__tests__/app.test.tsx index 2ec3cfde8bd68..4b4f8f7d4de66 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/components/__tests__/app.test.tsx +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/components/__tests__/app.test.tsx @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -/* - One test relies on react-dom at a version of 16.9... it can be enabled - once renovate completes the upgrade. Relevant code has been commented out - in the meantime. +/* + One test relies on react-dom at a version of 16.9... it can be enabled + once renovate completes the upgrade. Relevant code has been commented out + in the meantime. */ import { mount, ReactWrapper } from 'enzyme'; 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 ee80875890480..660cd256cebcd 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 5ce7be59a1c9c..7fdf32144972c 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 0cca122b52316..c2fd35eea5292 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/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_add.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_add.test.js index 476f01940d892..7359a24098186 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_add.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_add.test.js @@ -8,7 +8,6 @@ import { setupEnvironment, pageHelpers, nextTick, getRandomString } from './help import { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } from '../../../../../../src/legacy/ui/public/index_patterns'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); const { setup } = pageHelpers.autoFollowPatternAdd; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_edit.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_edit.test.js index 9ef412883522a..03155f5f55000 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_edit.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_edit.test.js @@ -9,7 +9,6 @@ import { setupEnvironment, pageHelpers, nextTick } from './helpers'; import { AUTO_FOLLOW_PATTERN_EDIT } from './helpers/constants'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); const { setup } = pageHelpers.autoFollowPatternEdit; const { setup: setupAutoFollowPatternAdd } = pageHelpers.autoFollowPatternAdd; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js index 8a6d382190945..904434e46dee0 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/auto_follow_pattern_list.test.js @@ -9,7 +9,6 @@ import { setupEnvironment, pageHelpers, nextTick, findTestSubject, getRandomStri import { getAutoFollowPatternClientMock } from '../../fixtures/auto_follow_pattern'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('ui/chrome', () => ({ addBasePath: () => 'api/cross_cluster_replication', diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_add.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_add.test.js index d28d671fb2ace..0d90d4cf3d272 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_add.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_add.test.js @@ -10,7 +10,6 @@ import { RemoteClustersFormField } from '../../public/app/components'; import { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } from '../../../../../../src/legacy/ui/public/index_patterns'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); const { setup } = pageHelpers.followerIndexAdd; const { setup: setupAutoFollowPatternAdd } = pageHelpers.autoFollowPatternAdd; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_edit.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_edit.test.js index 5e74d923d3af5..de1426bf4b72f 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_edit.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_index_edit.test.js @@ -10,7 +10,6 @@ import { FollowerIndexForm } from '../../public/app/components/follower_index_fo import { FOLLOWER_INDEX_EDIT } from './helpers/constants'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); const { setup } = pageHelpers.followerIndexEdit; const { setup: setupFollowerIndexAdd } = pageHelpers.followerIndexAdd; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js index 6aef850672179..13adea4592534 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/follower_indices_list.test.js @@ -9,7 +9,6 @@ import { setupEnvironment, pageHelpers, nextTick, getRandomString } from './help import { getFollowerIndexMock } from '../../fixtures/follower_index'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('ui/chrome', () => ({ addBasePath: () => 'api/cross_cluster_replication', diff --git a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js index 35ec99846990a..5691ff3a8bc3b 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/__jest__/client_integration/home.test.js @@ -8,7 +8,6 @@ import { setupEnvironment, pageHelpers, nextTick } from './helpers'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); const { setup } = pageHelpers.home; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/app.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/app.js index f0ae17c6bb8d4..37d1305d667bf 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/app.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/app.js @@ -56,7 +56,7 @@ export class App extends Component { }; } - componentWillMount() { + UNSAFE_componentWillMount() { routing.userHasLeftApp = false; } diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.test.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.test.js index f6cc9cb3742ea..eda275ba50c1a 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/components/auto_follow_pattern_form.test.js @@ -12,7 +12,6 @@ jest.mock('../services/auto_follow_pattern_validators', () => ({ })); jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); describe(' { describe('updateFormErrors()', () => { diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.test.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.test.js index f70caf2f8080b..6c1d5c8ce171c 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.test.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/auto_follow_pattern_validators.test.js @@ -8,7 +8,6 @@ import { validateAutoFollowPattern } from './auto_follow_pattern_validators'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); describe('Auto-follow pattern validators', () => { describe('validateAutoFollowPattern()', () => { diff --git a/x-pack/legacy/plugins/file_upload/index.js b/x-pack/legacy/plugins/file_upload/index.js index 37d4ad80fa2ca..1eefc0afa8f9c 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 0baef6f8ffa40..d9819bf26faea 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 0000000000000..a2b359ae11638 --- /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 46da040dc34f0..7969dd04ce31f 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/field_manager/field_editor.tsx b/x-pack/legacy/plugins/graph/public/components/field_manager/field_editor.tsx index 5ab5c498063f1..c0ad54fecb28b 100644 --- a/x-pack/legacy/plugins/graph/public/components/field_manager/field_editor.tsx +++ b/x-pack/legacy/plugins/graph/public/components/field_manager/field_editor.tsx @@ -24,6 +24,7 @@ import { EuiForm, EuiSpacer, EuiIconTip, + EuiComboBoxOptionProps, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import classNames from 'classnames'; @@ -221,7 +222,9 @@ export function FieldEditor({ }} singleSelection={{ asPlainText: true }} isClearable={false} - options={toOptions(allFields, initialField)} + options={ + toOptions(allFields, initialField) as Array> + } selectedOptions={[ { value: currentField.name, 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 fd2004558be77..a91e91258e240 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 82e50c702997f..79ffad26cf981 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) { > - => { const { rows } = table.getMetaData('templateTable'); const templateLink = findTestSubject(rows[index].reactWrapper, 'templateDetailsLink'); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { const { href } = templateLink.props(); router.navigateTo(href!); diff --git a/x-pack/legacy/plugins/index_management/__jest__/client_integration/home.test.ts b/x-pack/legacy/plugins/index_management/__jest__/client_integration/home.test.ts index a7c0ac4181618..9e8af02b74631 100644 --- a/x-pack/legacy/plugins/index_management/__jest__/client_integration/home.test.ts +++ b/x-pack/legacy/plugins/index_management/__jest__/client_integration/home.test.ts @@ -22,9 +22,7 @@ const removeWhiteSpaceOnArrayValues = (array: any[]) => jest.mock('ui/new_platform'); -// We need to skip the tests until react 16.9.0 is released -// which supports asynchronous code inside act() -describe.skip('', () => { +describe('', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: IdxMgmtHomeTestBed; @@ -38,7 +36,6 @@ describe.skip('', () => { testBed = await setup(); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { const { component } = testBed; @@ -81,7 +78,6 @@ describe.skip('', () => { actions.selectHomeTab('templatesTab'); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { await nextTick(); component.update(); @@ -101,7 +97,6 @@ describe.skip('', () => { actions.selectHomeTab('templatesTab'); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { await nextTick(); component.update(); @@ -147,7 +142,6 @@ describe.skip('', () => { actions.selectHomeTab('templatesTab'); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { await nextTick(); component.update(); @@ -186,7 +180,6 @@ describe.skip('', () => { expect(exists('reloadButton')).toBe(true); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { actions.clickReloadButton(); await nextTick(); @@ -214,7 +207,6 @@ describe.skip('', () => { expect(exists('systemTemplatesSwitch')).toBe(true); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { form.toggleEuiSwitch('systemTemplatesSwitch'); await nextTick(); @@ -290,7 +282,6 @@ describe.skip('', () => { test('should show a warning message when attempting to delete a system template', async () => { const { component, form, actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { form.toggleEuiSwitch('systemTemplatesSwitch'); await nextTick(); @@ -328,7 +319,6 @@ describe.skip('', () => { }, }); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { confirmButton!.click(); await nextTick(); @@ -384,7 +374,6 @@ describe.skip('', () => { actions.clickCloseDetailsButton(); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { await nextTick(); component.update(); @@ -474,7 +463,6 @@ describe.skip('', () => { await actions.clickTemplateAt(0); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { await nextTick(); component.update(); diff --git a/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_clone.test.tsx b/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_clone.test.tsx index bd8d9b8e35675..997fe8cff2dac 100644 --- a/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_clone.test.tsx +++ b/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_clone.test.tsx @@ -38,9 +38,7 @@ jest.mock('@elastic/eui', () => ({ ), })); -// We need to skip the tests until react 16.9.0 is released -// which supports asynchronous code inside act() -describe.skip('', () => { +describe('', () => { let testBed: TemplateFormTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); @@ -59,7 +57,6 @@ describe.skip('', () => { testBed = await setup(); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { await nextTick(); testBed.component.update(); @@ -77,7 +74,6 @@ describe.skip('', () => { beforeEach(async () => { const { actions, component } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) // Specify index patterns, but do not change name (keep default) @@ -105,7 +101,6 @@ describe.skip('', () => { it('should send the correct payload', async () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { actions.clickSubmitButton(); await nextTick(); diff --git a/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_create.test.tsx b/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_create.test.tsx index a391811257a9f..e678b7a7f52d6 100644 --- a/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_create.test.tsx +++ b/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_create.test.tsx @@ -43,9 +43,7 @@ jest.mock('@elastic/eui', () => ({ ), })); -// We need to skip the tests until react 16.9.0 is released -// which supports asynchronous code inside act() -describe.skip('', () => { +describe('', () => { let testBed: TemplateFormTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); @@ -71,7 +69,6 @@ describe.skip('', () => { expect(find('nextButton').props().disabled).toEqual(false); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { actions.clickNextButton(); await nextTick(); @@ -90,7 +87,6 @@ describe.skip('', () => { beforeEach(async () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) await actions.completeStepOne({ name: TEMPLATE_NAME, indexPatterns: ['index1'] }); @@ -107,7 +103,6 @@ describe.skip('', () => { it('should not allow invalid json', async () => { const { form, actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { actions.completeStepTwo('{ invalidJsonString '); }); @@ -120,7 +115,6 @@ describe.skip('', () => { beforeEach(async () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) await actions.completeStepOne({ name: TEMPLATE_NAME, indexPatterns: ['index1'] }); @@ -140,7 +134,6 @@ describe.skip('', () => { it('should not allow invalid json', async () => { const { actions, form } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 3 (mappings) with invalid json await actions.completeStepThree('{ invalidJsonString '); @@ -154,7 +147,6 @@ describe.skip('', () => { beforeEach(async () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) await actions.completeStepOne({ name: TEMPLATE_NAME, indexPatterns: ['index1'] }); @@ -177,7 +169,6 @@ describe.skip('', () => { it('should not allow invalid json', async () => { const { actions, form } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 4 (aliases) with invalid json await actions.completeStepFour('{ invalidJsonString '); @@ -194,7 +185,6 @@ describe.skip('', () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) await actions.completeStepOne({ @@ -249,7 +239,6 @@ describe.skip('', () => { const { actions, exists, find } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) await actions.completeStepOne({ @@ -280,7 +269,6 @@ describe.skip('', () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) await actions.completeStepOne({ @@ -302,7 +290,6 @@ describe.skip('', () => { it('should send the correct payload', async () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { actions.clickSubmitButton(); await nextTick(); @@ -333,7 +320,6 @@ describe.skip('', () => { httpRequestsMockHelpers.setCreateTemplateResponse(undefined, { body: error }); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { actions.clickSubmitButton(); await nextTick(); diff --git a/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_edit.test.tsx b/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_edit.test.tsx index 4056bd2ad63e7..975d82b936054 100644 --- a/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_edit.test.tsx +++ b/x-pack/legacy/plugins/index_management/__jest__/client_integration/template_edit.test.tsx @@ -40,9 +40,7 @@ jest.mock('@elastic/eui', () => ({ ), })); -// We need to skip the tests until react 16.9.0 is released -// which supports asynchronous code inside act() -describe.skip('', () => { +describe('', () => { let testBed: TemplateFormTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); @@ -61,7 +59,6 @@ describe.skip('', () => { testBed = await setup(); - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { await nextTick(); testBed.component.update(); @@ -87,7 +84,6 @@ describe.skip('', () => { beforeEach(async () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { // Complete step 1 (logistics) await actions.completeStepOne({ @@ -108,7 +104,6 @@ describe.skip('', () => { it('should send the correct payload with changed values', async () => { const { actions } = testBed; - // @ts-ignore (remove when react 16.9.0 is released) await act(async () => { actions.clickSubmitButton(); await nextTick(); diff --git a/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/edit_settings_json/edit_settings_json.js b/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/edit_settings_json/edit_settings_json.js index fe919ed7ae6b7..92b46e8e0da00 100644 --- a/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/edit_settings_json/edit_settings_json.js +++ b/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/edit_settings_json/edit_settings_json.js @@ -56,7 +56,7 @@ export class EditSettingsJson extends React.PureComponent { } return newSettings; } - componentWillMount() { + UNSAFE_componentWillMount() { const { indexName } = this.props; this.props.loadIndexData({ dataType: TAB_SETTINGS, indexName }); } diff --git a/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/show_json/show_json.js b/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/show_json/show_json.js index d41be90ba6ad3..854ccba2d3d19 100644 --- a/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/show_json/show_json.js +++ b/x-pack/legacy/plugins/index_management/public/app/sections/home/index_list/detail_panel/show_json/show_json.js @@ -10,10 +10,10 @@ import { EuiCodeEditor } from '@elastic/eui'; import 'brace/theme/textmate'; export class ShowJson extends React.PureComponent { - componentWillMount() { + UNSAFE_componentWillMount() { this.props.loadIndexData(this.props); } - componentWillUpdate(newProps) { + UNSAFE_componentWillUpdate(newProps) { const { data, loadIndexData } = newProps; if (!data) { loadIndexData(newProps); diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/kuery_bar.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/kuery_bar.tsx index 1353b065bc444..a851f8380b915 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/kuery_bar.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/kuery_bar.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { fromKueryExpression } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useState } from 'react'; @@ -12,6 +11,7 @@ import { StaticIndexPattern } from 'ui/index_patterns'; import { WithKueryAutocompletion } from '../../containers/with_kuery_autocompletion'; import { AutocompleteField } from '../autocomplete_field'; import { isDisplayable } from '../../utils/is_displayable'; +import { esKuery } from '../../../../../../../src/plugins/data/public'; interface Props { derivedIndexPattern: StaticIndexPattern; @@ -21,7 +21,7 @@ interface Props { function validateQuery(query: string) { try { - fromKueryExpression(query); + esKuery.fromKueryExpression(query); } catch (err) { return false; } diff --git a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.test.tsx b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.test.tsx index 932415bfe1afc..c43e2f5a544cf 100644 --- a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.test.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_data.test.tsx @@ -8,7 +8,7 @@ import { fetch } from '../../utils/fetch'; import { useMetricsExplorerData } from './use_metrics_explorer_data'; import { MetricsExplorerAggregation } from '../../../server/routes/metrics_explorer/types'; -import { renderHook } from 'react-hooks-testing-library'; +import { renderHook } from '@testing-library/react-hooks'; import { options, diff --git a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.test.tsx b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.test.tsx index 184655398bd9c..e58184c78b4b8 100644 --- a/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.test.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/metrics_explorer/use_metrics_explorer_options.test.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import { renderHook, act } from 'react-hooks-testing-library'; +import { renderHook, act } from '@testing-library/react-hooks'; import { useMetricsExplorerOptions, MetricsExplorerOptionsContainer, @@ -65,7 +65,7 @@ describe('useMetricExplorerOptions', () => { }); it('should change the store when options update', () => { - const { result, waitForNextUpdate } = renderUseMetricsExplorerOptionsHook(); + const { result, rerender } = renderUseMetricsExplorerOptionsHook(); const newOptions: MetricsExplorerOptions = { ...DEFAULT_OPTIONS, metrics: [{ aggregation: MetricsExplorerAggregation.count }], @@ -73,13 +73,13 @@ describe('useMetricExplorerOptions', () => { act(() => { result.current.setOptions(newOptions); }); - waitForNextUpdate(); + rerender(); expect(result.current.options).toEqual(newOptions); expect(STORE.MetricsExplorerOptions).toEqual(JSON.stringify(newOptions)); }); it('should change the store when timerange update', () => { - const { result, waitForNextUpdate } = renderUseMetricsExplorerOptionsHook(); + const { result, rerender } = renderUseMetricsExplorerOptionsHook(); const newTimeRange: MetricsExplorerTimeOptions = { ...DEFAULT_TIMERANGE, from: 'now-15m', @@ -87,7 +87,7 @@ describe('useMetricExplorerOptions', () => { act(() => { result.current.setTimeRange(newTimeRange); }); - waitForNextUpdate(); + rerender(); expect(result.current.currentTimerange).toEqual(newTimeRange); expect(STORE.MetricsExplorerTimeRange).toEqual(JSON.stringify(newTimeRange)); }); diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.test.tsx b/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.test.tsx index 4c0d95c5529e8..0512fb0a46b90 100644 --- a/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.test.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/metrics_explorer/use_metric_explorer_state.test.tsx @@ -5,7 +5,7 @@ */ import { fetch } from '../../../utils/fetch'; -import { renderHook } from 'react-hooks-testing-library'; +import { renderHook } from '@testing-library/react-hooks'; import { useMetricsExplorerState } from './use_metric_explorer_state'; import { MetricsExplorerOptionsContainer } from '../../../containers/metrics_explorer/use_metrics_explorer_options'; import React from 'react'; @@ -172,7 +172,7 @@ describe('useMetricsExplorerState', () => { describe('handleLoadMore', () => { it('should load more based on the afterKey', async () => { - const { result, waitForNextUpdate } = renderUseMetricsExplorerStateHook(); + const { result, waitForNextUpdate, rerender } = renderUseMetricsExplorerStateHook(); expect(result.current.data).toBe(null); expect(result.current.loading).toBe(true); await waitForNextUpdate(); @@ -189,7 +189,7 @@ describe('useMetricsExplorerState', () => { } as any); const { handleLoadMore } = result.current; handleLoadMore(pageInfo.afterKey!); - await waitForNextUpdate(); + await rerender(); expect(result.current.loading).toBe(true); await waitForNextUpdate(); expect(result.current.loading).toBe(false); diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/use_host_ip_to_name.test.ts b/x-pack/legacy/plugins/infra/public/pages/link_to/use_host_ip_to_name.test.ts index 32f3864bbfe4e..3b61181dfc6e0 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/use_host_ip_to_name.test.ts +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/use_host_ip_to_name.test.ts @@ -6,7 +6,7 @@ import { useHostIpToName } from './use_host_ip_to_name'; import { fetch } from '../../utils/fetch'; -import { renderHook } from 'react-hooks-testing-library'; +import { renderHook } from '@testing-library/react-hooks'; const renderUseHostIpToNameHook = () => renderHook(props => useHostIpToName(props.ipAddress, props.indexPattern), { diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_filter/selectors.ts b/x-pack/legacy/plugins/infra/public/store/local/log_filter/selectors.ts index 069f631b9c026..f17f7be4defe9 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/log_filter/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/log_filter/selectors.ts @@ -5,10 +5,8 @@ */ import { createSelector } from 'reselect'; - -import { fromKueryExpression } from '@kbn/es-query'; - import { LogFilterState } from './reducer'; +import { esKuery } from '../../../../../../../../src/plugins/data/public'; export const selectLogFilterQuery = (state: LogFilterState) => 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 7d518b5e20f2d..0acce82950f77 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 4a767f2777512..2e793d53b4622 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 48ef846ec5275..91bcd6be95a75 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 018c903009bbe..601beddc0a2db 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 8a82194470ace..7b0e42283d5f5 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/common/constants.ts b/x-pack/legacy/plugins/lens/common/constants.ts index c2eed1940fa1a..57f2a633e4524 100644 --- a/x-pack/legacy/plugins/lens/common/constants.ts +++ b/x-pack/legacy/plugins/lens/common/constants.ts @@ -5,7 +5,7 @@ */ export const PLUGIN_ID = 'lens'; - +export const NOT_INTERNATIONALIZED_PRODUCT_NAME = 'Lens Visualizations'; export const BASE_APP_URL = '/app/kibana'; export const BASE_API_URL = '/api/lens'; diff --git a/x-pack/legacy/plugins/lens/index.ts b/x-pack/legacy/plugins/lens/index.ts index d4cea28d14085..c4a684381b17c 100644 --- a/x-pack/legacy/plugins/lens/index.ts +++ b/x-pack/legacy/plugins/lens/index.ts @@ -9,11 +9,9 @@ import { resolve } from 'path'; import { LegacyPluginInitializer } from 'src/legacy/types'; import KbnServer, { Server } from 'src/legacy/server/kbn_server'; import mappings from './mappings.json'; -import { PLUGIN_ID, getEditPath } from './common'; +import { PLUGIN_ID, getEditPath, NOT_INTERNATIONALIZED_PRODUCT_NAME } from './common'; import { lensServerPlugin } from './server'; -export const NOT_INTERNATIONALIZED_PRODUCT_NAME = 'Lens Visualizations'; - export const lens: LegacyPluginInitializer = kibana => { return new kibana.Plugin({ id: PLUGIN_ID, @@ -58,10 +56,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/app_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx index 93f5928f58aa1..f2678463f57da 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx @@ -38,7 +38,7 @@ import { stopReportManager, trackUiEvent, } from '../lens_ui_telemetry'; -import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../../index'; +import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../../common'; import { KibanaLegacySetup } from '../../../../../../src/plugins/kibana_legacy/public'; import { EditorFrameStart } from '../types'; @@ -50,6 +50,7 @@ export interface LensPluginStartDependencies { data: DataPublicPluginStart; dataShim: DataStart; } + export class AppPlugin { private startDependencies: { data: DataPublicPluginStart; 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 21a69bfc3a0b3..3dd4373347129 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/dimension_panel/dimension_panel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx index d4cf4f7ffbaa6..f615914360a35 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx @@ -9,7 +9,11 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { EuiComboBox, EuiSideNav, EuiPopover } from '@elastic/eui'; import { changeColumn } from '../state_helpers'; -import { IndexPatternDimensionPanel, IndexPatternDimensionPanelProps } from './dimension_panel'; +import { + IndexPatternDimensionPanel, + IndexPatternDimensionPanelComponent, + IndexPatternDimensionPanelProps, +} from './dimension_panel'; import { DropHandler, DragContextState } from '../../drag_drop'; import { createMockedDragDropContext } from '../mocks'; import { mountWithIntl as mount, shallowWithIntl as shallow } from 'test_utils/enzyme_helpers'; @@ -164,7 +168,7 @@ describe('IndexPatternDimensionPanel', () => { const filterOperations = jest.fn().mockReturnValue(true); wrapper = shallow( - + ); expect(filterOperations).toBeCalled(); @@ -1076,7 +1080,7 @@ describe('IndexPatternDimensionPanel', () => { it('is not droppable if the dragged item has no field', () => { wrapper = shallow( - { it('is not droppable if field is not supported by filterOperations', () => { wrapper = shallow( - { it('is droppable if the field is supported by filterOperations', () => { wrapper = shallow( - { it('is notdroppable if the field belongs to another index pattern', () => { wrapper = shallow( - { }; const testState = dragDropState(); wrapper = shallow( - { }; const testState = dragDropState(); wrapper = shallow( - { }; const testState = dragDropState(); wrapper = shallow( - >; } -export const IndexPatternDimensionPanel = memo(function IndexPatternDimensionPanel( +export const IndexPatternDimensionPanelComponent = function IndexPatternDimensionPanel( props: IndexPatternDimensionPanelProps ) { const layerId = props.layerId; @@ -188,4 +188,6 @@ export const IndexPatternDimensionPanel = memo(function IndexPatternDimensionPan ); -}); +}; + +export const IndexPatternDimensionPanel = memo(IndexPatternDimensionPanelComponent); 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 a50d3371f47cf..d0b77a425d14a 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 185df12054a3c..0c4e6d9f7cb10 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 a4c8e9b268df5..0223b90c37046 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 94a7c8e0d85c1..274b72c33e59a 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 03e085cc9e669..feb73538f44f0 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/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.js b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.js index aaf9f45441657..58e90b100c698 100644 --- a/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.js +++ b/x-pack/legacy/plugins/license_management/public/np_ready/application/sections/license_dashboard/start_trial/start_trial.js @@ -34,7 +34,7 @@ export class StartTrial extends React.PureComponent { super(props); this.state = { showConfirmation: false }; } - componentWillMount() { + UNSAFE_componentWillMount() { this.props.loadTrialStatus(); } startLicenseTrial = () => { diff --git a/x-pack/legacy/plugins/maps/index.js b/x-pack/legacy/plugins/maps/index.js index 739e98beec10f..c59fbe42a1754 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/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js index b9354dd0a0ddd..810dc263f8b78 100644 --- a/x-pack/legacy/plugins/maps/public/angular/map_controller.js +++ b/x-pack/legacy/plugins/maps/public/angular/map_controller.js @@ -53,11 +53,10 @@ import { MAP_SAVED_OBJECT_TYPE, MAP_APP_PATH } from '../../common/constants'; -import { start as data } from '../../../../../../src/legacy/core_plugins/data/public/legacy'; import { npStart } from 'ui/new_platform'; import { esFilters } from '../../../../../../src/plugins/data/public'; -const { savedQueryService } = data.search.services; +const savedQueryService = npStart.plugins.data.query.savedQueries; const REACT_ANCHOR_DOM_ELEMENT_ID = 'react-maps-root'; diff --git a/x-pack/legacy/plugins/maps/public/components/map_listing.js b/x-pack/legacy/plugins/maps/public/components/map_listing.js index 5a9cb54109363..6fb5930e81a20 100644 --- a/x-pack/legacy/plugins/maps/public/components/map_listing.js +++ b/x-pack/legacy/plugins/maps/public/components/map_listing.js @@ -44,7 +44,7 @@ export class MapListing extends React.Component { perPage: 20, }; - componentWillMount() { + UNSAFE_componentWillMount() { this._isMounted = true; } diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js index 44629d16e6fb3..01c323d73f19e 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js @@ -16,7 +16,6 @@ import { EuiFormHelpText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { IndexPatternSelect } from 'ui/index_patterns'; import { SingleFieldSelect } from '../../../../components/single_field_select'; import { FormattedMessage } from '@kbn/i18n/react'; import { getTermsFields } from '../../../../index_pattern_util'; @@ -25,6 +24,9 @@ import { indexPatternService, } from '../../../../kibana_services'; +import { npStart } from 'ui/new_platform'; +const { IndexPatternSelect } = npStart.plugins.data.ui; + export class JoinExpression extends Component { state = { diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.js b/x-pack/legacy/plugins/maps/public/layers/layer.js index 72a89046ed2f5..1c2f33df66bf8 100644 --- a/x-pack/legacy/plugins/maps/public/layers/layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/layer.js @@ -6,8 +6,6 @@ import _ from 'lodash'; import React from 'react'; import { EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; -import turf from 'turf'; -import turfBooleanContains from '@turf/boolean-contains'; import { DataRequest } from './util/data_request'; import { MAX_ZOOM, @@ -19,9 +17,6 @@ import uuid from 'uuid/v4'; import { copyPersistentState } from '../reducers/util'; import { i18n } from '@kbn/i18n'; -const SOURCE_UPDATE_REQUIRED = true; -const NO_SOURCE_UPDATE_REQUIRED = false; - export class AbstractLayer { constructor({ layerDescriptor, source }) { @@ -316,42 +311,7 @@ export class AbstractLayer { throw new Error('Should implement AbstractLayer#syncLayerWithMB'); } - updateDueToExtent(source, prevMeta = {}, nextMeta = {}) { - const extentAware = source.isFilterByMapBounds(); - if (!extentAware) { - return NO_SOURCE_UPDATE_REQUIRED; - } - const { buffer: previousBuffer } = prevMeta; - const { buffer: newBuffer } = nextMeta; - - if (!previousBuffer) { - return SOURCE_UPDATE_REQUIRED; - } - - if (_.isEqual(previousBuffer, newBuffer)) { - return NO_SOURCE_UPDATE_REQUIRED; - } - - const previousBufferGeometry = turf.bboxPolygon([ - previousBuffer.minLon, - previousBuffer.minLat, - previousBuffer.maxLon, - previousBuffer.maxLat - ]); - const newBufferGeometry = turf.bboxPolygon([ - newBuffer.minLon, - newBuffer.minLat, - newBuffer.maxLon, - newBuffer.maxLat - ]); - const doesPreviousBufferContainNewBuffer = turfBooleanContains(previousBufferGeometry, newBufferGeometry); - - const isTrimmed = _.get(prevMeta, 'areResultsTrimmed', false); - return doesPreviousBufferContainNewBuffer && !isTrimmed - ? NO_SOURCE_UPDATE_REQUIRED - : SOURCE_UPDATE_REQUIRED; - } getLayerTypeIconName() { throw new Error('should implement Layer#getLayerTypeIconName'); @@ -407,4 +367,3 @@ export class AbstractLayer { } } - diff --git a/x-pack/legacy/plugins/maps/public/layers/layer.test.js b/x-pack/legacy/plugins/maps/public/layers/layer.test.js deleted file mode 100644 index 98be0855cd4b7..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/layer.test.js +++ /dev/null @@ -1,113 +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 { AbstractLayer } from './layer'; - -describe('layer', () => { - const layer = new AbstractLayer({ layerDescriptor: {} }); - - describe('updateDueToExtent', () => { - - it('should be false when the source is not extent aware', async () => { - const sourceMock = { - isFilterByMapBounds: () => { return false; } - }; - const updateDueToExtent = layer.updateDueToExtent(sourceMock); - expect(updateDueToExtent).toBe(false); - }); - - it('should be false when buffers are the same', async () => { - const sourceMock = { - isFilterByMapBounds: () => { return true; } - }; - const oldBuffer = { - maxLat: 12.5, - maxLon: 102.5, - minLat: 2.5, - minLon: 92.5, - }; - const newBuffer = { - maxLat: 12.5, - maxLon: 102.5, - minLat: 2.5, - minLon: 92.5, - }; - const updateDueToExtent = layer.updateDueToExtent(sourceMock, { buffer: oldBuffer }, { buffer: newBuffer }); - expect(updateDueToExtent).toBe(false); - }); - - it('should be false when the new buffer is contained in the old buffer', async () => { - const sourceMock = { - isFilterByMapBounds: () => { return true; } - }; - const oldBuffer = { - maxLat: 12.5, - maxLon: 102.5, - minLat: 2.5, - minLon: 92.5, - }; - const newBuffer = { - maxLat: 10, - maxLon: 100, - minLat: 5, - minLon: 95, - }; - const updateDueToExtent = layer.updateDueToExtent(sourceMock, { buffer: oldBuffer }, { buffer: newBuffer }); - expect(updateDueToExtent).toBe(false); - }); - - it('should be true when the new buffer is contained in the old buffer and the past results were truncated', async () => { - const sourceMock = { - isFilterByMapBounds: () => { return true; } - }; - const oldBuffer = { - maxLat: 12.5, - maxLon: 102.5, - minLat: 2.5, - minLon: 92.5, - }; - const newBuffer = { - maxLat: 10, - maxLon: 100, - minLat: 5, - minLon: 95, - }; - const updateDueToExtent = layer.updateDueToExtent( - sourceMock, - { buffer: oldBuffer, areResultsTrimmed: true }, - { buffer: newBuffer }); - expect(updateDueToExtent).toBe(true); - }); - - it('should be true when meta has no old buffer', async () => { - const sourceMock = { - isFilterByMapBounds: () => { return true; } - }; - const updateDueToExtent = layer.updateDueToExtent(sourceMock); - expect(updateDueToExtent).toBe(true); - }); - - it('should be true when the new buffer is not contained in the old buffer', async () => { - const sourceMock = { - isFilterByMapBounds: () => { return true; } - }; - const oldBuffer = { - maxLat: 12.5, - maxLon: 102.5, - minLat: 2.5, - minLon: 92.5, - }; - const newBuffer = { - maxLat: 7.5, - maxLon: 92.5, - minLat: -2.5, - minLon: 82.5, - }; - const updateDueToExtent = layer.updateDueToExtent(sourceMock, { buffer: oldBuffer }, { buffer: newBuffer }); - expect(updateDueToExtent).toBe(true); - }); - }); -}); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js index 395b6ac5cc431..3d02b075b3b81 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js @@ -8,7 +8,6 @@ import _ from 'lodash'; import React, { Fragment, Component } from 'react'; import PropTypes from 'prop-types'; -import { IndexPatternSelect } from 'ui/index_patterns'; import { SingleFieldSelect } from '../../../components/single_field_select'; import { RENDER_AS } from './render_as'; import { indexPatternService } from '../../../kibana_services'; @@ -22,6 +21,9 @@ import { } from '@elastic/eui'; import { ES_GEO_FIELD_TYPE } from '../../../../common/constants'; +import { npStart } from 'ui/new_platform'; +const { IndexPatternSelect } = npStart.plugins.data.ui; + function filterGeoField({ type }) { return [ES_GEO_FIELD_TYPE.GEO_POINT].includes(type); } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js index 9f9789374274a..897ded43be28b 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js @@ -8,7 +8,6 @@ import _ from 'lodash'; import React, { Fragment, Component } from 'react'; import PropTypes from 'prop-types'; -import { IndexPatternSelect } from 'ui/index_patterns'; import { SingleFieldSelect } from '../../../components/single_field_select'; import { indexPatternService } from '../../../kibana_services'; import { i18n } from '@kbn/i18n'; @@ -20,6 +19,8 @@ import { } from '@elastic/eui'; import { ES_GEO_FIELD_TYPE } from '../../../../common/constants'; +import { npStart } from 'ui/new_platform'; +const { IndexPatternSelect } = npStart.plugins.data.ui; const GEO_FIELD_TYPES = [ES_GEO_FIELD_TYPE.GEO_POINT]; function filterGeoField({ type }) { diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js index 61300ed209c1f..a6ba31366d504 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js @@ -9,7 +9,6 @@ import React, { Fragment, Component } from 'react'; import PropTypes from 'prop-types'; import { EuiFormRow, EuiSpacer, EuiSwitch, EuiCallOut } from '@elastic/eui'; -import { IndexPatternSelect } from 'ui/index_patterns'; import { SingleFieldSelect } from '../../../components/single_field_select'; import { indexPatternService } from '../../../kibana_services'; import { NoIndexPatternCallout } from '../../../components/no_index_pattern_callout'; @@ -19,6 +18,9 @@ import { kfetch } from 'ui/kfetch'; import { ES_GEO_FIELD_TYPE, GIS_API_PATH, ES_SIZE_LIMIT } from '../../../../common/constants'; import { DEFAULT_FILTER_BY_MAP_BOUNDS } from './constants'; +import { npStart } from 'ui/new_platform'; +const { IndexPatternSelect } = npStart.plugins.data.ui; + function filterGeoField(field) { return [ES_GEO_FIELD_TYPE.GEO_POINT, ES_GEO_FIELD_TYPE.GEO_SHAPE].includes(field.type); } diff --git a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.js b/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.js new file mode 100644 index 0000000000000..610c704b34ec6 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.js @@ -0,0 +1,130 @@ +/* + * 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 _ from 'lodash'; +import turf from 'turf'; +import turfBooleanContains from '@turf/boolean-contains'; +import { isRefreshOnlyQuery } from './is_refresh_only_query'; + +const SOURCE_UPDATE_REQUIRED = true; +const NO_SOURCE_UPDATE_REQUIRED = false; + +export function updateDueToExtent(source, prevMeta = {}, nextMeta = {}) { + const extentAware = source.isFilterByMapBounds(); + if (!extentAware) { + return NO_SOURCE_UPDATE_REQUIRED; + } + + const { buffer: previousBuffer } = prevMeta; + const { buffer: newBuffer } = nextMeta; + + if (!previousBuffer) { + return SOURCE_UPDATE_REQUIRED; + } + + if (_.isEqual(previousBuffer, newBuffer)) { + return NO_SOURCE_UPDATE_REQUIRED; + } + + const previousBufferGeometry = turf.bboxPolygon([ + previousBuffer.minLon, + previousBuffer.minLat, + previousBuffer.maxLon, + previousBuffer.maxLat + ]); + const newBufferGeometry = turf.bboxPolygon([ + newBuffer.minLon, + newBuffer.minLat, + newBuffer.maxLon, + newBuffer.maxLat + ]); + const doesPreviousBufferContainNewBuffer = turfBooleanContains(previousBufferGeometry, newBufferGeometry); + + const isTrimmed = _.get(prevMeta, 'areResultsTrimmed', false); + return doesPreviousBufferContainNewBuffer && !isTrimmed + ? NO_SOURCE_UPDATE_REQUIRED + : SOURCE_UPDATE_REQUIRED; +} + +export async function canSkipSourceUpdate({ source, prevDataRequest, nextMeta }) { + + const timeAware = await source.isTimeAware(); + const refreshTimerAware = await source.isRefreshTimerAware(); + const extentAware = source.isFilterByMapBounds(); + const isFieldAware = source.isFieldAware(); + const isQueryAware = source.isQueryAware(); + const isGeoGridPrecisionAware = source.isGeoGridPrecisionAware(); + + if ( + !timeAware && + !refreshTimerAware && + !extentAware && + !isFieldAware && + !isQueryAware && + !isGeoGridPrecisionAware + ) { + return (prevDataRequest && prevDataRequest.hasDataOrRequestInProgress()); + } + + if (!prevDataRequest) { + return false; + } + const prevMeta = prevDataRequest.getMeta(); + if (!prevMeta) { + return false; + } + + let updateDueToTime = false; + if (timeAware) { + updateDueToTime = !_.isEqual(prevMeta.timeFilters, nextMeta.timeFilters); + } + + let updateDueToRefreshTimer = false; + if (refreshTimerAware && nextMeta.refreshTimerLastTriggeredAt) { + updateDueToRefreshTimer = !_.isEqual(prevMeta.refreshTimerLastTriggeredAt, nextMeta.refreshTimerLastTriggeredAt); + } + + let updateDueToFields = false; + if (isFieldAware) { + updateDueToFields = !_.isEqual(prevMeta.fieldNames, nextMeta.fieldNames); + } + + let updateDueToQuery = false; + let updateDueToFilters = false; + let updateDueToSourceQuery = false; + let updateDueToApplyGlobalQuery = false; + if (isQueryAware) { + updateDueToApplyGlobalQuery = prevMeta.applyGlobalQuery !== nextMeta.applyGlobalQuery; + updateDueToSourceQuery = !_.isEqual(prevMeta.sourceQuery, nextMeta.sourceQuery); + if (nextMeta.applyGlobalQuery) { + updateDueToQuery = !_.isEqual(prevMeta.query, nextMeta.query); + updateDueToFilters = !_.isEqual(prevMeta.filters, nextMeta.filters); + } else { + // Global filters and query are not applied to layer search request so no re-fetch required. + // Exception is "Refresh" query. + updateDueToQuery = isRefreshOnlyQuery(prevMeta.query, nextMeta.query); + } + } + + let updateDueToPrecisionChange = false; + if (isGeoGridPrecisionAware) { + updateDueToPrecisionChange = !_.isEqual(prevMeta.geogridPrecision, nextMeta.geogridPrecision); + } + + const updateDueToExtentChange = updateDueToExtent(source, prevMeta, nextMeta); + + const updateDueToSourceMetaChange = !_.isEqual(prevMeta.sourceMeta, nextMeta.sourceMeta); + + return !updateDueToTime + && !updateDueToRefreshTimer + && !updateDueToExtentChange + && !updateDueToFields + && !updateDueToQuery + && !updateDueToFilters + && !updateDueToSourceQuery + && !updateDueToApplyGlobalQuery + && !updateDueToPrecisionChange + && !updateDueToSourceMetaChange; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js b/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js new file mode 100644 index 0000000000000..77359a6def48f --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.test.js @@ -0,0 +1,287 @@ +/* + * 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 { canSkipSourceUpdate, updateDueToExtent } from './can_skip_fetch'; +import { DataRequest } from './data_request'; + +describe('updateDueToExtent', () => { + + it('should be false when the source is not extent aware', async () => { + const sourceMock = { + isFilterByMapBounds: () => { return false; } + }; + expect(updateDueToExtent(sourceMock)).toBe(false); + }); + + describe('source is extent aware', () => { + const sourceMock = { + isFilterByMapBounds: () => { return true; } + }; + + it('should be false when buffers are the same', async () => { + const oldBuffer = { + maxLat: 12.5, + maxLon: 102.5, + minLat: 2.5, + minLon: 92.5, + }; + const newBuffer = { + maxLat: 12.5, + maxLon: 102.5, + minLat: 2.5, + minLon: 92.5, + }; + expect(updateDueToExtent(sourceMock, { buffer: oldBuffer }, { buffer: newBuffer })) + .toBe(false); + }); + + it('should be false when the new buffer is contained in the old buffer', async () => { + const oldBuffer = { + maxLat: 12.5, + maxLon: 102.5, + minLat: 2.5, + minLon: 92.5, + }; + const newBuffer = { + maxLat: 10, + maxLon: 100, + minLat: 5, + minLon: 95, + }; + expect(updateDueToExtent(sourceMock, { buffer: oldBuffer }, { buffer: newBuffer })).toBe(false); + }); + + it('should be true when the new buffer is contained in the old buffer and the past results were truncated', async () => { + const oldBuffer = { + maxLat: 12.5, + maxLon: 102.5, + minLat: 2.5, + minLon: 92.5, + }; + const newBuffer = { + maxLat: 10, + maxLon: 100, + minLat: 5, + minLon: 95, + }; + expect(updateDueToExtent( + sourceMock, + { buffer: oldBuffer, areResultsTrimmed: true }, + { buffer: newBuffer } + )).toBe(true); + }); + + it('should be true when meta has no old buffer', async () => { + expect(updateDueToExtent(sourceMock)).toBe(true); + }); + + it('should be true when the new buffer is not contained in the old buffer', async () => { + const oldBuffer = { + maxLat: 12.5, + maxLon: 102.5, + minLat: 2.5, + minLon: 92.5, + }; + const newBuffer = { + maxLat: 7.5, + maxLon: 92.5, + minLat: -2.5, + minLon: 82.5, + }; + expect(updateDueToExtent(sourceMock, { buffer: oldBuffer }, { buffer: newBuffer })).toBe(true); + }); + }); +}); + +describe('canSkipSourceUpdate', () => { + const SOURCE_DATA_REQUEST_ID = 'foo'; + + describe('isQueryAware', () => { + + const queryAwareSourceMock = { + isTimeAware: () => { return false; }, + isRefreshTimerAware: () => { return false; }, + isFilterByMapBounds: () => { return false; }, + isFieldAware: () => { return false; }, + isQueryAware: () => { return true; }, + isGeoGridPrecisionAware: () => { return false; }, + }; + const prevFilters = []; + const prevQuery = { + language: 'kuery', + query: 'machine.os.keyword : "win 7"', + queryLastTriggeredAt: '2019-04-25T20:53:22.331Z' + }; + + describe('applyGlobalQuery is false', () => { + + const prevApplyGlobalQuery = false; + + const prevDataRequest = new DataRequest({ + dataId: SOURCE_DATA_REQUEST_ID, + dataMeta: { + applyGlobalQuery: prevApplyGlobalQuery, + filters: prevFilters, + query: prevQuery, + } + }); + + it('can skip update when filter changes', async () => { + const nextMeta = { + applyGlobalQuery: prevApplyGlobalQuery, + filters: [prevQuery], + query: prevQuery, + }; + + const canSkipUpdate = await canSkipSourceUpdate({ + source: queryAwareSourceMock, + prevDataRequest, + nextMeta, + }); + + expect(canSkipUpdate).toBe(true); + }); + + it('can skip update when query changes', async () => { + const nextMeta = { + applyGlobalQuery: prevApplyGlobalQuery, + filters: prevFilters, + query: { + ...prevQuery, + query: 'a new query string', + } + }; + + const canSkipUpdate = await canSkipSourceUpdate({ + source: queryAwareSourceMock, + prevDataRequest, + nextMeta + }); + + expect(canSkipUpdate).toBe(true); + }); + + it('can not skip update when query is refreshed', async () => { + const nextMeta = { + applyGlobalQuery: prevApplyGlobalQuery, + filters: prevFilters, + query: { + ...prevQuery, + queryLastTriggeredAt: 'sometime layer when Refresh button is clicked' + } + }; + + const canSkipUpdate = await canSkipSourceUpdate({ + source: queryAwareSourceMock, + prevDataRequest, + nextMeta + }); + + expect(canSkipUpdate).toBe(false); + }); + + it('can not skip update when applyGlobalQuery changes', async () => { + const nextMeta = { + applyGlobalQuery: !prevApplyGlobalQuery, + filters: prevFilters, + query: prevQuery + }; + + const canSkipUpdate = await canSkipSourceUpdate({ + source: queryAwareSourceMock, + prevDataRequest, + nextMeta + }); + + expect(canSkipUpdate).toBe(false); + }); + }); + + describe('applyGlobalQuery is true', () => { + + const prevApplyGlobalQuery = true; + + const prevDataRequest = new DataRequest({ + dataId: SOURCE_DATA_REQUEST_ID, + dataMeta: { + applyGlobalQuery: prevApplyGlobalQuery, + filters: prevFilters, + query: prevQuery, + } + }); + + it('can not skip update when filter changes', async () => { + const nextMeta = { + applyGlobalQuery: prevApplyGlobalQuery, + filters: [prevQuery], + query: prevQuery, + }; + + const canSkipUpdate = await canSkipSourceUpdate({ + source: queryAwareSourceMock, + prevDataRequest, + nextMeta + }); + + expect(canSkipUpdate).toBe(false); + }); + + it('can not skip update when query changes', async () => { + const nextMeta = { + applyGlobalQuery: prevApplyGlobalQuery, + filters: prevFilters, + query: { + ...prevQuery, + query: 'a new query string', + } + }; + + const canSkipUpdate = await canSkipSourceUpdate({ + source: queryAwareSourceMock, + prevDataRequest, + nextMeta + }); + + expect(canSkipUpdate).toBe(false); + }); + + it('can not skip update when query is refreshed', async () => { + const nextMeta = { + applyGlobalQuery: prevApplyGlobalQuery, + filters: prevFilters, + query: { + ...prevQuery, + queryLastTriggeredAt: 'sometime layer when Refresh button is clicked' + } + }; + + const canSkipUpdate = await canSkipSourceUpdate({ + source: queryAwareSourceMock, + prevDataRequest, + nextMeta + }); + + expect(canSkipUpdate).toBe(false); + }); + + it('can not skip update when applyGlobalQuery changes', async () => { + const nextMeta = { + applyGlobalQuery: !prevApplyGlobalQuery, + filters: prevFilters, + query: prevQuery + }; + + const canSkipUpdate = await canSkipSourceUpdate({ + source: queryAwareSourceMock, + prevDataRequest, + nextMeta + }); + + expect(canSkipUpdate).toBe(false); + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js index 9b553803606ed..57126bb7681b8 100644 --- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/vector_layer.js @@ -18,10 +18,10 @@ import { } from '../../common/constants'; import _ from 'lodash'; import { JoinTooltipProperty } from './tooltips/join_tooltip_property'; -import { isRefreshOnlyQuery } from './util/is_refresh_only_query'; import { EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { DataRequestAbortError } from './util/data_request'; +import { canSkipSourceUpdate } from './util/can_skip_fetch'; import { assignFeatureIds } from './util/assign_feature_ids'; import { getFillFilterExpression, @@ -229,109 +229,31 @@ export class VectorLayer extends AbstractLayer { return this._dataRequests.find(dataRequest => dataRequest.getDataId() === sourceDataId); } - async _canSkipSourceUpdate(source, sourceDataId, nextMeta) { - const timeAware = await source.isTimeAware(); - const refreshTimerAware = await source.isRefreshTimerAware(); - const extentAware = source.isFilterByMapBounds(); - const isFieldAware = source.isFieldAware(); - const isQueryAware = source.isQueryAware(); - const isGeoGridPrecisionAware = source.isGeoGridPrecisionAware(); - - if ( - !timeAware && - !refreshTimerAware && - !extentAware && - !isFieldAware && - !isQueryAware && - !isGeoGridPrecisionAware - ) { - const sourceDataRequest = this._findDataRequestForSource(sourceDataId); - return (sourceDataRequest && sourceDataRequest.hasDataOrRequestInProgress()); - } - - const sourceDataRequest = this._findDataRequestForSource(sourceDataId); - if (!sourceDataRequest) { - return false; - } - const prevMeta = sourceDataRequest.getMeta(); - if (!prevMeta) { - return false; - } - - let updateDueToTime = false; - if (timeAware) { - updateDueToTime = !_.isEqual(prevMeta.timeFilters, nextMeta.timeFilters); - } - - let updateDueToRefreshTimer = false; - if (refreshTimerAware && nextMeta.refreshTimerLastTriggeredAt) { - updateDueToRefreshTimer = !_.isEqual(prevMeta.refreshTimerLastTriggeredAt, nextMeta.refreshTimerLastTriggeredAt); - } - - let updateDueToFields = false; - if (isFieldAware) { - updateDueToFields = !_.isEqual(prevMeta.fieldNames, nextMeta.fieldNames); - } - - let updateDueToQuery = false; - let updateDueToFilters = false; - let updateDueToSourceQuery = false; - let updateDueToApplyGlobalQuery = false; - if (isQueryAware) { - updateDueToApplyGlobalQuery = prevMeta.applyGlobalQuery !== nextMeta.applyGlobalQuery; - updateDueToSourceQuery = !_.isEqual(prevMeta.sourceQuery, nextMeta.sourceQuery); - if (nextMeta.applyGlobalQuery) { - updateDueToQuery = !_.isEqual(prevMeta.query, nextMeta.query); - updateDueToFilters = !_.isEqual(prevMeta.filters, nextMeta.filters); - } else { - // Global filters and query are not applied to layer search request so no re-fetch required. - // Exception is "Refresh" query. - updateDueToQuery = isRefreshOnlyQuery(prevMeta.query, nextMeta.query); - } - } - - let updateDueToPrecisionChange = false; - if (isGeoGridPrecisionAware) { - updateDueToPrecisionChange = !_.isEqual(prevMeta.geogridPrecision, nextMeta.geogridPrecision); - } - - const updateDueToExtentChange = this.updateDueToExtent(source, prevMeta, nextMeta); - - const updateDueToSourceMetaChange = !_.isEqual(prevMeta.sourceMeta, nextMeta.sourceMeta); - - return !updateDueToTime - && !updateDueToRefreshTimer - && !updateDueToExtentChange - && !updateDueToFields - && !updateDueToQuery - && !updateDueToFilters - && !updateDueToSourceQuery - && !updateDueToApplyGlobalQuery - && !updateDueToPrecisionChange - && !updateDueToSourceMetaChange; - } async _syncJoin({ join, startLoading, stopLoading, onLoadError, registerCancelCallback, dataFilters }) { const joinSource = join.getRightJoinSource(); const sourceDataId = join.getSourceId(); const requestToken = Symbol(`layer-join-refresh:${this.getId()} - ${sourceDataId}`); - const searchFilters = { ...dataFilters, fieldNames: joinSource.getFieldNames(), sourceQuery: joinSource.getWhereQuery(), applyGlobalQuery: joinSource.getApplyGlobalQuery(), }; - const canSkip = await this._canSkipSourceUpdate(joinSource, sourceDataId, searchFilters); - if (canSkip) { - const sourceDataRequest = this._findDataRequestForSource(sourceDataId); - const propertiesMap = sourceDataRequest ? sourceDataRequest.getData() : null; + const prevDataRequest = this._findDataRequestForSource(sourceDataId); + + const canSkipFetch = await canSkipSourceUpdate({ + source: joinSource, + prevDataRequest, + nextMeta: searchFilters, + }); + if (canSkipFetch) { return { dataHasChanged: false, join: join, - propertiesMap: propertiesMap + propertiesMap: prevDataRequest.getData() }; } @@ -430,12 +352,17 @@ export class VectorLayer extends AbstractLayer { const requestToken = Symbol(`layer-source-refresh:${this.getId()} - source`); const searchFilters = this._getSearchFilters(dataFilters); - const canSkip = await this._canSkipSourceUpdate(this._source, SOURCE_DATA_ID_ORIGIN, searchFilters); - if (canSkip) { - const sourceDataRequest = this.getSourceDataRequest(); + const prevDataRequest = this.getSourceDataRequest(); + + const canSkipFetch = await canSkipSourceUpdate({ + source: this._source, + prevDataRequest, + nextMeta: searchFilters, + }); + if (canSkipFetch) { return { refreshed: false, - featureCollection: sourceDataRequest.getData() + featureCollection: prevDataRequest.getData() }; } diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_layer.test.js b/x-pack/legacy/plugins/maps/public/layers/vector_layer.test.js deleted file mode 100644 index 0a07582c57856..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/vector_layer.test.js +++ /dev/null @@ -1,185 +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. - */ - -jest.mock('./joins/inner_join', () => ({ - InnerJoin: Object -})); - -jest.mock('./tooltips/join_tooltip_property', () => ({ - JoinTooltipProperty: Object -})); - -import { VectorLayer } from './vector_layer'; - -describe('_canSkipSourceUpdate', () => { - const SOURCE_DATA_REQUEST_ID = 'foo'; - - describe('isQueryAware', () => { - - const queryAwareSourceMock = { - isTimeAware: () => { return false; }, - isRefreshTimerAware: () => { return false; }, - isFilterByMapBounds: () => { return false; }, - isFieldAware: () => { return false; }, - isQueryAware: () => { return true; }, - isGeoGridPrecisionAware: () => { return false; }, - }; - const prevFilters = []; - const prevQuery = { - language: 'kuery', - query: 'machine.os.keyword : "win 7"', - queryLastTriggeredAt: '2019-04-25T20:53:22.331Z' - }; - - describe('applyGlobalQuery is false', () => { - - const prevApplyGlobalQuery = false; - - const vectorLayer = new VectorLayer({ - layerDescriptor: { - __dataRequests: [ - { - dataId: SOURCE_DATA_REQUEST_ID, - dataMeta: { - applyGlobalQuery: prevApplyGlobalQuery, - filters: prevFilters, - query: prevQuery, - } - } - ] - } - }); - - it('can skip update when filter changes', async () => { - const searchFilters = { - applyGlobalQuery: prevApplyGlobalQuery, - filters: [prevQuery], - query: prevQuery, - }; - - const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); - - expect(canSkipUpdate).toBe(true); - }); - - it('can skip update when query changes', async () => { - const searchFilters = { - applyGlobalQuery: prevApplyGlobalQuery, - filters: prevFilters, - query: { - ...prevQuery, - query: 'a new query string', - } - }; - - const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); - - expect(canSkipUpdate).toBe(true); - }); - - it('can not skip update when query is refreshed', async () => { - const searchFilters = { - applyGlobalQuery: prevApplyGlobalQuery, - filters: prevFilters, - query: { - ...prevQuery, - queryLastTriggeredAt: 'sometime layer when Refresh button is clicked' - } - }; - - const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); - - expect(canSkipUpdate).toBe(false); - }); - - it('can not skip update when applyGlobalQuery changes', async () => { - const searchFilters = { - applyGlobalQuery: !prevApplyGlobalQuery, - filters: prevFilters, - query: prevQuery - }; - - const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); - - expect(canSkipUpdate).toBe(false); - }); - }); - - describe('applyGlobalQuery is true', () => { - - const prevApplyGlobalQuery = true; - - const vectorLayer = new VectorLayer({ - layerDescriptor: { - __dataRequests: [ - { - dataId: SOURCE_DATA_REQUEST_ID, - dataMeta: { - applyGlobalQuery: prevApplyGlobalQuery, - filters: prevFilters, - query: prevQuery, - } - } - ] - } - }); - - it('can not skip update when filter changes', async () => { - const searchFilters = { - applyGlobalQuery: prevApplyGlobalQuery, - filters: [prevQuery], - query: prevQuery, - }; - - const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); - - expect(canSkipUpdate).toBe(false); - }); - - it('can not skip update when query changes', async () => { - const searchFilters = { - applyGlobalQuery: prevApplyGlobalQuery, - filters: prevFilters, - query: { - ...prevQuery, - query: 'a new query string', - } - }; - - const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); - - expect(canSkipUpdate).toBe(false); - }); - - it('can not skip update when query is refreshed', async () => { - const searchFilters = { - applyGlobalQuery: prevApplyGlobalQuery, - filters: prevFilters, - query: { - ...prevQuery, - queryLastTriggeredAt: 'sometime layer when Refresh button is clicked' - } - }; - - const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); - - expect(canSkipUpdate).toBe(false); - }); - - it('can not skip update when applyGlobalQuery changes', async () => { - const searchFilters = { - applyGlobalQuery: !prevApplyGlobalQuery, - filters: prevFilters, - query: prevQuery - }; - - const canSkipUpdate = await vectorLayer._canSkipSourceUpdate(queryAwareSourceMock, SOURCE_DATA_REQUEST_ID, searchFilters); - - expect(canSkipUpdate).toBe(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 c0ac5a781b796..c4d755b5908f0 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 13b7c56d6fc8b..e9f97101759f0 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 3cafa232f0744..90e1e748492cb 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/anomalies_table/severity_cell/severity_cell.test.tsx b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/severity_cell/severity_cell.test.tsx index 0ac7e5eb09331..98439d76627b9 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/severity_cell/severity_cell.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/severity_cell/severity_cell.test.tsx @@ -5,12 +5,10 @@ */ import React from 'react'; -import { cleanup, render } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { SeverityCell } from './severity_cell'; describe('SeverityCell', () => { - afterEach(cleanup); - test('should render a single-bucket marker with rounded severity score', () => { const props = { score: 75.2, diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/id_badges/id_badges.test.js b/x-pack/legacy/plugins/ml/public/application/components/job_selector/id_badges/id_badges.test.js index 5f94e89ad2ba5..3f72209f22456 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/job_selector/id_badges/id_badges.test.js +++ b/x-pack/legacy/plugins/ml/public/application/components/job_selector/id_badges/id_badges.test.js @@ -4,37 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ - import React from 'react'; -import { cleanup, render } from 'react-testing-library'; +import { render } from '@testing-library/react'; // eslint-disable-line import/no-extraneous-dependencies import { IdBadges } from './id_badges'; - - const props = { limit: 2, maps: { groupsMap: { - 'group1': ['job1', 'job2'], - 'group2': ['job3'] + group1: ['job1', 'job2'], + group2: ['job3'], }, jobsMap: { - 'job1': ['group1'], - 'job2': ['group1'], - 'job3': ['group2'], - 'job4': [] - } + job1: ['group1'], + job2: ['group1'], + job3: ['group2'], + job4: [], + }, }, onLinkClick: jest.fn(), selectedIds: ['group1', 'job1', 'job3'], - showAllBarBadges: false + showAllBarBadges: false, }; -const overLimitProps = { ...props, selectedIds: ['group1', 'job1', 'job3', 'job4'], }; +const overLimitProps = { ...props, selectedIds: ['group1', 'job1', 'job3', 'job4'] }; describe('IdBadges', () => { - afterEach(cleanup); - test('When group selected renders groupId and not corresponding jobIds', () => { const { getByText, queryByText } = render(); // group1 badge should be present @@ -46,7 +41,6 @@ describe('IdBadges', () => { }); describe('showAllBarBadges is false', () => { - test('shows link to show more badges if selection is over limit', () => { const { getByText } = render(); const showMoreLink = getByText('And 1 more'); @@ -58,14 +52,13 @@ describe('IdBadges', () => { const showMoreLink = queryByText(/ more/); expect(showMoreLink).toBeNull(); }); - }); describe('showAllBarBadges is true', () => { const overLimitShowAllProps = { ...props, showAllBarBadges: true, - selectedIds: ['group1', 'job1', 'job3', 'job4'] + selectedIds: ['group1', 'job1', 'job3', 'job4'], }; test('shows all badges when selection is over limit', () => { @@ -86,7 +79,5 @@ describe('IdBadges', () => { const hideLink = getByText('Hide'); expect(hideLink).toBeDefined(); }); - }); - }); diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.test.js b/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.test.js index af300e51eef99..41e510459fcea 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.test.js +++ b/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector_table/job_selector_table.test.js @@ -4,20 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ - - import React from 'react'; -import { cleanup, fireEvent, render } from 'react-testing-library'; +import { fireEvent, render } from '@testing-library/react'; // eslint-disable-line import/no-extraneous-dependencies import { JobSelectorTable } from './job_selector_table'; - jest.mock('../../../services/job_service', () => ({ mlJobService: { - getJob: jest.fn() - } + getJob: jest.fn(), + }, })); - const props = { ganttBarWidth: 299, groupsList: [ @@ -27,8 +23,8 @@ const props = { timeRange: { fromPx: 15.1, label: 'Apr 20th 2019, 20: 39 to Jun 20th 2019, 17: 45', - widthPx: 283.89 - } + widthPx: 283.89, + }, }, { id: 'ecommerce', @@ -36,8 +32,8 @@ const props = { timeRange: { fromPx: 1, label: 'Apr 17th 2019, 20:04 to May 18th 2019, 19:45', - widthPx: 144.5 - } + widthPx: 144.5, + }, }, { id: 'flights', @@ -45,9 +41,9 @@ const props = { timeRange: { fromPx: 19.6, label: 'Apr 21st 2019, 20:00 to Jun 2nd 2019, 19:50', - widthPx: 195.8 - } - } + widthPx: 195.8, + }, + }, ], jobs: [ { @@ -59,8 +55,8 @@ const props = { timeRange: { fromPx: 12.3, label: 'Apr 20th 2019, 20:39 to Jun 20th 2019, 17:45', - widthPx: 228.6 - } + widthPx: 228.6, + }, }, { groups: ['logs'], @@ -71,8 +67,8 @@ const props = { timeRange: { fromPx: 10, label: 'Apr 20th 2019, 20:39 to Jun 20th 2019, 17:45', - widthPx: 182.9 - } + widthPx: 182.9, + }, }, { groups: ['ecommerce'], @@ -83,7 +79,7 @@ const props = { timeRange: { fromPx: 1, label: 'Apr 17th 2019, 20:04 to May 18th 2019, 19:45', - widthPx: 93.1 + widthPx: 93.1, }, }, { @@ -95,19 +91,16 @@ const props = { timeRange: { fromPx: 1, label: 'Apr 17th 2019, 20:04 to May 18th 2019, 19:45', - widthPx: 93.1 + widthPx: 93.1, }, - } + }, ], onSelection: jest.fn(), selectedIds: ['price-by-day'], }; describe('JobSelectorTable', () => { - afterEach(cleanup); - describe('Single Selection', () => { - test('Does not render tabs', () => { const singleSelectionProps = { ...props, singleSelection: true }; const { queryByRole } = render(); @@ -128,28 +121,26 @@ describe('JobSelectorTable', () => { const radioButton = getByTestId('non-timeseries-job-radio-button'); expect(radioButton.firstChild.disabled).toEqual(true); }); - }); describe('Not Single Selection', () => { - test('renders tabs when not singleSelection', () => { - const { getByRole } = render(); - const tabs = getByRole('tab'); + const { getAllByRole } = render(); + const tabs = getAllByRole('tab'); expect(tabs).toBeDefined(); }); test('toggles content when tabs clicked', () => { // Default is Jobs tab so select Groups tab - const { getByText } = render(); + const { getByText, getAllByText } = render(); const groupsTab = getByText('Groups'); fireEvent.click(groupsTab); - const groupsTableHeader = getByText('jobs in group'); + const groupsTableHeader = getAllByText('jobs in group'); expect(groupsTableHeader).toBeDefined(); // switch back to Jobs tab const jobsTab = getByText('Jobs'); fireEvent.click(jobsTab); - const jobsTableHeader = getByText('job ID'); + const jobsTableHeader = getAllByText('job ID'); expect(jobsTableHeader).toBeDefined(); }); @@ -160,7 +151,10 @@ describe('JobSelectorTable', () => { }); test('incoming selectedIds are checked in the table when multiple ids', () => { - const multipleSelectedIdsProps = { ...props, selectedIds: ['price-by-day', 'bytes-by-geo-dest'] }; + const multipleSelectedIdsProps = { + ...props, + selectedIds: ['price-by-day', 'bytes-by-geo-dest'], + }; const { getByTestId } = render(); const priceByDayCheckbox = getByTestId('price-by-day-checkbox'); const bytesByGeoCheckbox = getByTestId('bytes-by-geo-dest-checkbox'); @@ -175,7 +169,5 @@ describe('JobSelectorTable', () => { const groupDropdownButton = getByText('Group'); expect(groupDropdownButton).toBeDefined(); }); - }); - }); diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/new_selection_id_badges/new_selection_id_badges.test.js b/x-pack/legacy/plugins/ml/public/application/components/job_selector/new_selection_id_badges/new_selection_id_badges.test.js index a9e07c7e15f46..0ddeb15d5c2c7 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/job_selector/new_selection_id_badges/new_selection_id_badges.test.js +++ b/x-pack/legacy/plugins/ml/public/application/components/job_selector/new_selection_id_badges/new_selection_id_badges.test.js @@ -4,32 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ - import React from 'react'; -import { cleanup, render } from 'react-testing-library'; +import { render } from '@testing-library/react'; // eslint-disable-line import/no-extraneous-dependencies import { NewSelectionIdBadges } from './new_selection_id_badges'; - - const props = { limit: 2, maps: { groupsMap: { - 'group1': ['job1', 'job2'], - 'group2': ['job3'] - } + group1: ['job1', 'job2'], + group2: ['job3'], + }, }, onLinkClick: jest.fn(), onDeleteClick: jest.fn(), newSelection: ['group1', 'job1', 'job3'], - showAllBadges: false + showAllBadges: false, }; describe('NewSelectionIdBadges', () => { - afterEach(cleanup); - describe('showAllBarBadges is false', () => { - test('shows link to show more badges if selection is over limit', () => { const { getByText } = render(); const showMoreLink = getByText('And 1 more'); @@ -37,18 +31,17 @@ describe('NewSelectionIdBadges', () => { }); test('does not show link to show more badges if selection is within limit', () => { - const underLimitProps = { ...props, newSelection: ['group1', 'job1'], }; + const underLimitProps = { ...props, newSelection: ['group1', 'job1'] }; const { queryByText } = render(); const showMoreLink = queryByText(/ more/); expect(showMoreLink).toBeNull(); }); - }); describe('showAllBarBadges is true', () => { const showAllTrueProps = { ...props, - showAllBadges: true + showAllBadges: true, }; test('shows all badges when selection is over limit', () => { @@ -69,7 +62,5 @@ describe('NewSelectionIdBadges', () => { const hideLink = getByText('Hide'); expect(hideLink).toBeDefined(); }); - }); - }); 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 16e4a563c33ae..18b3382175fdd 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/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx index 6fd80c524f8ef..2a939d93a48b3 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/action_delete.test.tsx @@ -5,8 +5,7 @@ */ import React from 'react'; -import { cleanup, render } from 'react-testing-library'; -import 'jest-dom/extend-expect'; +import { render } from '@testing-library/react'; import * as CheckPrivilige from '../../../../../privilege/check_privilege'; import { queryByTestSubj } from '../../../../../util/test_utils'; @@ -22,8 +21,6 @@ jest.mock('../../../../../privilege/check_privilege', () => ({ jest.mock('ui/new_platform'); describe('DeleteAction', () => { - afterEach(cleanup); - test('When canDeleteDataFrameAnalytics permission is false, button should be disabled.', () => { const { container } = render(); expect(queryByTestSubj(container, 'mlAnalyticsJobDeleteButton')).toHaveAttribute('disabled'); diff --git a/x-pack/legacy/plugins/ml/public/application/util/test_utils.ts b/x-pack/legacy/plugins/ml/public/application/util/test_utils.ts index 00c06757beca8..5c020840182e5 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/test_utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/test_utils.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { queryHelpers, Matcher } from 'react-testing-library'; +import { queryHelpers } from '@testing-library/react'; /** * 'react-testing-library provides 'queryByTestId()' to get @@ -14,5 +14,5 @@ import { queryHelpers, Matcher } from 'react-testing-library'; * @param {Matcher} id The 'data-test-subj' id. * @returns {HTMLElement | null} */ -export const queryByTestSubj = (container: HTMLElement, id: Matcher) => - queryHelpers.queryByAttribute('data-test-subj', container, id); + +export const queryByTestSubj = queryHelpers.queryByAttribute.bind(null, 'data-test-subj'); 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 6bc98ba68f60b..7a9766f36a6ed 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 b2b697a851703..b789121beebfc 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 97046bfb7d5b4..79db8cb920ea3 100644 --- a/x-pack/legacy/plugins/monitoring/index.js +++ b/x-pack/legacy/plugins/monitoring/index.js @@ -56,9 +56,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, @@ -70,11 +67,12 @@ 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, infra: server.plugins.infra, + usageCollection, }; new Plugin().setup(serverFacade, plugins); diff --git a/x-pack/legacy/plugins/monitoring/public/components/chart/_chart.scss b/x-pack/legacy/plugins/monitoring/public/components/chart/_chart.scss index d3b705a5eb492..1b8ebb762533d 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/chart/_chart.scss +++ b/x-pack/legacy/plugins/monitoring/public/components/chart/_chart.scss @@ -1,7 +1,5 @@ -@mixin monitoringNoUserSelect(){ +@mixin monitoringNoUserSelect { user-select: none; - -webkit-touch-callout: none; - -webkit-tap-highlight-color: transparent; } .monRhythmChart__wrapper .monRhythmChart__zoom { @@ -12,7 +10,7 @@ .monRhythmChart__wrapper:hover .monRhythmChart__zoom { visibility: visible; } - + .monRhythmChart { position: relative; display: flex; @@ -50,7 +48,7 @@ // SASSTODO: generic selector div { - @include monitoringNoUserSelect(); + @include monitoringNoUserSelect; } } @@ -58,6 +56,9 @@ font-size: $euiFontSizeXS; cursor: pointer; color: $euiTextColor; + display: flex; + flex-direction: row; + align-items: center; &-isDisabled { opacity: 0.5; @@ -71,7 +72,11 @@ .monRhythmChart__legendLabel { overflow: hidden; white-space: nowrap; + display: flex; + flex-direction: row; + align-items: center; } + .monRhythmChart__legendValue { overflow: hidden; white-space: nowrap; diff --git a/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js b/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js index 5443d6cbee6b5..78501aca566f6 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js +++ b/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js @@ -47,7 +47,7 @@ export class ChartTarget extends React.Component { return (_metric) => true; } - componentWillReceiveProps(newProps) { + UNSAFE_componentWillReceiveProps(newProps) { if (this.plot && !_.isEqual(newProps, this.props)) { const { series, timeRange } = newProps; diff --git a/x-pack/legacy/plugins/monitoring/public/components/chart/horizontal_legend.js b/x-pack/legacy/plugins/monitoring/public/components/chart/horizontal_legend.js index 9ce4d6224c45e..ab322324ac200 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/chart/horizontal_legend.js +++ b/x-pack/legacy/plugins/monitoring/public/components/chart/horizontal_legend.js @@ -6,9 +6,7 @@ import React from 'react'; import { includes, isFunction } from 'lodash'; -import { - EuiKeyboardAccessible, -} from '@elastic/eui'; +import { EuiFlexItem, EuiFlexGroup, EuiIcon, EuiKeyboardAccessible } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -23,11 +21,7 @@ export class HorizontalLegend extends React.Component { * @param {Number} value Final value to display */ displayValue(value) { - return ( - - { value } - - ); + return {value}; } /** @@ -44,10 +38,12 @@ export class HorizontalLegend extends React.Component { */ formatter(value, row) { if (!this.validValue(value)) { - return (); + return ( + + ); } if (row && row.tickFormatter) { @@ -61,38 +57,38 @@ export class HorizontalLegend extends React.Component { } createSeries(row, rowIdx) { - const classes = ['col-md-4 col-xs-6 monRhythmChart__legendItem']; + const classes = ['monRhythmChart__legendItem']; if (!includes(this.props.seriesFilter, row.id)) { classes.push('monRhythmChart__legendItem-isDisabled'); } if (!row.label || row.legend === false) { - return ( -
    - ); + return
    ; } return ( -
    this.props.onToggle(event, row.id)} - > - - - { ' ' + row.label + ' ' } - - { this.formatter(this.props.seriesValues[row.id], row) } -
    + + +
    ); } @@ -102,9 +98,9 @@ export class HorizontalLegend extends React.Component { return (
    -
    - { rows } -
    + + {rows} +
    ); } diff --git a/x-pack/legacy/plugins/monitoring/public/components/chart/monitoring_timeseries_container.js b/x-pack/legacy/plugins/monitoring/public/components/chart/monitoring_timeseries_container.js index 9216ac7c28705..6760a037fbe8a 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/chart/monitoring_timeseries_container.js +++ b/x-pack/legacy/plugins/monitoring/public/components/chart/monitoring_timeseries_container.js @@ -12,12 +12,18 @@ import { MonitoringTimeseries } from './monitoring_timeseries'; import { InfoTooltip } from './info_tooltip'; import { - EuiIconTip, EuiFlexGroup, EuiFlexItem, EuiTitle, EuiScreenReaderOnly, EuiTextAlign, EuiButtonEmpty + EuiIconTip, + EuiFlexGroup, + EuiFlexItem, + EuiTitle, + EuiScreenReaderOnly, + EuiTextAlign, + EuiButtonEmpty, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -const zoomOutBtn = (zoomInfo) => { +const zoomOutBtn = zoomInfo => { if (!zoomInfo || !zoomInfo.showZoomOutBtn()) { return null; } @@ -28,9 +34,9 @@ const zoomOutBtn = (zoomInfo) => { - {' '} `${item.metric.label}: ${item.metric.description}`)); + bucketSize, + }, + }), + ].concat(series.map(item => `${item.metric.label}: ${item.metric.description}`)); return ( @@ -68,7 +73,8 @@ export function MonitoringTimeseriesContainer({ series, onBrush, zoomInfo }) {

    - { getTitle(series) }{ units ? ` (${units})` : '' } + {getTitle(series)} + {units ? ` (${units})` : ''} } + content={} /> @@ -95,14 +101,11 @@ export function MonitoringTimeseriesContainer({ series, onBrush, zoomInfo }) { - { zoomOutBtn(zoomInfo) } + {zoomOutBtn(zoomInfo)} - + ); diff --git a/x-pack/legacy/plugins/monitoring/public/components/chart/timeseries_visualization.js b/x-pack/legacy/plugins/monitoring/public/components/chart/timeseries_visualization.js index 1ae997c5ebaa4..540460478cf53 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/chart/timeseries_visualization.js +++ b/x-pack/legacy/plugins/monitoring/public/components/chart/timeseries_visualization.js @@ -101,7 +101,7 @@ export class TimeseriesVisualization extends React.Component { } - componentWillReceiveProps(props) { + UNSAFE_componentWillReceiveProps(props) { const values = this.getLastValues(props); const currentKeys = _.keys(this.state.values); const keys = _.keys(values); diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js index d53f267865232..232815e930388 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js @@ -31,12 +31,9 @@ const columns = [ field: 'name', width: '350px', sortable: true, - render: (value) => ( + render: value => (
    - + {value}
    @@ -48,12 +45,13 @@ const columns = [ }), field: 'status', sortable: true, - render: (value) => ( -
    -   + render: value => ( +
    + +   {capitalize(value)}
    - ) + ), }, { name: i18n.translate('xpack.monitoring.elasticsearch.indices.documentCountTitle', { @@ -62,10 +60,8 @@ const columns = [ field: 'doc_count', sortable: true, render: value => ( -
    - {formatMetric(value, LARGE_ABBREVIATED)} -
    - ) +
    {formatMetric(value, LARGE_ABBREVIATED)}
    + ), }, { name: i18n.translate('xpack.monitoring.elasticsearch.indices.dataTitle', { @@ -73,11 +69,7 @@ const columns = [ }), field: 'data_size', sortable: true, - render: value => ( -
    - {formatMetric(value, LARGE_BYTES)} -
    - ) + render: value =>
    {formatMetric(value, LARGE_BYTES)}
    , }, { name: i18n.translate('xpack.monitoring.elasticsearch.indices.indexRateTitle', { @@ -85,11 +77,7 @@ const columns = [ }), field: 'index_rate', sortable: true, - render: value => ( -
    - {formatMetric(value, LARGE_FLOAT, '/s')} -
    - ) + render: value =>
    {formatMetric(value, LARGE_FLOAT, '/s')}
    , }, { name: i18n.translate('xpack.monitoring.elasticsearch.indices.searchRateTitle', { @@ -98,10 +86,8 @@ const columns = [ field: 'search_rate', sortable: true, render: value => ( -
    - {formatMetric(value, LARGE_FLOAT, '/s')} -
    - ) +
    {formatMetric(value, LARGE_FLOAT, '/s')}
    + ), }, { name: i18n.translate('xpack.monitoring.elasticsearch.indices.unassignedShardsTitle', { @@ -109,12 +95,8 @@ const columns = [ }), field: 'unassigned_shards', sortable: true, - render: value => ( -
    - {formatMetric(value, '0')} -
    - ) - } + render: value =>
    {formatMetric(value, '0')}
    , + }, ]; const getNoDataMessage = () => { @@ -154,16 +136,16 @@ export const ElasticsearchIndices = ({ - )} + } checked={showSystemIndices} onChange={e => toggleShowSystemIndices(e.target.checked)} /> - + diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js index 72a74964fd35e..b06cbb44503d1 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js @@ -12,6 +12,7 @@ import { EuiMonitoringSSPTable } from '../../table'; import { MetricCell, OfflineCell } from './cells'; import { SetupModeBadge } from '../../setup_mode/badge'; import { + EuiIcon, EuiLink, EuiToolTip, EuiSpacer, @@ -21,20 +22,23 @@ import { EuiPanel, EuiCallOut, EuiButton, - EuiText + EuiText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import _ from 'lodash'; import { ELASTICSEARCH_SYSTEM_ID } from '../../../../common/constants'; import { ListingCallOut } from '../../setup_mode/listing_callout'; -const getSortHandler = (type) => (item) => _.get(item, [type, 'summary', 'lastVal']); +const getSortHandler = type => item => _.get(item, [type, 'summary', 'lastVal']); const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { const cols = []; - const cpuUsageColumnTitle = i18n.translate('xpack.monitoring.elasticsearch.nodes.cpuUsageColumnTitle', { - defaultMessage: 'CPU Usage', - }); + const cpuUsageColumnTitle = i18n.translate( + 'xpack.monitoring.elasticsearch.nodes.cpuUsageColumnTitle', + { + defaultMessage: 'CPU Usage', + } + ); cols.push({ name: i18n.translate('xpack.monitoring.elasticsearch.nodes.nameColumnTitle', { @@ -59,7 +63,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { const status = list[node.resolver] || {}; const instance = { uuid: node.resolver, - name: node.name + name: node.name, }; setupModeStatus = ( @@ -82,25 +86,18 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => {
    - - + + {node.nodeTypeClass && }   - - {nameLink} - + {nameLink}
    -
    - {extractIp(node.transport_address)} -
    +
    {extractIp(node.transport_address)}
    {setupModeStatus}
    ); - } + }, }); cols.push({ @@ -110,21 +107,19 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { field: 'isOnline', sortable: true, render: value => { - const status = value ? i18n.translate('xpack.monitoring.elasticsearch.nodes.statusColumn.onlineLabel', { - defaultMessage: 'Online', - }) : i18n.translate('xpack.monitoring.elasticsearch.nodes.statusColumn.offlineLabel', { - defaultMessage: 'Offline', - }); + const status = value + ? i18n.translate('xpack.monitoring.elasticsearch.nodes.statusColumn.onlineLabel', { + defaultMessage: 'Online', + }) + : i18n.translate('xpack.monitoring.elasticsearch.nodes.statusColumn.offlineLabel', { + defaultMessage: 'Offline', + }); return (
    - {' '} - {status} + {status}
    ); - } + }, }); cols.push({ @@ -138,8 +133,10 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => {
    {value}
    - ) : ; - } + ) : ( + + ); + }, }); if (showCgroupMetricsElasticsearch) { @@ -154,7 +151,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { isPercent={true} data-test-subj="cpuQuota" /> - ) + ), }); cols.push({ @@ -170,7 +167,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { isPercent={false} data-test-subj="cpuThrottled" /> - ) + ), }); } else { cols.push({ @@ -184,7 +181,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { isPercent={true} data-test-subj="cpuUsage" /> - ) + ), }); cols.push({ @@ -200,7 +197,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { isPercent={false} data-test-subj="loadAverage" /> - ) + ), }); } @@ -208,8 +205,8 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { name: i18n.translate('xpack.monitoring.elasticsearch.nodes.jvmMemoryColumnTitle', { defaultMessage: '{javaVirtualMachine} Heap', values: { - javaVirtualMachine: 'JVM' - } + javaVirtualMachine: 'JVM', + }, }), field: 'node_jvm_mem_percent', sortable: getSortHandler('node_jvm_mem_percent'), @@ -220,7 +217,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { isPercent={true} data-test-subj="jvmMemory" /> - ) + ), }); cols.push({ @@ -236,7 +233,7 @@ const getColumns = (showCgroupMetricsElasticsearch, setupMode, clusterUuid) => { isPercent={false} data-test-subj="diskFreeSpace" /> - ) + ), }); return cols; @@ -252,18 +249,22 @@ export function ElasticsearchNodes({ clusterStatus, showCgroupMetricsElasticsear // We want to create a seamless experience for the user by merging in the setup data // and the node data from monitoring indices in the likely scenario where some nodes // are using MB collection and some are using no collection - const nodesByUuid = nodes.reduce((byUuid, node) => ({ - ...byUuid, - [node.id || node.resolver]: node - }), {}); + const nodesByUuid = nodes.reduce( + (byUuid, node) => ({ + ...byUuid, + [node.id || node.resolver]: node, + }), + {} + ); - nodes.push(...Object.entries(setupMode.data.byUuid) - .reduce((nodes, [nodeUuid, instance]) => { + nodes.push( + ...Object.entries(setupMode.data.byUuid).reduce((nodes, [nodeUuid, instance]) => { if (!nodesByUuid[nodeUuid] && instance.node) { nodes.push(instance.node); } return nodes; - }, [])); + }, []) + ); } let setupModeCallout = null; @@ -276,64 +277,81 @@ export function ElasticsearchNodes({ clusterStatus, showCgroupMetricsElasticsear customRenderer={() => { const customRenderResponse = { shouldRender: false, - componentToRender: null + componentToRender: null, }; const isNetNewUser = setupMode.data.totalUniqueInstanceCount === 0; - const hasNoInstances = setupMode.data.totalUniqueInternallyCollectedCount === 0 - && setupMode.data.totalUniqueFullyMigratedCount === 0 - && setupMode.data.totalUniquePartiallyMigratedCount === 0; + const hasNoInstances = + setupMode.data.totalUniqueInternallyCollectedCount === 0 && + setupMode.data.totalUniqueFullyMigratedCount === 0 && + setupMode.data.totalUniquePartiallyMigratedCount === 0; if (isNetNewUser || hasNoInstances) { customRenderResponse.shouldRender = true; customRenderResponse.componentToRender = ( 0 ? 'danger' : 'warning'} iconType="flag" >

    - {i18n.translate('xpack.monitoring.elasticsearch.nodes.metricbeatMigration.detectedNodeDescription', { - defaultMessage: `The following nodes are not monitored. Click 'Monitor with Metricbeat' below to start monitoring.`, - })} + {i18n.translate( + 'xpack.monitoring.elasticsearch.nodes.metricbeatMigration.detectedNodeDescription', + { + defaultMessage: `The following nodes are not monitored. Click 'Monitor with Metricbeat' below to start monitoring.`, + } + )}

    - +
    ); - } - else if (setupMode.data.totalUniquePartiallyMigratedCount === setupMode.data.totalUniqueInstanceCount) { - const finishMigrationAction = _.get(setupMode.meta, 'liveClusterUuid') === clusterUuid - ? setupMode.shortcutToFinishMigration - : setupMode.openFlyout; + } else if ( + setupMode.data.totalUniquePartiallyMigratedCount === + setupMode.data.totalUniqueInstanceCount + ) { + const finishMigrationAction = + _.get(setupMode.meta, 'liveClusterUuid') === clusterUuid + ? setupMode.shortcutToFinishMigration + : setupMode.openFlyout; customRenderResponse.shouldRender = true; customRenderResponse.componentToRender = (

    - {i18n.translate('xpack.monitoring.elasticsearch.nodes.metricbeatMigration.disableInternalCollectionDescription', { - defaultMessage: `Disable self monitoring to finish the migration.` - })} + {i18n.translate( + 'xpack.monitoring.elasticsearch.nodes.metricbeatMigration.disableInternalCollectionDescription', + { + defaultMessage: `Disable self monitoring to finish the migration.`, + } + )}

    {i18n.translate( - 'xpack.monitoring.elasticsearch.nodes.metricbeatMigration.disableInternalCollectionMigrationButtonLabel', { - defaultMessage: 'Disable self monitoring' + 'xpack.monitoring.elasticsearch.nodes.metricbeatMigration.disableInternalCollectionMigrationButtonLabel', + { + defaultMessage: 'Disable self monitoring', } )}
    - +
    ); } @@ -375,9 +393,12 @@ export function ElasticsearchNodes({ clusterStatus, showCgroupMetricsElasticsear search={{ box: { incremental: true, - placeholder: i18n.translate('xpack.monitoring.elasticsearch.nodes.monitoringTablePlaceholder', { - defaultMessage: 'Filter Nodes…' - }), + placeholder: i18n.translate( + 'xpack.monitoring.elasticsearch.nodes.monitoringTablePlaceholder', + { + defaultMessage: 'Filter Nodes…', + } + ), }, }} onTableChange={onTableChange} diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/_shard_allocation.scss b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/_shard_allocation.scss index 690b1b81a0d03..50e92d572908c 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/_shard_allocation.scss +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/_shard_allocation.scss @@ -1,9 +1,3 @@ -// SASSTODO: Generic selector -monitoring-shard-allocation { - display: block; - border-top: $euiSizeS solid $euiColorLightestShade; -} - .monClusterTitle { font-size: $euiFontSizeL; margin: 0; @@ -11,55 +5,48 @@ monitoring-shard-allocation { // SASSTODO: This needs a full rewrite / redesign .monCluster { - cluster-view { - display: block; - } - .parent { - padding-top: 14px; - border-left: 3px solid $euiColorSuccess !important; - &.red { - border-left: 3px solid $euiColorDanger !important; - } - &.yellow { - border-left: 3px solid $euiColorWarning !important; - } - } - td.unassigned { + .monUnassigned { vertical-align: middle; width: 150px; } - .child { + .monUnassigned__children, + .monAssigned__children { + padding-top: $euiSizeL; + } + + .monChild { float: left; align-self: center; - + background-color: $euiColorLightestShade; + margin: $euiSizeS; + border: 1px solid $euiColorMediumShade; + border-radius: $euiSizeXS; + padding: $euiSizeXS/2 0; + // SASS-TODO: Rename this class following Eui conventions &.index { border-left: $euiSizeXS solid $euiColorSuccess; - &.red { + + &.monChild--danger { border-left: $euiSizeXS solid $euiColorDanger; } - &.yellow { + + &.monChild--warning { border-left: $euiSizeXS solid $euiColorWarning; } } - background-color: $euiColorDarkShade; - margin: 5px; - .title { - padding: 5px 7px; - display: inline-block; + + .monChild__title { + padding: $euiSizeXS $euiSizeS; text-align: center; - font-size: 12px; - font: 10px sans-serif; + font-size: $euiFontSizeXS; color: $euiColorGhost; - a { - color: $euiColorGhost; - text-decoration: none; - } - i { - margin-left: 5px; - } + display: flex; + flex-direction: row; + align-items: center; } - &.unassigned { + + &.monClusterUnassigned { .title { display: none; } @@ -73,30 +60,12 @@ monitoring-shard-allocation { td:first-child { width: 200px; } - + // SASS-TODO: Rename this class following Eui conventions .shard { align-self: center; - padding: 5px 7px; - font: 10px sans-serif; - border-left: 1px solid $euiColorEmptyShade; + padding: $euiSizeXS $euiSizeS; + font-size: $euiFontSizeXS; position: relative; display: inline-block; } - - .legend { - font-size: 12px; - background-color: $euiColorEmptyShade; - .title { - margin-left: 5px; - font-weight: bold; - } - color: $euiColorDarkestShade; - padding: 5px; - span.shard { - float: none; - display: inline-block; - margin: 0 5px 0 10px; - padding: 0 4px; - } - } } diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/assigned.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/assigned.js index ec1b36837af92..012bc81135e34 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/assigned.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/assigned.js @@ -4,39 +4,40 @@ * you may not use this file except in compliance with the Elastic License. */ - - import { get, sortBy } from 'lodash'; import React from 'react'; import { Shard } from './shard'; import { calculateClass } from '../lib/calculate_class'; import { generateQueryAndLink } from '../lib/generate_query_and_link'; -import { - EuiKeyboardAccessible, -} from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiKeyboardAccessible } from '@elastic/eui'; function sortByName(item) { if (item.type === 'node') { - return [ !item.master, item.name]; + return [!item.master, item.name]; } - return [ item.name ]; + return [item.name]; } export class Assigned extends React.Component { - createShard = (shard) => { + createShard = shard => { const type = shard.primary ? 'primary' : 'replica'; const key = `${shard.index}.${shard.node}.${type}.${shard.state}.${shard.shard}`; - return ( - - ); + return ; }; - createChild = (data) => { + createChild = data => { const key = data.id; - const initialClasses = ['child']; + const initialClasses = ['monChild']; const shardStats = get(this.props.shardStats.indices, key); if (shardStats) { - initialClasses.push(shardStats.status); + switch (shardStats.status) { + case 'red': + initialClasses.push('monChild--danger'); + break; + case 'yellow': + initialClasses.push('monChild--warning'); + break; + } } const changeUrl = () => { @@ -52,28 +53,39 @@ export class Assigned extends React.Component { ); - const master = (data.node_type === 'master') ? : null; + const master = + data.node_type === 'master' ? : null; const shards = sortBy(data.children, 'shard').map(this.createShard); return ( -
    -
    {name}{master}
    - {shards} -
    + + + + {name} + {master} + + + + {shards} + + + ); }; render() { const data = sortBy(this.props.data, sortByName).map(this.createChild); return ( - -
    + + {data} -
    + ); } diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/cluster_view.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/cluster_view.js index 8c85c40951777..a4640fa45119b 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/cluster_view.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/cluster_view.js @@ -43,7 +43,7 @@ export class ClusterView extends React.Component { this.setState({ shardStats: stats }); }; - componentWillMount() { + UNSAFE_componentWillMount() { this.props.scope.$watch('showing', this.setShowing); this.props.scope.$watch(() => this.props.scope.pageData.shardStats, this.setShardStats); } diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/unassigned.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/unassigned.js index e350e3b037712..728165386cd18 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/unassigned.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/unassigned.js @@ -4,30 +4,44 @@ * you may not use this file except in compliance with the Elastic License. */ - - import _ from 'lodash'; import React from 'react'; import { Shard } from './shard'; import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup } from '@elastic/eui'; export class Unassigned extends React.Component { - static displayName = i18n.translate('xpack.monitoring.elasticsearch.shardAllocation.unassignedDisplayName', { - defaultMessage: 'Unassigned', - }); + static displayName = i18n.translate( + 'xpack.monitoring.elasticsearch.shardAllocation.unassignedDisplayName', + { + defaultMessage: 'Unassigned', + } + ); - createShard = (shard) => { + createShard = shard => { const type = shard.primary ? 'primary' : 'replica'; const additionId = shard.state === 'UNASSIGNED' ? Math.random() : ''; - const key = shard.index + '.' + shard.node + '.' + type + '.' + shard.state + '.' + shard.shard + additionId; - return (); + const key = + shard.index + + '.' + + shard.node + + '.' + + type + + '.' + + shard.state + + '.' + + shard.shard + + additionId; + return ; }; render() { const shards = _.sortBy(this.props.shards, 'shard').map(this.createShard); return ( - -
    {shards}
    + + + {shards} + ); } diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/shard_allocation.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/shard_allocation.js index 50ab2653ced37..5e93e698a33a9 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/shard_allocation.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/shard_allocation.js @@ -66,7 +66,7 @@ export const ShardAllocation = ({

    - + { types.map(type => ( diff --git a/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js b/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js index dadb31f2cc83b..83c42c6dff37b 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js +++ b/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js @@ -35,7 +35,7 @@ export class SetupModeRenderer extends React.Component { isSettingUpNew: false, }; - componentWillMount() { + UNSAFE_componentWillMount() { const { scope, injector } = this.props; initSetupModeState(scope, injector, _oldData => { const newState = { renderState: true }; diff --git a/x-pack/legacy/plugins/monitoring/public/components/sparkline/index.js b/x-pack/legacy/plugins/monitoring/public/components/sparkline/index.js index 49d4dcfbf9a66..6f52a82c40820 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/sparkline/index.js +++ b/x-pack/legacy/plugins/monitoring/public/components/sparkline/index.js @@ -22,7 +22,7 @@ export class Sparkline extends React.Component { }; } - componentWillReceiveProps({ series, options }) { + UNSAFE_componentWillReceiveProps({ series, options }) { if (!isEqual(options, this.props.options)) { this.sparklineFlotChart.shutdown(); this.makeSparklineFlotChart(options); diff --git a/x-pack/legacy/plugins/monitoring/public/components/status_icon/index.js b/x-pack/legacy/plugins/monitoring/public/components/status_icon/index.js index a054f83704176..a31823ef2e773 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/status_icon/index.js +++ b/x-pack/legacy/plugins/monitoring/public/components/status_icon/index.js @@ -5,25 +5,18 @@ */ import React from 'react'; +import { EuiIcon } from '@elastic/eui'; export function StatusIcon({ type, label }) { const typeToIconMap = { - [StatusIcon.TYPES.RED]: 'health-red.svg', - [StatusIcon.TYPES.YELLOW]: 'health-yellow.svg', - [StatusIcon.TYPES.GREEN]: 'health-green.svg', - [StatusIcon.TYPES.GRAY]: 'health-gray.svg', + [StatusIcon.TYPES.RED]: 'danger', + [StatusIcon.TYPES.YELLOW]: 'warning', + [StatusIcon.TYPES.GREEN]: 'success', + [StatusIcon.TYPES.GRAY]: 'subdued', }; const icon = typeToIconMap[type]; - return ( - - {label} - - ); + return ; } StatusIcon.TYPES = { diff --git a/x-pack/legacy/plugins/monitoring/public/components/summary_status/__snapshots__/summary_status.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/summary_status/__snapshots__/summary_status.test.js.snap index 0842406774f73..a3d321f9e39b6 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/summary_status/__snapshots__/summary_status.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/summary_status/__snapshots__/summary_status.test.js.snap @@ -28,15 +28,16 @@ exports[`Summary Status Component should allow label to be optional 1`] = ` class="euiTitle euiTitle--xsmall euiStat__title" > Status: - - Status: yellow - +  Yellow

    Status: - - Status: green - +  Green

    { + 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 25efc63fafb5d..5d2ebf8dc2abc 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 f1f47761d9f0c..2c0250fb78592 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 bb561ddda42ab..2a56deaad4f8a 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 3c8eb5ebdf2d3..1099a23dea103 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 ae691f49e2b80..c202fe9589ab3 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/elasticsearch/nodes/__tests__/get_node_summary.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/__tests__/get_node_summary.js index 1df42256482be..30e9d1698b56a 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/__tests__/get_node_summary.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/__tests__/get_node_summary.js @@ -34,13 +34,13 @@ describe('Elasticsearch Node Summary get_node_summary handleResponse', () => { it('should handle incomplete shardStats data', () => { const clusterState = { nodes: { - fooNode: {} - } + fooNode: {}, + }, }; const shardStats = { nodes: { - fooNode: {} - } + fooNode: {}, + }, }; const resolver = 'fooNode'; @@ -62,7 +62,7 @@ describe('Elasticsearch Node Summary get_node_summary handleResponse', () => { totalSpace: undefined, usedHeap: undefined, nodeTypeLabel: 'Node', - nodeTypeClass: 'fa-server', + nodeTypeClass: 'storage', node_ids: [], status: 'Online', isOnline: true, @@ -72,17 +72,17 @@ describe('Elasticsearch Node Summary get_node_summary handleResponse', () => { it('should handle incomplete shardStats data, master node', () => { const clusterState = { nodes: { - 'fooNode-Uuid': {} + 'fooNode-Uuid': {}, }, - master_node: 'fooNode-Uuid' + master_node: 'fooNode-Uuid', }; const shardStats = { nodes: { 'fooNode-Uuid': { shardCount: 22, - indexCount: 11 - } - } + indexCount: 11, + }, + }, }; const resolver = 'fooNode-Uuid'; @@ -101,28 +101,28 @@ describe('Elasticsearch Node Summary get_node_summary handleResponse', () => { node_stats: { indices: { docs: { - count: 11000 + count: 11000, }, store: { - size_in_bytes: 35000 - } + size_in_bytes: 35000, + }, }, fs: { total: { available_in_bytes: 8700, - total_in_bytes: 10000 - } + total_in_bytes: 10000, + }, }, jvm: { mem: { - heap_used_percent: 33 - } - } - } - } - } - ] - } + heap_used_percent: 33, + }, + }, + }, + }, + }, + ], + }, }; const result = handleFn(response); @@ -140,10 +140,8 @@ describe('Elasticsearch Node Summary get_node_summary handleResponse', () => { totalSpace: 10000, usedHeap: 33, nodeTypeLabel: 'Master Node', - nodeTypeClass: 'fa-star', - node_ids: [ - 'fooNode-Uuid' - ], + nodeTypeClass: 'starFilled', + node_ids: ['fooNode-Uuid'], status: 'Online', isOnline: true, }); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/__tests__/get_node_type_class_label.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/__tests__/get_node_type_class_label.js index 4c21391a9ae62..2dc30a57db3d9 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/__tests__/get_node_type_class_label.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/__tests__/get_node_type_class_label.js @@ -11,12 +11,12 @@ describe('Node Type and Label', () => { describe('when master node', () => { it('type is indicated by boolean flag', () => { const node = { - master: true + master: true, }; const { nodeType, nodeTypeLabel, nodeTypeClass } = getNodeTypeClassLabel(node); expect(nodeType).to.be('master'); expect(nodeTypeLabel).to.be('Master Node'); - expect(nodeTypeClass).to.be('fa-star'); + expect(nodeTypeClass).to.be('starFilled'); }); it('type is indicated by string', () => { const node = {}; @@ -24,7 +24,7 @@ describe('Node Type and Label', () => { const { nodeType, nodeTypeLabel, nodeTypeClass } = getNodeTypeClassLabel(node, type); expect(nodeType).to.be('master'); expect(nodeTypeLabel).to.be('Master Node'); - expect(nodeTypeClass).to.be('fa-star'); + expect(nodeTypeClass).to.be('starFilled'); }); }); it('when type is generic node', () => { @@ -33,6 +33,6 @@ describe('Node Type and Label', () => { const { nodeType, nodeTypeLabel, nodeTypeClass } = getNodeTypeClassLabel(node, type); expect(nodeType).to.be('node'); expect(nodeTypeLabel).to.be('Node'); - expect(nodeTypeClass).to.be('fa-server'); + expect(nodeTypeClass).to.be('storage'); }); }); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap index ba72d697388c6..db74cc5e330a1 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/handle_response.test.js.snap @@ -5,7 +5,7 @@ Array [ Object { "isOnline": false, "name": "hello01", - "nodeTypeClass": "fa-server", + "nodeTypeClass": "storage", "nodeTypeLabel": "Node", "resolver": "_x_V2YzPQU-a9KRRBxUxZQ", "shardCount": 6, @@ -15,7 +15,7 @@ Array [ Object { "isOnline": false, "name": "hello02", - "nodeTypeClass": "fa-server", + "nodeTypeClass": "storage", "nodeTypeLabel": "Node", "resolver": "DAiX7fFjS3Wii7g2HYKrOg", "shardCount": 6, @@ -32,7 +32,7 @@ Array [ Object { "isOnline": true, "name": "hello01", - "nodeTypeClass": "fa-star", + "nodeTypeClass": "starFilled", "nodeTypeLabel": "Master Node", "node_cgroup_quota": Object { "metric": Object { @@ -160,7 +160,7 @@ Array [ Object { "isOnline": true, "name": "hello02", - "nodeTypeClass": "fa-server", + "nodeTypeClass": "storage", "nodeTypeLabel": "Node", "node_cgroup_quota": undefined, "node_cgroup_throttled": Object { @@ -274,7 +274,7 @@ Array [ Object { "isOnline": true, "name": "hello01", - "nodeTypeClass": "fa-star", + "nodeTypeClass": "starFilled", "nodeTypeLabel": "Master Node", "node_cgroup_quota": null, "node_cgroup_throttled": null, @@ -290,7 +290,7 @@ Array [ Object { "isOnline": true, "name": "hello02", - "nodeTypeClass": "fa-server", + "nodeTypeClass": "storage", "nodeTypeLabel": "Node", "node_cgroup_quota": null, "node_cgroup_throttled": null, @@ -311,7 +311,7 @@ Array [ Object { "isOnline": true, "name": "hello01", - "nodeTypeClass": "fa-star", + "nodeTypeClass": "starFilled", "nodeTypeLabel": "Master Node", "node_cgroup_quota": Object { "metric": Object { @@ -439,7 +439,7 @@ Array [ Object { "isOnline": true, "name": "hello02", - "nodeTypeClass": "fa-server", + "nodeTypeClass": "storage", "nodeTypeLabel": "Node", "node_cgroup_quota": undefined, "node_cgroup_throttled": Object { diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/map_nodes_info.test.js.snap b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/map_nodes_info.test.js.snap index 9f75dd1f1ee0f..7eb22b0063745 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/map_nodes_info.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/__snapshots__/map_nodes_info.test.js.snap @@ -5,7 +5,7 @@ Object { "ENVgDIKRSdCVJo-YqY4kUQ": Object { "isOnline": true, "name": "node01", - "nodeTypeClass": "fa-star", + "nodeTypeClass": "starFilled", "nodeTypeLabel": "Master Node", "shardCount": 57, "transport_address": "127.0.0.1:9300", @@ -14,7 +14,7 @@ Object { "t9J9jvHpQ2yDw9c1LJ0tHA": Object { "isOnline": false, "name": "node02", - "nodeTypeClass": "fa-server", + "nodeTypeClass": "storage", "nodeTypeLabel": "Node", "shardCount": 0, "transport_address": "127.0.0.1:9301", diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/lookups.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/lookups.js index f8d97acf792c3..23b4021ee7c0c 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/lookups.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/lookups.js @@ -12,25 +12,31 @@ import { i18n } from '@kbn/i18n'; export const nodeTypeClass = { - invalid: 'fa-exclamation-triangle', - node: 'fa-server', - master: 'fa-star', - master_only: 'fa-star-o', - data: 'fa-database', - client: 'fa-binoculars' + invalid: 'alert', + node: 'storage', + master: 'starFilled', + master_only: 'starEmpty', + data: 'database', + client: 'glasses', }; export const nodeTypeLabel = { invalid: i18n.translate('xpack.monitoring.es.nodeType.invalidNodeLabel', { - defaultMessage: 'Invalid Node' }), + defaultMessage: 'Invalid Node', + }), node: i18n.translate('xpack.monitoring.es.nodeType.nodeLabel', { - defaultMessage: 'Node' }), + defaultMessage: 'Node', + }), master: i18n.translate('xpack.monitoring.es.nodeType.masterNodeLabel', { - defaultMessage: 'Master Node' }), + defaultMessage: 'Master Node', + }), master_only: i18n.translate('xpack.monitoring.es.nodeType.masterOnlyNodeLabel', { - defaultMessage: 'Master Only Node' }), + defaultMessage: 'Master Only Node', + }), data: i18n.translate('xpack.monitoring.es.nodeType.dataOnlyNodeLabel', { - defaultMessage: 'Data Only Node' }), + defaultMessage: 'Data Only Node', + }), client: i18n.translate('xpack.monitoring.es.nodeType.clientNodeLabel', { - defaultMessage: 'Client Node' }) + defaultMessage: 'Client Node', + }), }; 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 bb42dad26786a..36f085c424881 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 d25d8af4aaa20..540de7d1e3a7f 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 48a02109a3f6f..97930610e0593 100644 --- a/x-pack/legacy/plugins/monitoring/server/plugin.js +++ b/x-pack/legacy/plugins/monitoring/server/plugin.js @@ -9,35 +9,27 @@ import { LOGGING_TAG, KIBANA_MONITORING_LOGGING_TAG } from '../common/constants' 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'; - 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 @@ -110,7 +102,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 a0072e52fc7f7..c6bb368745830 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 012f987627369..1b592dabf2053 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 eeee9e18f9112..f86baef020aa2 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 8b825b13178f2..0121ed4304d26 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 555c7ac27b49d..09843a6f87ad7 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 998a1d2beeab1..1cebe78b9c7f0 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/export_types/common/layouts/preserve_layout.css b/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css index 9e8415a1ff18c..ab88e4780936e 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css +++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css @@ -92,11 +92,6 @@ visualize-app .visEditor__canvas { display: none; } -/* slightly increate legend text size for readability */ -.visualize visualize-legend .visLegend__valueTitle { - font-size: 1.2em; -} - /* Ensure the min-height of the small breakpoint isn't used */ .vis-editor visualization { min-height: 0 !important; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css index 30c253f36840a..8aca042144b3b 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css +++ b/x-pack/legacy/plugins/reporting/export_types/common/layouts/print.css @@ -91,11 +91,6 @@ visualize-app .visEditor__canvas { display: none; } -/* slightly increate legend text size for readability */ -.visualize visualize-legend .visLegend__valueTitle { - font-size: 1.2em; -} - /* Ensure the min-height of the small breakpoint isn't used */ .vis-editor visualization { min-height: 0 !important; diff --git a/x-pack/legacy/plugins/reporting/index.ts b/x-pack/legacy/plugins/reporting/index.ts index e2b5970d1efb7..9add3accd262f 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 91e2a9284550b..141ecb9c77656 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 32022c6fa642c..f23f679865146 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 5c52193769057..0a7ef0a194434 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/__jest__/client_integration/job_create_clone.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_clone.test.js index 29d2d00163ad8..204bab5c497be 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_clone.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_clone.test.js @@ -8,7 +8,6 @@ import { setupEnvironment, pageHelpers, nextTick } from './helpers'; import { JOB_TO_CLONE, JOB_CLONE_INDEX_PATTERN_CHECK } from './helpers/constants'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_date_histogram.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_date_histogram.test.js index 59814474396fe..b7b555d986597 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_date_histogram.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_date_histogram.test.js @@ -9,7 +9,6 @@ import moment from 'moment-timezone'; import { setupEnvironment, pageHelpers } from './helpers'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_histogram.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_histogram.test.js index 09417fa8ed307..dbbd7501b1518 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_histogram.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_histogram.test.js @@ -7,7 +7,6 @@ import { setupEnvironment, pageHelpers } from './helpers'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js index 99a0aa0935152..a853ef36e01cd 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js @@ -9,7 +9,6 @@ import { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } from '../../../../../../src/ import { setupEnvironment, pageHelpers } from './helpers'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_metrics.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_metrics.test.js index 2f26d2a7475de..d2f63983a3e36 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_metrics.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_metrics.test.js @@ -7,7 +7,6 @@ import { setupEnvironment, pageHelpers } from './helpers'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_review.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_review.test.js index 8ca736e62be7f..c89d37f4e0ac3 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_review.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_review.test.js @@ -9,7 +9,6 @@ import { first } from 'lodash'; import { JOBS } from './helpers/constants'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_terms.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_terms.test.js index 78e8d9ec0c53a..c27b9d0e4ef0f 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_terms.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_terms.test.js @@ -7,7 +7,6 @@ import { setupEnvironment, pageHelpers } from './helpers'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list.test.js index 05272bf222612..db7dddad4e3c1 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list.test.js @@ -9,7 +9,6 @@ import { setupEnvironment, pageHelpers, nextTick } from './helpers'; import { JOBS } from './helpers/constants'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('../../public/crud_app/services', () => { const services = require.requireActual('../../public/crud_app/services'); diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list_clone.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list_clone.test.js index ce62f6c67ae03..6feabe7f772ee 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list_clone.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_list_clone.test.js @@ -10,7 +10,6 @@ import { getRouter } from '../../public/crud_app/services/routing'; import { CRUD_APP_BASE_PATH } from '../../public/crud_app/constants'; jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); jest.mock('lodash/function/debounce', () => fn => fn); diff --git a/x-pack/legacy/plugins/rollup/index.js b/x-pack/legacy/plugins/rollup/index.js index 3b6c033a2d85a..e0c00a7db62f0 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 977253dfa53fb..99fffa774baaf 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/searchprofiler/public/np_ready/application/containers/main/components/empty_tree_placeholder.tsx b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/empty_tree_placeholder.tsx index bf27620dcac18..d709a8feb48bd 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/empty_tree_placeholder.tsx +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/empty_tree_placeholder.tsx @@ -15,13 +15,12 @@ export const EmptyTreePlaceHolder = () => { {/* TODO: translations */}

    {i18n.translate('xpack.searchProfiler.emptyProfileTreeTitle', { - defaultMessage: 'Nothing to see here yet.', + defaultMessage: 'No queries to profile', })}

    {i18n.translate('xpack.searchProfiler.emptyProfileTreeDescription', { - defaultMessage: - 'Enter a query and press the "Profile" button or provide profile data in the editor.', + defaultMessage: 'Enter a query, click Profile, and see the results here.', })}

    diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/profile_loading_placeholder.tsx b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/profile_loading_placeholder.tsx index fb09c6cddf70a..a7db54b670a84 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/profile_loading_placeholder.tsx +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/components/profile_loading_placeholder.tsx @@ -13,7 +13,7 @@ export const ProfileLoadingPlaceholder = () => {

    {i18n.translate('xpack.searchProfiler.profilingLoaderText', { - defaultMessage: 'Profiling...', + defaultMessage: 'Loading query profiles...', })}

    diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/main.tsx b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/main.tsx index 7f5d223949e61..63ae5c7583625 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/main.tsx +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/containers/main/main.tsx @@ -93,7 +93,7 @@ export const Main = () => { return ( <> - + {renderLicenseWarning()} diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.ts index dac9dab9bd092..615511786afd1 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.ts +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.ts @@ -12,7 +12,6 @@ import { OnHighlightChangeArgs } from '../components/profile_tree'; import { ShardSerialized, Targets } from '../types'; export type Action = - | { type: 'setPristine'; value: boolean } | { type: 'setProfiling'; value: boolean } | { type: 'setHighlightDetails'; value: OnHighlightChangeArgs | null } | { type: 'setActiveTab'; value: Targets | null } @@ -20,12 +19,8 @@ export type Action = export const reducer: Reducer = (state, action) => produce(state, draft => { - if (action.type === 'setPristine') { - draft.pristine = action.value; - return; - } - if (action.type === 'setProfiling') { + draft.pristine = false; draft.profiling = action.value; if (draft.profiling) { draft.currentResponse = null; diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/store.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/store.ts index 7b5a1ce93583d..7008854a16285 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/store.ts +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/store.ts @@ -18,7 +18,7 @@ export interface State { export const initialState: State = { profiling: false, - pristine: false, + pristine: true, highlightDetails: null, activeTab: null, currentResponse: null, diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss index a72d079354f89..d36a587b9257f 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/_index.scss @@ -10,12 +10,6 @@ @import 'containers/main'; @import 'containers/profile_query_editor'; -#searchProfilerAppRoot { - height: 100%; - display: flex; - flex: 1 1 auto; -} - .prfDevTool__licenseWarning { &__container { max-width: 1000px; @@ -55,19 +49,10 @@ } } -.prfDevTool { - height: calc(100vh - #{$euiHeaderChildSize}); +.appRoot { + height: calc(100vh - calc(#{$euiHeaderChildSize} * 2)); overflow: hidden; - - .devApp__container { - height: 100%; - overflow: hidden; - flex-shrink: 1; - } - - &__container { - overflow: hidden; - } + flex-shrink: 1; } .prfDevTool__detail { diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_profile_tree.scss b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_profile_tree.scss index cc4d334f58fd3..c7dc4a305acb2 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_profile_tree.scss +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/styles/components/_profile_tree.scss @@ -5,10 +5,6 @@ $badgeSize: $euiSize * 5.5; .prfDevTool__profileTree { - &__container { - height: 100%; - } - &__shardDetails--dim small { color: $euiColorDarkShade; } diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index d147c2572ceeb..60374d562f96c 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -29,7 +29,10 @@ export const security = (kibana) => new kibana.Plugin({ enabled: Joi.boolean().default(true), cookieName: Joi.any().description('This key is handled in the new platform security plugin ONLY'), encryptionKey: Joi.any().description('This key is handled in the new platform security plugin ONLY'), - sessionTimeout: Joi.any().description('This key is handled in the new platform security plugin ONLY'), + session: Joi.object({ + idleTimeout: Joi.any().description('This key is handled in the new platform security plugin ONLY'), + lifespan: Joi.any().description('This key is handled in the new platform security plugin ONLY'), + }).default(), secureCookies: Joi.any().description('This key is handled in the new platform security plugin ONLY'), loginAssistanceMessage: Joi.string().default(), authorization: Joi.object({ @@ -44,9 +47,10 @@ export const security = (kibana) => new kibana.Plugin({ }).default(); }, - deprecations: function ({ unused }) { + deprecations: function ({ rename, unused }) { return [ unused('authorization.legacyFallback.enabled'), + rename('sessionTimeout', 'session.idleTimeout'), ]; }, @@ -89,7 +93,11 @@ export const security = (kibana) => new kibana.Plugin({ return { secureCookies: securityPlugin.__legacyCompat.config.secureCookies, - sessionTimeout: securityPlugin.__legacyCompat.config.sessionTimeout, + session: { + tenant: server.newPlatform.setup.core.http.basePath.serverBasePath, + idleTimeout: securityPlugin.__legacyCompat.config.session.idleTimeout, + lifespan: securityPlugin.__legacyCompat.config.session.lifespan, + }, enableSpaceAwarePrivileges: server.config().get('xpack.spaces.enabled'), }; }, diff --git a/x-pack/legacy/plugins/security/public/hacks/on_session_timeout.js b/x-pack/legacy/plugins/security/public/hacks/on_session_timeout.js index 81b14ee7d8bf4..d9fb450779411 100644 --- a/x-pack/legacy/plugins/security/public/hacks/on_session_timeout.js +++ b/x-pack/legacy/plugins/security/public/hacks/on_session_timeout.js @@ -7,28 +7,20 @@ import _ from 'lodash'; import { uiModules } from 'ui/modules'; import { isSystemApiRequest } from 'ui/system_api'; -import { Path } from 'plugins/xpack_main/services/path'; import { npSetup } from 'ui/new_platform'; -/** - * Client session timeout is decreased by this number so that Kibana server - * can still access session content during logout request to properly clean - * user session up (invalidate access tokens, redirect to logout portal etc.). - * @type {number} - */ - const module = uiModules.get('security', []); module.config(($httpProvider) => { $httpProvider.interceptors.push(( $q, ) => { - const isUnauthenticated = Path.isUnauthenticated(); + const isAnonymous = npSetup.core.http.anonymousPaths.isAnonymous(window.location.pathname); function interceptorFactory(responseHandler) { return function interceptor(response) { - if (!isUnauthenticated && !isSystemApiRequest(response.config)) { - npSetup.plugins.security.sessionTimeout.extend(); + if (!isAnonymous && !isSystemApiRequest(response.config)) { + npSetup.plugins.security.sessionTimeout.extend(response.config.url); } return responseHandler(response); }; diff --git a/x-pack/legacy/plugins/security/public/views/logged_out/logged_out.tsx b/x-pack/legacy/plugins/security/public/views/logged_out/logged_out.tsx index 369b531e8ddf8..dbeb68875c1a9 100644 --- a/x-pack/legacy/plugins/security/public/views/logged_out/logged_out.tsx +++ b/x-pack/legacy/plugins/security/public/views/logged_out/logged_out.tsx @@ -31,7 +31,7 @@ chrome } > - + , diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx index b91c67b8f7d0a..c5bf910b007d0 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/edit_role_page.tsx @@ -86,7 +86,7 @@ class EditRolePageUI extends Component { }; } - public componentWillMount() { + public UNSAFE_componentWillMount() { if (this.props.action === 'clone' && isReservedRole(this.props.role)) { this.backToRoleList(); } 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 e42a01f4ad8c1..39a61401c15b3 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 bc6d037432771..e3495b6a78127 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/hosts/selectors.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/hosts/selectors.ts index ec6c64e116b24..1c900944752c4 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/hosts/selectors.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/hosts/selectors.ts @@ -8,7 +8,7 @@ export const ALL_HOSTS_WIDGET = '[data-test-subj="table-allHosts-loading-false"]'; /** A single draggable host in the `All Hosts` widget on the `Hosts` page */ -export const ALL_HOSTS_WIDGET_HOST = '[data-react-beautiful-dnd-drag-handle]'; +export const ALL_HOSTS_WIDGET_HOST = '[data-test-subj="draggable-content-host.name"]'; /** All the draggable hosts in the `All Hosts` widget on the `Hosts` page */ export const ALL_HOSTS_WIDGET_DRAGGABLE_HOSTS = `${ALL_HOSTS_WIDGET} ${ALL_HOSTS_WIDGET_HOST}`; 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 5c12bd528030e..ef1892b3d382c 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 baf6b7cd2027d..8f5c6e6f660cc 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/pagination/pagination.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/pagination/pagination.spec.ts index 7822f4d30365d..ebd0ad0125efb 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/pagination/pagination.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/pagination/pagination.spec.ts @@ -23,19 +23,19 @@ describe('Pagination', () => { return logout(); }); - it.skip('pagination updates results and page number', () => { + it('pagination updates results and page number', () => { loginAndWaitForPage(HOSTS_PAGE_TAB_URLS.uncommonProcesses); waitForTableLoad(UNCOMMON_PROCCESSES_TABLE); cy.get(getPageButtonSelector(0)).should('have.class', 'euiPaginationButton-isActive'); - cy.get(getDraggableField('user.name')) + cy.get(getDraggableField('process.name')) .first() .invoke('text') .then(text1 => { cy.get(getPageButtonSelector(2)).click({ force: true }); // wait for table to be done loading waitForTableLoad(UNCOMMON_PROCCESSES_TABLE); - cy.get(getDraggableField('user.name')) + cy.get(getDraggableField('process.name')) .first() .invoke('text') .should(text2 => { @@ -55,7 +55,7 @@ describe('Pagination', () => { // wait for table to be done loading waitForTableLoad(UNCOMMON_PROCCESSES_TABLE); - cy.get(getDraggableField('user.name')) + cy.get(getDraggableField('process.name')) .first() .invoke('text') .then(text2 => { @@ -70,7 +70,7 @@ describe('Pagination', () => { waitForTableLoad(UNCOMMON_PROCCESSES_TABLE); // check uncommon processes table picks up at 3 cy.get(getPageButtonSelector(2)).should('have.class', 'euiPaginationButton-isActive'); - cy.get(getDraggableField('user.name')) + cy.get(getDraggableField('process.name')) .first() .invoke('text') .should(text1 => { @@ -82,7 +82,7 @@ describe('Pagination', () => { * We only want to comment this code/test for now because it can be nondeterministic * when we figure out a way to really mock the data, we should come back to it */ - it.skip('pagination resets results and page number to first page when refresh is clicked', () => { + it('pagination resets results and page number to first page when refresh is clicked', () => { loginAndWaitForPage(HOSTS_PAGE_TAB_URLS.uncommonProcesses); cy.get(NUMBERED_PAGINATION, { timeout: DEFAULT_TIMEOUT }); cy.get(getPageButtonSelector(0)).should('have.class', 'euiPaginationButton-isActive'); @@ -100,7 +100,7 @@ describe('Pagination', () => { .last() .click({ force: true }); waitForTableLoad(UNCOMMON_PROCCESSES_TABLE); - cy.get(getPageButtonSelector(0)).should('have.class', 'euiPaginationButton-isActive'); + cy.get(getPageButtonSelector(2)).should('have.class', 'euiPaginationButton-isActive'); // cy.get(getDraggableField('user.name')) // .first() // .invoke('text') 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 8c2902fd804ac..8197f77db9a08 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 4ba8b8c44f366..b1867a437f7f4 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 29c26c5f674e3..d239961ee75d7 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/charts/areachart.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx index 910e576e6e1e7..25bd2a9d56059 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx @@ -7,7 +7,7 @@ import { ShallowWrapper, shallow } from 'enzyme'; import * as React from 'react'; -import { AreaChartBaseComponent, AreaChart } from './areachart'; +import { AreaChartBaseComponent, AreaChartComponent } from './areachart'; import { ChartSeriesData } from './common'; import { ScaleType, AreaSeries, Axis } from '@elastic/charts'; @@ -325,7 +325,7 @@ describe('AreaChart', () => { }; describe.each(chartDataSets as Array<[ChartSeriesData[]]>)('with valid data [%o]', data => { beforeAll(() => { - shallowWrapper = shallow(); + shallowWrapper = shallow(); }); it(`should render area chart`, () => { @@ -338,7 +338,7 @@ describe('AreaChart', () => { 'with invalid data [%o]', data => { beforeAll(() => { - shallowWrapper = shallow(); + shallowWrapper = shallow(); }); it(`should render a chart place holder`, () => { diff --git a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx index d51f5e081468c..c644d148cc1c3 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx @@ -63,12 +63,15 @@ const checkIfAnyValidSeriesExist = ( Array.isArray(data) && data.some(checkIfAllTheDataInTheSeriesAreValid); // https://ela.st/multi-areaseries -export const AreaChartBaseComponent = React.memo<{ +export const AreaChartBaseComponent = ({ + data, + ...chartConfigs +}: { data: ChartSeriesData[]; width: string | null | undefined; height: string | null | undefined; configs?: ChartSeriesConfigs | undefined; -}>(({ data, ...chartConfigs }) => { +}) => { const xTickFormatter = get('configs.axis.xTickFormatter', chartConfigs); const yTickFormatter = get('configs.axis.yTickFormatter', chartConfigs); const xAxisId = getAxisId(`group-${data[0].key}-x`); @@ -113,14 +116,21 @@ export const AreaChartBaseComponent = React.memo<{
    ) : null; -}); +}; AreaChartBaseComponent.displayName = 'AreaChartBaseComponent'; -export const AreaChart = React.memo<{ +export const AreaChartBase = React.memo(AreaChartBaseComponent); + +AreaChartBase.displayName = 'AreaChartBase'; + +export const AreaChartComponent = ({ + areaChart, + configs, +}: { areaChart: ChartSeriesData[] | null | undefined; configs?: ChartSeriesConfigs | undefined; -}>(({ areaChart, configs }) => { +}) => { const customHeight = get('customHeight', configs); const customWidth = get('customWidth', configs); @@ -128,7 +138,7 @@ export const AreaChart = React.memo<{ {({ measureRef, content: { height, width } }) => ( - ); -}); +}; + +AreaChartComponent.displayName = 'AreaChartComponent'; + +export const AreaChart = React.memo(AreaChartComponent); AreaChart.displayName = 'AreaChart'; diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx index 4b3ec577e6488..e28d330d31ba9 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx @@ -7,7 +7,7 @@ import { shallow, ShallowWrapper } from 'enzyme'; import * as React from 'react'; -import { BarChartBaseComponent, BarChart } from './barchart'; +import { BarChartBaseComponent, BarChartComponent } from './barchart'; import { ChartSeriesData } from './common'; import { BarSeries, ScaleType, Axis } from '@elastic/charts'; @@ -272,7 +272,7 @@ describe.each(chartDataSets)('BarChart with valid data [%o]', data => { let shallowWrapper: ShallowWrapper; beforeAll(() => { - shallowWrapper = shallow(); + shallowWrapper = shallow(); }); it(`should render chart`, () => { @@ -285,7 +285,7 @@ describe.each(chartHolderDataSets)('BarChart with invalid data [%o]', data => { let shallowWrapper: ShallowWrapper; beforeAll(() => { - shallowWrapper = shallow(); + shallowWrapper = shallow(); }); it(`should render chart holder`, () => { diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx index 04bedb827aa40..7218d7a497f19 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx @@ -45,12 +45,15 @@ const checkIfAnyValidSeriesExist = ( data.some(checkIfAllTheDataInTheSeriesAreValid); // Bar chart rotation: https://ela.st/chart-rotations -export const BarChartBaseComponent = React.memo<{ +export const BarChartBaseComponent = ({ + data, + ...chartConfigs +}: { data: ChartSeriesData[]; width: string | null | undefined; height: string | null | undefined; configs?: ChartSeriesConfigs | undefined; -}>(({ data, ...chartConfigs }) => { +}) => { const xTickFormatter = get('configs.axis.xTickFormatter', chartConfigs); const yTickFormatter = get('configs.axis.yTickFormatter', chartConfigs); const tickSize = getOr(0, 'configs.axis.tickSize', chartConfigs); @@ -96,14 +99,21 @@ export const BarChartBaseComponent = React.memo<{ ) : null; -}); +}; BarChartBaseComponent.displayName = 'BarChartBaseComponent'; -export const BarChart = React.memo<{ +export const BarChartBase = React.memo(BarChartBaseComponent); + +BarChartBase.displayName = 'BarChartBase'; + +export const BarChartComponent = ({ + barChart, + configs, +}: { barChart: ChartSeriesData[] | null | undefined; configs?: ChartSeriesConfigs | undefined; -}>(({ barChart, configs }) => { +}) => { const customHeight = get('customHeight', configs); const customWidth = get('customWidth', configs); return checkIfAnyValidSeriesExist(barChart) ? ( @@ -126,6 +136,10 @@ export const BarChart = React.memo<{ data={barChart} /> ); -}); +}; + +BarChartComponent.displayName = 'BarChartComponent'; + +export const BarChart = React.memo(BarChartComponent); BarChart.displayName = 'BarChart'; 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 0000000000000..ee9533341a4f8 --- /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 c513f7a451240..a3528158a0317 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 0f0e61e0206ec..c314785511201 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/droppable_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx index b7ceac77aa1f1..3f789a39832f1 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx @@ -40,7 +40,7 @@ const ReactDndDropTarget = styled.div<{ isDraggingOver: boolean; height: string background-color: ${rgba(props.theme.eui.euiColorSuccess, 0.3)}; } > div.timeline-drop-area-empty { - color: ${props.theme.eui.euiColorSuccess} + color: ${props.theme.eui.euiColorSuccess}; background-color: ${rgba(props.theme.eui.euiColorSuccess, 0.2)}; & .euiTextColor--subdued { 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 8d3334b05bfaf..af4b9b280f3cd 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 415970474db4c..ae3a8828491e3 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 214ac926e8868..18b271a3abc29 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/embeddables/__snapshots__/embedded_map.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap index bf0dfd9417875..2444fd0bc2b7d 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`EmbeddedMap renders correctly against snapshot 1`] = ` +exports[`EmbeddedMapComponent renders correctly against snapshot 1`] = ` ({ timezoneProvider: () => () => 'America/New_York', })); -describe('EmbeddedMap', () => { +describe('EmbeddedMapComponent', () => { let setQuery: SetQuery; beforeEach(() => { @@ -48,7 +48,7 @@ describe('EmbeddedMap', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - ( - ({ endDate, filters, query, setQuery, startDate }) => { - const [embeddable, setEmbeddable] = React.useState(null); - const [isLoading, setIsLoading] = useState(true); - const [isError, setIsError] = useState(false); - const [isIndexError, setIsIndexError] = useState(false); - - const [, dispatchToaster] = useStateToaster(); - const [loadingKibanaIndexPatterns, kibanaIndexPatterns] = useIndexPatterns(); - const [siemDefaultIndices] = useKibanaUiSetting(DEFAULT_INDEX_KEY); - - // This portalNode provided by react-reverse-portal allows us re-parent the MapToolTip within our - // own component tree instead of the embeddables (default). This is necessary to have access to - // the Redux store, theme provider, etc, which is required to register and un-register the draggable - // Search InPortal/OutPortal for implementation touch points - const portalNode = React.useMemo(() => createPortalNode(), []); - - const plugins = useKibanaPlugins(); - const core = useKibanaCore(); - - // Setup embeddables API (i.e. detach extra actions) useEffect - useEffect(() => { - try { - setupEmbeddablesAPI(plugins); - } catch (e) { - displayErrorToast(i18n.ERROR_CONFIGURING_EMBEDDABLES_API, e.message, dispatchToaster); +export const EmbeddedMapComponent = ({ + endDate, + filters, + query, + setQuery, + startDate, +}: EmbeddedMapProps) => { + const [embeddable, setEmbeddable] = React.useState(null); + const [isLoading, setIsLoading] = useState(true); + const [isError, setIsError] = useState(false); + const [isIndexError, setIsIndexError] = useState(false); + + const [, dispatchToaster] = useStateToaster(); + const [loadingKibanaIndexPatterns, kibanaIndexPatterns] = useIndexPatterns(); + const [siemDefaultIndices] = useKibanaUiSetting(DEFAULT_INDEX_KEY); + + // This portalNode provided by react-reverse-portal allows us re-parent the MapToolTip within our + // own component tree instead of the embeddables (default). This is necessary to have access to + // the Redux store, theme provider, etc, which is required to register and un-register the draggable + // Search InPortal/OutPortal for implementation touch points + const portalNode = React.useMemo(() => createPortalNode(), []); + + const plugins = useKibanaPlugins(); + const core = useKibanaCore(); + + // Setup embeddables API (i.e. detach extra actions) useEffect + useEffect(() => { + try { + setupEmbeddablesAPI(plugins); + } catch (e) { + displayErrorToast(i18n.ERROR_CONFIGURING_EMBEDDABLES_API, e.message, dispatchToaster); + setIsLoading(false); + setIsError(true); + } + }, []); + + // Initial Load useEffect + useEffect(() => { + let isSubscribed = true; + async function setupEmbeddable() { + // Ensure at least one `siem:defaultIndex` index pattern exists before trying to import + const matchingIndexPatterns = kibanaIndexPatterns.filter(ip => + siemDefaultIndices.includes(ip.attributes.title) + ); + if (matchingIndexPatterns.length === 0 && isSubscribed) { setIsLoading(false); - setIsError(true); + setIsIndexError(true); + return; } - }, []); - - // Initial Load useEffect - useEffect(() => { - let isSubscribed = true; - async function setupEmbeddable() { - // Ensure at least one `siem:defaultIndex` index pattern exists before trying to import - const matchingIndexPatterns = kibanaIndexPatterns.filter(ip => - siemDefaultIndices.includes(ip.attributes.title) - ); - if (matchingIndexPatterns.length === 0 && isSubscribed) { - setIsLoading(false); - setIsIndexError(true); - return; - } - // Create & set Embeddable - try { - const embeddableObject = await createEmbeddable( - filters, - getIndexPatternTitleIdMapping(matchingIndexPatterns), - query, - startDate, - endDate, - setQuery, - portalNode, - plugins.embeddable - ); - if (isSubscribed) { - setEmbeddable(embeddableObject); - } - } catch (e) { - if (isSubscribed) { - displayErrorToast(i18n.ERROR_CREATING_EMBEDDABLE, e.message, dispatchToaster); - setIsError(true); - } + // Create & set Embeddable + try { + const embeddableObject = await createEmbeddable( + filters, + getIndexPatternTitleIdMapping(matchingIndexPatterns), + query, + startDate, + endDate, + setQuery, + portalNode, + plugins.embeddable + ); + if (isSubscribed) { + setEmbeddable(embeddableObject); } + } catch (e) { if (isSubscribed) { - setIsLoading(false); + displayErrorToast(i18n.ERROR_CREATING_EMBEDDABLE, e.message, dispatchToaster); + setIsError(true); } } - - if (!loadingKibanaIndexPatterns) { - setupEmbeddable(); + if (isSubscribed) { + setIsLoading(false); } - return () => { - isSubscribed = false; + } + + if (!loadingKibanaIndexPatterns) { + setupEmbeddable(); + } + return () => { + isSubscribed = false; + }; + }, [loadingKibanaIndexPatterns, kibanaIndexPatterns]); + + // queryExpression updated useEffect + useEffect(() => { + if (embeddable != null) { + embeddable.updateInput({ query }); + } + }, [query]); + + useEffect(() => { + if (embeddable != null) { + embeddable.updateInput({ filters }); + } + }, [filters]); + + // DateRange updated useEffect + useEffect(() => { + if (embeddable != null && startDate != null && endDate != null) { + const timeRange = { + from: new Date(startDate).toISOString(), + to: new Date(endDate).toISOString(), }; - }, [loadingKibanaIndexPatterns, kibanaIndexPatterns]); - - // queryExpression updated useEffect - useEffect(() => { - if (embeddable != null) { - embeddable.updateInput({ query }); - } - }, [query]); - - useEffect(() => { - if (embeddable != null) { - embeddable.updateInput({ filters }); - } - }, [filters]); - - // DateRange updated useEffect - useEffect(() => { - if (embeddable != null && startDate != null && endDate != null) { - const timeRange = { - from: new Date(startDate).toISOString(), - to: new Date(endDate).toISOString(), - }; - embeddable.updateInput({ timeRange }); - } - }, [startDate, endDate]); - - return isError ? null : ( - - - - - {i18n.EMBEDDABLE_HEADER_HELP} - - - - - - - - - - {embeddable != null ? ( - - ) : !isLoading && isIndexError ? ( - - ) : ( - - )} - - - ); - } -); + embeddable.updateInput({ timeRange }); + } + }, [startDate, endDate]); + + return isError ? null : ( + + + + + {i18n.EMBEDDABLE_HEADER_HELP} + + + + + + + + + + {embeddable != null ? ( + + ) : !isLoading && isIndexError ? ( + + ) : ( + + )} + + + ); +}; + +EmbeddedMapComponent.displayName = 'EmbeddedMapComponent'; + +export const EmbeddedMap = React.memo(EmbeddedMapComponent); EmbeddedMap.displayName = 'EmbeddedMap'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx index 48a49835b284f..d32b62900431c 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx @@ -7,7 +7,7 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt'; +import { IndexPatternsMissingPromptComponent } from './index_patterns_missing_prompt'; jest.mock('ui/documentation_links', () => ({ ELASTIC_WEBSITE_URL: 'https://www.elastic.co', @@ -16,7 +16,7 @@ jest.mock('ui/documentation_links', () => ({ describe('IndexPatternsMissingPrompt', () => { test('renders correctly against snapshot', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx index e71398455ee88..1e29676415d79 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx @@ -12,7 +12,7 @@ import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; import * as i18n from './translations'; -export const IndexPatternsMissingPrompt = React.memo(() => ( +export const IndexPatternsMissingPromptComponent = () => ( {i18n.ERROR_TITLE}} @@ -58,4 +58,10 @@ export const IndexPatternsMissingPrompt = React.memo(() => ( } /> -)); +); + +IndexPatternsMissingPromptComponent.displayName = 'IndexPatternsMissingPromptComponent'; + +export const IndexPatternsMissingPrompt = React.memo(IndexPatternsMissingPromptComponent); + +IndexPatternsMissingPrompt.displayName = 'IndexPatternsMissingPrompt'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap index 2a17a2aae8497..2ef4d9df89a1b 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/__snapshots__/point_tool_tip_content.test.tsx.snap @@ -2,7 +2,7 @@ exports[`PointToolTipContent renders correctly against snapshot 1`] = ` - { test('renders correctly against snapshot', () => { const wrapper = shallow( - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx index 7cdf3a545a2d6..0c416868bfb03 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.tsx @@ -26,41 +26,46 @@ interface LineToolTipContentProps { featureProps: FeatureProperty[]; } -export const LineToolTipContent = React.memo( - ({ contextId, featureProps }) => { - const lineProps = featureProps.reduce>( - (acc, f) => ({ - ...acc, - ...{ [f._propertyKey]: Array.isArray(f._rawValue) ? f._rawValue : [f._rawValue] }, - }), - {} - ); +export const LineToolTipContentComponent = ({ + contextId, + featureProps, +}: LineToolTipContentProps) => { + const lineProps = featureProps.reduce>( + (acc, f) => ({ + ...acc, + ...{ [f._propertyKey]: Array.isArray(f._rawValue) ? f._rawValue : [f._rawValue] }, + }), + {} + ); - return ( - - - - - {i18n.SOURCE} - - - - - - - - {i18n.DESTINATION} - - - - - ); - } -); + return ( + + + + + {i18n.SOURCE} + + + + + + + + {i18n.DESTINATION} + + + + + ); +}; + +LineToolTipContentComponent.displayName = 'LineToolTipContentComponent'; + +export const LineToolTipContent = React.memo(LineToolTipContentComponent); LineToolTipContent.displayName = 'LineToolTipContent'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx index a73e6dabc68ae..13eefb252fb04 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx @@ -7,7 +7,7 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { MapToolTip } from './map_tool_tip'; +import { MapToolTipComponent } from './map_tool_tip'; import { MapFeature } from '../types'; jest.mock('../../search_bar', () => ({ @@ -18,7 +18,7 @@ jest.mock('../../search_bar', () => ({ describe('MapToolTip', () => { test('placeholder component renders correctly against snapshot', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); @@ -36,7 +36,7 @@ describe('MapToolTip', () => { const loadFeatureGeometry = jest.fn(); const wrapper = shallow( - ( - ({ - addFilters, - closeTooltip, - features = [], - isLocked, - getLayerName, - loadFeatureProperties, - loadFeatureGeometry, - }) => { - const [isLoading, setIsLoading] = useState(true); - const [isLoadingNextFeature, setIsLoadingNextFeature] = useState(false); - const [isError, setIsError] = useState(false); - const [featureIndex, setFeatureIndex] = useState(0); - const [featureProps, setFeatureProps] = useState([]); - const [featureGeometry, setFeatureGeometry] = useState(null); - const [, setLayerName] = useState(''); +export const MapToolTipComponent = ({ + addFilters, + closeTooltip, + features = [], + isLocked, + getLayerName, + loadFeatureProperties, + loadFeatureGeometry, +}: MapToolTipProps) => { + const [isLoading, setIsLoading] = useState(true); + const [isLoadingNextFeature, setIsLoadingNextFeature] = useState(false); + const [isError, setIsError] = useState(false); + const [featureIndex, setFeatureIndex] = useState(0); + const [featureProps, setFeatureProps] = useState([]); + const [featureGeometry, setFeatureGeometry] = useState(null); + const [, setLayerName] = useState(''); - useEffect(() => { - // Early return if component doesn't yet have props -- result of mounting in portal before actual rendering - if ( - features.length === 0 || - getLayerName == null || - loadFeatureProperties == null || - loadFeatureGeometry == null - ) { - return; - } + useEffect(() => { + // Early return if component doesn't yet have props -- result of mounting in portal before actual rendering + if ( + features.length === 0 || + getLayerName == null || + loadFeatureProperties == null || + loadFeatureGeometry == null + ) { + return; + } - // Separate loaders for initial load vs loading next feature to keep tooltip from drastically resizing - if (!isLoadingNextFeature) { - setIsLoading(true); - } - setIsError(false); + // Separate loaders for initial load vs loading next feature to keep tooltip from drastically resizing + if (!isLoadingNextFeature) { + setIsLoading(true); + } + setIsError(false); - const fetchFeatureProps = async () => { - if (features[featureIndex] != null) { - const layerId = features[featureIndex].layerId; - const featureId = features[featureIndex].id; + const fetchFeatureProps = async () => { + if (features[featureIndex] != null) { + const layerId = features[featureIndex].layerId; + const featureId = features[featureIndex].id; - try { - const featureGeo = loadFeatureGeometry({ layerId, featureId }); - const [featureProperties, layerNameString] = await Promise.all([ - loadFeatureProperties({ layerId, featureId }), - getLayerName(layerId), - ]); + try { + const featureGeo = loadFeatureGeometry({ layerId, featureId }); + const [featureProperties, layerNameString] = await Promise.all([ + loadFeatureProperties({ layerId, featureId }), + getLayerName(layerId), + ]); - setFeatureProps(featureProperties); - setFeatureGeometry(featureGeo); - setLayerName(layerNameString); - } catch (e) { - setIsError(true); - } finally { - setIsLoading(false); - setIsLoadingNextFeature(false); - } + setFeatureProps(featureProperties); + setFeatureGeometry(featureGeo); + setLayerName(layerNameString); + } catch (e) { + setIsError(true); + } finally { + setIsLoading(false); + setIsLoadingNextFeature(false); } - }; - - fetchFeatureProps(); - }, [ - featureIndex, - features - .map(f => `${f.id}-${f.layerId}`) - .sort() - .join(), - ]); + } + }; - if (isError) { - return ( - - {i18n.MAP_TOOL_TIP_ERROR} - - ); - } + fetchFeatureProps(); + }, [ + featureIndex, + features + .map(f => `${f.id}-${f.layerId}`) + .sort() + .join(), + ]); - return isLoading && !isLoadingNextFeature ? ( + if (isError) { + return ( - - - + {i18n.MAP_TOOL_TIP_ERROR} - ) : ( - - { - if (closeTooltip != null) { - closeTooltip(); - setFeatureIndex(0); - } - }} - > -
    - {featureGeometry != null && featureGeometry.type === 'LineString' ? ( - - ) : ( - - )} - {features.length > 1 && ( - { - setFeatureIndex(featureIndex - 1); - setIsLoadingNextFeature(true); - }} - nextFeature={() => { - setFeatureIndex(featureIndex + 1); - setIsLoadingNextFeature(true); - }} - /> - )} - {isLoadingNextFeature && } -
    -
    -
    ); } -); + + return isLoading && !isLoadingNextFeature ? ( + + + + + + ) : ( + + { + if (closeTooltip != null) { + closeTooltip(); + setFeatureIndex(0); + } + }} + > +
    + {featureGeometry != null && featureGeometry.type === 'LineString' ? ( + + ) : ( + + )} + {features.length > 1 && ( + { + setFeatureIndex(featureIndex - 1); + setIsLoadingNextFeature(true); + }} + nextFeature={() => { + setFeatureIndex(featureIndex + 1); + setIsLoadingNextFeature(true); + }} + /> + )} + {isLoadingNextFeature && } +
    +
    +
    + ); +}; + +MapToolTipComponent.displayName = 'MapToolTipComponent'; + +export const MapToolTip = React.memo(MapToolTipComponent); MapToolTip.displayName = 'MapToolTip'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx index 567f091e78cb5..1733fb3aa7480 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx @@ -8,7 +8,7 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; import { FeatureProperty } from '../types'; -import { getRenderedFieldValue, PointToolTipContent } from './point_tool_tip_content'; +import { getRenderedFieldValue, PointToolTipContentComponent } from './point_tool_tip_content'; import { TestProviders } from '../../../mock'; import { getEmptyStringTag } from '../../empty_value'; import { HostDetailsLink, IPDetailsLink } from '../../links'; @@ -39,7 +39,7 @@ describe('PointToolTipContent', () => { const wrapper = shallow( - { const wrapper = mount( - ( - ({ contextId, featureProps, closeTooltip }) => { - const featureDescriptionListItems = featureProps.map( - ({ _propertyKey: key, _rawValue: value }) => ({ - title: sourceDestinationFieldMappings[key], - description: ( - - {value != null ? ( - getRenderedFieldValue(key, item)} - /> - ) : ( - getEmptyTagValue() - )} - - ), - }) - ); +export const PointToolTipContentComponent = ({ + contextId, + featureProps, + closeTooltip, +}: PointToolTipContentProps) => { + const featureDescriptionListItems = featureProps.map( + ({ _propertyKey: key, _rawValue: value }) => ({ + title: sourceDestinationFieldMappings[key], + description: ( + + {value != null ? ( + getRenderedFieldValue(key, item)} + /> + ) : ( + getEmptyTagValue() + )} + + ), + }) + ); - return ; - } -); + return ; +}; + +PointToolTipContentComponent.displayName = 'PointToolTipContentComponent'; + +export const PointToolTipContent = React.memo(PointToolTipContentComponent); PointToolTipContent.displayName = 'PointToolTipContent'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx index f2673c17d246c..4c77570cfbc9f 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx @@ -7,7 +7,7 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { ToolTipFooter } from './tooltip_footer'; +import { ToolTipFooterComponent } from './tooltip_footer'; describe('ToolTipFilter', () => { let nextFeature = jest.fn(); @@ -20,7 +20,7 @@ describe('ToolTipFilter', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - { describe('Lower bounds', () => { test('previousButton is disabled when featureIndex is 0', () => { const wrapper = mount( - { test('previousFeature is not called when featureIndex is 0', () => { const wrapper = mount( - { test('nextButton is enabled when featureIndex is < totalFeatures', () => { const wrapper = mount( - { test('nextFeature is called when featureIndex is < totalFeatures', () => { const wrapper = mount( - { describe('Upper bounds', () => { test('previousButton is enabled when featureIndex >== totalFeatures', () => { const wrapper = mount( - { test('previousFunction is called when featureIndex >== totalFeatures', () => { const wrapper = mount( - { test('nextButton is disabled when featureIndex >== totalFeatures', () => { const wrapper = mount( - { test('nextFunction is not called when featureIndex >== totalFeatures', () => { const wrapper = mount( - { describe('Within bounds, single feature', () => { test('previousButton is not enabled when only a single feature is provided', () => { const wrapper = mount( - { test('previousFunction is not called when only a single feature is provided', () => { const wrapper = mount( - { test('nextButton is not enabled when only a single feature is provided', () => { const wrapper = mount( - { test('nextFunction is not called when only a single feature is provided', () => { const wrapper = mount( - { describe('Within bounds, multiple features', () => { test('previousButton is enabled when featureIndex > 0 && featureIndex < totalFeatures', () => { const wrapper = mount( - { test('previousFunction is called when featureIndex > 0 && featureIndex < totalFeatures', () => { const wrapper = mount( - { test('nextButton is enabled when featureIndex > 0 && featureIndex < totalFeatures', () => { const wrapper = mount( - { test('nextFunction is called when featureIndex > 0 && featureIndex < totalFeatures', () => { const wrapper = mount( - void; } -export const ToolTipFooter = React.memo( - ({ featureIndex, totalFeatures, previousFeature, nextFeature }) => { - return ( - <> - - - - - {i18n.MAP_TOOL_TIP_FEATURES_FOOTER(featureIndex + 1, totalFeatures)} - - - - - - = totalFeatures - 1} - /> - - - - - ); - } -); +export const ToolTipFooterComponent = ({ + featureIndex, + totalFeatures, + previousFeature, + nextFeature, +}: MapToolTipFooterProps) => { + return ( + <> + + + + + {i18n.MAP_TOOL_TIP_FEATURES_FOOTER(featureIndex + 1, totalFeatures)} + + + + + + = totalFeatures - 1} + /> + + + + + ); +}; + +ToolTipFooterComponent.displayName = 'ToolTipFooterComponent'; + +export const ToolTipFooter = React.memo(ToolTipFooterComponent); ToolTipFooter.displayName = 'ToolTipFooter'; 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 6c4bc797d39f8..6233fcfe7c823 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/events_viewer/events_viewer.test.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx index 4e59acc4f6713..25b2427d34d6a 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx @@ -27,17 +27,8 @@ mockUseKibanaCore.mockImplementation(() => ({ const from = 1566943856794; const to = 1566857456791; -// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 -/* eslint-disable no-console */ -const originalError = console.error; -describe('EventsViewer', () => { - beforeAll(() => { - console.error = jest.fn(); - }); - afterAll(() => { - console.error = originalError; - }); +describe('EventsViewer', () => { test('it renders the "Showing..." subtitle with the expected event count', async () => { const wrapper = mount( diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx index 671711c60bd16..4d2e44f9a3d92 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx @@ -27,17 +27,7 @@ mockUseKibanaCore.mockImplementation(() => ({ const from = 1566943856794; const to = 1566857456791; -// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 -/* eslint-disable no-console */ -const originalError = console.error; describe('StatefulEventsViewer', () => { - beforeAll(() => { - console.error = jest.fn(); - }); - - afterAll(() => { - console.error = originalError; - }); test('it renders the events viewer', async () => { const wrapper = mount( diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx index e943ca6f3e863..54847cda281f4 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx @@ -12,24 +12,15 @@ import { TestProviders } from '../../mock'; import { FIELD_BROWSER_HEIGHT, FIELD_BROWSER_WIDTH } from './helpers'; -import { StatefulFieldsBrowser } from '.'; -// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 -/* eslint-disable no-console */ -const originalError = console.error; -describe('StatefulFieldsBrowser', () => { - beforeAll(() => { - console.error = jest.fn(); - }); +import { StatefulFieldsBrowserComponent } from '.'; - afterAll(() => { - console.error = originalError; - }); +describe('StatefulFieldsBrowser', () => { const timelineId = 'test'; test('it renders the Fields button, which displays the fields browser on click', () => { const wrapper = mount( - { test('it does NOT render the fields browser until the Fields button is clicked', () => { const wrapper = mount( - { test('it renders the fields browser when the Fields button is clicked', () => { const wrapper = mount( - { test('it updates the selectedCategoryId state, which makes the category bold, when the user clicks a category name in the left hand side of the field browser', () => { const wrapper = mount( - { test('it updates the selectedCategoryId state according to most fields returned', () => { const wrapper = mount( - { const wrapper = mount( - { const wrapper = mount( - { const wrapper = mount( - ({ @@ -27,13 +27,13 @@ describe('formatted_bytes', () => { mockUseKibanaUiSetting.mockImplementation( getMockKibanaUiSetting(mockFrameworks.default_browser) ); - const wrapper = shallow(); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); test('it renders bytes to hardcoded format when no configuration exists', () => { mockUseKibanaUiSetting.mockImplementation(() => [null]); - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.text()).toEqual('2.7MB'); }); @@ -41,7 +41,7 @@ describe('formatted_bytes', () => { mockUseKibanaUiSetting.mockImplementation( getMockKibanaUiSetting(mockFrameworks.default_browser) ); - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.text()).toEqual('2.7MB'); }); @@ -49,7 +49,7 @@ describe('formatted_bytes', () => { mockUseKibanaUiSetting.mockImplementation( getMockKibanaUiSetting(mockFrameworks.default_browser) ); - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.text()).toEqual('2.7MB'); }); @@ -57,7 +57,7 @@ describe('formatted_bytes', () => { mockUseKibanaUiSetting.mockImplementation( getMockKibanaUiSetting(mockFrameworks.bytes_short) ); - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.text()).toEqual('3MB'); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx index 76d2c1ea7e3d0..408e8d7ad4d80 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx @@ -10,11 +10,15 @@ import numeral from '@elastic/numeral'; import { DEFAULT_BYTES_FORMAT } from '../../../common/constants'; import { useKibanaUiSetting } from '../../lib/settings/use_kibana_ui_setting'; -export const PreferenceFormattedBytes = React.memo<{ value: string | number }>(({ value }) => { +export const PreferenceFormattedBytesComponent = ({ value }: { value: string | number }) => { const [bytesFormat] = useKibanaUiSetting(DEFAULT_BYTES_FORMAT); return ( <>{bytesFormat ? numeral(value).format(bytesFormat) : numeral(value).format('0,0.[0]b')} ); -}); +}; + +PreferenceFormattedBytesComponent.displayName = 'PreferenceFormattedBytesComponent'; + +export const PreferenceFormattedBytes = React.memo(PreferenceFormattedBytesComponent); PreferenceFormattedBytes.displayName = 'PreferenceFormattedBytes'; 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 eb85edce78a8f..42867c09b971b 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/markdown/__snapshots__/markdown_hint.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/markdown/__snapshots__/markdown_hint.test.tsx.snap index 60464c46f1ac0..7f350072439c5 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/__snapshots__/markdown_hint.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/markdown/__snapshots__/markdown_hint.test.tsx.snap @@ -1,7 +1,55 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MarkdownHint rendering it renders the expected hints 1`] = ` - +exports[`MarkdownHintComponent rendering it renders the expected hints 1`] = ` + + + # heading + + + **bold** + + + _italics_ + + + \`code\` + + + [link](url) + + + * bullet + + + \`\`\`preformatted\`\`\` + + + >quote + + ~~ + + strikethrough + + ~~ + + ![image](url) + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx index 6319af3e6ffa1..c3268270919e2 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx @@ -8,11 +8,11 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { MarkdownHint } from './markdown_hint'; +import { MarkdownHintComponent } from './markdown_hint'; -describe('MarkdownHint', () => { +describe.skip('MarkdownHintComponent ', () => { test('it has inline visibility when show is true', () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find('[data-test-subj="markdown-hint"]').first()).toHaveStyleRule( 'visibility', @@ -21,7 +21,7 @@ describe('MarkdownHint', () => { }); test('it has hidden visibility when show is false', () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find('[data-test-subj="markdown-hint"]').first()).toHaveStyleRule( 'visibility', @@ -30,7 +30,7 @@ describe('MarkdownHint', () => { }); test('it renders the heading hint', () => { - const wrapper = mount(); + const wrapper = mount(); expect( wrapper @@ -41,7 +41,7 @@ describe('MarkdownHint', () => { }); test('it renders the bold hint with a bold font-weight', () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find('[data-test-subj="bold-hint"]').first()).toHaveStyleRule( 'font-weight', @@ -50,7 +50,7 @@ describe('MarkdownHint', () => { }); test('it renders the italic hint with an italic font-style', () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find('[data-test-subj="italic-hint"]').first()).toHaveStyleRule( 'font-style', @@ -59,7 +59,7 @@ describe('MarkdownHint', () => { }); test('it renders the code hint with a monospace font family', () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find('[data-test-subj="code-hint"]').first()).toHaveStyleRule( 'font-family', @@ -68,7 +68,7 @@ describe('MarkdownHint', () => { }); test('it renders the preformatted hint with a monospace font family', () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find('[data-test-subj="preformatted-hint"]').first()).toHaveStyleRule( 'font-family', @@ -77,7 +77,7 @@ describe('MarkdownHint', () => { }); test('it renders the strikethrough hint with a line-through text-decoration', () => { - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.find('[data-test-subj="strikethrough-hint"]').first()).toHaveStyleRule( 'text-decoration', @@ -87,7 +87,7 @@ describe('MarkdownHint', () => { describe('rendering', () => { test('it renders the expected hints', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx index 18f3a35a23f7f..5ecd1d4c9d2ad 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx @@ -6,7 +6,6 @@ import { EuiText } from '@elastic/eui'; import * as React from 'react'; -import { pure } from 'recompose'; import styled from 'styled-components'; import * as i18n from './translations'; @@ -62,7 +61,7 @@ const TrailingWhitespace = styled.span` TrailingWhitespace.displayName = 'TrailingWhitespace'; -export const MarkdownHint = pure<{ show: boolean }>(({ show }) => ( +export const MarkdownHintComponent = ({ show }: { show: boolean }) => ( (({ show }) => ( {'~~'} {i18n.MARKDOWN_HINT_IMAGE_URL} -)); +); + +MarkdownHintComponent.displayName = 'MarkdownHintComponent'; + +export const MarkdownHint = React.memo(MarkdownHintComponent); MarkdownHint.displayName = 'MarkdownHint'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx index e3894ee6e7c66..c401075af42ce 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx @@ -7,13 +7,17 @@ import React from 'react'; import toJson from 'enzyme-to-json'; import { shallow, mount } from 'enzyme'; -import { EntityDraggable } from './entity_draggable'; +import { EntityDraggableComponent } from './entity_draggable'; import { TestProviders } from '../../mock/test_providers'; describe('entity_draggable', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); @@ -21,7 +25,11 @@ describe('entity_draggable', () => { test('renders with entity name with entity value as text', () => { const wrapper = mount( - + ); expect(wrapper.text()).toEqual('entity-name: "entity-value"'); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.tsx b/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.tsx index d7f25c49fd7ca..b0636b08a5634 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.tsx @@ -16,37 +16,43 @@ interface Props { entityValue: string; } -export const EntityDraggable = React.memo( - ({ idPrefix, entityName, entityValue }): JSX.Element => { - const id = escapeDataProviderId(`entity-draggable-${idPrefix}-${entityName}-${entityValue}`); - return ( - - snapshot.isDragging ? ( - - - - ) : ( - <>{`${entityName}: "${entityValue}"`} - ) - } - /> - ); - } -); +export const EntityDraggableComponent = ({ + idPrefix, + entityName, + entityValue, +}: Props): JSX.Element => { + const id = escapeDataProviderId(`entity-draggable-${idPrefix}-${entityName}-${entityValue}`); + return ( + + snapshot.isDragging ? ( + + + + ) : ( + <>{`${entityName}: "${entityValue}"`} + ) + } + /> + ); +}; + +EntityDraggableComponent.displayName = 'EntityDraggableComponent'; + +export const EntityDraggable = React.memo(EntityDraggableComponent); EntityDraggable.displayName = 'EntityDraggable'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx index 3509d92ce7051..a28077ba63ddd 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx @@ -8,7 +8,7 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; import * as React from 'react'; -import { AnomalyScore } from './anomaly_score'; +import { AnomalyScoreComponent } from './anomaly_score'; import { mockAnomalies } from '../mock'; import { TestProviders } from '../../../mock/test_providers'; import { Anomalies } from '../types'; @@ -26,7 +26,7 @@ describe('anomaly_scores', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - { test('should not show a popover on initial render', () => { const wrapper = mount( - { test('show a popover on a mouse click', () => { const wrapper = mount( - ( - ({ jobKey, startDate, endDate, index = 0, score, interval, narrowDateRange }): JSX.Element => { - const [isOpen, setIsOpen] = useState(false); - return ( - <> - - { + const [isOpen, setIsOpen] = useState(false); + return ( + <> + + + + + setIsOpen(!isOpen)} + closePopover={() => setIsOpen(!isOpen)} + button={} + > + - - - setIsOpen(!isOpen)} - closePopover={() => setIsOpen(!isOpen)} - button={} - > - - - - - ); - } -); + + + + ); +}; + +AnomalyScoreComponent.displayName = 'AnomalyScoreComponent'; + +export const AnomalyScore = React.memo(AnomalyScoreComponent); AnomalyScore.displayName = 'AnomalyScore'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx index 17d36ffcc9099..5bd11169e4840 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx @@ -8,7 +8,7 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; import * as React from 'react'; -import { AnomalyScores, createJobKey } from './anomaly_scores'; +import { AnomalyScoresComponent, createJobKey } from './anomaly_scores'; import { mockAnomalies } from '../mock'; import { TestProviders } from '../../../mock/test_providers'; import { getEmptyValue } from '../../empty_value'; @@ -28,7 +28,7 @@ describe('anomaly_scores', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - { test('renders spinner when isLoading is true is passed', () => { const wrapper = mount( - { test('does NOT render spinner when isLoading is false is passed', () => { const wrapper = mount( - { test('renders an empty value if anomalies is null', () => { const wrapper = mount( - { anomalies.anomalies = []; const wrapper = mount( - { test('should not show a popover on initial render', () => { const wrapper = mount( - { test('showing a popover on a mouse click', () => { const wrapper = mount( - `${score.jobId}-${score.severity}-${score.entityName}-${score.entityValue}`; -export const AnomalyScores = React.memo( - ({ anomalies, startDate, endDate, isLoading, narrowDateRange, limit }): JSX.Element => { - if (isLoading) { - return ; - } else if (anomalies == null || anomalies.anomalies.length === 0) { - return getEmptyTagValue(); - } else { - return ( - <> - - {getTopSeverityJobs(anomalies.anomalies, limit).map((score, index) => { - const jobKey = createJobKey(score); - return ( - - ); - })} - - - ); - } +export const AnomalyScoresComponent = ({ + anomalies, + startDate, + endDate, + isLoading, + narrowDateRange, + limit, +}: Args): JSX.Element => { + if (isLoading) { + return ; + } else if (anomalies == null || anomalies.anomalies.length === 0) { + return getEmptyTagValue(); + } else { + return ( + <> + + {getTopSeverityJobs(anomalies.anomalies, limit).map((score, index) => { + const jobKey = createJobKey(score); + return ( + + ); + })} + + + ); } -); +}; + +AnomalyScoresComponent.displayName = 'AnomalyScoresComponent'; + +export const AnomalyScores = React.memo(AnomalyScoresComponent); AnomalyScores.displayName = 'AnomalyScores'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx index eec0c65c7679f..0d389ae14a825 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx @@ -9,7 +9,7 @@ import toJson from 'enzyme-to-json'; import { mockAnomalies } from '../mock'; import { cloneDeep } from 'lodash/fp'; import { shallow } from 'enzyme'; -import { DraggableScore } from './draggable_score'; +import { DraggableScoreComponent } from './draggable_score'; describe('draggable_score', () => { let anomalies = cloneDeep(mockAnomalies); @@ -20,13 +20,15 @@ describe('draggable_score', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); test('renders correctly against snapshot when the index is not included', () => { - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(toJson(wrapper)).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.tsx index d156b5f0463f6..6ae31c0ac1fb9 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.tsx @@ -12,46 +12,52 @@ import { Provider } from '../../timeline/data_providers/provider'; import { Spacer } from '../../page'; import { getScoreString } from './score_health'; -export const DraggableScore = React.memo<{ +export const DraggableScoreComponent = ({ + id, + index = 0, + score, +}: { id: string; index?: number; score: Anomaly; -}>( - ({ id, index = 0, score }): JSX.Element => ( - - snapshot.isDragging ? ( - - - - ) : ( - <> - {index !== 0 && ( - <> - {','} - - - )} - {getScoreString(score.severity)} - - ) - } - /> - ) +}): JSX.Element => ( + + snapshot.isDragging ? ( + + + + ) : ( + <> + {index !== 0 && ( + <> + {','} + + + )} + {getScoreString(score.severity)} + + ) + } + /> ); +DraggableScoreComponent.displayName = 'DraggableScoreComponent'; + +export const DraggableScore = React.memo(DraggableScoreComponent); + DraggableScore.displayName = 'DraggableScore'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap index 2c4f750ffeac5..983eb2409bd77 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`JobsTable renders correctly against snapshot 1`] = ` +exports[`JobsTableComponent renders correctly against snapshot 1`] = ` { test('renders correctly against snapshot', () => { const wrapper = shallow( - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); @@ -29,7 +29,7 @@ describe('GroupsFilterPopover', () => { test('when a filter is clicked, it becomes checked ', () => { const mockOnSelectedGroupsChanged = jest.fn(); const wrapper = mount( - diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx index e39046ba013c7..9f05ce8a5bfce 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.tsx @@ -31,61 +31,66 @@ interface GroupsFilterPopoverProps { * @param siemJobs jobs to fetch groups from to display for filtering * @param onSelectedGroupsChanged change listener to be notified when group selection changes */ -export const GroupsFilterPopover = React.memo( - ({ siemJobs, onSelectedGroupsChanged }) => { - const [isGroupPopoverOpen, setIsGroupPopoverOpen] = useState(false); - const [selectedGroups, setSelectedGroups] = useState([]); +export const GroupsFilterPopoverComponent = ({ + siemJobs, + onSelectedGroupsChanged, +}: GroupsFilterPopoverProps) => { + const [isGroupPopoverOpen, setIsGroupPopoverOpen] = useState(false); + const [selectedGroups, setSelectedGroups] = useState([]); - const groups = siemJobs - .map(j => j.groups) - .flat() - .filter(g => g !== 'siem'); - const uniqueGroups = Array.from(new Set(groups)); + const groups = siemJobs + .map(j => j.groups) + .flat() + .filter(g => g !== 'siem'); + const uniqueGroups = Array.from(new Set(groups)); - useEffect(() => { - onSelectedGroupsChanged(selectedGroups); - }, [selectedGroups.sort().join()]); + useEffect(() => { + onSelectedGroupsChanged(selectedGroups); + }, [selectedGroups.sort().join()]); - return ( - setIsGroupPopoverOpen(!isGroupPopoverOpen)} - isSelected={isGroupPopoverOpen} - hasActiveFilters={selectedGroups.length > 0} - numActiveFilters={selectedGroups.length} - > - {i18n.GROUPS} - - } - isOpen={isGroupPopoverOpen} - closePopover={() => setIsGroupPopoverOpen(!isGroupPopoverOpen)} - panelPaddingSize="none" - > - {uniqueGroups.map((group, index) => ( - toggleSelectedGroup(group, selectedGroups, setSelectedGroups)} - > - {`${group} (${groups.filter(g => g === group).length})`} - - ))} - {uniqueGroups.length === 0 && ( - - - - -

    {i18n.NO_GROUPS_AVAILABLE}

    -
    -
    - )} -
    - ); - } -); + return ( + setIsGroupPopoverOpen(!isGroupPopoverOpen)} + isSelected={isGroupPopoverOpen} + hasActiveFilters={selectedGroups.length > 0} + numActiveFilters={selectedGroups.length} + > + {i18n.GROUPS} + + } + isOpen={isGroupPopoverOpen} + closePopover={() => setIsGroupPopoverOpen(!isGroupPopoverOpen)} + panelPaddingSize="none" + > + {uniqueGroups.map((group, index) => ( + toggleSelectedGroup(group, selectedGroups, setSelectedGroups)} + > + {`${group} (${groups.filter(g => g === group).length})`} + + ))} + {uniqueGroups.length === 0 && ( + + + + +

    {i18n.NO_GROUPS_AVAILABLE}

    +
    +
    + )} +
    + ); +}; + +GroupsFilterPopoverComponent.displayName = 'GroupsFilterPopoverComponent'; + +export const GroupsFilterPopover = React.memo(GroupsFilterPopoverComponent); GroupsFilterPopover.displayName = 'GroupsFilterPopover'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx index 5838c3105de6d..0711cc1c87966 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx @@ -7,7 +7,7 @@ import { mount, shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { JobsTableFilters } from './jobs_table_filters'; +import { JobsTableFiltersComponent } from './jobs_table_filters'; import { SiemJob } from '../../types'; import { cloneDeep } from 'lodash/fp'; import { mockSiemJobs } from '../../__mocks__/api'; @@ -20,14 +20,16 @@ describe('JobsTableFilters', () => { }); test('renders correctly against snapshot', () => { - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(toJson(wrapper)).toMatchSnapshot(); }); test('when you click Elastic Jobs filter, state is updated and it is selected', () => { const onFilterChanged = jest.fn(); const wrapper = mount( - + ); wrapper @@ -47,7 +49,7 @@ describe('JobsTableFilters', () => { test('when you click Custom Jobs filter, state is updated and it is selected', () => { const onFilterChanged = jest.fn(); const wrapper = mount( - + ); wrapper @@ -67,7 +69,7 @@ describe('JobsTableFilters', () => { test('when you click Custom Jobs filter once, then Elastic Jobs filter, state is updated and selected changed', () => { const onFilterChanged = jest.fn(); const wrapper = mount( - + ); wrapper @@ -99,7 +101,7 @@ describe('JobsTableFilters', () => { test('when you click Custom Jobs filter twice, state is updated and it is revert', () => { const onFilterChanged = jest.fn(); const wrapper = mount( - + ); wrapper diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.tsx index ba080757d34a8..74e61f27fb2d1 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.tsx @@ -31,65 +31,67 @@ interface JobsTableFiltersProps { * @param siemJobs jobs to fetch groups from to display for filtering * @param onFilterChanged change listener to be notified on filter changes */ -export const JobsTableFilters = React.memo( - ({ siemJobs, onFilterChanged }) => { - const [filterQuery, setFilterQuery] = useState(''); - const [selectedGroups, setSelectedGroups] = useState([]); - const [showCustomJobs, setShowCustomJobs] = useState(false); - const [showElasticJobs, setShowElasticJobs] = useState(false); +export const JobsTableFiltersComponent = ({ siemJobs, onFilterChanged }: JobsTableFiltersProps) => { + const [filterQuery, setFilterQuery] = useState(''); + const [selectedGroups, setSelectedGroups] = useState([]); + const [showCustomJobs, setShowCustomJobs] = useState(false); + const [showElasticJobs, setShowElasticJobs] = useState(false); - // Propagate filter changes to parent - useEffect(() => { - onFilterChanged({ filterQuery, showCustomJobs, showElasticJobs, selectedGroups }); - }, [filterQuery, selectedGroups.sort().join(), showCustomJobs, showElasticJobs]); + // Propagate filter changes to parent + useEffect(() => { + onFilterChanged({ filterQuery, showCustomJobs, showElasticJobs, selectedGroups }); + }, [filterQuery, selectedGroups.sort().join(), showCustomJobs, showElasticJobs]); - return ( - - - + + setFilterQuery(query.queryText.trim())} + /> + + + + + + + + + + + { + setShowElasticJobs(!showElasticJobs); + setShowCustomJobs(false); + }} + data-test-subj="show-elastic-jobs-filter-button" + withNext + > + {i18n.SHOW_ELASTIC_JOBS} + + { + setShowCustomJobs(!showCustomJobs); + setShowElasticJobs(false); }} - onChange={(query: EuiSearchBarQuery) => setFilterQuery(query.queryText.trim())} - /> - + data-test-subj="show-custom-jobs-filter-button" + > + {i18n.SHOW_CUSTOM_JOBS} + + + + + ); +}; - - - - - +JobsTableFiltersComponent.displayName = 'JobsTableFiltersComponent'; - - - { - setShowElasticJobs(!showElasticJobs); - setShowCustomJobs(false); - }} - data-test-subj="show-elastic-jobs-filter-button" - withNext - > - {i18n.SHOW_ELASTIC_JOBS} - - { - setShowCustomJobs(!showCustomJobs); - setShowElasticJobs(false); - }} - data-test-subj="show-custom-jobs-filter-button" - > - {i18n.SHOW_CUSTOM_JOBS} - - - - - ); - } -); +export const JobsTableFilters = React.memo(JobsTableFiltersComponent); JobsTableFilters.displayName = 'JobsTableFilters'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx index de703ca819388..91e5510f4938d 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx @@ -8,7 +8,7 @@ import { shallow, mount } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { isChecked, isFailure, isJobLoading, JobSwitch } from './job_switch'; +import { isChecked, isFailure, isJobLoading, JobSwitchComponent } from './job_switch'; import { cloneDeep } from 'lodash/fp'; import { mockSiemJobs } from '../__mocks__/api'; import { SiemJob } from '../types'; @@ -23,7 +23,7 @@ describe('JobSwitch', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - { test('should call onJobStateChange when the switch is clicked to be true/open', () => { const wrapper = mount( - { test('should have a switch when it is not in the loading state', () => { const wrapper = mount( - { test('should not have a switch when it is in the loading state', () => { const wrapper = mount( - { return failureStates.includes(jobState) || failureStates.includes(datafeedState); }; -export const JobSwitch = React.memo( - ({ job, isSiemJobsLoading, onJobStateChange }) => { - const [isLoading, setIsLoading] = useState(false); +export const JobSwitchComponent = ({ + job, + isSiemJobsLoading, + onJobStateChange, +}: JobSwitchProps) => { + const [isLoading, setIsLoading] = useState(false); - return ( - - - {isSiemJobsLoading || isLoading || isJobLoading(job.jobState, job.datafeedId) ? ( - - ) : ( - { - setIsLoading(true); - onJobStateChange(job, job.latestTimestampMs || 0, e.target.checked); - }} - showLabel={false} - label="" - /> - )} - - - ); - } -); + return ( + + + {isSiemJobsLoading || isLoading || isJobLoading(job.jobState, job.datafeedId) ? ( + + ) : ( + { + setIsLoading(true); + onJobStateChange(job, job.latestTimestampMs || 0, e.target.checked); + }} + showLabel={false} + label="" + /> + )} + + + ); +}; + +JobSwitchComponent.displayName = 'JobSwitchComponent'; + +export const JobSwitch = React.memo(JobSwitchComponent); JobSwitch.displayName = 'JobSwitch'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx index 10c9587ea10ad..691d43a8b18b3 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx @@ -7,12 +7,12 @@ import { shallow, mount } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { JobsTable } from './jobs_table'; +import { JobsTableComponent } from './jobs_table'; import { mockSiemJobs } from '../__mocks__/api'; import { cloneDeep } from 'lodash/fp'; import { SiemJob } from '../types'; -describe('JobsTable', () => { +describe('JobsTableComponent', () => { let siemJobs: SiemJob[]; let onJobStateChangeMock = jest.fn(); beforeEach(() => { @@ -22,14 +22,22 @@ describe('JobsTable', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - + ); expect(toJson(wrapper)).toMatchSnapshot(); }); test('should render the hyperlink which points specifically to the job id', () => { const wrapper = mount( - + ); expect( wrapper @@ -44,7 +52,11 @@ describe('JobsTable', () => { test('should render the hyperlink with URI encodings which points specifically to the job id', () => { siemJobs[0].id = 'job id with spaces'; const wrapper = mount( - + ); expect( wrapper @@ -56,7 +68,11 @@ describe('JobsTable', () => { test('should call onJobStateChange when the switch is clicked to be true/open', () => { const wrapper = mount( - + ); wrapper .find('button[data-test-subj="job-switch"]') @@ -69,14 +85,22 @@ describe('JobsTable', () => { test('should have a switch when it is not in the loading state', () => { const wrapper = mount( - + ); expect(wrapper.find('[data-test-subj="job-switch"]').exists()).toBe(true); }); test('should not have a switch when it is in the loading state', () => { const wrapper = mount( - + ); expect(wrapper.find('[data-test-subj="job-switch"]').exists()).toBe(false); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.tsx index b15c684b1bbbe..86f28ebda2086 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.tsx @@ -93,7 +93,7 @@ export interface JobTableProps { onJobStateChange: (job: SiemJob, latestTimestampMs: number, enable: boolean) => void; } -export const JobsTable = React.memo(({ isLoading, jobs, onJobStateChange }: JobTableProps) => { +export const JobsTableComponent = ({ isLoading, jobs, onJobStateChange }: JobTableProps) => { const [pageIndex, setPageIndex] = useState(0); const pageSize = 5; @@ -123,7 +123,11 @@ export const JobsTable = React.memo(({ isLoading, jobs, onJobStateChange }: JobT }} /> ); -}); +}; + +JobsTableComponent.displayName = 'JobsTableComponent'; + +export const JobsTable = React.memo(JobsTableComponent); JobsTable.displayName = 'JobsTable'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx index 6502dc909a775..2e2445fe933bb 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx @@ -7,11 +7,11 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { ShowingCount } from './showing_count'; +import { ShowingCountComponent } from './showing_count'; describe('ShowingCount', () => { test('renders correctly against snapshot', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.tsx index ef8a4fb197f93..1f008ecf712ef 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.tsx @@ -21,7 +21,7 @@ export interface ShowingCountProps { filterResultsLength: number; } -export const ShowingCount = React.memo(({ filterResultsLength }) => ( +export const ShowingCountComponent = ({ filterResultsLength }: ShowingCountProps) => ( (({ filterResultsLength /> -)); +); + +ShowingCountComponent.displayName = 'ShowingCountComponent'; + +export const ShowingCount = React.memo(ShowingCountComponent); ShowingCount.displayName = 'ShowingCount'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx index 198f99fdd84a2..4ea9e0cdafacb 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx @@ -8,10 +8,6 @@ import { mount } from 'enzyme'; import * as React from 'react'; import { MlPopover } from './ml_popover'; -// Suppress warnings about "act" until async/await syntax is supported: https://github.com/facebook/react/issues/14769 -/* eslint-disable no-console */ -const originalError = console.error; - jest.mock('../../lib/settings/use_kibana_ui_setting'); jest.mock('../ml/permissions/has_ml_admin_permissions', () => ({ @@ -19,14 +15,6 @@ jest.mock('../ml/permissions/has_ml_admin_permissions', () => ({ })); describe('MlPopover', () => { - beforeAll(() => { - console.error = jest.fn(); - }); - - afterAll(() => { - console.error = originalError; - }); - test('shows upgrade popover on mouse click', () => { const wrapper = mount(); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx index 8f90877feb72f..d409f5de200a4 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx @@ -7,11 +7,11 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { PopoverDescription } from './popover_description'; +import { PopoverDescriptionComponent } from './popover_description'; describe('JobsTableFilters', () => { test('renders correctly against snapshot', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx index 67a4654d8368a..c9cc1c5d4e539 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { EuiLink, EuiText } from '@elastic/eui'; import chrome from 'ui/chrome'; -export const PopoverDescription = React.memo(() => ( +export const PopoverDescriptionComponent = () => ( ( }} /> -)); +); + +PopoverDescriptionComponent.displayName = 'PopoverDescriptionComponent'; + +export const PopoverDescription = React.memo(PopoverDescriptionComponent); PopoverDescription.displayName = 'PopoverDescription'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx index 13d48c0e62b6d..c522b7750c414 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx @@ -7,11 +7,11 @@ import { shallow } from 'enzyme'; import toJson from 'enzyme-to-json'; import * as React from 'react'; -import { UpgradeContents } from './upgrade_contents'; +import { UpgradeContentsComponent } from './upgrade_contents'; describe('JobsTableFilters', () => { test('renders correctly against snapshot', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(toJson(wrapper)).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.tsx index 45ea80d6a303e..a337e234f11d3 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.tsx @@ -26,50 +26,50 @@ const PopoverContentsDiv = styled.div` PopoverContentsDiv.displayName = 'PopoverContentsDiv'; -export const UpgradeContents = React.memo(() => { - return ( - - {i18n.UPGRADE_TITLE} - - - - - ), - }} - /> - - - - - - {i18n.UPGRADE_BUTTON} - - - - - {i18n.LICENSE_BUTTON} - - - - - ); -}); +export const UpgradeContentsComponent = () => ( + + {i18n.UPGRADE_TITLE} + + + + + ), + }} + /> + + + + + + {i18n.UPGRADE_BUTTON} + + + + + {i18n.LICENSE_BUTTON} + + + + +); + +export const UpgradeContents = React.memo(UpgradeContentsComponent); UpgradeContents.displayName = 'UpgradeContents'; diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx index c2156bd6c046c..e84e3066e4f69 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx @@ -13,7 +13,7 @@ import { navTabsHostDetails } from '../../../pages/hosts/details/nav_tabs'; import { HostsTableType } from '../../../store/hosts/model'; import { RouteSpyState } from '../../../utils/route/types'; import { CONSTANTS } from '../../url_state/constants'; -import { TabNavigation } from './'; +import { TabNavigationComponent } from './'; import { TabNavigationProps } from './types'; describe('Tab Navigation', () => { @@ -60,12 +60,12 @@ describe('Tab Navigation', () => { }, }; test('it mounts with correct tab highlighted', () => { - const wrapper = shallow(); + const wrapper = shallow(); const hostsTab = wrapper.find('[data-test-subj="navigation-hosts"]'); expect(hostsTab.prop('isSelected')).toBeTruthy(); }); test('it changes active tab when nav changes by props', () => { - const wrapper = mount(); + const wrapper = mount(); const networkTab = () => wrapper.find('[data-test-subj="navigation-network"]').first(); expect(networkTab().prop('isSelected')).toBeFalsy(); wrapper.setProps({ @@ -77,7 +77,7 @@ describe('Tab Navigation', () => { expect(networkTab().prop('isSelected')).toBeTruthy(); }); test('it carries the url state in the link', () => { - const wrapper = shallow(); + const wrapper = shallow(); const firstTab = wrapper.find('[data-test-subj="navigation-network"]'); expect(firstTab.props().href).toBe( "#/link-to/network?query=(language:kuery,query:'host.name:%22siem-es%22')&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))" @@ -124,7 +124,7 @@ describe('Tab Navigation', () => { }, }; test('it mounts with correct tab highlighted', () => { - const wrapper = shallow(); + const wrapper = shallow(); const tableNavigationTab = wrapper.find( `[data-test-subj="navigation-${HostsTableType.authentications}"]` ); @@ -132,7 +132,7 @@ describe('Tab Navigation', () => { expect(tableNavigationTab.prop('isSelected')).toBeTruthy(); }); test('it changes active tab when nav changes by props', () => { - const wrapper = mount(); + const wrapper = mount(); const tableNavigationTab = () => wrapper.find(`[data-test-subj="navigation-${HostsTableType.events}"]`).first(); expect(tableNavigationTab().prop('isSelected')).toBeFalsy(); @@ -145,7 +145,7 @@ describe('Tab Navigation', () => { expect(tableNavigationTab().prop('isSelected')).toBeTruthy(); }); test('it carries the url state in the link', () => { - const wrapper = shallow(); + const wrapper = shallow(); const firstTab = wrapper.find( `[data-test-subj="navigation-${HostsTableType.authentications}"]` ); diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx index 27d10cb02a856..d405ec404b111 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.tsx @@ -11,7 +11,7 @@ import { trackUiAction as track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../l import { getSearch } from '../helpers'; import { TabNavigationProps } from './types'; -export const TabNavigation = React.memo(props => { +export const TabNavigationComponent = (props: TabNavigationProps) => { const { display, navTabs, pageName, tabName } = props; const mapLocationToTab = (): string => { @@ -51,5 +51,10 @@ export const TabNavigation = React.memo(props => { )); return {renderTabs()}; -}); +}; + +TabNavigationComponent.displayName = 'TabNavigationComponent'; + +export const TabNavigation = React.memo(TabNavigationComponent); + TabNavigation.displayName = 'TabNavigation'; 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 7dd5eccb4a6c6..71e61e2425373 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/hosts/first_last_seen_host/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx index bd3f736bf2d19..234d5ac959c8c 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx @@ -7,7 +7,7 @@ import { cloneDeep } from 'lodash/fp'; import * as React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; -import { render } from 'react-testing-library'; +import { render } from '@testing-library/react'; import { mockFirstLastSeenHostQuery } from '../../../../containers/hosts/first_last_seen/mock'; import { wait } from '../../../../lib/helpers'; @@ -19,31 +19,9 @@ import { FirstLastSeenHost, FirstLastSeenHostType } from '.'; jest.mock('../../../../lib/settings/use_kibana_ui_setting'); describe('FirstLastSeen Component', () => { - // this is just a little hack to silence a warning that we'll get until react - // fixes this: https://github.com/facebook/react/pull/14853 - // For us that mean we need to upgrade to 16.9.0 - // and we will be able to do that when we are in master - const firstSeen = 'Apr 8, 2019 @ 16:09:40.692'; const lastSeen = 'Apr 8, 2019 @ 18:35:45.064'; - // eslint-disable-next-line no-console - const originalError = console.error; - beforeAll(() => { - // eslint-disable-next-line no-console - console.error = (...args: string[]) => { - if (/Warning.*not wrapped in act/.test(args[0])) { - return; - } - originalError.call(console, ...args); - }; - }); - - afterAll(() => { - // eslint-disable-next-line no-console - console.error = originalError; - }); - test('Loading', async () => { const { container } = render( diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx index 63642119b430c..2cdac754198af 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx @@ -14,27 +14,6 @@ import { mockData } from './mock'; import { mockAnomalies } from '../../../ml/mock'; describe('Host Summary Component', () => { - // this is just a little hack to silence a warning that we'll get until react - // fixes this: https://github.com/facebook/react/pull/14853 - // For us that mean we need to upgrade to 16.9.0 - // and we will be able to do that when we are in master - // eslint-disable-next-line no-console - const originalError = console.error; - beforeAll(() => { - // eslint-disable-next-line no-console - console.error = (...args: string[]) => { - if (/Warning.*not wrapped in act/.test(args[0])) { - return; - } - originalError.call(console, ...args); - }; - }); - - afterAll(() => { - // eslint-disable-next-line no-console - console.error = originalError; - }); - describe('rendering', () => { test('it renders the default Host Summary', () => { const wrapper = shallow( diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx index 135d45907b35e..577ec5ff51470 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx @@ -8,7 +8,7 @@ import { mockKpiHostsData, mockKpiHostDetailsData } from './mock'; import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import toJson from 'enzyme-to-json'; -import { KpiHostsComponent } from '.'; +import { KpiHostsComponentBase } from '.'; import * as statItems from '../../../stat_items'; import { kpiHostsMapping } from './kpi_hosts_mapping'; import { kpiHostDetailsMapping } from './kpi_host_details_mapping'; @@ -21,7 +21,7 @@ describe('kpiHostsComponent', () => { describe('render', () => { test('it should render spinner if it is loading', () => { const wrapper: ShallowWrapper = shallow( - { test('it should render KpiHostsData', () => { const wrapper: ShallowWrapper = shallow( - { test('it should render KpiHostDetailsData', () => { const wrapper: ShallowWrapper = shallow( - { beforeEach(() => { shallow( - ( - ({ data, from, loading, id, to, narrowDateRange }) => { - const mappings = - (data as KpiHostsData).hosts !== undefined ? kpiHostsMapping : kpiHostDetailsMapping; - const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( - mappings, - data, - id, - from, - to, - narrowDateRange - ); - return loading ? ( - - - - - - ) : ( - - {statItemsProps.map((mappedStatItemProps, idx) => { - return ; - })} - - ); - } -); +export const KpiHostsComponentBase = ({ + data, + from, + loading, + id, + to, + narrowDateRange, +}: KpiHostsProps | KpiHostDetailsProps) => { + const mappings = + (data as KpiHostsData).hosts !== undefined ? kpiHostsMapping : kpiHostDetailsMapping; + const statItemsProps: StatItemsProps[] = useKpiMatrixStatus( + mappings, + data, + id, + from, + to, + narrowDateRange + ); + return loading ? ( + + + + + + ) : ( + + {statItemsProps.map((mappedStatItemProps, idx) => { + return ; + })} + + ); +}; + +KpiHostsComponentBase.displayName = 'KpiHostsComponentBase'; + +export const KpiHostsComponent = React.memo(KpiHostsComponentBase); + +KpiHostsComponent.displayName = 'KpiHostsComponent'; 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 bc701006c3a9c..582ef2d01fb7e 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 eb6204044bdb7..964617c4c85b1 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 98f55b29c8fc4..8bf338d17c47b 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 277e136d776fa..c92661a909a6e 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 d8a5da6036f95..ca7a3c0bb4387 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 df9e0f9f89645..884825422beb0 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 612896c878ef9..8c397053380c5 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 00a0a34a2b30b..d178164fd3fd7 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/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx index 3f460560b79b5..591fe6a73359d 100644 --- a/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx @@ -15,8 +15,8 @@ import { Query, TimeHistory, TimeRange, + SavedQueryTimeFilter, } from '../../../../../../../src/plugins/data/public'; -import { SavedQueryTimeFilter } from '../../../../../../../src/legacy/core_plugins/data/public/search'; import { Storage } from '../../../../../../../src/plugins/kibana_utils/public'; export interface QueryBarComponentProps { diff --git a/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx index 33fb2b9239a6a..710c1e230faba 100644 --- a/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/search_bar/index.tsx @@ -38,11 +38,10 @@ import { TimeRange, Query, esFilters } from '../../../../../../../src/plugins/da const { ui: { SearchBar }, - search, } = data; export const siemFilterManager = npStart.plugins.data.query.filterManager; -export const savedQueryService = search.services.savedQueryService; +export const savedQueryService = npStart.plugins.data.query.savedQueries; interface SiemSearchBarRedux { end: number; diff --git a/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx index f8afd3aeb9dca..eb06fe8a01d79 100644 --- a/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx @@ -8,7 +8,7 @@ import { getRowItemDraggables, getRowItemOverflow, getRowItemDraggable, - OverflowField, + OverflowFieldComponent, } from './helpers'; import * as React from 'react'; import { mount, shallow } from 'enzyme'; @@ -210,19 +210,21 @@ describe('Table Helpers', () => { describe('OverflowField', () => { test('it returns correctly against snapshot', () => { const overflowString = 'This string is exactly fifty-one chars in length!!!'; - const wrapper = shallow(); + const wrapper = shallow( + + ); expect(toJson(wrapper)).toMatchSnapshot(); }); test('it does not truncates as per custom overflowLength value', () => { const overflowString = 'This string is short'; - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.text()).toBe('This string is short'); }); test('it truncates as per custom overflowLength value', () => { const overflowString = 'This string is exactly fifty-one chars in length!!!'; - const wrapper = mount(); + const wrapper = mount(); expect(wrapper.text()).toBe('This string is exact'); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/tables/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/tables/helpers.tsx index b4ee93f9963e4..f4f7375c26d14 100644 --- a/x-pack/legacy/plugins/siem/public/components/tables/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/tables/helpers.tsx @@ -177,11 +177,15 @@ export const getRowItemOverflow = ( ); }; -export const Popover = React.memo<{ +export const PopoverComponent = ({ + children, + count, + idPrefix, +}: { children: React.ReactNode; count: number; idPrefix: string; -}>(({ children, count, idPrefix }) => { +}) => { const [isOpen, setIsOpen] = useState(false); return ( @@ -196,15 +200,23 @@ export const Popover = React.memo<{ ); -}); +}; + +PopoverComponent.displayName = 'PopoverComponent'; + +export const Popover = React.memo(PopoverComponent); Popover.displayName = 'Popover'; -export const OverflowField = React.memo<{ +export const OverflowFieldComponent = ({ + value, + showToolTip = true, + overflowLength = 50, +}: { value: string; showToolTip?: boolean; overflowLength?: number; -}>(({ value, showToolTip = true, overflowLength = 50 }) => ( +}) => ( {showToolTip ? ( @@ -219,6 +231,10 @@ export const OverflowField = React.memo<{ )} -)); +); + +OverflowFieldComponent.displayName = 'OverflowFieldComponent'; + +export const OverflowField = React.memo(OverflowFieldComponent); OverflowField.displayName = 'OverflowField'; 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 8bf7b1543b923..65818b697e0b3 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 747ef8f3ffe47..4f414af74a914 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/column_headers/header/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx index ce465ac4f837e..35a4f4a74ae20 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx @@ -15,7 +15,7 @@ import { CloseButton } from '../actions'; import { ColumnHeaderType } from '../column_header'; import { defaultHeaders } from '../default_headers'; -import { Header } from '.'; +import { HeaderComponent } from '.'; import { getNewSortDirectionOnClick, getNextSortDirection, getSortDirection } from './helpers'; const filteredColumnHeader: ColumnHeaderType = 'text-filter'; @@ -30,7 +30,7 @@ describe('Header', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( -
    { test('it renders the header text', () => { const wrapper = mount( -
    { const headerSortable = { ...columnHeader, aggregatable: true }; const wrapper = mount( -
    { const wrapper = mount( -
    { const headerSortable = { ...columnHeader, aggregatable: true }; const wrapper = mount( -
    { const headerSortable = { ...columnHeader, aggregatable: false }; const wrapper = mount( -
    { const headerSortable = { ...columnHeader }; const wrapper = mount( -
    { const headerSortable = { ...columnHeader, aggregatable: undefined }; const wrapper = mount( -
    { test('truncates the header text with an ellipsis', () => { const wrapper = mount( -
    { test('it has a tooltip to display the properties of the field', () => { const wrapper = mount( -
    { const mockSetIsResizing = jest.fn(); mount( -
    ( - ({ - header, - onColumnRemoved, - onColumnResized, - onColumnSorted, - onFilterChange = noop, - setIsResizing, - sort, - }) => { - const onClick = () => { - onColumnSorted!({ - columnId: header.id, - sortDirection: getNewSortDirectionOnClick({ - clickedHeader: header, - currentSort: sort, - }), - }); - }; - - const onResize: OnResize = ({ delta, id }) => { - onColumnResized({ columnId: id, delta }); - }; - - const renderActions = (isResizing: boolean) => { - setIsResizing(isResizing); - return ( - <> - - - - - - - ); - }; +export const HeaderComponent = ({ + header, + onColumnRemoved, + onColumnResized, + onColumnSorted, + onFilterChange = noop, + setIsResizing, + sort, +}: Props) => { + const onClick = () => { + onColumnSorted!({ + columnId: header.id, + sortDirection: getNewSortDirectionOnClick({ + clickedHeader: header, + currentSort: sort, + }), + }); + }; + const onResize: OnResize = ({ delta, id }) => { + onColumnResized({ columnId: id, delta }); + }; + + const renderActions = (isResizing: boolean) => { + setIsResizing(isResizing); return ( - } - id={header.id} - onResize={onResize} - positionAbsolute - render={renderActions} - right="-1px" - top={0} - /> + <> + + + + + + ); - } -); + }; + + return ( + } + id={header.id} + onResize={onResize} + positionAbsolute + render={renderActions} + right="-1px" + top={0} + /> + ); +}; + +HeaderComponent.displayName = 'HeaderComponent'; + +export const Header = React.memo(HeaderComponent); Header.displayName = 'Header'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx index 851d48a19c2e4..370f864f51f3c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx @@ -15,7 +15,7 @@ import { mockBrowserFields } from '../../../../../public/containers/source/mock' import { Sort } from '../sort'; import { TestProviders } from '../../../../mock/test_providers'; -import { ColumnHeaders } from '.'; +import { ColumnHeadersComponent } from '.'; jest.mock('../../../resize_handle/is_resizing', () => ({ ...jest.requireActual('../../../resize_handle/is_resizing'), @@ -34,7 +34,7 @@ describe('ColumnHeaders', () => { test('renders correctly against snapshot', () => { const wrapper = shallow( - { test('it renders the field browser', () => { const wrapper = mount( - { test('it renders every column header', () => { const wrapper = mount( - { test('it disables dragging during a column resize', () => { const wrapper = mount( - ( - ({ - actionsColumnWidth, - browserFields, - columnHeaders, - isEventViewer = false, - onColumnRemoved, - onColumnResized, - onColumnSorted, - onUpdateColumns, - onFilterChange = noop, - showEventsSelect, - sort, - timelineId, - toggleColumn, - }) => { - const { isResizing, setIsResizing } = useIsContainerResizing(); - - return ( - - - - {showEventsSelect && ( - - - - - - )} +export const ColumnHeadersComponent = ({ + actionsColumnWidth, + browserFields, + columnHeaders, + isEventViewer = false, + onColumnRemoved, + onColumnResized, + onColumnSorted, + onUpdateColumns, + onFilterChange = noop, + showEventsSelect, + sort, + timelineId, + toggleColumn, +}: Props) => { + const { isResizing, setIsResizing } = useIsContainerResizing(); + return ( + + + + {showEventsSelect && ( - + - + )} + + + + + + + + + + {dropProvided => ( + + {columnHeaders.map((header, i) => ( + + {(dragProvided, dragSnapshot) => ( + + {!dragSnapshot.isDragging ? ( + +
    + + ) : ( + + + + )} + + )} + + ))} + + )} + + + + ); +}; + +ColumnHeadersComponent.displayName = 'ColumnHeadersComponent'; + +export const ColumnHeaders = React.memo(ColumnHeadersComponent); - - {dropProvided => ( - - {columnHeaders.map((header, i) => ( - - {(dragProvided, dragSnapshot) => ( - - {!dragSnapshot.isDragging ? ( - -
    - - ) : ( - - - - )} - - )} - - ))} - - )} - - - - ); - } -); ColumnHeaders.displayName = 'ColumnHeaders'; 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 47fbcec4aab23..07e37346ac968 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/body/renderers/args.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx index 284cd0b49cb58..dbf6db6cd2bd9 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx @@ -10,13 +10,13 @@ import * as React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { TestProviders } from '../../../../mock'; -import { Args } from './args'; +import { ArgsComponent } from './args'; describe('Args', () => { describe('rendering', () => { test('it renders against shallow snapshot', () => { const wrapper = shallow( - { test('it returns an empty string when both args and process title are undefined', () => { const wrapper = mountWithIntl( - { test('it returns an empty string when both args and process title are null', () => { const wrapper = mountWithIntl( - + ); expect(wrapper.text()).toEqual(''); @@ -52,7 +57,7 @@ describe('Args', () => { test('it returns an empty string when args is an empty array, and title is an empty string', () => { const wrapper = mountWithIntl( - + ); expect(wrapper.text()).toEqual(''); @@ -61,7 +66,7 @@ describe('Args', () => { test('it returns args when args are provided, and process title is NOT provided', () => { const wrapper = mountWithIntl( - { test('it returns process title when process title is provided, and args is NOT provided', () => { const wrapper = mountWithIntl( - { test('it returns both args and process title, when both are provided', () => { const wrapper = mountWithIntl( - (({ args, contextId, eventId, processTitle }) => { +export const ArgsComponent = ({ args, contextId, eventId, processTitle }: Props) => { if (isNillEmptyOrNotFinite(args) && isNillEmptyOrNotFinite(processTitle)) { return null; } @@ -47,6 +47,10 @@ export const Args = React.memo(({ args, contextId, eventId, processTitle )} ); -}); +}; + +ArgsComponent.displayName = 'ArgsComponent'; + +export const Args = React.memo(ArgsComponent); Args.displayName = 'Args'; 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 29d2df5172457..3ef7240ee0375 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 112962367cd36..5a8654509fa88 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/footer/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx index 6e8a0e8cfb17f..07b7741e5c152 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx @@ -11,7 +11,7 @@ import * as React from 'react'; import { TestProviders } from '../../../mock/test_providers'; -import { Footer, PagingControl } from './index'; +import { FooterComponent, PagingControlComponent } from './index'; import { mockData } from './mock'; describe('Footer Timeline Component', () => { @@ -23,7 +23,7 @@ describe('Footer Timeline Component', () => { describe('rendering', () => { test('it renders the default timeline footer', () => { const wrapper = shallow( -