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 2b3b6c899e8ded..6ef7022f10e624 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 478e29696966cd..77f50abc60369e 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 1272a134ef5c44..56a7f644d34ccc 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.irouter.delete.md b/docs/development/core/server/kibana-plugin-server.irouter.delete.md index 5202e0cfd5ebb7..a479c03ecede3a 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 32552a49cb999a..0d52ef26f008c7 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 b5d3c893d745dc..73e96191e02e71 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 00000000000000..460f1b9d23640f --- /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 cd655c9ce0dc8b..a2ac27ebc731ae 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 e553d4b79dd2b3..219c5d8805661f 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.kibanarequest.md b/docs/development/core/server/kibana-plugin-server.kibanarequest.md index b2460cd58f7a72..bc805fdc0b86f1 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 88954eedf4cfb5..1905070a99068b 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 b92fe45d19edb8..29836394582007 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 c003b06e128e43..5775d28b1e053b 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 98c898449a5b1d..438263f61eb20a 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 00000000000000..f48711ac11f927 --- /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 38c7ad75d1db97..17c5136fdc3188 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -84,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. | @@ -133,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 @@ -156,6 +159,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | [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. | @@ -176,8 +180,9 @@ 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) | diff --git a/docs/development/core/server/kibana-plugin-server.requesthandler.md b/docs/development/core/server/kibana-plugin-server.requesthandler.md index 035d16c9fca3c0..79abfd4293e9fe 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 769d0dda426447..1970b23c7ec099 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 12ca36da6de7cb..90ad294457101f 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 00000000000000..fee5528ce3378e --- /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 b4d210ac0b7110..99339db81065c1 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 00000000000000..f48c9a1d73b11e --- /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 00000000000000..3d22dc07d5bae0 --- /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 00000000000000..6ef04de459fcf2 --- /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 00000000000000..b84bc709df3eca --- /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 00000000000000..d395f67c696690 --- /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 00000000000000..010388c7b8f177 --- /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 dd1a050708bb35..4f83344f842b3c 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 535927dc73743f..0f5f49636fdd5e 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 00000000000000..78a9d25c25d9d6 --- /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 00000000000000..77b980551a8ffe --- /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 00000000000000..3dbf9fed94dc09 --- /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 00000000000000..5be5830cb4bc87 --- /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.validbodyoutput.md b/docs/development/core/server/kibana-plugin-server.validbodyoutput.md new file mode 100644 index 00000000000000..ea866abf887fb8 --- /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/package.json b/package.json index 45a376a2913591..4b48731bd9a887 100644 --- a/package.json +++ b/package.json @@ -79,17 +79,16 @@ }, "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", - "**/deepmerge": "^4.2.2", - "**/react": "16.8.6", - "**/react-dom": "16.8.6", - "**/react-test-renderer": "16.8.6" + "**/react-dom": "^16.12.0", + "**/react-test-renderer": "^16.12.0", + "**/deepmerge": "^4.2.2" }, "workspaces": { "packages": [ @@ -216,15 +215,13 @@ "pug": "^2.0.3", "querystring-browser": "1.0.4", "raw-loader": "3.1.0", - "react": "^16.8.6", - "react-addons-shallow-compare": "15.6.2", + "react": "^16.12.0", "react-color": "^2.13.8", - "react-dom": "^16.8.6", + "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", @@ -287,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", @@ -312,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", @@ -332,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", @@ -347,6 +346,8 @@ "@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", @@ -410,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 0c79669c15e733..7afd3da3a7b939 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 2aa9627404a6c2..d682277ff905a4 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/typescript.js b/packages/eslint-config-kibana/typescript.js index 8ffae5edc88ebe..3337bfc8eb1015 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 8ba2c43b5e1fe6..fd62f1b3c03b22 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 210b044421e7e7..56b3096433c247 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 e5a5b446de4f5c..4d5091eaa09b13 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 00000000000000..96a7ab34dac26a --- /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 00000000000000..e813b4f68a09e1 --- /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 00000000000000..63d59296aec843 --- /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/src/legacy/core_plugins/data/public/search/search_service.ts b/packages/kbn-config-schema/src/types/buffer_type.ts similarity index 57% rename from src/legacy/core_plugins/data/public/search/search_service.ts rename to packages/kbn-config-schema/src/types/buffer_type.ts index 90ac288912f642..194163e5096f04 100644 --- a/src/legacy/core_plugins/data/public/search/search_service.ts +++ b/packages/kbn-config-schema/src/types/buffer_type.ts @@ -17,30 +17,18 @@ * under the License. */ -import { SavedObjectsClientContract } from 'src/core/public'; -import { createSavedQueryService } from './search_bar/lib/saved_query_service'; +import typeDetect from 'type-detect'; +import { internals } from '../internals'; +import { Type, TypeOptions } from './type'; -/** - * Search Service - * @internal - */ - -export class SearchService { - public setup() { - // Service requires index patterns, which are only available in `start` +export class BufferType extends Type { + constructor(options?: TypeOptions) { + super(internals.binary(), options); } - public start(savedObjectsClient: SavedObjectsClientContract) { - return { - services: { - savedQueryService: createSavedQueryService(savedObjectsClient), - }, - }; + protected handleError(type: string, { value }: Record) { + if (type === 'any.required' || type === 'binary.base') { + return `expected value of type [Buffer] but got [${typeDetect(value)}]`; + } } - - public stop() {} } - -/** @public */ - -export type SearchStart = ReturnType; diff --git a/packages/kbn-config-schema/src/types/index.ts b/packages/kbn-config-schema/src/types/index.ts index cfa8cc4b7553d5..9db79b8bf9e00b 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 00000000000000..011fa6373df335 --- /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 00000000000000..db1559f537490e --- /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 5c7e42d0d6f5fd..770314faa8ebd8 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-i18n/package.json b/packages/kbn-i18n/package.json index 0146111941044f..bbc5126da1dce0 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.6", + "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 53bc42ce332761..cb4654522e4e36 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 6d4c9cddae0be3..f5a71bfb1b296f 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 99a68e95751144..dbad59ffb3bd51 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 7a9d7a01b820a0..fc14417564d727 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 ee5424370fb06a..b4d9d3dfee03f6 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.6", + "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.6", - "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/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts index 0ac2f59525c326..8469a1d23a44b8 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 ceecfcfea1449c..df47ffdc1176b9 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -30,6 +30,7 @@ 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', @@ -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 da97ab535516c8..a587eed1f54eca 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 e9a2571382edc4..6dab120b20e50d 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 bed76201bb4f99..f9a3a91ec18adb 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'; diff --git a/src/core/server/http/router/error_wrapper.ts b/src/core/server/http/router/error_wrapper.ts index 706a9fe3b88871..c4b4d3840d1b95 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 f07ad3cfe85c03..35bfb3ba9c33ad 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 5d3b70ba27eeef..b132899910569e 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 bffa23551dd52d..129cf4c922ffd3 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 9fdf7297ed7755..f5469a95b51069 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 a13eae51a19a61..3bed8fe4186ac8 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 f792f6e604c152..a54ada233bbc94 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -94,6 +94,7 @@ export { IsAuthenticated, KibanaRequest, KibanaRequestRoute, + KibanaRequestRouteOptions, IKibanaResponse, LifecycleResponseFactory, KnownHeaders, @@ -113,9 +114,13 @@ export { KibanaResponseFactory, RouteConfig, IRouter, + RouteRegistrar, RouteMethod, RouteConfigOptions, - RouteRegistrar, + RouteSchemas, + RouteConfigOptionsBody, + RouteContentType, + validBodyOutput, SessionStorage, SessionStorageCookieOptions, SessionCookieValidationResult, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 411e5636069c18..25ca8ade77aca5 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 @@ -718,15 +718,16 @@ 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; } @@ -753,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; @@ -1050,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 { @@ -1092,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 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 =

(route: RouteConfig, handler: RequestHandler) => void; +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 { @@ -1696,6 +1720,9 @@ export interface UserProvidedValues { userValue?: T; } +// @public +export const validBodyOutput: readonly ["data", "stream"]; + // Warnings were encountered during analysis: // diff --git a/src/legacy/core_plugins/data/index.ts b/src/legacy/core_plugins/data/index.ts index 71f2fa5ffec7cf..c91500cd545d4b 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.ts b/src/legacy/core_plugins/data/public/index.ts index 184084e3cc3e65..833d8c248f46aa 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -38,7 +38,12 @@ export { StaticIndexPattern, } from './index_patterns'; export { QueryStringInput } from './query'; -export { SearchBar, SearchBarProps, SavedQueryAttributes, SavedQuery } from './search'; +export { SearchBar, SearchBarProps } from './search'; +export { + SavedQueryAttributes, + SavedQuery, + SavedQueryTimeFilter, +} from '../../../../plugins/data/public'; /** @public static code */ export * from '../common'; diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index da24576655d2b3..6cce91a5a25b5e 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 { return { FilterBar: () =>

, + createSavedQueryService: () => {}, }; }); 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 6a1ef77a566538..da08165289afc2 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 f369bf997c1a91..faf6e24aa6ed5d 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/field_select.js b/src/legacy/core_plugins/input_control_vis/public/components/editor/field_select.js index 11c6f27af38c5f..ce7d48a3f13762 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/kbn_vislib_vis_types/public/controller.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/controller.js index 319f7d9b9fa9f9..014606fb375ab3 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/kibana/public/dashboard/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts index 9c50adeeefccbd..f98a4ca53f4672 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 0ce8f2ef59fc0c..26b86204b03dbc 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 1a0e13417d1e1f..dcd25033e9d151 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 609bd717f3c485..cb0980c914983f 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 ba74ea069c4ab7..0e92d048a65a98 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 b3efd23ea48d03..ee80f29c053dce 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 083a5997ac5dd9..2420eb2cd22bb7 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 5a10e02ba8131f..822bf69e52e0b7 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, 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 086fa5a0591216..567bb3f83f22c8 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 df90abce00e48d..d0441ce3ceb8b3 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 617262c13b034f..4764b516dffec4 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 a1f302dc87f6c1..566277802174a9 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 cb1c316c8af9be..b32c325e936535 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 d91f4836ee1d8f..c7677955a8069b 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 ba93485a1739a0..1201e23c48e443 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 278f7de38b19d9..ee9fb70e31fb26 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 c2dabdffb2cd21..525f8495027c17 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 a953d09906ed11..c790930e32aa0f 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 6840c1386bee23..5410289bfc2d73 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 e2201cdca9a576..61b1cde0dcaf9e 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/telemetry/public/components/telemetry_form.js b/src/legacy/core_plugins/telemetry/public/components/telemetry_form.js index 6c6ace71af4d09..d4bbe1029b40d7 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/vis_type_timeseries/public/components/aggs/calculation.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/aggs/calculation.js index 2eefeb35c26ff8..76306ecf28994c 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 a73460798ebd81..c62012927f951b 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 ec16a0f2eb3eec..3ce5be5b6875a8 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 9fe237d9cb6713..835628368efab6 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 6da10f6a808165..19770519ef010a 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/metric.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/panel_config/metric.js index 705adc07e7314c..526649766a008a 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 029962f189afff..3acaa728bb50fc 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 8f5619909754e6..7dbecb9e674b01 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/split.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/split.js index fe91cb39f4acec..d1c53899db8793 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_types/table/config.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/table/config.js index d0e3acb2ef2fa4..a917a93ffbf5e5 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/visualizations/views/gauge.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/gauge.js index 68b738503c9b3d..3be2e9daed58c6 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 aa4ac992433971..d66eddae253a15 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 d6831769c6a6a6..004d59efca3330 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/ui/public/exit_full_screen/exit_full_screen_button.tsx b/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.tsx index 2e9a39b047e0b2..db4101010f6d67 100644 --- a/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.tsx +++ b/src/legacy/ui/public/exit_full_screen/exit_full_screen_button.tsx @@ -31,7 +31,7 @@ interface Props { } export class ExitFullScreenButton extends PureComponent { - public componentWillMount() { + public UNSAFE_componentWillMount() { chrome.setVisible(false); } 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 ff89ef69d53cad..e816b1858f21e8 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 @@ -80,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), }, 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 4ad579e1e45f9a..00000000000000 --- 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 '../../../../../core_plugins/visualizations/public/np_ready/public/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 8de88959cfb59b..4d7c0e2bdcadb4 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/vislib_vis_legend.html b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend.html deleted file mode 100644 index 70d2a796658f2f..00000000000000 --- 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 3d054b8f8a2fbd..00000000000000 --- 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 '../../../../core_plugins/visualizations/public'; -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 00000000000000..f2c9f4e1b53ec3 --- /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/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/index.ts new file mode 100644 index 00000000000000..ebf132f0ab697f --- /dev/null +++ b/src/legacy/ui/public/vis/vis_types/vislib_vis_legend/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 { 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 00000000000000..1c8d5baf011a34 --- /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 00000000000000..66acc9e247e630 --- /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 00000000000000..f0100e369f0507 --- /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 00000000000000..7376fabfe738be --- /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/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index d8c45b6786c0cf..35d8edc4884671 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -59,7 +59,7 @@ 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 206f8ac284ec37..ebef8b8d450500 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 00000000000000..f9b58e137b2768 --- /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 415da8a2c32cc1..ecb3fc2d606ec7 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 2668ce911c3718..434efe80ecd8cd 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 00000000000000..c278c2476c2e72 --- /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/ui/index.ts b/src/plugins/data/public/ui/index.ts index 607f690d41c67b..6fb8e260dd7205 100644 --- a/src/plugins/data/public/ui/index.ts +++ b/src/plugins/data/public/ui/index.ts @@ -21,3 +21,5 @@ 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/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx index f868e4b1f7504c..f41024ed16191c 100644 --- a/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx +++ b/src/plugins/data/public/ui/index_pattern_select/index_pattern_select.tsx @@ -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 ab210071870ca4..e3ec5212abfd2c 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 31b0e375eaac69..d86a8a970a8e70 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/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index cad095a6b0814d..2b48bf237829c0 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 019ca4fcbc18d9..a42eba838fe23f 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/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 a880d3c6cf87c3..09e702c55ac783 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/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx index dde8efa7e11066..e3be0b08ab83fe 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 { diff --git a/test/functional/page_objects/visualize_page.js b/test/functional/page_objects/visualize_page.js index 81d26a4b69478e..a6792670fdb3fa 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/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index 97ad71eaddd7cb..51b9a5c5f17860 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.6", - "react-dom": "^16.8.6" + "react": "^16.12.0", + "react-dom": "^16.12.0" } } 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 ca584b4b4e7714..fd0ce478eb6fb2 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.6" + "react": "^16.12.0" } } 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 71545fa582c664..98df7f4b246dc6 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.6" + "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 d5c97bb212ea05..32f441ba6ccda8 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.6" + "react": "^16.12.0" }, "scripts": { "kbn": "node ../../../../scripts/kbn.js", diff --git a/test/visual_regression/tests/discover/chart_visualization.js b/test/visual_regression/tests/discover/chart_visualization.js index 540d95973b547f..c90f29c66acb89 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 533ea58a561ac5..f54be89f309556 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 150cc4c0472b77..2eec667ce95c41 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 a58c936c637492..98d4d9f84a7296 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 40f61d11e9ace0..85dbd75e141743 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 b3e33f782688c2..5baec07fa1182a 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 1063e20e4ba3b3..a465aebc8bd86d 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 bacb3460421872..b75d681b6586ae 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/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.tsx index 118473a471561c..9f48880090369d 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 1f3403be70aa08..a5356be72f5e40 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 2ce8feb08d4adb..20125afb52f482 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/ManagedTable/__test__/ManagedTable.test.js b/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/__test__/ManagedTable.test.js index ff8d54935e9b29..1b63274dd3cf40 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 8c848722b32b2d..3c851252666e0c 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 d503929cf04d2d..1e06648f21eead 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 4398c129aa7b84..fbdd6bad3457df 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 4378c7fdeee0c3..7a150f81580d86 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 e9e7466cd81a8d..4bb018c760f1f6 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 f55d8d470351ca..57e634df22837e 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 38f26c2ba9fbd7..4763a560e0f857 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 94c2ee09b5d177..36a8377c02527c 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 }) => ( )( { path, options, 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 508649d6ad22d6..6ec2a7f02f3a35 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 a96fb493c4ab22..0e07c2b4960b72 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 30e4290c64158a..155917c894f42b 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 65b1f8677e384f..f7bf197395ccb8 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/pages/beat/details.tsx b/x-pack/legacy/plugins/beats_management/public/pages/beat/details.tsx index 9b3707b1661f4f..3eaf550cb8c774 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 3cbb84dfb954b1..672c0d89bb0028 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/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 66a7aa0639dba7..fb0baa22c16f4c 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 b8779e7d44fcfb..d486440c1fd7db 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( > + } selectedOptions={[ { value: currentField.name, diff --git a/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx b/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx index 43ad52abc4cc1e..a615901f40e253 100644 --- a/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx +++ b/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx @@ -9,7 +9,7 @@ import { EuiTab, EuiListGroupItem, EuiButton, EuiAccordion, EuiFieldText } from import * as Rx from 'rxjs'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { Settings, AngularProps } from './settings'; -import { act } from 'react-testing-library'; +import { act } from '@testing-library/react'; import { ReactWrapper } from 'enzyme'; import { UrlTemplateForm } from './url_template_form'; import { diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/node_attrs_details/node_attrs_details.js b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/node_attrs_details/node_attrs_details.js index 81272c748f60a5..2478dec36547ce 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/node_attrs_details/node_attrs_details.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/node_attrs_details/node_attrs_details.js @@ -27,7 +27,7 @@ export class NodeAttrsDetails extends PureComponent { selectedNodeAttrs: PropTypes.string.isRequired, }; - componentWillMount() { + UNSAFE_componentWillMount() { this.props.fetchNodeDetails(this.props.selectedNodeAttrs); } diff --git a/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts b/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts index 50dd8215102ffe..4a4896347333c8 100644 --- a/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts +++ b/x-pack/legacy/plugins/index_management/__jest__/client_integration/helpers/home.helpers.ts @@ -105,7 +105,6 @@ export const setup = async (): Promise => { 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 a7c0ac41816184..9e8af02b74631c 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 bd8d9b8e356754..997fe8cff2dace 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 a391811257a9f3..e678b7a7f52d61 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 4056bd2ad63e7c..975d82b9360540 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 fe919ed7ae6b72..92b46e8e0da003 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 d41be90ba6ad3c..854ccba2d3d190 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/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 932415bfe1afc5..c43e2f5a544cf0 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 184655398bd9c2..e58184c78b4b83 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 4c0d95c5529e8e..0512fb0a46b901 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 32f3864bbfe4e3..3b61181dfc6e09 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/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 d4cf4f7ffbaa61..f615914360a350 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/license_management/public/sections/license_dashboard/start_trial/start_trial.js b/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.js index 6b3bc1f1065313..20b130d80a211b 100644 --- a/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.js +++ b/x-pack/legacy/plugins/license_management/public/sections/license_dashboard/start_trial/start_trial.js @@ -36,7 +36,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/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js index b9354dd0a0dddd..810dc263f8b78a 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 5a9cb54109363b..6fb5930e81a209 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/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 0ac7e5eb093318..98439d76627b9d 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 5f94e89ad2ba5b..3f72209f224568 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 af300e51eef999..41e510459fceae 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 a9e07c7e15f460..0ddeb15d5c2c76 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/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 6fd80c524f8ef7..2a939d93a48b3c 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 00c06757beca8f..5c020840182e57 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/monitoring/public/components/chart/chart_target.js b/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js index 5443d6cbee6b59..78501aca566f69 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/timeseries_visualization.js b/x-pack/legacy/plugins/monitoring/public/components/chart/timeseries_visualization.js index 1ae997c5ebaa47..540460478cf53d 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/shard_allocation/components/cluster_view.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/cluster_view.js index 8c85c40951777c..a4640fa45119b4 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/renderers/setup_mode.js b/x-pack/legacy/plugins/monitoring/public/components/renderers/setup_mode.js index dadb31f2cc83bb..83c42c6dff37b8 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 49d4dcfbf9a66f..6f52a82c40820f 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/reporting/export_types/common/layouts/preserve_layout.css b/x-pack/legacy/plugins/reporting/export_types/common/layouts/preserve_layout.css index 9e8415a1ff18c1..ab88e4780936ea 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 30c253f36840af..8aca042144b3b4 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/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 b91c67b8f7d0ae..c5bf910b007d00 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/hosts/selectors.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/hosts/selectors.ts index ec6c64e116b24f..1c900944752c48 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/public/components/charts/areachart.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx index 910e576e6e1e71..25bd2a9d560593 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 d51f5e081468c5..c644d148cc1c3d 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 4b3ec577e64880..e28d330d31ba90 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 04bedb827aa402..7218d7a497f19d 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/droppable_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx index b7ceac77aa1f13..3f789a39832f11 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/embeddables/__snapshots__/embedded_map.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/embeddables/__snapshots__/embedded_map.test.tsx.snap index bf0dfd9417875a..2444fd0bc2b7d4 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 48a49835b284fc..d32b62900431c2 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 e71398455ee883..1e29676415d79f 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 2a17a2aae84973..2ef4d9df89a1bd 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 7cdf3a545a2d6d..0c416868bfb031 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 a73e6dabc68ae0..13eefb252fb04b 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 567f091e78cb56..1733fb3aa74809 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 f2673c17d246cf..4c77570cfbc9f2 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/events_viewer/events_viewer.test.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx index 4e59acc4f67136..25b2427d34d6ab 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 671711c60bd165..4d2e44f9a3d92b 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 e943ca6f3e8631..54847cda281f49 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 76d2c1ea7e3d08..408e8d7ad4d802 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/markdown/__snapshots__/markdown_hint.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/markdown/__snapshots__/markdown_hint.test.tsx.snap index 60464c46f1ac07..7f350072439c52 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 6319af3e6ffa13..c3268270919e28 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 18f3a35a23f7f5..5ecd1d4c9d2ad3 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 e3894ee6e7c66f..c401075af42ce2 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 d7f25c49fd7cae..b0636b08a56346 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 3509d92ce70513..a28077ba63ddd6 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 17d36ffcc90998..5bd11169e48408 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 eec0c65c7679f5..0d389ae14a8255 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 d156b5f0463f62..6ae31c0ac1fb9b 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 2c4f750ffeac5d..983eb2409bd775 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 e39046ba013c74..9f05ce8a5bfcee 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 5838c3105de6d8..0711cc1c879660 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 ba080757d34a84..74e61f27fb2d19 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 de703ca819388d..91e5510f4938d4 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 10c9587ea10ad4..691d43a8b18b36 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 b15c684b1bbbea..86f28ebda2086d 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 6502dc909a775c..2e2445fe933bbc 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 ef8a4fb197f934..1f008ecf712ef5 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 198f99fdd84a22..4ea9e0cdafacb2 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 8f90877feb72fb..d409f5de200a47 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 67a4654d8368ab..c9cc1c5d4e5396 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 13d48c0e62b6d3..c522b7750c4149 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 45ea80d6a303eb..a337e234f11d3b 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 c2156bd6c046cc..e84e3066e4f695 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 27d10cb02a856a..d405ec404b1112 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/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 bd3f736bf2d19a..234d5ac959c8cb 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 63642119b430c2..2cdac754198af7 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 135d45907b35e7..577ec5ff514700 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/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/components/query_bar/index.tsx index 3f460560b79b58..591fe6a73359da 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 33fb2b9239a6a4..710c1e230fabab 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 f8afd3aeb9dca1..eb06fe8a01d79a 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 b4ee93f9963e43..f4f7375c26d14c 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/header/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx index ce465ac4f837e2..35a4f4a74ae200 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 851d48a19c2e4c..370f864f51f3c7 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/renderers/args.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx index 284cd0b49cb584..dbf6db6cd2bd92 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/footer/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx index 6e8a0e8cfb17fc..07b7741e5c1521 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( -