diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9a4f2b71da1f..acfb7307f49c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -132,6 +132,9 @@ /x-pack/test/alerting_api_integration @elastic/kibana-alerting-services /x-pack/test/plugin_api_integration/plugins/task_manager @elastic/kibana-alerting-services /x-pack/test/plugin_api_integration/test_suites/task_manager @elastic/kibana-alerting-services +/x-pack/legacy/plugins/triggers_actions_ui/ @elastic/kibana-alerting-services +/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/ @elastic/kibana-alerting-services +/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/ @elastic/kibana-alerting-services # Design **/*.scss @elastic/kibana-design diff --git a/docs/development/core/public/kibana-plugin-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-public.doclinksstart.links.md index cbda9abead9d..9e662c543eb5 100644 --- a/docs/development/core/public/kibana-plugin-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-public.doclinksstart.links.md @@ -79,7 +79,10 @@ readonly links: { readonly introduction: string; }; readonly kibana: string; - readonly siem: string; + readonly siem: { + readonly guide: string; + readonly gettingStarted: string; + }; readonly query: { readonly luceneQuerySyntax: string; readonly queryDsl: string; diff --git a/docs/development/core/public/kibana-plugin-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-public.doclinksstart.md index c43569e24c63..cefac180d88c 100644 --- a/docs/development/core/public/kibana-plugin-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-public.doclinksstart.links.md) | {
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly kibana: string;
readonly siem: string;
readonly query: {
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
} | | +| [links](./kibana-plugin-public.doclinksstart.links.md) | {
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly kibana: string;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
} | | diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index 64cbdd880fed..27ca9f2d9fd5 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -1,151 +1,151 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) - -## kibana-plugin-public package - -The Kibana Core APIs for client-side plugins. - -A plugin's `public/index` file must contain a named import, `plugin`, that implements [PluginInitializer](./kibana-plugin-public.plugininitializer.md) which returns an object that implements [Plugin](./kibana-plugin-public.plugin.md). - -The plugin integrates with the core system via lifecycle events: `setup`, `start`, and `stop`. In each lifecycle method, the plugin will receive the corresponding core services available (either [CoreSetup](./kibana-plugin-public.coresetup.md) or [CoreStart](./kibana-plugin-public.corestart.md)) and any interfaces returned by dependency plugins' lifecycle method. Anything returned by the plugin's lifecycle method will be exposed to downstream dependencies when their corresponding lifecycle methods are invoked. - -## Classes - -| Class | Description | -| --- | --- | -| [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state. The client-side SavedObjectsClient is a thin convenience library around the SavedObjects HTTP API for interacting with Saved Objects. | -| [SimpleSavedObject](./kibana-plugin-public.simplesavedobject.md) | This class is a very simple wrapper for SavedObjects loaded from the server with the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md).It provides basic functionality for creating/saving/deleting saved objects, but doesn't include any type-specific implementations. | -| [ToastsApi](./kibana-plugin-public.toastsapi.md) | Methods for adding and removing global toast messages. | - -## Enumerations - -| Enumeration | Description | -| --- | --- | -| [AppLeaveActionType](./kibana-plugin-public.appleaveactiontype.md) | Possible type of actions on application leave. | -| [AppNavLinkStatus](./kibana-plugin-public.appnavlinkstatus.md) | Status of the application's navLink. | -| [AppStatus](./kibana-plugin-public.appstatus.md) | Accessibility status of an application. | - -## Interfaces - -| Interface | Description | -| --- | --- | -| [App](./kibana-plugin-public.app.md) | Extension of [common app properties](./kibana-plugin-public.appbase.md) with the mount function. | -| [AppBase](./kibana-plugin-public.appbase.md) | | -| [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to show a confirmation message when trying to leave an application.See | -| [AppLeaveDefaultAction](./kibana-plugin-public.appleavedefaultaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to execute the default behaviour when leaving the application.See | -| [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) | | -| [ApplicationStart](./kibana-plugin-public.applicationstart.md) | | -| [AppMountContext](./kibana-plugin-public.appmountcontext.md) | The context object received when applications are mounted to the DOM. Deprecated, use [CoreSetup.getStartServices()](./kibana-plugin-public.coresetup.getstartservices.md). | -| [AppMountParameters](./kibana-plugin-public.appmountparameters.md) | | -| [Capabilities](./kibana-plugin-public.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | -| [ChromeBadge](./kibana-plugin-public.chromebadge.md) | | -| [ChromeBrand](./kibana-plugin-public.chromebrand.md) | | -| [ChromeDocTitle](./kibana-plugin-public.chromedoctitle.md) | APIs for accessing and updating the document title. | -| [ChromeHelpExtension](./kibana-plugin-public.chromehelpextension.md) | | -| [ChromeNavControl](./kibana-plugin-public.chromenavcontrol.md) | | -| [ChromeNavControls](./kibana-plugin-public.chromenavcontrols.md) | [APIs](./kibana-plugin-public.chromenavcontrols.md) for registering new controls to be displayed in the navigation bar. | -| [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) | | -| [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) | [APIs](./kibana-plugin-public.chromenavlinks.md) for manipulating nav links. | -| [ChromeRecentlyAccessed](./kibana-plugin-public.chromerecentlyaccessed.md) | [APIs](./kibana-plugin-public.chromerecentlyaccessed.md) for recently accessed history. | -| [ChromeRecentlyAccessedHistoryItem](./kibana-plugin-public.chromerecentlyaccessedhistoryitem.md) | | -| [ChromeStart](./kibana-plugin-public.chromestart.md) | ChromeStart allows plugins to customize the global chrome header UI and enrich the UX with additional information about the current location of the browser. | -| [ContextSetup](./kibana-plugin-public.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | -| [CoreSetup](./kibana-plugin-public.coresetup.md) | Core services exposed to the Plugin setup lifecycle | -| [CoreStart](./kibana-plugin-public.corestart.md) | Core services exposed to the Plugin start lifecycle | -| [DocLinksStart](./kibana-plugin-public.doclinksstart.md) | | -| [EnvironmentMode](./kibana-plugin-public.environmentmode.md) | | -| [ErrorToastOptions](./kibana-plugin-public.errortoastoptions.md) | Options available for [IToasts](./kibana-plugin-public.itoasts.md) APIs. | -| [FatalErrorInfo](./kibana-plugin-public.fatalerrorinfo.md) | Represents the message and stack of a fatal Error | -| [FatalErrorsSetup](./kibana-plugin-public.fatalerrorssetup.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. | -| [HttpErrorRequest](./kibana-plugin-public.httperrorrequest.md) | | -| [HttpErrorResponse](./kibana-plugin-public.httperrorresponse.md) | | -| [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) | All options that may be used with a [HttpHandler](./kibana-plugin-public.httphandler.md). | -| [HttpFetchQuery](./kibana-plugin-public.httpfetchquery.md) | | -| [HttpHandler](./kibana-plugin-public.httphandler.md) | A function for making an HTTP requests to Kibana's backend. See [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) for options and [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) for the response. | -| [HttpHeadersInit](./kibana-plugin-public.httpheadersinit.md) | | -| [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md) | An object that may define global interceptor functions for different parts of the request and response lifecycle. See [IHttpInterceptController](./kibana-plugin-public.ihttpinterceptcontroller.md). | -| [HttpRequestInit](./kibana-plugin-public.httprequestinit.md) | Fetch API options available to [HttpHandler](./kibana-plugin-public.httphandler.md)s. | -| [HttpSetup](./kibana-plugin-public.httpsetup.md) | | -| [I18nStart](./kibana-plugin-public.i18nstart.md) | I18nStart.Context is required by any localizable React component from @kbn/i18n and @elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. | -| [IAnonymousPaths](./kibana-plugin-public.ianonymouspaths.md) | APIs for denoting paths as not requiring authentication | -| [IBasePath](./kibana-plugin-public.ibasepath.md) | APIs for manipulating the basePath on URL segments. | -| [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | -| [IHttpFetchError](./kibana-plugin-public.ihttpfetcherror.md) | | -| [IHttpInterceptController](./kibana-plugin-public.ihttpinterceptcontroller.md) | Used to halt a request Promise chain in a [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md). | -| [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) | | -| [IHttpResponseInterceptorOverrides](./kibana-plugin-public.ihttpresponseinterceptoroverrides.md) | Properties that can be returned by HttpInterceptor.request to override the response. | -| [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) | -| [LegacyCoreSetup](./kibana-plugin-public.legacycoresetup.md) | Setup interface exposed to the legacy platform via the ui/new_platform module. | -| [LegacyCoreStart](./kibana-plugin-public.legacycorestart.md) | Start interface exposed to the legacy platform via the ui/new_platform module. | -| [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) | | -| [NotificationsSetup](./kibana-plugin-public.notificationssetup.md) | | -| [NotificationsStart](./kibana-plugin-public.notificationsstart.md) | | -| [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) | | -| [OverlayRef](./kibana-plugin-public.overlayref.md) | Returned by [OverlayStart](./kibana-plugin-public.overlaystart.md) methods for closing a mounted overlay. | -| [OverlayStart](./kibana-plugin-public.overlaystart.md) | | -| [PackageInfo](./kibana-plugin-public.packageinfo.md) | | -| [Plugin](./kibana-plugin-public.plugin.md) | The interface that should be returned by a PluginInitializer. | -| [PluginInitializerContext](./kibana-plugin-public.plugininitializercontext.md) | The available core services passed to a PluginInitializer | -| [SavedObject](./kibana-plugin-public.savedobject.md) | | -| [SavedObjectAttributes](./kibana-plugin-public.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | -| [SavedObjectReference](./kibana-plugin-public.savedobjectreference.md) | A reference to another saved object. | -| [SavedObjectsBaseOptions](./kibana-plugin-public.savedobjectsbaseoptions.md) | | -| [SavedObjectsBatchResponse](./kibana-plugin-public.savedobjectsbatchresponse.md) | | -| [SavedObjectsBulkCreateObject](./kibana-plugin-public.savedobjectsbulkcreateobject.md) | | -| [SavedObjectsBulkCreateOptions](./kibana-plugin-public.savedobjectsbulkcreateoptions.md) | | -| [SavedObjectsBulkUpdateObject](./kibana-plugin-public.savedobjectsbulkupdateobject.md) | | -| [SavedObjectsBulkUpdateOptions](./kibana-plugin-public.savedobjectsbulkupdateoptions.md) | | -| [SavedObjectsCreateOptions](./kibana-plugin-public.savedobjectscreateoptions.md) | | -| [SavedObjectsFindOptions](./kibana-plugin-public.savedobjectsfindoptions.md) | | -| [SavedObjectsFindResponsePublic](./kibana-plugin-public.savedobjectsfindresponsepublic.md) | Return type of the Saved Objects find() method.\*Note\*: this type is different between the Public and Server Saved Objects clients. | -| [SavedObjectsImportConflictError](./kibana-plugin-public.savedobjectsimportconflicterror.md) | Represents a failure to import due to a conflict. | -| [SavedObjectsImportError](./kibana-plugin-public.savedobjectsimporterror.md) | Represents a failure to import. | -| [SavedObjectsImportMissingReferencesError](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.md) | Represents a failure to import due to missing references. | -| [SavedObjectsImportResponse](./kibana-plugin-public.savedobjectsimportresponse.md) | The response describing the result of an import. | -| [SavedObjectsImportRetry](./kibana-plugin-public.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. | -| [SavedObjectsImportUnknownError](./kibana-plugin-public.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. | -| [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-public.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. | -| [SavedObjectsMigrationVersion](./kibana-plugin-public.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | -| [SavedObjectsStart](./kibana-plugin-public.savedobjectsstart.md) | | -| [SavedObjectsUpdateOptions](./kibana-plugin-public.savedobjectsupdateoptions.md) | | -| [UiSettingsState](./kibana-plugin-public.uisettingsstate.md) | | - -## Type Aliases - -| Type Alias | Description | -| --- | --- | -| [AppLeaveAction](./kibana-plugin-public.appleaveaction.md) | Possible actions to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md)See [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) and [AppLeaveDefaultAction](./kibana-plugin-public.appleavedefaultaction.md) | -| [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) | A handler that will be executed before leaving the application, either when going to another application or when closing the browser tab or manually changing the url. Should return confirm to to prompt a message to the user before leaving the page, or default to keep the default behavior (doing nothing).See [AppMountParameters](./kibana-plugin-public.appmountparameters.md) for detailed usage examples. | -| [AppMount](./kibana-plugin-public.appmount.md) | A mount function called when the user navigates to this app's route. | -| [AppMountDeprecated](./kibana-plugin-public.appmountdeprecated.md) | A mount function called when the user navigates to this app's route. | -| [AppUnmount](./kibana-plugin-public.appunmount.md) | A function called when an application should be unmounted from the page. This function should be synchronous. | -| [AppUpdatableFields](./kibana-plugin-public.appupdatablefields.md) | Defines the list of fields that can be updated via an [AppUpdater](./kibana-plugin-public.appupdater.md). | -| [AppUpdater](./kibana-plugin-public.appupdater.md) | Updater for applications. see [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) | -| [ChromeBreadcrumb](./kibana-plugin-public.chromebreadcrumb.md) | | -| [ChromeHelpExtensionMenuCustomLink](./kibana-plugin-public.chromehelpextensionmenucustomlink.md) | | -| [ChromeHelpExtensionMenuDiscussLink](./kibana-plugin-public.chromehelpextensionmenudiscusslink.md) | | -| [ChromeHelpExtensionMenuDocumentationLink](./kibana-plugin-public.chromehelpextensionmenudocumentationlink.md) | | -| [ChromeHelpExtensionMenuGitHubLink](./kibana-plugin-public.chromehelpextensionmenugithublink.md) | | -| [ChromeHelpExtensionMenuLink](./kibana-plugin-public.chromehelpextensionmenulink.md) | | -| [ChromeNavLinkUpdateableFields](./kibana-plugin-public.chromenavlinkupdateablefields.md) | | -| [HandlerContextType](./kibana-plugin-public.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md) to represent the type of the context. | -| [HandlerFunction](./kibana-plugin-public.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | -| [HandlerParameters](./kibana-plugin-public.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-public.handlercontexttype.md). | -| [HttpStart](./kibana-plugin-public.httpstart.md) | See [HttpSetup](./kibana-plugin-public.httpsetup.md) | -| [IContextProvider](./kibana-plugin-public.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | -| [IToasts](./kibana-plugin-public.itoasts.md) | Methods for adding and removing global toast messages. See [ToastsApi](./kibana-plugin-public.toastsapi.md). | -| [MountPoint](./kibana-plugin-public.mountpoint.md) | A function that should mount DOM content inside the provided container element and return a handler to unmount it. | -| [PluginInitializer](./kibana-plugin-public.plugininitializer.md) | The plugin export at the root of a plugin's public directory should conform to this interface. | -| [PluginOpaqueId](./kibana-plugin-public.pluginopaqueid.md) | | -| [RecursiveReadonly](./kibana-plugin-public.recursivereadonly.md) | | -| [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | -| [SavedObjectAttributeSingle](./kibana-plugin-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) | -| [SavedObjectsClientContract](./kibana-plugin-public.savedobjectsclientcontract.md) | SavedObjectsClientContract as implemented by the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) | -| [Toast](./kibana-plugin-public.toast.md) | | -| [ToastInput](./kibana-plugin-public.toastinput.md) | Inputs for [IToasts](./kibana-plugin-public.itoasts.md) APIs. | -| [ToastInputFields](./kibana-plugin-public.toastinputfields.md) | Allowed fields for [ToastInput](./kibana-plugin-public.toastinput.md). | -| [ToastsSetup](./kibana-plugin-public.toastssetup.md) | [IToasts](./kibana-plugin-public.itoasts.md) | -| [ToastsStart](./kibana-plugin-public.toastsstart.md) | [IToasts](./kibana-plugin-public.itoasts.md) | -| [UnmountCallback](./kibana-plugin-public.unmountcallback.md) | A function that will unmount the element previously mounted by the associated [MountPoint](./kibana-plugin-public.mountpoint.md) | - + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) + +## kibana-plugin-public package + +The Kibana Core APIs for client-side plugins. + +A plugin's `public/index` file must contain a named import, `plugin`, that implements [PluginInitializer](./kibana-plugin-public.plugininitializer.md) which returns an object that implements [Plugin](./kibana-plugin-public.plugin.md). + +The plugin integrates with the core system via lifecycle events: `setup`, `start`, and `stop`. In each lifecycle method, the plugin will receive the corresponding core services available (either [CoreSetup](./kibana-plugin-public.coresetup.md) or [CoreStart](./kibana-plugin-public.corestart.md)) and any interfaces returned by dependency plugins' lifecycle method. Anything returned by the plugin's lifecycle method will be exposed to downstream dependencies when their corresponding lifecycle methods are invoked. + +## Classes + +| Class | Description | +| --- | --- | +| [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state. The client-side SavedObjectsClient is a thin convenience library around the SavedObjects HTTP API for interacting with Saved Objects. | +| [SimpleSavedObject](./kibana-plugin-public.simplesavedobject.md) | This class is a very simple wrapper for SavedObjects loaded from the server with the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md).It provides basic functionality for creating/saving/deleting saved objects, but doesn't include any type-specific implementations. | +| [ToastsApi](./kibana-plugin-public.toastsapi.md) | Methods for adding and removing global toast messages. | + +## Enumerations + +| Enumeration | Description | +| --- | --- | +| [AppLeaveActionType](./kibana-plugin-public.appleaveactiontype.md) | Possible type of actions on application leave. | +| [AppNavLinkStatus](./kibana-plugin-public.appnavlinkstatus.md) | Status of the application's navLink. | +| [AppStatus](./kibana-plugin-public.appstatus.md) | Accessibility status of an application. | + +## Interfaces + +| Interface | Description | +| --- | --- | +| [App](./kibana-plugin-public.app.md) | Extension of [common app properties](./kibana-plugin-public.appbase.md) with the mount function. | +| [AppBase](./kibana-plugin-public.appbase.md) | | +| [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to show a confirmation message when trying to leave an application.See | +| [AppLeaveDefaultAction](./kibana-plugin-public.appleavedefaultaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to execute the default behaviour when leaving the application.See | +| [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) | | +| [ApplicationStart](./kibana-plugin-public.applicationstart.md) | | +| [AppMountContext](./kibana-plugin-public.appmountcontext.md) | The context object received when applications are mounted to the DOM. Deprecated, use [CoreSetup.getStartServices()](./kibana-plugin-public.coresetup.getstartservices.md). | +| [AppMountParameters](./kibana-plugin-public.appmountparameters.md) | | +| [Capabilities](./kibana-plugin-public.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | +| [ChromeBadge](./kibana-plugin-public.chromebadge.md) | | +| [ChromeBrand](./kibana-plugin-public.chromebrand.md) | | +| [ChromeDocTitle](./kibana-plugin-public.chromedoctitle.md) | APIs for accessing and updating the document title. | +| [ChromeHelpExtension](./kibana-plugin-public.chromehelpextension.md) | | +| [ChromeNavControl](./kibana-plugin-public.chromenavcontrol.md) | | +| [ChromeNavControls](./kibana-plugin-public.chromenavcontrols.md) | [APIs](./kibana-plugin-public.chromenavcontrols.md) for registering new controls to be displayed in the navigation bar. | +| [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) | | +| [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) | [APIs](./kibana-plugin-public.chromenavlinks.md) for manipulating nav links. | +| [ChromeRecentlyAccessed](./kibana-plugin-public.chromerecentlyaccessed.md) | [APIs](./kibana-plugin-public.chromerecentlyaccessed.md) for recently accessed history. | +| [ChromeRecentlyAccessedHistoryItem](./kibana-plugin-public.chromerecentlyaccessedhistoryitem.md) | | +| [ChromeStart](./kibana-plugin-public.chromestart.md) | ChromeStart allows plugins to customize the global chrome header UI and enrich the UX with additional information about the current location of the browser. | +| [ContextSetup](./kibana-plugin-public.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | +| [CoreSetup](./kibana-plugin-public.coresetup.md) | Core services exposed to the Plugin setup lifecycle | +| [CoreStart](./kibana-plugin-public.corestart.md) | Core services exposed to the Plugin start lifecycle | +| [DocLinksStart](./kibana-plugin-public.doclinksstart.md) | | +| [EnvironmentMode](./kibana-plugin-public.environmentmode.md) | | +| [ErrorToastOptions](./kibana-plugin-public.errortoastoptions.md) | Options available for [IToasts](./kibana-plugin-public.itoasts.md) APIs. | +| [FatalErrorInfo](./kibana-plugin-public.fatalerrorinfo.md) | Represents the message and stack of a fatal Error | +| [FatalErrorsSetup](./kibana-plugin-public.fatalerrorssetup.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. | +| [HttpErrorRequest](./kibana-plugin-public.httperrorrequest.md) | | +| [HttpErrorResponse](./kibana-plugin-public.httperrorresponse.md) | | +| [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) | All options that may be used with a [HttpHandler](./kibana-plugin-public.httphandler.md). | +| [HttpFetchQuery](./kibana-plugin-public.httpfetchquery.md) | | +| [HttpHandler](./kibana-plugin-public.httphandler.md) | A function for making an HTTP requests to Kibana's backend. See [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) for options and [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) for the response. | +| [HttpHeadersInit](./kibana-plugin-public.httpheadersinit.md) | | +| [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md) | An object that may define global interceptor functions for different parts of the request and response lifecycle. See [IHttpInterceptController](./kibana-plugin-public.ihttpinterceptcontroller.md). | +| [HttpRequestInit](./kibana-plugin-public.httprequestinit.md) | Fetch API options available to [HttpHandler](./kibana-plugin-public.httphandler.md)s. | +| [HttpSetup](./kibana-plugin-public.httpsetup.md) | | +| [I18nStart](./kibana-plugin-public.i18nstart.md) | I18nStart.Context is required by any localizable React component from @kbn/i18n and @elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. | +| [IAnonymousPaths](./kibana-plugin-public.ianonymouspaths.md) | APIs for denoting paths as not requiring authentication | +| [IBasePath](./kibana-plugin-public.ibasepath.md) | APIs for manipulating the basePath on URL segments. | +| [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | +| [IHttpFetchError](./kibana-plugin-public.ihttpfetcherror.md) | | +| [IHttpInterceptController](./kibana-plugin-public.ihttpinterceptcontroller.md) | Used to halt a request Promise chain in a [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md). | +| [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) | | +| [IHttpResponseInterceptorOverrides](./kibana-plugin-public.ihttpresponseinterceptoroverrides.md) | Properties that can be returned by HttpInterceptor.request to override the response. | +| [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) | +| [LegacyCoreSetup](./kibana-plugin-public.legacycoresetup.md) | Setup interface exposed to the legacy platform via the ui/new_platform module. | +| [LegacyCoreStart](./kibana-plugin-public.legacycorestart.md) | Start interface exposed to the legacy platform via the ui/new_platform module. | +| [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) | | +| [NotificationsSetup](./kibana-plugin-public.notificationssetup.md) | | +| [NotificationsStart](./kibana-plugin-public.notificationsstart.md) | | +| [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) | | +| [OverlayRef](./kibana-plugin-public.overlayref.md) | Returned by [OverlayStart](./kibana-plugin-public.overlaystart.md) methods for closing a mounted overlay. | +| [OverlayStart](./kibana-plugin-public.overlaystart.md) | | +| [PackageInfo](./kibana-plugin-public.packageinfo.md) | | +| [Plugin](./kibana-plugin-public.plugin.md) | The interface that should be returned by a PluginInitializer. | +| [PluginInitializerContext](./kibana-plugin-public.plugininitializercontext.md) | The available core services passed to a PluginInitializer | +| [SavedObject](./kibana-plugin-public.savedobject.md) | | +| [SavedObjectAttributes](./kibana-plugin-public.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | +| [SavedObjectReference](./kibana-plugin-public.savedobjectreference.md) | A reference to another saved object. | +| [SavedObjectsBaseOptions](./kibana-plugin-public.savedobjectsbaseoptions.md) | | +| [SavedObjectsBatchResponse](./kibana-plugin-public.savedobjectsbatchresponse.md) | | +| [SavedObjectsBulkCreateObject](./kibana-plugin-public.savedobjectsbulkcreateobject.md) | | +| [SavedObjectsBulkCreateOptions](./kibana-plugin-public.savedobjectsbulkcreateoptions.md) | | +| [SavedObjectsBulkUpdateObject](./kibana-plugin-public.savedobjectsbulkupdateobject.md) | | +| [SavedObjectsBulkUpdateOptions](./kibana-plugin-public.savedobjectsbulkupdateoptions.md) | | +| [SavedObjectsCreateOptions](./kibana-plugin-public.savedobjectscreateoptions.md) | | +| [SavedObjectsFindOptions](./kibana-plugin-public.savedobjectsfindoptions.md) | | +| [SavedObjectsFindResponsePublic](./kibana-plugin-public.savedobjectsfindresponsepublic.md) | Return type of the Saved Objects find() method.\*Note\*: this type is different between the Public and Server Saved Objects clients. | +| [SavedObjectsImportConflictError](./kibana-plugin-public.savedobjectsimportconflicterror.md) | Represents a failure to import due to a conflict. | +| [SavedObjectsImportError](./kibana-plugin-public.savedobjectsimporterror.md) | Represents a failure to import. | +| [SavedObjectsImportMissingReferencesError](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.md) | Represents a failure to import due to missing references. | +| [SavedObjectsImportResponse](./kibana-plugin-public.savedobjectsimportresponse.md) | The response describing the result of an import. | +| [SavedObjectsImportRetry](./kibana-plugin-public.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. | +| [SavedObjectsImportUnknownError](./kibana-plugin-public.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. | +| [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-public.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. | +| [SavedObjectsMigrationVersion](./kibana-plugin-public.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | +| [SavedObjectsStart](./kibana-plugin-public.savedobjectsstart.md) | | +| [SavedObjectsUpdateOptions](./kibana-plugin-public.savedobjectsupdateoptions.md) | | +| [UiSettingsState](./kibana-plugin-public.uisettingsstate.md) | | + +## Type Aliases + +| Type Alias | Description | +| --- | --- | +| [AppLeaveAction](./kibana-plugin-public.appleaveaction.md) | Possible actions to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md)See [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) and [AppLeaveDefaultAction](./kibana-plugin-public.appleavedefaultaction.md) | +| [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) | A handler that will be executed before leaving the application, either when going to another application or when closing the browser tab or manually changing the url. Should return confirm to to prompt a message to the user before leaving the page, or default to keep the default behavior (doing nothing).See [AppMountParameters](./kibana-plugin-public.appmountparameters.md) for detailed usage examples. | +| [AppMount](./kibana-plugin-public.appmount.md) | A mount function called when the user navigates to this app's route. | +| [AppMountDeprecated](./kibana-plugin-public.appmountdeprecated.md) | A mount function called when the user navigates to this app's route. | +| [AppUnmount](./kibana-plugin-public.appunmount.md) | A function called when an application should be unmounted from the page. This function should be synchronous. | +| [AppUpdatableFields](./kibana-plugin-public.appupdatablefields.md) | Defines the list of fields that can be updated via an [AppUpdater](./kibana-plugin-public.appupdater.md). | +| [AppUpdater](./kibana-plugin-public.appupdater.md) | Updater for applications. see [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) | +| [ChromeBreadcrumb](./kibana-plugin-public.chromebreadcrumb.md) | | +| [ChromeHelpExtensionMenuCustomLink](./kibana-plugin-public.chromehelpextensionmenucustomlink.md) | | +| [ChromeHelpExtensionMenuDiscussLink](./kibana-plugin-public.chromehelpextensionmenudiscusslink.md) | | +| [ChromeHelpExtensionMenuDocumentationLink](./kibana-plugin-public.chromehelpextensionmenudocumentationlink.md) | | +| [ChromeHelpExtensionMenuGitHubLink](./kibana-plugin-public.chromehelpextensionmenugithublink.md) | | +| [ChromeHelpExtensionMenuLink](./kibana-plugin-public.chromehelpextensionmenulink.md) | | +| [ChromeNavLinkUpdateableFields](./kibana-plugin-public.chromenavlinkupdateablefields.md) | | +| [HandlerContextType](./kibana-plugin-public.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md) to represent the type of the context. | +| [HandlerFunction](./kibana-plugin-public.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | +| [HandlerParameters](./kibana-plugin-public.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-public.handlercontexttype.md). | +| [HttpStart](./kibana-plugin-public.httpstart.md) | See [HttpSetup](./kibana-plugin-public.httpsetup.md) | +| [IContextProvider](./kibana-plugin-public.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | +| [IToasts](./kibana-plugin-public.itoasts.md) | Methods for adding and removing global toast messages. See [ToastsApi](./kibana-plugin-public.toastsapi.md). | +| [MountPoint](./kibana-plugin-public.mountpoint.md) | A function that should mount DOM content inside the provided container element and return a handler to unmount it. | +| [PluginInitializer](./kibana-plugin-public.plugininitializer.md) | The plugin export at the root of a plugin's public directory should conform to this interface. | +| [PluginOpaqueId](./kibana-plugin-public.pluginopaqueid.md) | | +| [RecursiveReadonly](./kibana-plugin-public.recursivereadonly.md) | | +| [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | +| [SavedObjectAttributeSingle](./kibana-plugin-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) | +| [SavedObjectsClientContract](./kibana-plugin-public.savedobjectsclientcontract.md) | SavedObjectsClientContract as implemented by the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) | +| [Toast](./kibana-plugin-public.toast.md) | | +| [ToastInput](./kibana-plugin-public.toastinput.md) | Inputs for [IToasts](./kibana-plugin-public.itoasts.md) APIs. | +| [ToastInputFields](./kibana-plugin-public.toastinputfields.md) | Allowed fields for [ToastInput](./kibana-plugin-public.toastinput.md). | +| [ToastsSetup](./kibana-plugin-public.toastssetup.md) | [IToasts](./kibana-plugin-public.itoasts.md) | +| [ToastsStart](./kibana-plugin-public.toastsstart.md) | [IToasts](./kibana-plugin-public.itoasts.md) | +| [UnmountCallback](./kibana-plugin-public.unmountcallback.md) | A function that will unmount the element previously mounted by the associated [MountPoint](./kibana-plugin-public.mountpoint.md) | + diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.deprecation.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.deprecation.md new file mode 100644 index 000000000000..7ad26b85bf81 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.deprecation.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [deprecation](./kibana-plugin-server.uisettingsparams.deprecation.md) + +## UiSettingsParams.deprecation property + +optional deprecation information. Used to generate a deprecation warning. + +Signature: + +```typescript +deprecation?: DeprecationSettings; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.md index 89eb5b10b9de..fc2f8038f973 100644 --- a/docs/development/core/server/kibana-plugin-server.uisettingsparams.md +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.md @@ -17,6 +17,7 @@ export interface UiSettingsParams | Property | Type | Description | | --- | --- | --- | | [category](./kibana-plugin-server.uisettingsparams.category.md) | string[] | used to group the configured setting in the UI | +| [deprecation](./kibana-plugin-server.uisettingsparams.deprecation.md) | DeprecationSettings | optional deprecation information. Used to generate a deprecation warning. | | [description](./kibana-plugin-server.uisettingsparams.description.md) | string | description provided to a user in UI | | [name](./kibana-plugin-server.uisettingsparams.name.md) | string | title in the UI | | [optionLabels](./kibana-plugin-server.uisettingsparams.optionlabels.md) | Record<string, string> | text labels for 'select' type UI element | diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 757c6f10f2a9..695a4d4f45b0 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -217,6 +217,8 @@ might increase the search time. This setting is off by default. Users must opt-i [horizontal] `siem:defaultAnomalyScore`:: The threshold above which Machine Learning job anomalies are displayed in the SIEM app. `siem:defaultIndex`:: A comma-delimited list of Elasticsearch indices from which the SIEM app collects events. +`siem:enableNewsFeed`:: Enables the News feed +`siem:newsFeedUrl`:: News feed content will be retrieved from this URL `siem:refreshIntervalDefaults`:: The default refresh interval for the SIEM time filter, in milliseconds. `siem:timeDefaults`:: The default period of time in the SIEM time filter. diff --git a/docs/settings/monitoring-settings.asciidoc b/docs/settings/monitoring-settings.asciidoc index 38a46a3cde5a..8f445ff25218 100644 --- a/docs/settings/monitoring-settings.asciidoc +++ b/docs/settings/monitoring-settings.asciidoc @@ -7,10 +7,10 @@ By default, the Monitoring application is enabled, but data collection is disabled. When you first start {kib} monitoring, you are prompted to -enable data collection. If you are using {security}, you must be +enable data collection. If you are using {security}, you must be signed in as a user with the `cluster:manage` privilege to enable data collection. The built-in `superuser` role has this privilege and the -built-in `elastic` user has this role. +built-in `elastic` user has this role. You can adjust how monitoring data is collected from {kib} and displayed in {kib} by configuring settings in the @@ -134,3 +134,11 @@ For {es} clusters that are running in containers, this setting changes the statistics. It also adds the calculated Cgroup CPU utilization to the *Node Overview* page instead of the overall operating system's CPU utilization. Defaults to `false`. + +`xpack.monitoring.ui.container.logstash.enabled`:: + +For {ls} nodes that are running in containers, this setting +changes the {ls} *Node Listing* to display the CPU utilization +based on the reported Cgroup statistics. It also adds the +calculated Cgroup CPU utilization to the {ls} node detail +pages instead of the overall operating system’s CPU utilization. Defaults to `false`. diff --git a/docs/user/security/reporting.asciidoc b/docs/user/security/reporting.asciidoc index c2ed295e83ce..5f5d85fe8d3b 100644 --- a/docs/user/security/reporting.asciidoc +++ b/docs/user/security/reporting.asciidoc @@ -125,23 +125,33 @@ the {reporting} endpoints to authorized users. This requires that you: . Enable {security} on your {es} cluster. For more information, see {ref}/security-getting-started.html[Getting Started with Security]. -. Configure an SSL certificate for Kibana. For more information, see -<>. -. Configure {watcher} to trust the Kibana server's certificate by adding it to -the {watcher} truststore on each node: -.. Import the {kib} server certificate into the {watcher} truststore using -Java Keytool: +. Configure TLS/SSL encryption for the {kib} server. For more information, see +<>. +. Specify the {kib} server's CA certificate chain in `elasticsearch.yml`: + -[source,shell] ---------------------------------------------------------- -keytool -importcert -keystore watcher-truststore.jks -file server.crt ---------------------------------------------------------- -+ -NOTE: If the truststore doesn't already exist, it is created. +-- +If you are using your own CA to sign the {kib} server certificate, then you need +to specify the CA certificate chain in {es} to properly establish trust in TLS +connections between {watcher} and {kib}. If your CA certificate chain is +contained in a PKCS #12 trust store, specify it like so: + +[source,yaml] +-------------------------------------------------------------------------------- +xpack.http.ssl.truststore.path: "/path/to/your/truststore.p12" +xpack.http.ssl.truststore.type: "PKCS12" +xpack.http.ssl.truststore.password: "optional decryption password" +-------------------------------------------------------------------------------- + +Otherwise, if your CA certificate chain is in PEM format, specify it like so: + +[source,yaml] +-------------------------------------------------------------------------------- +xpack.http.ssl.certificate_authorities: ["/path/to/your/cacert1.pem", "/path/to/your/cacert2.pem"] +-------------------------------------------------------------------------------- + +For more information, see {ref}/notification-settings.html#ssl-notification-settings[the {watcher} HTTP TLS/SSL Settings]. +-- -.. Make sure the `xpack.http.ssl.truststore.path` setting in -`elasticsearch.yml` specifies the location of the {watcher} -truststore. . Add one or more users who have the permissions necessary to use {kib} and {reporting}. For more information, see <>. diff --git a/package.json b/package.json index 6b9640d214a5..365f597fe04f 100644 --- a/package.json +++ b/package.json @@ -117,8 +117,8 @@ "@elastic/apm-rum": "^4.6.0", "@elastic/charts": "^16.1.0", "@elastic/datemath": "5.0.2", - "@elastic/ems-client": "1.0.5", - "@elastic/eui": "17.3.1", + "@elastic/ems-client": "7.6.0", + "@elastic/eui": "18.2.0", "@elastic/filesaver": "1.1.2", "@elastic/good": "8.1.1-kibana2", "@elastic/numeral": "2.3.3", @@ -163,7 +163,6 @@ "compare-versions": "3.5.1", "core-js": "^3.2.1", "css-loader": "2.1.1", - "custom-event-polyfill": "^0.3.0", "d3": "3.5.17", "d3-cloud": "1.2.5", "deep-freeze-strict": "^1.1.1", @@ -254,6 +253,7 @@ "rison-node": "1.0.2", "rxjs": "^6.5.3", "script-loader": "0.7.2", + "seedrandom": "^3.0.5", "semver": "^5.5.0", "style-it": "^2.1.3", "style-loader": "0.23.1", @@ -365,7 +365,7 @@ "@types/semver": "^5.5.0", "@types/sinon": "^7.0.13", "@types/strip-ansi": "^3.0.0", - "@types/styled-components": "^4.4.1", + "@types/styled-components": "^4.4.2", "@types/supertest": "^2.0.5", "@types/supertest-as-promised": "^2.0.38", "@types/testing-library__react": "^9.1.2", diff --git a/packages/kbn-spec-to-console/lib/convert.js b/packages/kbn-spec-to-console/lib/convert.js index 4c3128186076..5dbdd6e1c94e 100644 --- a/packages/kbn-spec-to-console/lib/convert.js +++ b/packages/kbn-spec-to-console/lib/convert.js @@ -24,10 +24,16 @@ const convertParts = require('./convert/parts'); module.exports = spec => { const result = {}; - // TODO: - // Since https://github.com/elastic/elasticsearch/pull/42346 has been merged into ES master - // the JSON doc specification has been updated. We need to update this script to take advantage - // of the added information but it will also require updating console's editor autocomplete. + /** + * TODO: + * Since https://github.com/elastic/elasticsearch/pull/42346 has been merged into ES master + * the JSON doc specification has been updated. We need to update this script to take advantage + * of the added information but it will also require updating console editor autocomplete. + * + * Note: for now we exclude all deprecated patterns from the generated spec to prevent them + * from being used in autocompletion. It would be really nice if we could use this information + * instead of just not including it. + */ Object.keys(spec).forEach(api => { const source = spec[api]; if (!source.url) { @@ -46,8 +52,10 @@ module.exports = spec => { const urlComponents = {}; if (source.url.paths) { - patterns = convertPaths(source.url.paths); - source.url.paths.forEach(pathsObject => { + // We filter out all deprecated url patterns here. + const paths = source.url.paths.filter(path => !path.deprecated); + patterns = convertPaths(paths); + paths.forEach(pathsObject => { pathsObject.methods.forEach(method => methodSet.add(method)); if (pathsObject.parts) { for (const partName of Object.keys(pathsObject.parts)) { diff --git a/packages/kbn-ui-shared-deps/entry.js b/packages/kbn-ui-shared-deps/entry.js index 250abd162f91..7a15c3bb742c 100644 --- a/packages/kbn-ui-shared-deps/entry.js +++ b/packages/kbn-ui-shared-deps/entry.js @@ -17,6 +17,9 @@ * under the License. */ +// import global polyfills before everything else +require('./polyfills'); + // must load before angular export const Jquery = require('jquery'); window.$ = window.jQuery = Jquery; diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json index 014467d204d9..af44991e625a 100644 --- a/packages/kbn-ui-shared-deps/package.json +++ b/packages/kbn-ui-shared-deps/package.json @@ -9,12 +9,15 @@ "kbn:watch": "node scripts/build --watch" }, "devDependencies": { - "@elastic/eui": "17.3.1", + "@elastic/eui": "18.2.0", "@elastic/charts": "^16.1.0", "@kbn/dev-utils": "1.0.0", "@yarnpkg/lockfile": "^1.1.0", + "abortcontroller-polyfill": "^1.3.0", "angular": "^1.7.9", + "core-js": "^3.2.1", "css-loader": "^2.1.1", + "custom-event-polyfill": "^0.3.0", "del": "^5.1.0", "jquery": "^3.4.1", "mini-css-extract-plugin": "0.8.0", @@ -24,6 +27,9 @@ "react-intl": "^2.8.0", "react": "^16.12.0", "read-pkg": "^5.2.0", - "webpack": "4.41.0" + "regenerator-runtime": "^0.13.3", + "symbol-observable": "^1.2.0", + "webpack": "4.41.0", + "whatwg-fetch": "^3.0.0" } } \ No newline at end of file diff --git a/packages/kbn-ui-shared-deps/polyfills.js b/packages/kbn-ui-shared-deps/polyfills.js new file mode 100644 index 000000000000..d2305d643e4d --- /dev/null +++ b/packages/kbn-ui-shared-deps/polyfills.js @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +require('core-js/stable'); +require('regenerator-runtime/runtime'); +require('custom-event-polyfill'); +require('whatwg-fetch'); +require('abortcontroller-polyfill/dist/polyfill-patch-fetch'); +require('./vendor/childnode_remove_polyfill'); +require('symbol-observable'); diff --git a/webpackShims/childnode-remove-polyfill.js b/packages/kbn-ui-shared-deps/vendor/childnode_remove_polyfill.js similarity index 68% rename from webpackShims/childnode-remove-polyfill.js rename to packages/kbn-ui-shared-deps/vendor/childnode_remove_polyfill.js index 26c21d1674b0..d8818fe809cc 100644 --- a/webpackShims/childnode-remove-polyfill.js +++ b/packages/kbn-ui-shared-deps/vendor/childnode_remove_polyfill.js @@ -1,21 +1,4 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ +/* eslint-disable @kbn/eslint/require-license-header */ /* @notice * This product bundles childnode-remove which is available under a diff --git a/src/core/CONVENTIONS.md b/src/core/CONVENTIONS.md index 18f82766bdbc..61c5d5b076a4 100644 --- a/src/core/CONVENTIONS.md +++ b/src/core/CONVENTIONS.md @@ -1,6 +1,12 @@ # Kibana Conventions -- [Plugin Structure](#plugin-structure) +- [Kibana Conventions](#kibana-conventions) + - [Plugin Structure](#plugin-structure) + - [The PluginInitializer](#the-plugininitializer) + - [The Plugin class](#the-plugin-class) + - [Applications](#applications) + - [Services](#services) + - [Usage Collection](#usage-collection) ## Plugin Structure diff --git a/src/core/CORE_CONVENTIONS.md b/src/core/CORE_CONVENTIONS.md new file mode 100644 index 000000000000..76f3be159525 --- /dev/null +++ b/src/core/CORE_CONVENTIONS.md @@ -0,0 +1,140 @@ +- [Core Conventions](#core-conventions) + - [1. Exposing API Types](#1-exposing-api-types) + - [2. API Structure and nesting](#2-api-structure-and-nesting) + - [3. Tests and mocks](#3-tests-and-mocks) + +# Core Conventions + +This document contains conventions for development inside `src/core`. Although +many of these might be more widely applicable, adoption within the rest of +Kibana is not the primary objective. + +## 1. Exposing API Types +The following section applies to the types that describe the entire surface +area of Core API's and does not apply to internal types. + + - 1.1 All API types must be exported from the top-level `server` or `public` + directories. + + ```ts + // -- good -- + import { IRouter } from 'src/core/server'; + + // -- bad -- + import { IRouter } from 'src/core/server/http/router.ts'; + ``` + + > Why? This is required for generating documentation from our inline + > typescript doc comments, makes it easier for API consumers to find the + > relevant types and creates a clear distinction between external and + > internal types. + + - 1.2 Classes must not be exposed directly. Instead, use a separate type, + prefixed with an 'I', to describe the public contract of the class. + + ```ts + // -- good (alternative 1) -- + /** + * @public + * {@link UiSettingsClient} + */ + export type IUiSettingsClient = PublicContractOf; + + /** internal only */ + export class UiSettingsClient { + constructor(private setting: string) {} + /** Retrieve all settings */ + public getSettings(): { return this.settings; } + }; + + // -- good (alternative 2) -- + export interface IUiSettingsClient { + /** Retrieve all settings */ + public getSettings(): string; + } + + export class UiSettingsClient implements IUiSettingsClient { + public getSettings(): string; + } + + // -- bad -- + /** external */ + export class UiSettingsClient { + constructor(private setting: string) {} + public getSettings(): { return this.settings; } + } + ``` + + > Why? Classes' private members form part of their type signature making it + > impossible to mock a dependency typed as a `class`. + > + > Until we can use ES private field support in Typescript 3.8 + > https://github.com/elastic/kibana/issues/54906 we have two alternatives + > each with their own pro's and cons: + > + > #### Using a derived class (alternative 1) + > + > Pro's: + > - TSDoc comments are located with the source code + > - The class acts as a single source of type information + > + > Con's: + > - "Go to definition" first takes you to where the type gets derived + > requiring a second "Go to definition" to navigate to the type source. + > + > #### Using a separate interface (alternative 2) + > Pro's: + > - Creates an explicit external API contract + > - "Go to definition" will take you directly to the type definition. + > + > Con's: + > - TSDoc comments are located with the interface not next to the + > implementation source code. + > - Creates duplicate type information between the interface and + > implementation class. + +## 2. API Structure and nesting + - 2.1 Nest API methods into their own namespace only if we expect we will be + adding additional methods to that namespace. + + ```ts + // good + core.overlays.openFlyout(...); + core.overlays.openModal(...); + core.overlays.banners.add(...); + core.overlays.banners.remove(...); + core.overlays.banners.replace(...); + + // bad + core.overlays.flyouts.open(...); + core.overlays.modals.open(...); + ``` + + > Why? Nested namespaces should facilitate discovery and navigation for + > consumers of the API. Having namespaces with a single method, effectively + > hides the method under an additional layer without improving the + > organization. However, introducing namespaces early on can avoid API + > churn when we know related API methods will be introduced. + +## 3. Tests and mocks + - 3.1 Declare Jest mocks with a temporary variable to ensure types are + correctly inferred. + + ```ts + // -- good -- + const createMock => { + const mocked: jest.Mocked = { + start: jest.fn(), + }; + mocked.start.mockReturnValue(createStartContractMock()); + return mocked; + }; + // -- bad -- + const createMock = (): jest.Mocked => ({ + start: jest.fn().mockReturnValue(createSetupContractMock()), + }); + ``` + + > Why? Without the temporary variable, Jest types the `start` function as + > `jest` and, as a result, doesn't typecheck the mock return + > value. diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index a674b49a8e13..09ea1afe3576 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -127,7 +127,7 @@ export class ChromeService { ) ) ); - this.isVisible$ = combineLatest(this.appHidden$, this.toggleHidden$).pipe( + this.isVisible$ = combineLatest([this.appHidden$, this.toggleHidden$]).pipe( map(([appHidden, toggleHidden]) => !(appHidden || toggleHidden)), takeUntil(this.stop$) ); diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index 0447add49178..d05a6bb53405 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -57,7 +57,7 @@ import { } from '../..'; import { HttpStart } from '../../../http'; import { ChromeHelpExtension } from '../../chrome_service'; -import { ApplicationStart, InternalApplicationStart } from '../../../application/types'; +import { InternalApplicationStart } from '../../../application/types'; // Providing a buffer between the limit and the cut off index // protects from truncating just the last couple (6) characters @@ -108,7 +108,7 @@ function extendRecentlyAccessedHistoryItem( }; } -function extendNavLink(navLink: ChromeNavLink, urlForApp: ApplicationStart['getUrlForApp']) { +function extendNavLink(navLink: ChromeNavLink) { if (navLink.legacy) { return { ...navLink, @@ -118,7 +118,7 @@ function extendNavLink(navLink: ChromeNavLink, urlForApp: ApplicationStart['getU return { ...navLink, - href: urlForApp(navLink.id), + href: navLink.baseUrl, }; } @@ -229,9 +229,7 @@ class HeaderUI extends Component { appTitle, isVisible, forceNavigation, - navLinks: navLinks.map(navLink => - extendNavLink(navLink, this.props.application.getUrlForApp) - ), + navLinks: navLinks.map(extendNavLink), recentlyAccessed: recentlyAccessed.map(ra => extendRecentlyAccessedHistoryItem(navLinks, ra, this.props.basePath) ), diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 44dc76bfe6e3..1046f7a17dc5 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -106,7 +106,10 @@ export class DocLinksService { introduction: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index-patterns.html`, }, kibana: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/index.html`, - siem: `${ELASTIC_WEBSITE_URL}guide/en/siem/guide/${DOC_LINK_VERSION}/index.html`, + siem: { + guide: `${ELASTIC_WEBSITE_URL}guide/en/siem/guide/${DOC_LINK_VERSION}/index.html`, + gettingStarted: `${ELASTIC_WEBSITE_URL}guide/en/siem/guide/${DOC_LINK_VERSION}/install-siem.html`, + }, query: { luceneQuerySyntax: `${ELASTICSEARCH_DOCS}query-dsl-query-string-query.html#query-string-syntax`, queryDsl: `${ELASTICSEARCH_DOCS}query-dsl.html`, @@ -115,6 +118,9 @@ export class DocLinksService { date: { dateMath: `${ELASTICSEARCH_DOCS}common-options.html#date-math`, }, + management: { + kibanaSearchSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-search-settings`, + }, }, }); } @@ -196,7 +202,10 @@ export interface DocLinksStart { readonly introduction: string; }; readonly kibana: string; - readonly siem: string; + readonly siem: { + readonly guide: string; + readonly gettingStarted: string; + }; readonly query: { readonly luceneQuerySyntax: string; readonly queryDsl: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index aef689162f45..610b08708c68 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -486,7 +486,10 @@ export interface DocLinksStart { readonly introduction: string; }; readonly kibana: string; - readonly siem: string; + readonly siem: { + readonly guide: string; + readonly gettingStarted: string; + }; readonly query: { readonly luceneQuerySyntax: string; readonly queryDsl: string; diff --git a/src/core/public/rendering/app_containers.test.tsx b/src/core/public/rendering/app_containers.test.tsx new file mode 100644 index 000000000000..746e37b1214d --- /dev/null +++ b/src/core/public/rendering/app_containers.test.tsx @@ -0,0 +1,105 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { BehaviorSubject } from 'rxjs'; +import { act } from 'react-dom/test-utils'; +import { mount } from 'enzyme'; +import React from 'react'; + +import { AppWrapper, AppContainer } from './app_containers'; + +describe('AppWrapper', () => { + it('toggles the `hidden-chrome` class depending on the chrome visibility state', () => { + const chromeVisible$ = new BehaviorSubject(true); + + const component = mount(app-content); + expect(component.getDOMNode()).toMatchInlineSnapshot(` +
+ app-content +
+ `); + + act(() => chromeVisible$.next(false)); + component.update(); + expect(component.getDOMNode()).toMatchInlineSnapshot(` +
+ app-content +
+ `); + + act(() => chromeVisible$.next(true)); + component.update(); + expect(component.getDOMNode()).toMatchInlineSnapshot(` +
+ app-content +
+ `); + }); +}); + +describe('AppContainer', () => { + it('adds classes supplied by chrome', () => { + const appClasses$ = new BehaviorSubject([]); + + const component = mount(app-content); + expect(component.getDOMNode()).toMatchInlineSnapshot(` +
+ app-content +
+ `); + + act(() => appClasses$.next(['classA', 'classB'])); + component.update(); + expect(component.getDOMNode()).toMatchInlineSnapshot(` +
+ app-content +
+ `); + + act(() => appClasses$.next(['classC'])); + component.update(); + expect(component.getDOMNode()).toMatchInlineSnapshot(` +
+ app-content +
+ `); + + act(() => appClasses$.next([])); + component.update(); + expect(component.getDOMNode()).toMatchInlineSnapshot(` +
+ app-content +
+ `); + }); +}); diff --git a/src/core/public/rendering/app_containers.tsx b/src/core/public/rendering/app_containers.tsx new file mode 100644 index 000000000000..72faaeac588b --- /dev/null +++ b/src/core/public/rendering/app_containers.tsx @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { Observable } from 'rxjs'; +import useObservable from 'react-use/lib/useObservable'; +import classNames from 'classnames'; + +export const AppWrapper: React.FunctionComponent<{ + chromeVisible$: Observable; +}> = ({ chromeVisible$, children }) => { + const visible = useObservable(chromeVisible$); + return
{children}
; +}; + +export const AppContainer: React.FunctionComponent<{ + classes$: Observable; +}> = ({ classes$, children }) => { + const classes = useObservable(classes$); + return
{children}
; +}; diff --git a/src/core/public/rendering/rendering_service.test.tsx b/src/core/public/rendering/rendering_service.test.tsx index ed835574a32f..437a602a3d44 100644 --- a/src/core/public/rendering/rendering_service.test.tsx +++ b/src/core/public/rendering/rendering_service.test.tsx @@ -18,72 +18,129 @@ */ import React from 'react'; +import { act } from 'react-dom/test-utils'; -import { chromeServiceMock } from '../chrome/chrome_service.mock'; import { RenderingService } from './rendering_service'; -import { InternalApplicationStart } from '../application'; +import { applicationServiceMock } from '../application/application_service.mock'; +import { chromeServiceMock } from '../chrome/chrome_service.mock'; import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock'; import { overlayServiceMock } from '../overlays/overlay_service.mock'; +import { BehaviorSubject } from 'rxjs'; describe('RenderingService#start', () => { - const getService = ({ legacyMode = false }: { legacyMode?: boolean } = {}) => { - const rendering = new RenderingService(); - const application = { - getComponent: () =>
Hello application!
, - } as InternalApplicationStart; - const chrome = chromeServiceMock.createStartContract(); + let application: ReturnType; + let chrome: ReturnType; + let overlays: ReturnType; + let injectedMetadata: ReturnType; + let targetDomElement: HTMLDivElement; + let rendering: RenderingService; + + beforeEach(() => { + application = applicationServiceMock.createInternalStartContract(); + application.getComponent.mockReturnValue(
Hello application!
); + + chrome = chromeServiceMock.createStartContract(); chrome.getHeaderComponent.mockReturnValue(
Hello chrome!
); - const overlays = overlayServiceMock.createStartContract(); + + overlays = overlayServiceMock.createStartContract(); overlays.banners.getComponent.mockReturnValue(
I'm a banner!
); - const injectedMetadata = injectedMetadataServiceMock.createStartContract(); - injectedMetadata.getLegacyMode.mockReturnValue(legacyMode); - const targetDomElement = document.createElement('div'); - const start = rendering.start({ + injectedMetadata = injectedMetadataServiceMock.createStartContract(); + + targetDomElement = document.createElement('div'); + + rendering = new RenderingService(); + }); + + const startService = () => { + return rendering.start({ application, chrome, injectedMetadata, overlays, targetDomElement, }); - return { start, targetDomElement }; }; - it('renders application service into provided DOM element', () => { - const { targetDomElement } = getService(); - expect(targetDomElement.querySelector('div.application')).toMatchInlineSnapshot(` -
-
- Hello application! -
-
- `); - }); + describe('standard mode', () => { + beforeEach(() => { + injectedMetadata.getLegacyMode.mockReturnValue(false); + }); - it('contains wrapper divs', () => { - const { targetDomElement } = getService(); - expect(targetDomElement.querySelector('div.app-wrapper')).toBeDefined(); - expect(targetDomElement.querySelector('div.app-wrapper-pannel')).toBeDefined(); - }); + it('renders application service into provided DOM element', () => { + startService(); + expect(targetDomElement.querySelector('div.application')).toMatchInlineSnapshot(` +
+
+ Hello application! +
+
+ `); + }); + + it('adds the `chrome-hidden` class to the AppWrapper when chrome is hidden', () => { + const isVisible$ = new BehaviorSubject(true); + chrome.getIsVisible$.mockReturnValue(isVisible$); + startService(); + + const appWrapper = targetDomElement.querySelector('div.app-wrapper')!; + expect(appWrapper.className).toEqual('app-wrapper'); + + act(() => isVisible$.next(false)); + expect(appWrapper.className).toEqual('app-wrapper hidden-chrome'); - it('renders the banner UI', () => { - const { targetDomElement } = getService(); - expect(targetDomElement.querySelector('#globalBannerList')).toMatchInlineSnapshot(` -
-
- I'm a banner! -
-
- `); + act(() => isVisible$.next(true)); + expect(appWrapper.className).toEqual('app-wrapper'); + }); + + it('adds the application classes to the AppContainer', () => { + const applicationClasses$ = new BehaviorSubject([]); + chrome.getApplicationClasses$.mockReturnValue(applicationClasses$); + startService(); + + const appContainer = targetDomElement.querySelector('div.application')!; + expect(appContainer.className).toEqual('application'); + + act(() => applicationClasses$.next(['classA', 'classB'])); + expect(appContainer.className).toEqual('application classA classB'); + + act(() => applicationClasses$.next(['classC'])); + expect(appContainer.className).toEqual('application classC'); + + act(() => applicationClasses$.next([])); + expect(appContainer.className).toEqual('application'); + }); + + it('contains wrapper divs', () => { + startService(); + expect(targetDomElement.querySelector('div.app-wrapper')).toBeDefined(); + expect(targetDomElement.querySelector('div.app-wrapper-pannel')).toBeDefined(); + }); + + it('renders the banner UI', () => { + startService(); + expect(targetDomElement.querySelector('#globalBannerList')).toMatchInlineSnapshot(` +
+
+ I'm a banner! +
+
+ `); + }); }); - describe('legacyMode', () => { + describe('legacy mode', () => { + beforeEach(() => { + injectedMetadata.getLegacyMode.mockReturnValue(true); + }); + it('renders into provided DOM element', () => { - const { targetDomElement } = getService({ legacyMode: true }); + startService(); + expect(targetDomElement).toMatchInlineSnapshot(`
{ }); it('returns a div for the legacy service to render into', () => { - const { - start: { legacyTargetDomElement }, - targetDomElement, - } = getService({ legacyMode: true }); + const { legacyTargetDomElement } = startService(); + expect(targetDomElement.contains(legacyTargetDomElement!)).toBe(true); }); }); diff --git a/src/core/public/rendering/rendering_service.tsx b/src/core/public/rendering/rendering_service.tsx index 7a747faa2673..58b8c1921e33 100644 --- a/src/core/public/rendering/rendering_service.tsx +++ b/src/core/public/rendering/rendering_service.tsx @@ -25,6 +25,7 @@ import { InternalChromeStart } from '../chrome'; import { InternalApplicationStart } from '../application'; import { InjectedMetadataStart } from '../injected_metadata'; import { OverlayStart } from '../overlays'; +import { AppWrapper, AppContainer } from './app_containers'; interface StartDeps { application: InternalApplicationStart; @@ -65,12 +66,12 @@ export class RenderingService { {chromeUi} {!legacyMode && ( -
+
{bannerUi}
-
{appUi}
+ {appUi}
-
+ )} {legacyMode &&
} diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index 36fe95e05cb5..c63c9384da9d 100644 --- a/src/core/server/config/deprecation/core_deprecations.ts +++ b/src/core/server/config/deprecation/core_deprecations.ts @@ -91,12 +91,25 @@ const cspRulesDeprecation: ConfigDeprecation = (settings, fromPath, log) => { return settings; }; +const mapManifestServiceUrlDeprecation: ConfigDeprecation = (settings, fromPath, log) => { + if (has(settings, 'map.manifestServiceUrl')) { + log( + 'You should no longer use the map.manifestServiceUrl setting in kibana.yml to configure the location ' + + 'of the Elastic Maps Service settings. These settings have moved to the "map.emsTileApiUrl" and ' + + '"map.emsFileApiUrl" settings instead. These settings are for development use only and should not be ' + + 'modified for use in production environments.' + ); + } + return settings; +}; + export const coreDeprecationProvider: ConfigDeprecationProvider = ({ unusedFromRoot, renameFromRoot, }) => [ unusedFromRoot('savedObjects.indexCheckTimeout'), unusedFromRoot('server.xsrf.token'), + unusedFromRoot('maps.manifestServiceUrl'), renameFromRoot('optimize.lazy', 'optimize.watch'), renameFromRoot('optimize.lazyPort', 'optimize.watchPort'), renameFromRoot('optimize.lazyHost', 'optimize.watchHost'), @@ -110,4 +123,5 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({ dataPathDeprecation, rewriteBasePathDeprecation, cspRulesDeprecation, + mapManifestServiceUrlDeprecation, ]; diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index 608392e4943f..af4db68ee95e 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -424,7 +424,7 @@ describe('#discoverPlugins()', () => { await legacyService.discoverPlugins(); expect(findLegacyPluginSpecs).toHaveBeenCalledTimes(1); - expect(findLegacyPluginSpecs).toHaveBeenCalledWith(expect.any(Object), logger); + expect(findLegacyPluginSpecs).toHaveBeenCalledWith(expect.any(Object), logger, env.packageInfo); }); it(`register legacy plugin's deprecation providers`, async () => { diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index cc36b90ec526..7a03cefc38c1 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -125,7 +125,11 @@ export class LegacyService implements CoreService { disabledPluginSpecs, uiExports, navLinks, - } = await findLegacyPluginSpecs(this.settings, this.coreContext.logger); + } = await findLegacyPluginSpecs( + this.settings, + this.coreContext.logger, + this.coreContext.env.packageInfo + ); this.legacyPlugins = { pluginSpecs, diff --git a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts index d2e7a39236d0..9867274d224b 100644 --- a/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts +++ b/src/core/server/legacy/plugins/find_legacy_plugin_specs.ts @@ -29,6 +29,8 @@ import { import { collectUiExports as collectLegacyUiExports } from '../../../../legacy/ui/ui_exports/collect_ui_exports'; import { LoggerFactory } from '../../logging'; +import { PackageInfo } from '../../config'; + import { LegacyUiExports, LegacyNavLink, @@ -92,7 +94,11 @@ function getNavLinks(uiExports: LegacyUiExports, pluginSpecs: LegacyPluginSpec[] .sort((a, b) => a.order - b.order); } -export async function findLegacyPluginSpecs(settings: unknown, loggerFactory: LoggerFactory) { +export async function findLegacyPluginSpecs( + settings: unknown, + loggerFactory: LoggerFactory, + packageInfo: PackageInfo +) { const configToMutate: LegacyConfig = defaultConfig(settings); const { pack$, @@ -152,8 +158,7 @@ export async function findLegacyPluginSpecs(settings: unknown, loggerFactory: Lo map(spec => { const name = spec.getId(); const pluginVersion = spec.getExpectedKibanaVersion(); - // @ts-ignore - const kibanaVersion = settings.pkg.version; + const kibanaVersion = packageInfo.version; return `Plugin "${name}" was disabled because it expected Kibana version "${pluginVersion}", and found "${kibanaVersion}".`; }), distinct(), diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 65477e93e225..7f3a96057101 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1928,6 +1928,8 @@ export type SharedGlobalConfig = RecursiveReadonly_2<{ // @public export interface UiSettingsParams { category?: string[]; + // Warning: (ae-forgotten-export) The symbol "DeprecationSettings" needs to be exported by the entry point index.d.ts + deprecation?: DeprecationSettings; description?: string; name?: string; optionLabels?: Record; diff --git a/src/core/server/types.ts b/src/core/server/types.ts index 9919c7f0386b..2433aad1a2be 100644 --- a/src/core/server/types.ts +++ b/src/core/server/types.ts @@ -23,4 +23,3 @@ export * from './saved_objects/types'; export * from './ui_settings/types'; export * from './legacy/types'; export { EnvironmentMode, PackageInfo } from './config/types'; -export { ICspConfig } from './csp'; diff --git a/src/core/server/ui_settings/types.ts b/src/core/server/ui_settings/types.ts index 2ab6114e7df8..14eb71a22cef 100644 --- a/src/core/server/ui_settings/types.ts +++ b/src/core/server/ui_settings/types.ts @@ -73,6 +73,15 @@ export interface UserProvidedValues { isOverridden?: boolean; } +/** + * UiSettings deprecation field options. + * @public + * */ +export interface DeprecationSettings { + message: string; + docLinksKey: string; +} + /** * UI element type to represent the settings. * @public @@ -102,6 +111,8 @@ export interface UiSettingsParams { readonly?: boolean; /** defines a type of UI element {@link UiSettingsType} */ type?: UiSettingsType; + /** optional deprecation information. Used to generate a deprecation warning. */ + deprecation?: DeprecationSettings; /* * Allows defining a custom validation applicable to value change on the client. * @deprecated diff --git a/src/dev/build/README.md b/src/dev/build/README.md index af08414f0bf4..3b579033fabe 100644 --- a/src/dev/build/README.md +++ b/src/dev/build/README.md @@ -44,7 +44,7 @@ The majority of this logic is extracted from the grunt build that has existed fo We have introduced in our bundle a webpack dll for the client vendor modules in order to improve the optimization time both in dev and in production. As for those modules we already have the -code into the vendors.bundle.dll.js we have decided to delete those bundled modules from the +code into the vendors_${chunk_number}.bundle.dll.js we have decided to delete those bundled modules from the distributable node_modules folder. However, in order to accomplish this, we need to exclude every node_module used in the server side code. This logic is performed under `nodejs_modules/clean_client_modules_on_dll_task.js`. In case we need to add any new cli diff --git a/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js b/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js index 19d74bcf89e3..52928d6e47fc 100644 --- a/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js +++ b/src/dev/build/tasks/nodejs_modules/clean_client_modules_on_dll_task.js @@ -98,12 +98,16 @@ export const CleanClientModulesOnDLLTask = { // Consider this as our whiteList for the modules we can't delete const whiteListedModules = [...serverDependencies, ...kbnWebpackLoaders, ...manualExceptions]; - // Resolve the client vendors dll manifest path - const dllManifestPath = `${baseDir}/built_assets/dlls/vendors.manifest.dll.json`; + // Resolve the client vendors dll manifest paths + // excluding the runtime one + const dllManifestPaths = await globby([ + `${baseDir}/built_assets/dlls/vendors_*.manifest.dll.json`, + `!${baseDir}/built_assets/dlls/vendors_runtime.manifest.dll.json`, + ]); // Get dll entries filtering out the ones // from any whitelisted module - const dllEntries = await getDllEntries(dllManifestPath, whiteListedModules, baseDir); + const dllEntries = await getDllEntries(dllManifestPaths, whiteListedModules, baseDir); for (const relativeEntryPath of dllEntries) { const entryPath = `${baseDir}/${relativeEntryPath}`; diff --git a/src/dev/build/tasks/nodejs_modules/webpack_dll.js b/src/dev/build/tasks/nodejs_modules/webpack_dll.js index ea8cc1e28640..72910226bb04 100644 --- a/src/dev/build/tasks/nodejs_modules/webpack_dll.js +++ b/src/dev/build/tasks/nodejs_modules/webpack_dll.js @@ -28,27 +28,37 @@ function checkDllEntryAccess(entry, baseDir = '') { return isFileAccessible(resolvedPath); } -export async function getDllEntries(manifestPath, whiteListedModules, baseDir = '') { - const manifest = JSON.parse(await read(manifestPath)); - - if (!manifest || !manifest.content) { - // It should fails because if we don't have the manifest file - // or it is malformed something wrong is happening and we - // should stop - throw new Error(`The following dll manifest doesn't exists: ${manifestPath}`); - } +export async function getDllEntries(manifestPaths, whiteListedModules, baseDir = '') { + // Read and parse all manifests + const manifests = await Promise.all( + manifestPaths.map(async manifestPath => JSON.parse(await read(manifestPath))) + ); - const modules = Object.keys(manifest.content); - if (!modules.length) { - // It should fails because if we don't have any - // module inside the client vendors dll something - // wrong is happening and we should stop too - throw new Error(`The following dll manifest is reporting an empty dll: ${manifestPath}`); - } + // Process and group modules from all manifests + const manifestsModules = manifests.flatMap((manifest, idx) => { + if (!manifest || !manifest.content) { + // It should fails because if we don't have the manifest file + // or it is malformed something wrong is happening and we + // should stop + throw new Error(`The following dll manifest doesn't exists: ${manifestPaths[idx]}`); + } + + const modules = Object.keys(manifest.content); + if (!modules.length) { + // It should fails because if we don't have any + // module inside the client vendors dll something + // wrong is happening and we should stop too + throw new Error( + `The following dll manifest is reporting an empty dll: ${manifestPaths[idx]}` + ); + } + + return modules; + }); // Only includes modules who are not in the white list of modules // and that are node_modules - return modules.filter(entry => { + return manifestsModules.filter(entry => { const isWhiteListed = whiteListedModules.some(nonEntry => normalizePosixPath(entry).includes(`node_modules/${nonEntry}`) ); diff --git a/src/dev/build/tasks/nodejs_modules/webpack_dll.test.js b/src/dev/build/tasks/nodejs_modules/webpack_dll.test.js index 1fdd7d8d4f5f..ce305169a777 100644 --- a/src/dev/build/tasks/nodejs_modules/webpack_dll.test.js +++ b/src/dev/build/tasks/nodejs_modules/webpack_dll.test.js @@ -52,7 +52,7 @@ describe('Webpack DLL Build Tasks Utils', () => { isFileAccessible.mockImplementation(() => true); - const mockManifestPath = '/mock/mock_dll_manifest.json'; + const mockManifestPath = ['/mock/mock_dll_manifest.json']; const mockModulesWhitelist = ['dep1']; const dllEntries = await getDllEntries(mockManifestPath, mockModulesWhitelist); @@ -66,7 +66,7 @@ describe('Webpack DLL Build Tasks Utils', () => { isFileAccessible.mockImplementation(() => false); - const mockManifestPath = '/mock/mock_dll_manifest.json'; + const mockManifestPath = ['/mock/mock_dll_manifest.json']; const mockModulesWhitelist = ['dep1']; const dllEntries = await getDllEntries(mockManifestPath, mockModulesWhitelist); @@ -78,7 +78,7 @@ describe('Webpack DLL Build Tasks Utils', () => { it('should throw an error for no manifest file', async () => { read.mockImplementationOnce(async () => noManifestMock); - const mockManifestPath = '/mock/mock_dll_manifest.json'; + const mockManifestPath = ['/mock/mock_dll_manifest.json']; try { await getDllEntries(mockManifestPath, []); @@ -92,7 +92,7 @@ describe('Webpack DLL Build Tasks Utils', () => { it('should throw an error for no manifest content field', async () => { read.mockImplementation(async () => noContentFieldManifestMock); - const mockManifestPath = '/mock/mock_dll_manifest.json'; + const mockManifestPath = ['/mock/mock_dll_manifest.json']; try { await getDllEntries(mockManifestPath, []); @@ -106,7 +106,7 @@ describe('Webpack DLL Build Tasks Utils', () => { it('should throw an error for manifest file without any content', async () => { read.mockImplementation(async () => emptyManifestContentMock); - const mockManifestPath = '/mock/mock_dll_manifest.json'; + const mockManifestPath = ['/mock/mock_dll_manifest.json']; try { await getDllEntries(mockManifestPath, []); diff --git a/src/dev/license_checker/config.ts b/src/dev/license_checker/config.ts index a4aa3474c076..bd084767a723 100644 --- a/src/dev/license_checker/config.ts +++ b/src/dev/license_checker/config.ts @@ -23,6 +23,7 @@ export const LICENSE_WHITELIST = [ 'Elastic-License', '(BSD-2-Clause OR MIT OR Apache-2.0)', '(BSD-2-Clause OR MIT)', + '(BSD-3-Clause AND Apache-2.0)', '(GPL-2.0 OR MIT)', '(MIT AND CC-BY-3.0)', '(MIT AND Zlib)', diff --git a/src/dev/sass/build_sass.js b/src/dev/sass/build_sass.js index 14f03a7a116a..1ff7c700d038 100644 --- a/src/dev/sass/build_sass.js +++ b/src/dev/sass/build_sass.js @@ -19,6 +19,7 @@ import { resolve } from 'path'; +import * as Rx from 'rxjs'; import { toArray } from 'rxjs/operators'; import { createFailError } from '@kbn/dev-utils'; @@ -61,9 +62,11 @@ export async function buildSass({ log, kibanaDir, watch }) { const scanDirs = [resolve(kibanaDir, 'src/legacy/core_plugins')]; const paths = [resolve(kibanaDir, 'x-pack')]; - const { spec$ } = findPluginSpecs({ plugins: { scanDirs, paths } }); - const enabledPlugins = await spec$.pipe(toArray()).toPromise(); - const uiExports = collectUiExports(enabledPlugins); + const { spec$, disabledSpec$ } = findPluginSpecs({ plugins: { scanDirs, paths } }); + const allPlugins = await Rx.merge(spec$, disabledSpec$) + .pipe(toArray()) + .toPromise(); + const uiExports = collectUiExports(allPlugins); const { styleSheetPaths } = uiExports; log.info('%s %d styleSheetPaths', watch ? 'watching' : 'found', styleSheetPaths.length); diff --git a/src/legacy/core_plugins/console/common/text_object.ts b/src/legacy/core_plugins/console/common/text_object.ts new file mode 100644 index 000000000000..3b3464a77ac4 --- /dev/null +++ b/src/legacy/core_plugins/console/common/text_object.ts @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const textObjectTypeName = 'text-object'; + +/** + * Describes the shape of persisted objects that contain information about the current text in the + * text editor. + */ +export interface TextObject { + /** + * An ID that uniquely identifies this object. + */ + id: string; + + /** + * UNIX timestamp of when the object was created. + */ + createdAt: number; + + /** + * UNIX timestamp of when the object was last updated. + */ + updatedAt: number; + + /** + * Text value input by the user. + * + * Used to re-populate a text editor buffer. + */ + text: string; +} diff --git a/src/legacy/core_plugins/console/common/types.ts b/src/legacy/core_plugins/console/common/types.ts new file mode 100644 index 000000000000..33d6907ff60b --- /dev/null +++ b/src/legacy/core_plugins/console/common/types.ts @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { TextObject } from './text_object'; + +export interface IdObject { + id: string; +} + +export interface ObjectStorage { + /** + * Creates a new object in the underlying persistance layer. + * + * @remarks Does not accept an ID, a new ID is generated and returned with the newly created object. + */ + create(obj: Omit): Promise; + + /** + * This method should update specific object in the persistance layer. + */ + update(obj: O): Promise; + + /** + * A function that will return all of the objects in the persistance layer. + * + * @remarks Unless an error is thrown this function should always return an array (empty if there are not objects present). + */ + findAll(): Promise; +} + +export interface ObjectStorageClient { + text: ObjectStorage; +} diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/editor_content_spinner.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/editor_content_spinner.tsx new file mode 100644 index 000000000000..2ae4545ad24e --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/editor_content_spinner.tsx @@ -0,0 +1,29 @@ +/* + * 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, { FunctionComponent } from 'react'; +import { EuiLoadingContent, EuiPageContent } from '@elastic/eui'; + +export const EditorContentSpinner: FunctionComponent = () => { + return ( + + + + ); +}; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/components/index.ts index 0f109e99b0b3..4669e210e7c2 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/components/index.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/index.ts @@ -18,8 +18,10 @@ */ export * from './split_panel'; +export { SomethingWentWrongCallout } from './something_went_wrong_callout'; export { TopNavMenuItem, TopNavMenu } from './top_nav_menu'; export { ConsoleMenu } from './console_menu'; export { WelcomePanel } from './welcome_panel'; export { AutocompleteOptions, DevToolsSettingsModal } from './settings_modal'; export { HelpPanel } from './help_panel'; +export { EditorContentSpinner } from './editor_content_spinner'; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/something_went_wrong_callout.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/something_went_wrong_callout.tsx new file mode 100644 index 000000000000..7b643bc84dd3 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/something_went_wrong_callout.tsx @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { FunctionComponent, useEffect } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { EuiCallOut, EuiText, EuiButton, EuiSpacer } from '@elastic/eui'; + +interface Props { + error: Error; + onButtonClick: () => void; +} + +export const SomethingWentWrongCallout: FunctionComponent = ({ error, onButtonClick }) => { + useEffect(() => { + // eslint-disable-next-line no-console + console.error(error); + }, [error]); + + return ( + + +

+ +

+
+ + onButtonClick()}> + + +
+ ); +}; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/components/top_nav_menu.tsx b/src/legacy/core_plugins/console/public/np_ready/application/components/top_nav_menu.tsx index c83237c52feb..92f3f32da9a4 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/components/top_nav_menu.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/components/top_nav_menu.tsx @@ -17,7 +17,7 @@ * under the License. */ -import React from 'react'; +import React, { FunctionComponent } from 'react'; import { EuiTabs, EuiTab } from '@elastic/eui'; export interface TopNavMenuItem { @@ -29,19 +29,26 @@ export interface TopNavMenuItem { } interface Props { + disabled?: boolean; items: TopNavMenuItem[]; } -export function TopNavMenu({ items }: Props) { +export const TopNavMenu: FunctionComponent = ({ items, disabled }) => { return ( {items.map((item, idx) => { return ( - + {item.label} ); })} ); -} +}; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx index 07b48c083bf6..56449bfb4541 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/editor.tsx @@ -20,19 +20,26 @@ import React, { useCallback } from 'react'; import { debounce } from 'lodash'; +import { EditorContentSpinner } from '../../components'; import { Panel, PanelsContainer } from '../../components/split_panel'; import { Editor as EditorUI, EditorOutput } from './legacy/console_editor'; import { StorageKeys } from '../../../services'; -import { useServicesContext } from '../../contexts'; +import { useEditorReadContext, useServicesContext } from '../../contexts'; const INITIAL_PANEL_WIDTH = 50; const PANEL_MIN_WIDTH = '100px'; -export const Editor = () => { +interface Props { + loading: boolean; +} + +export const Editor = ({ loading }: Props) => { const { services: { storage }, } = useServicesContext(); + const { currentTextObject } = useEditorReadContext(); + const [firstPanelWidth, secondPanelWidth] = storage.get(StorageKeys.WIDTH, [ INITIAL_PANEL_WIDTH, INITIAL_PANEL_WIDTH, @@ -45,19 +52,25 @@ export const Editor = () => { [] ); + if (!currentTextObject) return null; + return ( - + {loading ? ( + + ) : ( + + )} - + {loading ? : } ); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx index 73ee6d160613..d4079fcea33f 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.test.tsx @@ -52,7 +52,7 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => { - + @@ -72,6 +72,7 @@ describe('Legacy (Ace) Console Editor Component Smoke Test', () => { updateCurrentState: jest.fn(), } as any, notifications: notificationServiceMock.createSetupContract(), + objectStorageClient: {} as any, }, docLinkVersion: 'NA', }; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx index 761a252b56a8..759e3dbafb39 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/editor/legacy/console_editor/editor.tsx @@ -35,7 +35,11 @@ import { autoIndent, getDocumentation } from '../console_menu_actions'; import { registerCommands } from './keyboard_shortcuts'; import { applyCurrentSettings } from './apply_editor_settings'; -import { useSendCurrentRequestToES, useSetInputEditor } from '../../../../hooks'; +import { + useSendCurrentRequestToES, + useSetInputEditor, + useSaveCurrentTextObject, +} from '../../../../hooks'; import * as senseEditor from '../../../../models/sense_editor'; // @ts-ignore @@ -43,6 +47,10 @@ import mappings from '../../../../../lib/mappings/mappings'; import { subscribeResizeChecker } from '../subscribe_console_resize_checker'; +export interface EditorProps { + initialTextValue: string; +} + const abs: CSSProperties = { position: 'absolute', top: '0', @@ -58,7 +66,7 @@ const DEFAULT_INPUT_VALUE = `GET _search } }`; -function EditorUI() { +function EditorUI({ initialTextValue }: EditorProps) { const { services: { history, notifications }, docLinkVersion, @@ -68,6 +76,7 @@ function EditorUI() { const { settings } = useEditorReadContext(); const setInputEditor = useSetInputEditor(); const sendCurrentRequestToES = useSendCurrentRequestToES(); + const saveCurrentTextObject = useSaveCurrentTextObject(); const editorRef = useRef(null); const editorInstanceRef = useRef(null); @@ -132,10 +141,7 @@ function EditorUI() { if (initialQueryParams.load_from) { loadBufferFromRemote(initialQueryParams.load_from); } else { - const { content: text } = history.getSavedEditorState() || { - content: DEFAULT_INPUT_VALUE, - }; - editor.update(text); + editor.update(initialTextValue || DEFAULT_INPUT_VALUE); } function setupAutosave() { @@ -153,7 +159,7 @@ function EditorUI() { function saveCurrentState() { try { const content = editor.getCoreEditor().getValue(); - history.updateCurrentState(content); + saveCurrentTextObject(content); } catch (e) { // Ignoring saving error } @@ -172,7 +178,7 @@ function EditorUI() { mappings.clearSubscriptions(); window.removeEventListener('hashchange', onHashChange); }; - }, [history, setInputEditor]); + }, [saveCurrentTextObject, initialTextValue, history, setInputEditor]); useEffect(() => { const { current: editor } = editorInstanceRef; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/containers/main/main.tsx b/src/legacy/core_plugins/console/public/np_ready/application/containers/main/main.tsx index 764c4b8e8710..902d800b3e56 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/containers/main/main.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/containers/main/main.tsx @@ -19,14 +19,15 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiPageContent } from '@elastic/eui'; import { ConsoleHistory } from '../console_history'; import { Editor } from '../editor'; import { Settings } from '../settings'; -import { TopNavMenu, WelcomePanel, HelpPanel } from '../../components'; +import { TopNavMenu, WelcomePanel, HelpPanel, SomethingWentWrongCallout } from '../../components'; import { useServicesContext, useEditorReadContext } from '../../contexts'; +import { useDataInit } from '../../hooks'; import { getTopNavConfig } from './get_top_nav'; @@ -48,6 +49,15 @@ export function Main() { const renderConsoleHistory = () => { return editorsReady ? setShowHistory(false)} /> : null; }; + const { done, error, retry } = useDataInit(); + + if (error) { + return ( + + + + ); + } return (
@@ -66,6 +76,7 @@ export function Main() { setShowHistory(!showingHistory), onClickSettings: () => setShowSettings(true), @@ -75,11 +86,11 @@ export function Main() { {showingHistory ? {renderConsoleHistory()} : null} - + - {showWelcome ? ( + {done && showWelcome ? ( { storage.set('version_welcome_shown', '@@SENSE_REVISION'); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/contexts/services_context.tsx b/src/legacy/core_plugins/console/public/np_ready/application/contexts/services_context.tsx index f14685ecd4ac..d7f036e1aecb 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/contexts/services_context.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/contexts/services_context.tsx @@ -20,6 +20,7 @@ import React, { createContext, useContext } from 'react'; import { NotificationsSetup } from 'kibana/public'; import { History, Storage, Settings } from '../../services'; +import { ObjectStorageClient } from '../../../../common/types'; import { MetricsTracker } from '../../types'; export interface ContextValue { @@ -28,6 +29,7 @@ export interface ContextValue { storage: Storage; settings: Settings; notifications: NotificationsSetup; + objectStorageClient: ObjectStorageClient; trackUiMetric: MetricsTracker; }; elasticsearchUrl: string; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/hooks/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/index.ts index 8c5a8d599a0d..72e83e883553 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/hooks/index.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/index.ts @@ -20,3 +20,5 @@ export { useSetInputEditor } from './use_set_input_editor'; export { useRestoreRequestFromHistory } from './use_restore_request_from_history'; export { useSendCurrentRequestToES } from './use_send_current_request_to_es'; +export { useSaveCurrentTextObject } from './use_save_current_text_object'; +export { useDataInit } from './use_data_init'; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/data_migration.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/data_migration.ts new file mode 100644 index 000000000000..08acd78ba2b8 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/data_migration.ts @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { History } from '../../../services'; +import { ObjectStorageClient } from '../../../../../common/types'; + +export interface Dependencies { + history: History; + objectStorageClient: ObjectStorageClient; +} + +/** + * Once off migration to new text object data structure + */ +export async function migrateToTextObjects({ + history, + objectStorageClient: objectStorageClient, +}: Dependencies): Promise { + const legacyTextContent = history.getLegacySavedEditorState(); + + if (!legacyTextContent) return; + + await objectStorageClient.text.create({ + createdAt: Date.now(), + updatedAt: Date.now(), + text: legacyTextContent.content, + }); + + history.deleteLegacySavedEditorState(); +} diff --git a/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/index.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/index.ts new file mode 100644 index 000000000000..582aa047f7d4 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { useDataInit } from './use_data_init'; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/use_data_init.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/use_data_init.ts new file mode 100644 index 000000000000..2212827c1f59 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_data_init/use_data_init.ts @@ -0,0 +1,72 @@ +/* + * 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 { useCallback, useEffect, useState } from 'react'; +import { migrateToTextObjects } from './data_migration'; +import { useEditorActionContext, useServicesContext } from '../../contexts'; + +export const useDataInit = () => { + const [error, setError] = useState(null); + const [done, setDone] = useState(false); + const [retryToken, setRetryToken] = useState({}); + + const retry = useCallback(() => { + setRetryToken({}); + setDone(false); + setError(null); + }, []); + + const { + services: { objectStorageClient, history }, + } = useServicesContext(); + + const dispatch = useEditorActionContext(); + + useEffect(() => { + const load = async () => { + try { + await migrateToTextObjects({ history, objectStorageClient }); + const results = await objectStorageClient.text.findAll(); + if (!results.length) { + const newObject = await objectStorageClient.text.create({ + createdAt: Date.now(), + updatedAt: Date.now(), + text: '', + }); + dispatch({ type: 'setCurrentTextObject', payload: newObject }); + } else { + // For now, we always take the first text object returned. + dispatch({ type: 'setCurrentTextObject', payload: results[0] }); + } + } catch (e) { + setError(e); + } finally { + setDone(true); + } + }; + + load(); + }, [dispatch, objectStorageClient, history, retryToken]); + + return { + error, + done, + retry, + }; +}; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_save_current_text_object.ts b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_save_current_text_object.ts new file mode 100644 index 000000000000..ab517ba1bfdd --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/application/hooks/use_save_current_text_object.ts @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useRef, useCallback } from 'react'; +import { throttle } from 'lodash'; +import { useEditorReadContext, useServicesContext } from '../contexts'; + +const WAIT_MS = 500; + +export const useSaveCurrentTextObject = () => { + const promiseChainRef = useRef(Promise.resolve()); + + const { + services: { objectStorageClient }, + } = useServicesContext(); + + const { currentTextObject } = useEditorReadContext(); + + return useCallback( + throttle( + (text: string) => { + const { current: promise } = promiseChainRef; + if (!currentTextObject) return; + promise.finally(() => + objectStorageClient.text.update({ ...currentTextObject, text, updatedAt: Date.now() }) + ); + }, + WAIT_MS, + { trailing: true } + ), + [objectStorageClient, currentTextObject] + ); +}; diff --git a/src/legacy/core_plugins/console/public/np_ready/application/index.tsx b/src/legacy/core_plugins/console/public/np_ready/application/index.tsx index 89756513b2b2..efd0f2ba8602 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/index.tsx +++ b/src/legacy/core_plugins/console/public/np_ready/application/index.tsx @@ -18,10 +18,11 @@ */ import React from 'react'; -import { NotificationsSetup } from 'kibana/public'; +import { NotificationsSetup } from 'src/core/public'; import { ServicesContextProvider, EditorContextProvider, RequestContextProvider } from './contexts'; import { Main } from './containers'; import { createStorage, createHistory, createSettings, Settings } from '../services'; +import * as localStorageObjectClient from '../lib/local_storage_object_client'; import { createUsageTracker } from '../services/tracker'; let settingsRef: Settings; @@ -46,6 +47,7 @@ export function boot(deps: { }); const history = createHistory({ storage }); const settings = createSettings({ storage }); + const objectStorageClient = localStorageObjectClient.create(storage); settingsRef = settings; return ( @@ -60,6 +62,7 @@ export function boot(deps: { settings, notifications, trackUiMetric, + objectStorageClient, }, }} > diff --git a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.ts b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.ts index 6262c304e307..8301daa675b5 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/models/legacy_core_editor/legacy_core_editor.ts @@ -303,7 +303,9 @@ export class LegacyCoreEditor implements CoreEditor { const maxLineLength = this.getWrapLimit() - 5; const isWrapping = firstLine.length > maxLineLength; const getScreenCoords = (line: number) => - this.editor.renderer.textToScreenCoordinates(line - 1, startColumn).pageY - offsetFromPage; + this.editor.renderer.textToScreenCoordinates(line - 1, startColumn).pageY - + offsetFromPage + + (window.pageYOffset || 0); const topOfReq = getScreenCoords(startLine); if (topOfReq >= 0) { diff --git a/src/legacy/core_plugins/console/public/np_ready/application/stores/editor.ts b/src/legacy/core_plugins/console/public/np_ready/application/stores/editor.ts index 339a2f7a2c4a..844eacd9b91a 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/stores/editor.ts +++ b/src/legacy/core_plugins/console/public/np_ready/application/stores/editor.ts @@ -21,22 +21,26 @@ import { Reducer } from 'react'; import { produce } from 'immer'; import { identity } from 'fp-ts/lib/function'; import { DevToolsSettings } from '../../services'; +import { TextObject } from '../../../../common/text_object'; export interface Store { ready: boolean; settings: DevToolsSettings; + currentTextObject: TextObject | null; } export const initialValue: Store = produce( { ready: false, settings: null as any, + currentTextObject: null, }, identity ); export type Action = | { type: 'setInputEditor'; payload: any } + | { type: 'setCurrentTextObject'; payload: any } | { type: 'updateSettings'; payload: DevToolsSettings }; export const reducer: Reducer = (state, action) => @@ -53,5 +57,10 @@ export const reducer: Reducer = (state, action) => return; } + if (action.type === 'setCurrentTextObject') { + draft.currentTextObject = action.payload; + return; + } + return draft; }); diff --git a/src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss b/src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss index 159e9f9e8a17..c69440225236 100644 --- a/src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss +++ b/src/legacy/core_plugins/console/public/np_ready/application/styles/_app.scss @@ -26,6 +26,10 @@ // Required on IE11 to render ace editor correctly after first input. position: relative; + + &__spinner { + width: 100%; + } } .conApp__output { diff --git a/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/create.ts b/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/create.ts new file mode 100644 index 000000000000..36948b9acb96 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/create.ts @@ -0,0 +1,29 @@ +/* + * 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 { Storage } from '../../services'; +import { ObjectStorageClient } from '../../../../common/types'; +import { TextObject, textObjectTypeName } from '../../../../common/text_object'; +import { LocalObjectStorage } from './local_storage_object_client'; + +export const create = (storage: Storage): ObjectStorageClient => { + return { + text: new LocalObjectStorage(storage, textObjectTypeName), + }; +}; diff --git a/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/index.ts b/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/index.ts new file mode 100644 index 000000000000..c170b8721a04 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/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 { create } from './create'; +export { LocalObjectStorage } from './local_storage_object_client'; diff --git a/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/local_storage_object_client.ts b/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/local_storage_object_client.ts new file mode 100644 index 000000000000..41c88d23b253 --- /dev/null +++ b/src/legacy/core_plugins/console/public/np_ready/lib/local_storage_object_client/local_storage_object_client.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 uuid from 'uuid'; +import { ObjectStorage, IdObject } from '../../../../common/types'; +import { Storage } from '../../services'; + +export class LocalObjectStorage implements ObjectStorage { + private readonly prefix: string; + + constructor(private readonly client: Storage, type: string) { + this.prefix = `console_local_${type}`; + } + + async create(obj: Omit): Promise { + const id = uuid.v4(); + const newObj = { id, ...obj } as O; + this.client.set(`${this.prefix}_${id}`, newObj); + return newObj; + } + + async update(obj: O): Promise { + this.client.set(`${this.prefix}_${obj.id}`, obj); + } + + async findAll(): Promise { + const allLocalKeys = this.client.keys().filter(key => { + return key.includes(this.prefix); + }); + + const result = []; + for (const key of allLocalKeys) { + result.push(this.client.get(key)); + } + return result; + } +} diff --git a/src/legacy/core_plugins/console/public/np_ready/plugin.ts b/src/legacy/core_plugins/console/public/np_ready/plugin.ts index cbe262b12467..22351ae95ba8 100644 --- a/src/legacy/core_plugins/console/public/np_ready/plugin.ts +++ b/src/legacy/core_plugins/console/public/np_ready/plugin.ts @@ -55,11 +55,11 @@ export class ConsoleUIPlugin implements Plugin { defaultMessage: 'Console', }), enableRouting: false, - async mount(ctx, { element }) { + async mount({ core: { docLinks } }, { element }) { const { boot } = await import('./application'); render( boot({ - docLinkVersion: ctx.core.docLinks.DOC_LINK_VERSION, + docLinkVersion: docLinks.DOC_LINK_VERSION, I18nContext, notifications, elasticsearchUrl, diff --git a/src/legacy/core_plugins/console/public/np_ready/services/history.ts b/src/legacy/core_plugins/console/public/np_ready/services/history.ts index 6cb6f7ba35cd..04dae0beacef 100644 --- a/src/legacy/core_plugins/console/public/np_ready/services/history.ts +++ b/src/legacy/core_plugins/console/public/np_ready/services/history.ts @@ -74,13 +74,20 @@ export class History { }); } - getSavedEditorState() { + getLegacySavedEditorState() { const saved = this.storage.get('editor_state'); if (!saved) return; const { time, content } = saved; return { time, content }; } + /** + * This function should only ever be called once for a user if they had legacy state. + */ + deleteLegacySavedEditorState() { + this.storage.delete('editor_state'); + } + clearHistory() { this.getHistoryKeys().forEach(key => this.storage.delete(key)); } diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/cat.indices.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/cat.indices.json index 45da7f054bfb..e6ca1fb57539 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/cat.indices.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/cat.indices.json @@ -5,8 +5,15 @@ "bytes": [ "b", "k", + "kb", "m", - "g" + "mb", + "g", + "gb", + "t", + "tb", + "p", + "pb" ], "local": "__flag__", "master_timeout": "", diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/clear_scroll.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/clear_scroll.json index 55d967305427..7e6e6692f931 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/clear_scroll.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/clear_scroll.json @@ -4,8 +4,7 @@ "DELETE" ], "patterns": [ - "_search/scroll", - "_search/scroll/{scroll_id}" + "_search/scroll" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/search-request-body.html#_clear_scroll_api" } diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/create.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/create.json index 6c0ee8a2425e..8bbee143c299 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/create.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/create.json @@ -22,8 +22,7 @@ "POST" ], "patterns": [ - "{indices}/_create/{id}", - "{indices}/{type}/{id}/_create" + "{indices}/_create/{id}" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/docs-index_.html" } diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/delete.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/delete.json index aba84d0a10fc..0852d8d18483 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/delete.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/delete.json @@ -23,8 +23,7 @@ "DELETE" ], "patterns": [ - "{indices}/_doc/{id}", - "{indices}/{type}/{id}" + "{indices}/_doc/{id}" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/docs-delete.html" } diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/delete_by_query.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/delete_by_query.json index 3867efd81423..2d1636d5f2c0 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/delete_by_query.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/delete_by_query.json @@ -1,7 +1,6 @@ { "delete_by_query": { "url_params": { - "analyzer": "", "analyze_wildcard": "__flag__", "default_operator": [ "AND", @@ -31,6 +30,7 @@ "dfs_query_then_fetch" ], "search_timeout": "", + "size": "", "max_docs": "all documents", "sort": [], "_source": [], diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/exists_source.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/exists_source.json index e96273ffbc08..9ffc4b55f303 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/exists_source.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/exists_source.json @@ -20,8 +20,7 @@ "HEAD" ], "patterns": [ - "{indices}/_source/{id}", - "{indices}/{type}/{id}/_source" + "{indices}/_source/{id}" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/docs-get.html" } diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/get_script_languages.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/get_script_languages.json new file mode 100644 index 000000000000..10ea433ca68c --- /dev/null +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/get_script_languages.json @@ -0,0 +1,10 @@ +{ + "get_script_languages": { + "methods": [ + "GET" + ], + "patterns": [ + "_script_language" + ] + } +} diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.shrink.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.shrink.json index 31acc86a2fa5..6fbdea0f1244 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.shrink.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.shrink.json @@ -1,6 +1,7 @@ { "indices.shrink": { "url_params": { + "copy_settings": "__flag__", "timeout": "", "master_timeout": "", "wait_for_active_shards": "" diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.split.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.split.json index 1bfbaa078b79..68f2e338cd20 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.split.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.split.json @@ -1,6 +1,7 @@ { "indices.split": { "url_params": { + "copy_settings": "__flag__", "timeout": "", "master_timeout": "", "wait_for_active_shards": "" diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.validate_query.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.validate_query.json index ceffec26beec..33720576ef8a 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.validate_query.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/indices.validate_query.json @@ -28,8 +28,7 @@ ], "patterns": [ "_validate/query", - "{indices}/_validate/query", - "{indices}/{type}/_validate/query" + "{indices}/_validate/query" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/search-validate.html" } diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/msearch_template.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/msearch_template.json index 0b0ca087b181..c2f741066bbd 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/msearch_template.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/msearch_template.json @@ -9,8 +9,7 @@ ], "typed_keys": "__flag__", "max_concurrent_searches": "", - "rest_total_hits_as_int": "__flag__", - "ccs_minimize_roundtrips": "__flag__" + "rest_total_hits_as_int": "__flag__" }, "methods": [ "GET", diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/nodes.hot_threads.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/nodes.hot_threads.json index b8aa5dd4ca71..b3cbbe80e0d0 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/nodes.hot_threads.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/nodes.hot_threads.json @@ -17,13 +17,7 @@ ], "patterns": [ "_nodes/hot_threads", - "_nodes/{nodes}/hot_threads", - "_cluster/nodes/hotthreads", - "_cluster/nodes/{nodes}/hotthreads", - "_nodes/hotthreads", - "_nodes/{nodes}/hotthreads", - "_cluster/nodes/hot_threads", - "_cluster/nodes/{nodes}/hot_threads" + "_nodes/{nodes}/hot_threads" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/cluster-nodes-hot-threads.html" } diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/rank_eval.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/rank_eval.json index 620f1c629d95..c2bed081124a 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/rank_eval.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/rank_eval.json @@ -8,6 +8,10 @@ "closed", "none", "all" + ], + "search_type": [ + "query_then_fetch", + "dfs_query_then_fetch" ] }, "methods": [ diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/scroll.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/scroll.json index 3e959b9630e9..4ce82a2c25e0 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/scroll.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/scroll.json @@ -10,8 +10,7 @@ "POST" ], "patterns": [ - "_search/scroll", - "_search/scroll/{scroll_id}" + "_search/scroll" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/search-request-body.html#request-body-search-scroll" } diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/search_template.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/search_template.json index 582ecab1dd61..cf5a5c5f32db 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/search_template.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/search_template.json @@ -22,8 +22,7 @@ "explain": "__flag__", "profile": "__flag__", "typed_keys": "__flag__", - "rest_total_hits_as_int": "__flag__", - "ccs_minimize_roundtrips": "__flag__" + "rest_total_hits_as_int": "__flag__" }, "methods": [ "GET", diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/update.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/update.json index 4e103b0af219..43945dfada35 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/update.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/update.json @@ -21,8 +21,7 @@ "POST" ], "patterns": [ - "{indices}/_update/{id}", - "{indices}/{type}/{id}/_update" + "{indices}/_update/{id}" ], "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/master/docs-update.html" } diff --git a/src/legacy/core_plugins/console/server/api_server/spec/generated/update_by_query.json b/src/legacy/core_plugins/console/server/api_server/spec/generated/update_by_query.json index 739ea1688814..393197949e86 100644 --- a/src/legacy/core_plugins/console/server/api_server/spec/generated/update_by_query.json +++ b/src/legacy/core_plugins/console/server/api_server/spec/generated/update_by_query.json @@ -32,6 +32,7 @@ "dfs_query_then_fetch" ], "search_timeout": "", + "size": "", "max_docs": "all documents", "sort": [], "_source": [], diff --git a/src/legacy/core_plugins/console/server/api_server/spec/overrides/clear_scroll.json b/src/legacy/core_plugins/console/server/api_server/spec/overrides/clear_scroll.json new file mode 100644 index 000000000000..e9d4a6cee54c --- /dev/null +++ b/src/legacy/core_plugins/console/server/api_server/spec/overrides/clear_scroll.json @@ -0,0 +1,7 @@ +{ + "clear_scroll": { + "data_autocomplete_rules": { + "scroll_id": "" + } + } +} diff --git a/src/legacy/core_plugins/console/server/api_server/spec/overrides/create.json b/src/legacy/core_plugins/console/server/api_server/spec/overrides/create.json deleted file mode 100644 index 0bbf456245c8..000000000000 --- a/src/legacy/core_plugins/console/server/api_server/spec/overrides/create.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "create": { - "url_params": { - "timeout": "1m", - "ttl": "5m", - "version": "1" - }, - "methods": [ - "PUT", - "POST" - ], - "patterns": [ - "{indices}/{type}/{id}/_create" - ] - } -} diff --git a/src/legacy/core_plugins/data/public/search/fetch/components/__snapshots__/shard_failure_table.test.tsx.snap b/src/legacy/core_plugins/data/public/search/fetch/components/__snapshots__/shard_failure_table.test.tsx.snap index 55e2c63f608d..257513f20fa9 100644 --- a/src/legacy/core_plugins/data/public/search/fetch/components/__snapshots__/shard_failure_table.test.tsx.snap +++ b/src/legacy/core_plugins/data/public/search/fetch/components/__snapshots__/shard_failure_table.test.tsx.snap @@ -72,5 +72,6 @@ exports[`ShardFailureTable renders matching snapshot given valid properties 1`] }, } } + tableLayout="fixed" /> `; diff --git a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap index 632fe63e9e14..278811ca85df 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap +++ b/src/legacy/core_plugins/input_control_vis/public/components/editor/__snapshots__/controls_tab.test.tsx.snap @@ -135,11 +135,7 @@ exports[`renders ControlsTab 1`] = ` > ) => void; handleParentChange: (controlIndex: number, event: ChangeEvent) => void; - parentCandidates: EuiSelectProps['options']; + parentCandidates: React.ComponentProps['options']; deps: InputControlVisDependencies; } diff --git a/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/list_control.test.tsx.snap b/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/list_control.test.tsx.snap index 31c221b36e2b..99482a4be2d7 100644 --- a/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/list_control.test.tsx.snap +++ b/src/legacy/core_plugins/input_control_vis/public/components/vis/__snapshots__/list_control.test.tsx.snap @@ -8,10 +8,7 @@ exports[`disableMsg 1`] = ` label="list control" > diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/area.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/area.js index 049544b50491..fd13067c84cc 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/area.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/area.js @@ -33,7 +33,7 @@ import { getConfigCollections, } from './utils/collections'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { palettes } from '@elastic/eui/lib/services'; +import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { vislibVisController } from './controller'; export const areaDefinition = { @@ -117,7 +117,7 @@ export const areaDefinition = { value: 10, width: 1, style: ThresholdLineStyles.FULL, - color: palettes.euiPaletteColorBlind.colors[9], + color: euiPaletteColorBlind()[9], }, labels: {}, }, diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_ranges.tsx b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_ranges.tsx index 276e765ae7fe..947c7ae7e6e3 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_ranges.tsx +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/components/common/color_ranges.tsx @@ -48,6 +48,10 @@ function ColorRanges({ const validateRange = useCallback( ({ from, to }, index) => { + if (!colorsRange[index]) { + return [false, false]; + } + const leftBound = index === 0 ? -Infinity : colorsRange[index - 1].to || 0; const isFromValid = from >= leftBound; const isToValid = to >= from; diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/histogram.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/histogram.js index fdc18f5bfa0e..bc017b5a1a87 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/histogram.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/histogram.js @@ -32,7 +32,7 @@ import { getConfigCollections, } from './utils/collections'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { palettes } from '@elastic/eui/lib/services'; +import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { vislibVisController } from './controller'; export const histogramDefinition = { @@ -120,7 +120,7 @@ export const histogramDefinition = { value: 10, width: 1, style: ThresholdLineStyles.FULL, - color: palettes.euiPaletteColorBlind.colors[9], + color: euiPaletteColorBlind()[9], }, }, }, diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js index 15bbf9c01cd7..ee3570314618 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js @@ -32,7 +32,7 @@ import { getConfigCollections, } from './utils/collections'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; -import { palettes } from '@elastic/eui/lib/services'; +import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { vislibVisController } from './controller'; export const horizontalBarDefinition = { @@ -119,7 +119,7 @@ export const horizontalBarDefinition = { value: 10, width: 1, style: ThresholdLineStyles.FULL, - color: palettes.euiPaletteColorBlind.colors[9], + color: euiPaletteColorBlind()[9], }, }, }, diff --git a/src/legacy/core_plugins/kbn_vislib_vis_types/public/line.js b/src/legacy/core_plugins/kbn_vislib_vis_types/public/line.js index a3fb874b5aa1..d6d075f452fe 100644 --- a/src/legacy/core_plugins/kbn_vislib_vis_types/public/line.js +++ b/src/legacy/core_plugins/kbn_vislib_vis_types/public/line.js @@ -32,7 +32,7 @@ import { InterpolationModes, getConfigCollections, } from './utils/collections'; -import { palettes } from '@elastic/eui/lib/services'; +import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; import { getAreaOptionTabs, countLabel } from './utils/common_config'; import { vislibVisController } from './controller'; @@ -118,7 +118,7 @@ export const lineDefinition = { value: 10, width: 1, style: ThresholdLineStyles.FULL, - color: palettes.euiPaletteColorBlind.colors[9], + color: euiPaletteColorBlind()[9], }, }, }, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/url_helper.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/url_helper.test.ts index 16773c02f5a7..df2dbfd54c13 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/url_helper.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/url_helper.test.ts @@ -82,10 +82,11 @@ describe('Dashboard URL Helper', () => { x: 'y', y: 'z', }); - url = 'http://notDashboardUrl'; - expect(getUrlVars(url)).toEqual({}); url = 'http://localhost:5601/app/kibana#/dashboard/777182'; expect(getUrlVars(url)).toEqual({}); + url = + 'http://localhost:5601/app/kibana#/dashboard/777182?title=Some%20Dashboard%20With%20Spaces'; + expect(getUrlVars(url)).toEqual({ title: 'Some Dashboard With Spaces' }); }); it('getLensUrlFromDashboardAbsoluteUrl', () => { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts index 2a5dedab9815..7f7bf7cf47bd 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts @@ -86,11 +86,9 @@ export const renderApp = (element: HTMLElement, appBasePath: string, deps: Rende }; }; -const mainTemplate = (basePath: string) => `
+const mainTemplate = (basePath: string) => `
-
-
-`; +
`; const moduleName = 'app/dashboard'; @@ -98,7 +96,7 @@ const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react']; function mountDashboardApp(appBasePath: string, element: HTMLElement) { const mountpoint = document.createElement('div'); - mountpoint.setAttribute('style', 'height: 100%'); + mountpoint.setAttribute('class', 'kbnLocalApplicationWrapper'); // eslint-disable-next-line mountpoint.innerHTML = mainTemplate(appBasePath); // bootstrap angular into detached element and attach it later to diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/__snapshots__/clone_modal.test.js.snap b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/__snapshots__/clone_modal.test.js.snap index 6def1b1a198b..e76f65c45e42 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/__snapshots__/clone_modal.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/top_nav/__snapshots__/clone_modal.test.js.snap @@ -30,11 +30,8 @@ exports[`renders DashboardCloneModal 1`] = ` diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper.ts index ee9e3c4ef478..73383f2ff3f6 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper.ts @@ -25,11 +25,9 @@ import { DashboardConstants } from './dashboard_constants'; */ export function getUrlVars(url: string): Record { const vars: Record = {}; - // @ts-ignore - url.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(_, key, value) { - // @ts-ignore - vars[key] = value; - }); + for (const [, key, value] of url.matchAll(/[?&]+([^=&]+)=([^&]*)/gi)) { + vars[key] = decodeURIComponent(value); + } return vars; } diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts index a6c6d9108462..6054b9f8d03c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/context.ts @@ -74,9 +74,9 @@ function fetchContextProvider(indexPatterns: IndexPatternsContract) { const searchSource = await createSearchSource(indexPattern, filters); const sortDirToApply = type === 'successors' ? sortDir : reverseSortDir(sortDir); - const nanos = indexPattern.isTimeNanosBased() ? extractNanos(anchor._source[timeField]) : ''; + const nanos = indexPattern.isTimeNanosBased() ? extractNanos(anchor.fields[timeField][0]) : ''; const timeValueMillis = - nanos !== '' ? convertIsoToMillis(anchor._source[timeField]) : anchor.sort[0]; + nanos !== '' ? convertIsoToMillis(anchor.fields[timeField][0]) : anchor.sort[0]; const intervals = generateIntervals(LOOKUP_OFFSETS, timeValueMillis, type, sortDir); let documents: EsHitRecordList = []; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/get_es_query_search_after.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/get_es_query_search_after.ts index 3f9bf255aefa..d4ee9e0e0f28 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/get_es_query_search_after.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context/api/utils/get_es_query_search_after.ts @@ -39,14 +39,14 @@ export function getEsQuerySearchAfter( const afterTimeRecIdx = type === 'successors' && documents.length ? documents.length - 1 : 0; const afterTimeDoc = documents[afterTimeRecIdx]; const afterTimeValue = nanoSeconds - ? convertIsoToNanosAsStr(afterTimeDoc._source[timeFieldName]) + ? convertIsoToNanosAsStr(afterTimeDoc.fields[timeFieldName][0]) : afterTimeDoc.sort[0]; return [afterTimeValue, afterTimeDoc.sort[1]]; } // if data_nanos adapt timestamp value for sorting, since numeric value was rounded by browser // ES search_after also works when number is provided as string return [ - nanoSeconds ? convertIsoToNanosAsStr(anchor._source[timeFieldName]) : anchor.sort[0], + nanoSeconds ? convertIsoToNanosAsStr(anchor.fields[timeFieldName][0]) : anchor.sort[0], anchor.sort[1], ]; } diff --git a/src/legacy/core_plugins/kibana/public/home/index.ts b/src/legacy/core_plugins/kibana/public/home/index.ts index bd3a0b38ec3f..b2d90f144465 100644 --- a/src/legacy/core_plugins/kibana/public/home/index.ts +++ b/src/legacy/core_plugins/kibana/public/home/index.ts @@ -73,6 +73,6 @@ let copiedLegacyCatalogue = false; }, }); instance.start(npStart.core, { - data: npStart.plugins.data, + ...npStart.plugins, }); })(); diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index 3ec095f4f26b..0eb55a3902ed 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -29,7 +29,7 @@ import { UiSettingsState, } from 'kibana/public'; import { UiStatsMetricType } from '@kbn/analytics'; -import { FeatureCatalogueEntry } from '../../../../../plugins/home/public'; +import { Environment, FeatureCatalogueEntry } from '../../../../../plugins/home/public'; export interface HomeKibanaServices { indexPatternService: any; @@ -61,6 +61,7 @@ export interface HomeKibanaServices { shouldShowTelemetryOptIn: boolean; docLinks: DocLinksStart; addBasePath: (url: string) => string; + environment: Environment; } let services: HomeKibanaServices | null = null; diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home_app.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home_app.js index 6532737cc02e..e49f00b949da 100644 --- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home_app.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home_app.js @@ -28,22 +28,19 @@ import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom' import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; import { getServices } from '../../kibana_services'; -// TODO This is going to be refactored soon -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { npSetup } from 'ui/new_platform'; export function HomeApp({ directories }) { const { getInjected, savedObjectsClient, getBasePath, addBasePath, + environment, telemetryOptInProvider: { setOptInNoticeSeen, getOptIn }, } = getServices(); - const { cloud } = npSetup.plugins; - const isCloudEnabled = !!(cloud && cloud.isCloudEnabled); + const isCloudEnabled = environment.cloud; + const mlEnabled = environment.ml; + const apmUiEnabled = environment.apmUi; - const apmUiEnabled = getInjected('apmUiEnabled', true); - const mlEnabled = getInjected('mlEnabled', false); const defaultAppId = getInjected('kbnDefaultAppId', 'discover'); const renderTutorialDirectory = props => { diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts index a998e4d07ab1..42ab049eb5b3 100644 --- a/src/legacy/core_plugins/kibana/public/home/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/home/plugin.ts @@ -23,7 +23,11 @@ import { UiStatsMetricType } from '@kbn/analytics'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { setServices } from './kibana_services'; import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; -import { FeatureCatalogueEntry } from '../../../../../plugins/home/public'; +import { + Environment, + FeatureCatalogueEntry, + HomePublicPluginStart, +} from '../../../../../plugins/home/public'; export interface LegacyAngularInjectedDependencies { telemetryOptInProvider: any; @@ -32,6 +36,7 @@ export interface LegacyAngularInjectedDependencies { export interface HomePluginStartDependencies { data: DataPublicPluginStart; + home: HomePublicPluginStart; } export interface HomePluginSetupDependencies { @@ -60,6 +65,7 @@ export interface HomePluginSetupDependencies { export class HomePlugin implements Plugin { private dataStart: DataPublicPluginStart | null = null; private savedObjectsClient: any = null; + private environment: Environment | null = null; setup( core: CoreSetup, @@ -86,6 +92,7 @@ export class HomePlugin implements Plugin { addBasePath: core.http.basePath.prepend, getBasePath: core.http.basePath.get, indexPatternService: this.dataStart!.indexPatterns, + environment: this.environment!, ...angularDependencies, }); const { renderApp } = await import('./np_ready/application'); @@ -94,8 +101,8 @@ export class HomePlugin implements Plugin { }); } - start(core: CoreStart, { data }: HomePluginStartDependencies) { - // TODO is this really the right way? I though the app context would give us those + start(core: CoreStart, { data, home }: HomePluginStartDependencies) { + this.environment = home.environment.get(); this.dataStart = data; this.savedObjectsClient = core.savedObjects.client; } diff --git a/src/legacy/core_plugins/kibana/public/index.scss b/src/legacy/core_plugins/kibana/public/index.scss index 3b49af9a4a6a..dfe4aa1fd3b9 100644 --- a/src/legacy/core_plugins/kibana/public/index.scss +++ b/src/legacy/core_plugins/kibana/public/index.scss @@ -26,6 +26,9 @@ // Management styles @import './management/index'; +// Local application mount wrapper styles +@import 'local_application_service/index'; + // Dashboard styles // MUST STAY AT THE BOTTOM BECAUSE OF DARK THEME IMPORTS @import './dashboard/index'; diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/_index.scss b/src/legacy/core_plugins/kibana/public/local_application_service/_index.scss new file mode 100644 index 000000000000..12cc1444101e --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/local_application_service/_index.scss @@ -0,0 +1 @@ +@import 'local_application_service'; diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/_local_application_service.scss b/src/legacy/core_plugins/kibana/public/local_application_service/_local_application_service.scss new file mode 100644 index 000000000000..33a6100c4397 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/local_application_service/_local_application_service.scss @@ -0,0 +1,5 @@ +.kbnLocalApplicationWrapper { + display: flex; + flex-direction: column; + flex-grow: 1; +} diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts index c09995caab66..d52bec8304ff 100644 --- a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts +++ b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts @@ -56,7 +56,7 @@ export class LocalApplicationService { outerAngularWrapperRoute: true, reloadOnSearch: false, reloadOnUrl: false, - template: `
`, + template: `
`, controller($scope: IScope) { const element = document.getElementById(wrapperElementId)!; let unmountHandler: AppUnmount | null = null; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/header/__jest__/__snapshots__/header.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/header/__jest__/__snapshots__/header.test.js.snap index 11c41425a0bb..f2fb17cdb0d6 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/header/__jest__/__snapshots__/header.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/components/header/__jest__/__snapshots__/header.test.js.snap @@ -78,11 +78,8 @@ exports[`Header should mark the input as invalid 1`] = ` labelType="label" > `; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap index 4716fb8f7763..2da4d84463b2 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/scripted_fields_table/components/table/__jest__/__snapshots__/table.test.js.snap @@ -76,6 +76,7 @@ exports[`Table should render normally 1`] = ` } responsive={true} sorting={true} + tableLayout="fixed" /> `; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__jest__/__snapshots__/add_filter.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__jest__/__snapshots__/add_filter.test.js.snap index 432c57d4f473..879ea555d330 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__jest__/__snapshots__/add_filter.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/edit_index_pattern/source_filters_table/components/add_filter/__jest__/__snapshots__/add_filter.test.js.snap @@ -6,9 +6,7 @@ exports[`AddFilter should ignore strings with just spaces 1`] = ` grow={10} > `; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/__snapshots__/objects_table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/__snapshots__/objects_table.test.js.snap index 2aaa291f6122..4ba0fe480ac4 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/__snapshots__/objects_table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/__jest__/__snapshots__/objects_table.test.js.snap @@ -71,6 +71,7 @@ exports[`ObjectsTable delete should show a confirm modal 1`] = ` pagination={true} responsive={true} sorting={false} + tableLayout="fixed" /> `; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/__snapshots__/flyout.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/__snapshots__/flyout.test.js.snap index ace06e0420a7..34ce8394232e 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/__snapshots__/flyout.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/flyout/__jest__/__snapshots__/flyout.test.js.snap @@ -115,6 +115,7 @@ exports[`Flyout conflicts should allow conflict resolution 1`] = ` } } responsive={true} + tableLayout="fixed" /> @@ -445,6 +446,7 @@ exports[`Flyout legacy conflicts should allow conflict resolution 1`] = ` } } responsive={true} + tableLayout="fixed" /> diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap index 941a0ffded82..c1241d5d7c1e 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/relationships/__jest__/__snapshots__/relationships.test.js.snap @@ -154,6 +154,7 @@ exports[`Relationships should render dashboards normally 1`] = ` ], } } + tableLayout="fixed" /> @@ -368,6 +369,7 @@ exports[`Relationships should render index patterns normally 1`] = ` ], } } + tableLayout="fixed" /> @@ -533,6 +535,7 @@ exports[`Relationships should render searches normally 1`] = ` ], } } + tableLayout="fixed" /> @@ -693,6 +696,7 @@ exports[`Relationships should render visualizations normally 1`] = ` ], } } + tableLayout="fixed" /> diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/__snapshots__/table.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/__snapshots__/table.test.js.snap index daac04d07da2..805131042f38 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/__snapshots__/table.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/components/objects_table/components/table/__jest__/__snapshots__/table.test.js.snap @@ -203,6 +203,7 @@ exports[`Table prevents saved objects from being deleted 1`] = ` "onSelectionChange": [Function], } } + tableLayout="fixed" /> @@ -410,6 +411,7 @@ exports[`Table should render normally 1`] = ` "onSelectionChange": [Function], } } + tableLayout="fixed" /> diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap index 10d165d0d69c..eef8f3fc93d9 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap @@ -60,6 +60,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "defVal": Array [ "default_value", ], + "deprecation": undefined, "description": "Description for Test array setting", "displayName": "Test array setting", "isCustom": undefined, @@ -79,6 +80,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "elasticsearch", ], "defVal": true, + "deprecation": undefined, "description": "Description for Test boolean setting", "displayName": "Test boolean setting", "isCustom": undefined, @@ -100,6 +102,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test custom string setting", "displayName": "Test custom string setting", "isCustom": undefined, @@ -119,6 +122,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test image setting", "displayName": "Test image setting", "isCustom": undefined, @@ -140,6 +144,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "defVal": "{ \\"foo\\": \\"bar\\" }", + "deprecation": undefined, "description": "Description for overridden json", "displayName": "An overridden json", "isCustom": undefined, @@ -159,6 +164,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": 1234, + "deprecation": undefined, "description": "Description for overridden number", "displayName": "An overridden number", "isCustom": undefined, @@ -178,6 +184,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "orange", + "deprecation": undefined, "description": "Description for overridden select setting", "displayName": "Test overridden select setting", "isCustom": undefined, @@ -201,6 +208,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "foo", + "deprecation": undefined, "description": "Description for overridden string", "displayName": "An overridden string", "isCustom": undefined, @@ -220,6 +228,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "{\\"foo\\": \\"bar\\"}", + "deprecation": undefined, "description": "Description for Test json setting", "displayName": "Test json setting", "isCustom": undefined, @@ -239,6 +248,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "", + "deprecation": undefined, "description": "Description for Test markdown setting", "displayName": "Test markdown setting", "isCustom": undefined, @@ -258,6 +268,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": 5, + "deprecation": undefined, "description": "Description for Test number setting", "displayName": "Test number setting", "isCustom": undefined, @@ -277,6 +288,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "orange", + "deprecation": undefined, "description": "Description for Test select setting", "displayName": "Test select setting", "isCustom": undefined, @@ -300,6 +312,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test string setting", "displayName": "Test string setting", "isCustom": undefined, @@ -345,6 +358,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "defVal": Array [ "default_value", ], + "deprecation": undefined, "description": "Description for Test array setting", "displayName": "Test array setting", "isCustom": undefined, @@ -364,6 +378,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "elasticsearch", ], "defVal": true, + "deprecation": undefined, "description": "Description for Test boolean setting", "displayName": "Test boolean setting", "isCustom": undefined, @@ -385,6 +400,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test custom string setting", "displayName": "Test custom string setting", "isCustom": undefined, @@ -404,6 +420,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test image setting", "displayName": "Test image setting", "isCustom": undefined, @@ -425,6 +442,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "defVal": "{ \\"foo\\": \\"bar\\" }", + "deprecation": undefined, "description": "Description for overridden json", "displayName": "An overridden json", "isCustom": undefined, @@ -444,6 +462,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": 1234, + "deprecation": undefined, "description": "Description for overridden number", "displayName": "An overridden number", "isCustom": undefined, @@ -463,6 +482,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "orange", + "deprecation": undefined, "description": "Description for overridden select setting", "displayName": "Test overridden select setting", "isCustom": undefined, @@ -486,6 +506,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "foo", + "deprecation": undefined, "description": "Description for overridden string", "displayName": "An overridden string", "isCustom": undefined, @@ -505,6 +526,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "{\\"foo\\": \\"bar\\"}", + "deprecation": undefined, "description": "Description for Test json setting", "displayName": "Test json setting", "isCustom": undefined, @@ -524,6 +546,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "", + "deprecation": undefined, "description": "Description for Test markdown setting", "displayName": "Test markdown setting", "isCustom": undefined, @@ -543,6 +566,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": 5, + "deprecation": undefined, "description": "Description for Test number setting", "displayName": "Test number setting", "isCustom": undefined, @@ -562,6 +586,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "orange", + "deprecation": undefined, "description": "Description for Test select setting", "displayName": "Test select setting", "isCustom": undefined, @@ -585,6 +610,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test string setting", "displayName": "Test string setting", "isCustom": undefined, @@ -705,6 +731,7 @@ exports[`AdvancedSettings should render read-only when saving is disabled 1`] = "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test string setting", "displayName": "Test string setting", "isCustom": undefined, @@ -748,6 +775,7 @@ exports[`AdvancedSettings should render read-only when saving is disabled 1`] = "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test string setting", "displayName": "Test string setting", "isCustom": undefined, @@ -886,6 +914,7 @@ exports[`AdvancedSettings should render specific setting if given setting key 1` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test string setting", "displayName": "Test string setting", "isCustom": undefined, @@ -929,6 +958,7 @@ exports[`AdvancedSettings should render specific setting if given setting key 1` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test string setting", "displayName": "Test string setting", "isCustom": undefined, diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap index ae168e76d359..f4d20b456588 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/__snapshots__/field.test.js.snap @@ -50,10 +50,8 @@ exports[`Field for array setting should render as read only if saving is disable > + + { + window.open(links.management[setting.deprecation.docLinksKey], '_blank'); + }} + onClickAriaLabel={i18n.translate( + 'kbn.management.settings.field.deprecationClickAreaLabel', + { + defaultMessage: 'Click to view deprecation documentation for {settingName}.', + values: { + settingName: setting.name, + }, + } + )} + > + Deprecated + + + + + ); + } if (React.isValidElement(setting.description)) { description = setting.description; @@ -582,6 +615,7 @@ export class Field extends PureComponent { return ( + {deprecation} {description} {this.renderDefaultValue(setting)} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/to_editable_config.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/to_editable_config.js index bb561cbe0421..6efb89cfba2b 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/to_editable_config.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/to_editable_config.js @@ -43,6 +43,7 @@ export function toEditableConfig({ def, name, value, isCustom, isOverridden }) { defVal: def.value, type: getValType(def, value), description: def.description, + deprecation: def.deprecation, validation: def.validation && def.validation.regexString ? { diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts b/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts index dcd68a26743a..222b03570897 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/application.ts @@ -63,9 +63,8 @@ export const renderApp = async ( return () => $injector.get('$rootScope').$destroy(); }; -const mainTemplate = (basePath: string) => `
+const mainTemplate = (basePath: string) => `
-
`; @@ -75,7 +74,7 @@ const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react']; function mountVisualizeApp(appBasePath: string, element: HTMLElement) { const mountpoint = document.createElement('div'); - mountpoint.setAttribute('style', 'height: 100%'); + mountpoint.setAttribute('class', 'kbnLocalApplicationWrapper'); mountpoint.innerHTML = mainTemplate(appBasePath); // bootstrap angular into detached element and attach it later to // make angular-within-angular possible diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js index 64653730473c..a80aed5302d1 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js @@ -357,7 +357,10 @@ function VisualizeAppController( }; $scope.showQueryBarTimePicker = () => { - return vis.type.options.showTimePicker; + // tsvb loads without an indexPattern initially (TODO investigate). + // hide timefilter only if timeFieldName is explicitly undefined. + const hasTimeField = $scope.indexPattern ? !!$scope.indexPattern.timeFieldName : true; + return vis.type.options.showTimePicker && hasTimeField; }; $scope.timeRange = timefilter.getTime(); diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/__snapshots__/new_vis_modal.test.tsx.snap index 0b44c7dc4e86..c75fd2096fea 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/__snapshots__/new_vis_modal.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/__snapshots__/new_vis_modal.test.tsx.snap @@ -234,6 +234,26 @@ exports[`NewVisModal filter for visualization types should render as expected 1` />
+
+ +
@@ -565,6 +585,26 @@ exports[`NewVisModal filter for visualization types should render as expected 1` /> +
+ +
@@ -835,6 +875,26 @@ exports[`NewVisModal filter for visualization types should render as expected 1` /> +
+ +
@@ -1139,12 +1199,18 @@ exports[`NewVisModal filter for visualization types should render as expected 1` data-test-subj="filterVisType" fullWidth={true} incremental={false} + isClearable={true} isLoading={false} onChange={[Function]} placeholder="Filter" value="with" >
@@ -1209,6 +1280,50 @@ exports[`NewVisModal filter for visualization types should render as expected 1` +
+ + + + + +
@@ -1594,7 +1709,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` />
@@ -2775,12 +2890,14 @@ exports[`NewVisModal should render as expected 1`] = ` data-test-subj="filterVisType" fullWidth={true} incremental={false} + isClearable={true} isLoading={false} onChange={[Function]} placeholder="Filter" value="" > @@ -3218,7 +3336,7 @@ exports[`NewVisModal should render as expected 1`] = ` /> diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/new_vis_help.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/new_vis_help.test.tsx index 3093499a030c..a33a82c252fb 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/new_vis_help.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/new_vis_help.test.tsx @@ -41,7 +41,7 @@ describe('NewVisHelp', () => { stage: 'production', }, ]} - addBasePath={(url: string) => `testbasepath${url}`} + onPromotionClicked={() => {}} /> ) ).toMatchInlineSnapshot(` @@ -60,9 +60,9 @@ describe('NewVisHelp', () => {

Do it now! diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/new_vis_help.tsx b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/new_vis_help.tsx index 107cbc0e754b..2f7effb7a33c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/new_vis_help.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/new_vis_help.tsx @@ -21,10 +21,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import React, { Fragment } from 'react'; import { EuiText, EuiButton } from '@elastic/eui'; import { VisTypeAliasListEntry } from './type_selection'; +import { VisTypeAlias } from '../../../../../../visualizations/public'; interface Props { promotedTypes: VisTypeAliasListEntry[]; - addBasePath: (path: string) => string; + onPromotionClicked: (visType: VisTypeAlias) => void; } export function NewVisHelp(props: Props) { @@ -42,7 +43,7 @@ export function NewVisHelp(props: Props) { {t.promotion!.description}

props.onPromotionClicked(t)} fill size="s" iconType="popout" diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/type_selection.tsx b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/type_selection.tsx index 28cafde45a71..44da7cc8f2c4 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/type_selection.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/type_selection/type_selection.tsx @@ -154,7 +154,7 @@ class TypeSelection extends React.Component t.promotion)} - addBasePath={this.props.addBasePath} + onPromotionClicked={this.props.onVisTypeSelected} /> )} diff --git a/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable.ts index fc91742c53cc..b7a3a0f000d7 100644 --- a/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize_embeddable/visualize_embeddable.ts @@ -379,6 +379,7 @@ export class VisualizeEmbeddable extends Embeddable `; diff --git a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js index 02b1f5fec9c1..94263e7b76a9 100644 --- a/src/legacy/core_plugins/tests_bundle/tests_entry_template.js +++ b/src/legacy/core_plugins/tests_bundle/tests_entry_template.js @@ -29,16 +29,7 @@ export const createTestEntryTemplate = defaultUiSettings => bundle => ` * */ -// import global polyfills before everything else -import 'core-js/stable'; -import 'regenerator-runtime/runtime'; -import 'custom-event-polyfill'; -import 'whatwg-fetch'; -import 'abortcontroller-polyfill'; -import 'childnode-remove-polyfill'; import fetchMock from 'fetch-mock/es5/client'; -import Symbol_observable from 'symbol-observable'; - import { CoreSystem } from '__kibanaCore__'; // Fake uiCapabilities returned to Core in browser tests @@ -123,7 +114,8 @@ const coreSystem = new CoreSystem({ }, mapConfig: { includeElasticMapsService: true, - manifestServiceUrl: 'https://catalogue-staging.maps.elastic.co/v2/manifest' + emsFileApiUrl: 'https://vector-staging.maps.elastic.co', + emsTileApiUrl: 'https://tiles.maps.elastic.co', }, vegaConfig: { enabled: true, diff --git a/src/legacy/core_plugins/vis_type_timeseries/index.ts b/src/legacy/core_plugins/vis_type_timeseries/index.ts index 9ca14b28e19b..a502bb174bc9 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/index.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/index.ts @@ -32,6 +32,20 @@ const metricsPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPlu styleSheetPaths: resolve(__dirname, 'public/index.scss'), hacks: [resolve(__dirname, 'public/legacy')], injectDefaultVars: server => ({}), + mappings: { + 'tsvb-validation-telemetry': { + properties: { + failedRequests: { + type: 'long', + }, + }, + }, + }, + savedObjectSchemas: { + 'tsvb-validation-telemetry': { + isNamespaceAgnostic: true, + }, + }, }, init: (server: Legacy.Server) => { const visTypeTimeSeriesPlugin = server.newPlatform.setup.plugins diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/__snapshots__/terms.test.js.snap b/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/__snapshots__/terms.test.js.snap index ffd4d08204a7..654e7d9da4dc 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/__snapshots__/terms.test.js.snap +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/splits/__snapshots__/terms.test.js.snap @@ -87,9 +87,6 @@ exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js labelType="label" > @@ -112,9 +109,6 @@ exports[`src/legacy/core_plugins/metrics/public/components/splits/terms.test.js labelType="label" > diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts index 8740f84dab3b..225d81b71b8e 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/public/metrics_fn.ts @@ -31,6 +31,7 @@ type Context = KibanaContext | null; interface Arguments { params: string; uiState: string; + savedObjectId: string | null; } type VisParams = Required; @@ -64,10 +65,16 @@ export const createMetricsFn = (): ExpressionFunction { +export const metricsRequestHandler = async ({ + uiState, + timeRange, + filters, + query, + visParams, + savedObjectId, +}) => { const config = getUISettings(); const timezone = timezoneProvider(config)(); const uiStateObj = uiState.get(visParams.type, {}); @@ -49,6 +56,7 @@ export const metricsRequestHandler = async ({ uiState, timeRange, filters, query filters, panels: [visParams], state: uiStateObj, + savedObjectId: savedObjectId || 'unsaved', }), }); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/init.ts b/src/legacy/core_plugins/vis_type_timeseries/server/init.ts index 7b42ae809801..ae6eebc00fc1 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/init.ts +++ b/src/legacy/core_plugins/vis_type_timeseries/server/init.ts @@ -26,12 +26,17 @@ import { SearchStrategiesRegister } from './lib/search_strategies/search_strateg // @ts-ignore import { getVisData } from './lib/get_vis_data'; import { Framework } from '../../../../plugins/vis_type_timeseries/server'; +import { ValidationTelemetryServiceSetup } from '../../../../plugins/vis_type_timeseries/server'; -export const init = async (framework: Framework, __LEGACY: any) => { +export const init = async ( + framework: Framework, + __LEGACY: any, + validationTelemetry: ValidationTelemetryServiceSetup +) => { const { core } = framework; const router = core.http.createRouter(); - visDataRoutes(router, framework); + visDataRoutes(router, framework, validationTelemetry); // [LEGACY_TODO] fieldsRoutes(__LEGACY.server); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/routes/post_vis_schema.ts b/src/legacy/core_plugins/vis_type_timeseries/server/routes/post_vis_schema.ts new file mode 100644 index 000000000000..3aca50b5b471 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_timeseries/server/routes/post_vis_schema.ts @@ -0,0 +1,247 @@ +/* + * 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 Joi from 'joi'; +const stringOptionalNullable = Joi.string() + .allow('', null) + .optional(); +const stringRequired = Joi.string() + .allow('') + .required(); +const arrayNullable = Joi.array().allow(null); +const numberIntegerOptional = Joi.number() + .integer() + .optional(); +const numberIntegerRequired = Joi.number() + .integer() + .required(); +const numberOptional = Joi.number().optional(); +const numberRequired = Joi.number().required(); +const queryObject = Joi.object({ + language: Joi.string().allow(''), + query: Joi.string().allow(''), +}); + +const annotationsItems = Joi.object({ + color: stringOptionalNullable, + fields: stringOptionalNullable, + hidden: Joi.boolean().optional(), + icon: stringOptionalNullable, + id: stringOptionalNullable, + ignore_global_filters: numberIntegerOptional, + ignore_panel_filters: numberIntegerOptional, + index_pattern: stringOptionalNullable, + query_string: queryObject.optional(), + template: stringOptionalNullable, + time_field: stringOptionalNullable, +}); + +const backgroundColorRulesItems = Joi.object({ + value: Joi.number() + .allow(null) + .optional(), + id: stringOptionalNullable, + background_color: stringOptionalNullable, + color: stringOptionalNullable, +}); + +const gaugeColorRulesItems = Joi.object({ + gauge: stringOptionalNullable, + id: stringOptionalNullable, + operator: stringOptionalNullable, + value: Joi.number(), +}); +const metricsItems = Joi.object({ + field: stringOptionalNullable, + id: stringRequired, + metric_agg: stringOptionalNullable, + numerator: stringOptionalNullable, + denominator: stringOptionalNullable, + sigma: stringOptionalNullable, + function: stringOptionalNullable, + script: stringOptionalNullable, + variables: Joi.array() + .items( + Joi.object({ + field: stringOptionalNullable, + id: stringRequired, + name: stringOptionalNullable, + }) + ) + .optional(), + type: stringRequired, + value: stringOptionalNullable, + values: Joi.array() + .items(Joi.string().allow('', null)) + .allow(null) + .optional(), +}); + +const splitFiltersItems = Joi.object({ + id: stringOptionalNullable, + color: stringOptionalNullable, + filter: Joi.object({ + language: Joi.string().allow(''), + query: Joi.string().allow(''), + }).optional(), + label: stringOptionalNullable, +}); + +const seriesItems = Joi.object({ + aggregate_by: stringOptionalNullable, + aggregate_function: stringOptionalNullable, + axis_position: stringRequired, + axis_max: stringOptionalNullable, + axis_min: stringOptionalNullable, + chart_type: stringRequired, + color: stringRequired, + color_rules: Joi.array() + .items( + Joi.object({ + value: numberOptional, + id: stringRequired, + text: stringOptionalNullable, + operator: stringOptionalNullable, + }) + ) + .optional(), + fill: numberOptional, + filter: Joi.object({ + query: stringRequired, + language: stringOptionalNullable, + }).optional(), + formatter: stringRequired, + hide_in_legend: numberIntegerOptional, + hidden: Joi.boolean().optional(), + id: stringRequired, + label: stringOptionalNullable, + line_width: numberOptional, + metrics: Joi.array().items(metricsItems), + offset_time: stringOptionalNullable, + override_index_pattern: numberOptional, + point_size: numberRequired, + separate_axis: numberIntegerOptional, + seperate_axis: numberIntegerOptional, + series_index_pattern: stringOptionalNullable, + series_time_field: stringOptionalNullable, + series_interval: stringOptionalNullable, + series_drop_last_bucket: numberIntegerOptional, + split_color_mode: stringOptionalNullable, + split_filters: Joi.array() + .items(splitFiltersItems) + .optional(), + split_mode: stringRequired, + stacked: stringRequired, + steps: numberIntegerOptional, + terms_field: stringOptionalNullable, + terms_order_by: stringOptionalNullable, + terms_size: stringOptionalNullable, + terms_direction: stringOptionalNullable, + terms_include: stringOptionalNullable, + terms_exclude: stringOptionalNullable, + time_range_mode: stringOptionalNullable, + trend_arrows: numberOptional, + type: stringOptionalNullable, + value_template: stringOptionalNullable, + var_name: stringOptionalNullable, +}); + +export const visPayloadSchema = Joi.object({ + filters: arrayNullable, + panels: Joi.array().items( + Joi.object({ + annotations: Joi.array() + .items(annotationsItems) + .optional(), + axis_formatter: stringRequired, + axis_position: stringRequired, + axis_scale: stringRequired, + axis_min: stringOptionalNullable, + axis_max: stringOptionalNullable, + bar_color_rules: arrayNullable.optional(), + background_color: stringOptionalNullable, + background_color_rules: Joi.array() + .items(backgroundColorRulesItems) + .optional(), + default_index_pattern: stringOptionalNullable, + default_timefield: stringOptionalNullable, + drilldown_url: stringOptionalNullable, + drop_last_bucket: numberIntegerOptional, + filter: Joi.alternatives( + stringOptionalNullable, + Joi.object({ + language: stringOptionalNullable, + query: stringOptionalNullable, + }) + ), + gauge_color_rules: Joi.array() + .items(gaugeColorRulesItems) + .optional(), + gauge_width: [stringOptionalNullable, numberOptional], + gauge_inner_color: stringOptionalNullable, + gauge_inner_width: Joi.alternatives(stringOptionalNullable, numberIntegerOptional), + gauge_style: stringOptionalNullable, + gauge_max: stringOptionalNullable, + id: stringRequired, + ignore_global_filters: numberOptional, + ignore_global_filter: numberOptional, + index_pattern: stringRequired, + interval: stringRequired, + isModelInvalid: Joi.boolean().optional(), + legend_position: stringOptionalNullable, + markdown: stringOptionalNullable, + markdown_scrollbars: numberIntegerOptional, + markdown_openLinksInNewTab: numberIntegerOptional, + markdown_vertical_align: stringOptionalNullable, + markdown_less: stringOptionalNullable, + markdown_css: stringOptionalNullable, + pivot_id: stringOptionalNullable, + pivot_label: stringOptionalNullable, + pivot_type: stringOptionalNullable, + pivot_rows: stringOptionalNullable, + series: Joi.array() + .items(seriesItems) + .required(), + show_grid: numberIntegerRequired, + show_legend: numberIntegerRequired, + time_field: stringOptionalNullable, + time_range_mode: stringOptionalNullable, + type: stringRequired, + }) + ), + // general + query: Joi.array() + .items(queryObject) + .allow(null) + .required(), + state: Joi.object({ + sort: Joi.object({ + column: stringRequired, + order: Joi.string() + .valid(['asc', 'desc']) + .required(), + }).optional(), + }).required(), + savedObjectId: Joi.string().optional(), + timerange: Joi.object({ + timezone: stringRequired, + min: stringRequired, + max: stringRequired, + }).required(), +}); diff --git a/src/legacy/core_plugins/vis_type_timeseries/server/routes/vis.js b/src/legacy/core_plugins/vis_type_timeseries/server/routes/vis.ts similarity index 59% rename from src/legacy/core_plugins/vis_type_timeseries/server/routes/vis.js rename to src/legacy/core_plugins/vis_type_timeseries/server/routes/vis.ts index d2ded81309ff..32e87f5a3f66 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/server/routes/vis.js +++ b/src/legacy/core_plugins/vis_type_timeseries/server/routes/vis.ts @@ -17,12 +17,22 @@ * under the License. */ +import { IRouter } from 'kibana/server'; import { schema } from '@kbn/config-schema'; import { getVisData } from '../lib/get_vis_data'; +import { visPayloadSchema } from './post_vis_schema'; +import { + Framework, + ValidationTelemetryServiceSetup, +} from '../../../../../plugins/vis_type_timeseries/server'; const escapeHatch = schema.object({}, { allowUnknowns: true }); -export const visDataRoutes = (router, framework) => { +export const visDataRoutes = ( + router: IRouter, + framework: Framework, + { logFailedValidation }: ValidationTelemetryServiceSetup +) => { router.post( { path: '/api/metrics/vis/data', @@ -31,6 +41,16 @@ export const visDataRoutes = (router, framework) => { }, }, async (requestContext, request, response) => { + const { error: validationError } = visPayloadSchema.validate(request.body); + if (validationError) { + logFailedValidation(); + const savedObjectId = + (typeof request.body === 'object' && (request.body as any).savedObjectId) || + 'unavailable'; + framework.logger.warn( + `Request validation error: ${validationError.message} (saved object id: ${savedObjectId}). This most likely means your TSVB visualization contains outdated configuration. You can report this problem under https://github.com/elastic/kibana/issues/new?template=Bug_report.md` + ); + } try { const results = await getVisData(requestContext, request.body, framework); return response.ok({ body: results }); diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_image_512.png b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_image_512.png index 44cd0d320931..cc28886794f0 100644 Binary files a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_image_512.png and b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vega_image_512.png differ diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_graph.hjson b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_graph.hjson index ebdc5a07af06..fd7eb1ae7d87 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_graph.hjson +++ b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_graph.hjson @@ -39,7 +39,7 @@ range: { category: {scheme: "elastic"} } - mark: {color: "#00B3A4"} + mark: {color: "#54B399"} } autosize: {type: "fit", contains: "padding"} } diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_image_256.png b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_image_256.png index 3f247b57905d..8f2d146287b0 100644 Binary files a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_image_256.png and b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_image_256.png differ diff --git a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_image_512.png b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_image_512.png index c387c3ec789d..82077a1096b9 100644 Binary files a/src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_image_512.png and b/src/legacy/core_plugins/vis_type_vega/public/__tests__/vegalite_image_512.png differ diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/vega_parser.js b/src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/vega_parser.js index de2d91322145..50bcff246971 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/vega_parser.js +++ b/src/legacy/core_plugins/vis_type_vega/public/data_model/__tests__/vega_parser.js @@ -53,7 +53,7 @@ describe(`VegaParser._setDefaultColors`, () => { test({}, true, { config: { range: { category: { scheme: 'elastic' } }, - mark: { color: '#00B3A4' }, + mark: { color: '#54B399' }, }, }) ); @@ -63,15 +63,15 @@ describe(`VegaParser._setDefaultColors`, () => { test({}, false, { config: { range: { category: { scheme: 'elastic' } }, - arc: { fill: '#00B3A4' }, - area: { fill: '#00B3A4' }, - line: { stroke: '#00B3A4' }, - path: { stroke: '#00B3A4' }, - rect: { fill: '#00B3A4' }, - rule: { stroke: '#00B3A4' }, - shape: { stroke: '#00B3A4' }, - symbol: { fill: '#00B3A4' }, - trail: { fill: '#00B3A4' }, + arc: { fill: '#54B399' }, + area: { fill: '#54B399' }, + line: { stroke: '#54B399' }, + path: { stroke: '#54B399' }, + rect: { fill: '#54B399' }, + rule: { stroke: '#54B399' }, + shape: { stroke: '#54B399' }, + symbol: { fill: '#54B399' }, + trail: { fill: '#54B399' }, }, }) ); diff --git a/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.js b/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.js index 452397877a00..7c2638d1f516 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.js +++ b/src/legacy/core_plugins/vis_type_vega/public/data_model/vega_parser.js @@ -577,7 +577,7 @@ export class VegaParser { this._setDefaultValue({ scheme: 'elastic' }, 'config', 'range', 'category'); if (this.isVegaLite) { - // Vega-Lite: set default color, works for fill and strike -- config: { mark: { color: '#00B3A4' }} + // Vega-Lite: set default color, works for fill and strike -- config: { mark: { color: '#54B399' }} this._setDefaultValue(defaultColor, 'config', 'mark', 'color'); } else { // Vega - global mark has very strange behavior, must customize each mark type individually diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts index cc2ab133941d..ab1664d612b3 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts @@ -59,7 +59,12 @@ export interface Schemas { [key: string]: any[] | undefined; } -type buildVisFunction = (visState: VisState, schemas: Schemas, uiState: any) => string; +type buildVisFunction = ( + visState: VisState, + schemas: Schemas, + uiState: any, + meta?: { savedObjectId?: string } +) => string; type buildVisConfigFunction = (schemas: Schemas, visParams?: VisParams) => VisParams; interface BuildPipelineVisFunction { @@ -248,11 +253,13 @@ export const buildPipelineVisFunction: BuildPipelineVisFunction = { input_control_vis: visState => { return `input_control_vis ${prepareJson('visConfig', visState.params)}`; }, - metrics: (visState, schemas, uiState = {}) => { + metrics: (visState, schemas, uiState = {}, meta) => { const paramsJson = prepareJson('params', visState.params); const uiStateJson = prepareJson('uiState', uiState); + const savedObjectIdParam = prepareString('savedObjectId', meta?.savedObjectId); - return `tsvb ${paramsJson} ${uiStateJson}`; + const params = [paramsJson, uiStateJson, savedObjectIdParam].filter(param => Boolean(param)); + return `tsvb ${params.join(' ')}`; }, timelion: visState => { const expression = prepareString('expression', visState.params.expression); @@ -488,6 +495,7 @@ export const buildPipeline = async ( params: { searchSource: ISearchSource; timeRange?: any; + savedObjectId?: string; } ) => { const { searchSource } = params; @@ -521,7 +529,9 @@ export const buildPipeline = async ( const schemas = getSchemas(vis, params.timeRange); if (buildPipelineVisFunction[vis.type.name]) { - pipeline += buildPipelineVisFunction[vis.type.name](visState, schemas, uiState); + pipeline += buildPipelineVisFunction[vis.type.name](visState, schemas, uiState, { + savedObjectId: params.savedObjectId, + }); } else if (vislibCharts.includes(vis.type.name)) { const visConfig = visState.params; visConfig.dimensions = await buildVislibDimensions(vis, params); diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index a53e8e0498c4..88a794445870 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -254,7 +254,11 @@ export default () => ) .default([]), }).default(), - manifestServiceUrl: Joi.string().default('https://catalogue.maps.elastic.co/v7.2/manifest'), + manifestServiceUrl: Joi.string() + .default('') + .allow(''), + emsFileApiUrl: Joi.string().default('https://vector-staging.maps.elastic.co'), + emsTileApiUrl: Joi.string().default('https://tiles.maps.elastic.co'), emsLandingPageUrl: Joi.string().default('https://maps.elastic.co/v7.4'), emsFontLibraryUrl: Joi.string().default( 'https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf' diff --git a/src/legacy/ui/public/directives/field_name/field_type_name.ts b/src/legacy/ui/public/directives/field_name/field_type_name.ts index 14376b163d6f..c8c886015cea 100644 --- a/src/legacy/ui/public/directives/field_name/field_type_name.ts +++ b/src/legacy/ui/public/directives/field_name/field_type_name.ts @@ -61,6 +61,10 @@ export function getFieldTypeName(type: string) { return i18n.translate('common.ui.directives.fieldNameIcons.stringFieldAriaLabel', { defaultMessage: 'String field', }); + case 'nested': + return i18n.translate('common.ui.directives.fieldNameIcons.nestedFieldAriaLabel', { + defaultMessage: 'Nested field', + }); default: return i18n.translate('common.ui.directives.fieldNameIcons.unknownFieldAriaLabel', { defaultMessage: 'Unknown field', diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.js.snap b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.js.snap index 463c1bfb975f..1f77660c9784 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.js.snap +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/bytes/__snapshots__/bytes.test.js.snap @@ -44,10 +44,7 @@ exports[`BytesFormatEditor should render normally 1`] = ` labelType="label" > diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/color/__snapshots__/color.test.js.snap b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/color/__snapshots__/color.test.js.snap index d7026df761d9..7e49e93e4cc4 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/color/__snapshots__/color.test.js.snap +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/color/__snapshots__/color.test.js.snap @@ -75,6 +75,7 @@ exports[`ColorFormatEditor should render multiple colors 1`] = ` } noItemsMessage="No items found" responsive={true} + tableLayout="fixed" /> diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/date_nanos/__snapshots__/date_nanos.test.js.snap b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/date_nanos/__snapshots__/date_nanos.test.js.snap index 04d59640554f..cb570144fcee 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/date_nanos/__snapshots__/date_nanos.test.js.snap +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/date_nanos/__snapshots__/date_nanos.test.js.snap @@ -44,11 +44,8 @@ exports[`DateFormatEditor should render normally 1`] = ` labelType="label" > diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/duration/__snapshots__/duration.test.js.snap b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/duration/__snapshots__/duration.test.js.snap index 9722a0198643..ef11d70926ad 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/duration/__snapshots__/duration.test.js.snap +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/duration/__snapshots__/duration.test.js.snap @@ -20,11 +20,7 @@ exports[`DurationFormatEditor should render human readable output normally 1`] = labelType="label" > diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/percent/__snapshots__/percent.test.js.snap b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/percent/__snapshots__/percent.test.js.snap index fea665a918f0..30d1de270522 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/percent/__snapshots__/percent.test.js.snap +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/percent/__snapshots__/percent.test.js.snap @@ -44,10 +44,7 @@ exports[`PercentFormatEditor should render normally 1`] = ` labelType="label" > diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/static_lookup/__snapshots__/static_lookup.test.js.snap b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/static_lookup/__snapshots__/static_lookup.test.js.snap index 2891b99bba30..2bfb0bbd1501 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/static_lookup/__snapshots__/static_lookup.test.js.snap +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/static_lookup/__snapshots__/static_lookup.test.js.snap @@ -59,6 +59,7 @@ exports[`StaticLookupFormatEditor should render multiple lookup entries and unkn "maxWidth": "400px", } } + tableLayout="fixed" />
diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/url/__snapshots__/url.test.js.snap b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/url/__snapshots__/url.test.js.snap index 4b246fecb814..c727f54874db 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/url/__snapshots__/url.test.js.snap +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/url/__snapshots__/url.test.js.snap @@ -26,11 +26,7 @@ exports[`UrlFormatEditor should render label template help 1`] = ` labelType="label" > @@ -116,10 +109,7 @@ exports[`UrlFormatEditor should render label template help 1`] = ` labelType="label" > @@ -157,11 +147,7 @@ exports[`UrlFormatEditor should render normally 1`] = ` labelType="label" > @@ -247,10 +230,7 @@ exports[`UrlFormatEditor should render normally 1`] = ` labelType="label" > @@ -288,11 +268,7 @@ exports[`UrlFormatEditor should render url template help 1`] = ` labelType="label" > @@ -378,10 +351,7 @@ exports[`UrlFormatEditor should render url template help 1`] = ` labelType="label" > @@ -419,11 +389,7 @@ exports[`UrlFormatEditor should render width and height fields if image 1`] = ` labelType="label" > @@ -510,10 +473,7 @@ exports[`UrlFormatEditor should render width and height fields if image 1`] = ` labelType="label" > diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/url/__snapshots__/url_template_flyout.test.js.snap b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/url/__snapshots__/url_template_flyout.test.js.snap index 39189caeedb3..849e307f7b52 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/editors/url/__snapshots__/url_template_flyout.test.js.snap +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/editors/url/__snapshots__/url_template_flyout.test.js.snap @@ -110,6 +110,7 @@ exports[`UrlTemplateFlyout should render normally 1`] = ` } noItemsMessage="No items found" responsive={true} + tableLayout="fixed" />
diff --git a/src/legacy/ui/public/field_editor/components/field_format_editor/samples/__snapshots__/samples.test.js.snap b/src/legacy/ui/public/field_editor/components/field_format_editor/samples/__snapshots__/samples.test.js.snap index 25cbbb7c8684..73a7c1141c60 100644 --- a/src/legacy/ui/public/field_editor/components/field_format_editor/samples/__snapshots__/samples.test.js.snap +++ b/src/legacy/ui/public/field_editor/components/field_format_editor/samples/__snapshots__/samples.test.js.snap @@ -54,6 +54,7 @@ exports[`FormatEditorSamples should render normally 1`] = ` } noItemsMessage="No items found" responsive={true} + tableLayout="fixed" /> `; diff --git a/src/legacy/ui/public/field_editor/field_editor.test.js b/src/legacy/ui/public/field_editor/field_editor.test.js index f811ad916272..cf61b6140f42 100644 --- a/src/legacy/ui/public/field_editor/field_editor.test.js +++ b/src/legacy/ui/public/field_editor/field_editor.test.js @@ -50,9 +50,7 @@ jest.mock('@elastic/eui', () => ({ EuiText: 'eui-text', EuiTextArea: 'eui-textArea', htmlIdGenerator: () => 42, - palettes: { - euiPaletteColorBlind: { colors: ['red'] }, - }, + euiPaletteColorBlind: () => ['red'], })); jest.mock('ui/scripting_languages', () => ({ 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 06424ea48a40..d3f74a540b96 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 @@ -131,6 +131,9 @@ export const npSetup = { featureCatalogue: { register: sinon.fake(), }, + environment: { + update: sinon.fake(), + }, }, }, }; diff --git a/src/legacy/ui/public/styles/bootstrap/_custom_variables.less b/src/legacy/ui/public/styles/bootstrap/_custom_variables.less index aa174684a622..a348e7bfa86b 100644 --- a/src/legacy/ui/public/styles/bootstrap/_custom_variables.less +++ b/src/legacy/ui/public/styles/bootstrap/_custom_variables.less @@ -345,7 +345,7 @@ //** Background color of the whole progress component @progress-bg: shade(@gray-lighter, 13%); //** Default progress bar color -@progress-bar-bg: #00B3A4; +@progress-bar-bg: #54B399; //== List group // diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_files.json b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_files.json index 3ef17ea35352..cdbed7fa0636 100644 --- a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_files.json +++ b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_files.json @@ -24,7 +24,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/world_countries_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/world_countries_v1.geo.json", "legacy_default": true } ], @@ -430,7 +430,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/australia_states_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/australia_states_v1.geo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -629,7 +629,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/canada_provinces_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/canada_provinces_v1.geo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -908,7 +908,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/china_provinces_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/china_provinces_v1.geo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -1266,7 +1266,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/finland_regions_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/finland_regions_v1.geo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -1634,7 +1634,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/france_departments_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/france_departments_v1.geo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -1984,7 +1984,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/germany_states_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/germany_states_v1.geo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -2328,7 +2328,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/ireland_counties_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/ireland_counties_v1.geo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -2637,7 +2637,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/japan_prefectures_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/japan_prefectures_v1.geo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -3003,7 +3003,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/netherlands_provinces_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/netherlands_provinces_v1.geo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -3309,7 +3309,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/norway_counties_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/norway_counties_v1.geo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -3671,7 +3671,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/spain_provinces_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/spain_provinces_v1.geo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -4002,7 +4002,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/sweden_counties_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/sweden_counties_v1.geo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -4311,7 +4311,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/switzerland_cantons_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/switzerland_cantons_v1.geo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -4827,7 +4827,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/uk_subdivisions_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/uk_subdivisions_v1.geo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -5074,7 +5074,7 @@ "formats": [ { "type": "topojson", - "url": "https://vector-staging.maps.elastic.co/files/usa_counties_v2.topo.json?elastic_tile_service_tos=agree", + "url": "/files/usa_counties_v2.topo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -5441,7 +5441,7 @@ "formats": [ { "type": "geojson", - "url": "https://vector-staging.maps.elastic.co/files/usa_states_v1.geo.json?elastic_tile_service_tos=agree", + "url": "/files/usa_states_v1.geo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], @@ -5731,7 +5731,7 @@ "formats": [ { "type": "topojson", - "url": "https://vector-staging.maps.elastic.co/files/usa_zip_codes_v2.topo.json?elastic_tile_service_tos=agree", + "url": "/files/usa_zip_codes_v2.topo.json?elastic_tile_service_tos=agree", "legacy_default": true } ], diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_manifest.json b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_manifest.json index aaf1edbf4860..6030c8068884 100644 --- a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_manifest.json +++ b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_manifest.json @@ -3,13 +3,13 @@ { "id": "tiles_v2", "name": "Elastic Maps Tile Service", - "manifest": "https://tiles.foobar/manifest", + "manifest": "https://tiles.foobar/v7.6/manifest", "type": "tms" }, { "id": "geo_layers", "name": "Elastic Maps Vector Service", - "manifest": "https://files.foobar/manifest", + "manifest": "https://files.foobar/v7.6/manifest", "type": "file" } ] diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright.json b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright.json index 6ea1686dadb8..f757624ffbca 100644 --- a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright.json +++ b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright.json @@ -7,6 +7,6 @@ "bounds": [-180, -85.0511, 180, 85.0511], "format": "png", "type": "baselayer", - "tiles": ["https://raster-style.foobar/styles/osm-bright/{z}/{x}/{y}.png"], + "tiles": ["/raster/styles/osm-bright/{z}/{x}/{y}.png"], "center": [0, 0, 2] } diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright_vector.json b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright_vector.json index b14db5264445..52b70bff6b2a 100644 --- a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright_vector.json +++ b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright_vector.json @@ -41,11 +41,11 @@ "sources": { "openmaptiles": { "type": "vector", - "url": "https://tiles.maps.elastic.co/data/v3.json" + "url": "/data/v3.json" } }, - "sprite": "https://tiles.maps.elastic.co/styles/osm-bright/sprite", - "glyphs": "https://tiles.maps.elastic.co/fonts/{fontstack}/{range}.pbf", + "sprite": "/styles/osm-bright/sprite", + "glyphs": "/fonts/{fontstack}/{range}.pbf", "layers": [ { "id": "background", diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright_vector_source.json b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright_vector_source.json index a32b627dba2c..9961d54028b1 100644 --- a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright_vector_source.json +++ b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_bright_vector_source.json @@ -1,6 +1,6 @@ { "tiles": [ - "https://tiles.maps.elastic.co/data/v3/{z}/{x}/{y}.pbf" + "/data/v3/{z}/{x}/{y}.pbf" ], "name": "OpenMapTiles", "format": "pbf", diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_dark.json b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_dark.json index 9481297b99a2..411d9d59b89c 100644 --- a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_dark.json +++ b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_dark.json @@ -7,6 +7,6 @@ "bounds": [-180, -85.0511, 180, 85.0511], "format": "png", "type": "baselayer", - "tiles": ["https://raster-style.foobar/styles/dark-matter/{z}/{x}/{y}.png"], + "tiles": ["/raster/styles/dark-matter/{z}/{x}/{y}.png"], "center": [0, 0, 2] } diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_desaturated.json b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_desaturated.json index cbbd35d59ce8..c89bbe73b603 100644 --- a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_desaturated.json +++ b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_style_desaturated.json @@ -7,6 +7,6 @@ "bounds": [-180, -85.0511, 180, 85.0511], "format": "png", "type": "baselayer", - "tiles": ["https://raster-style.foobar/styles/osm-bright-desaturated/{z}/{x}/{y}.png"], + "tiles": ["/raster/styles/osm-bright-desaturated/{z}/{x}/{y}.png"], "center": [0, 0, 2] } diff --git a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_tiles.json b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_tiles.json index 9df72817bb94..c038bb411dae 100644 --- a/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_tiles.json +++ b/src/legacy/ui/public/vis/__tests__/map/ems_mocks/sample_tiles.json @@ -19,12 +19,12 @@ { "locale": "en", "format": "vector", - "url": "https://vector-style.foobar/styles/osm-bright/style.json" + "url": "/v7.6/styles/osm-bright/style.json" }, { "locale": "en", "format": "raster", - "url": "https://raster-style.foobar/styles/osm-bright.json" + "url": "/v7.6/styles/osm-bright.json" } ] }, @@ -47,12 +47,12 @@ { "locale": "en", "format": "vector", - "url": "https://vector-style.foobar/styles/osm-bright-desaturated/style.json" + "url": "/v7.6/styles/osm-bright-desaturated/style.json" }, { "locale": "en", "format": "raster", - "url": "https://raster-style.foobar/styles/osm-bright-desaturated.json" + "url": "/v7.6/styles/osm-bright-desaturated.json" } ] }, @@ -75,12 +75,12 @@ { "locale": "en", "format": "vector", - "url": "https://vector-style.foobar/styles/dark-matter/style.json" + "url": "/v7.6/styles/dark-matter/style.json" }, { "locale": "en", "format": "raster", - "url": "https://raster-style.foobar/styles/dark-matter.json" + "url": "/v7.6/styles/dark-matter.json" } ] } diff --git a/src/legacy/ui/public/vis/__tests__/map/service_settings.js b/src/legacy/ui/public/vis/__tests__/map/service_settings.js index 820b66897aff..61925760457c 100644 --- a/src/legacy/ui/public/vis/__tests__/map/service_settings.js +++ b/src/legacy/ui/public/vis/__tests__/map/service_settings.js @@ -21,7 +21,6 @@ import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import url from 'url'; -import EMS_CATALOGUE from './ems_mocks/sample_manifest.json'; import EMS_FILES from './ems_mocks/sample_files.json'; import EMS_TILES from './ems_mocks/sample_tiles.json'; import EMS_STYLE_ROAD_MAP_BRIGHT from './ems_mocks/sample_style_bright'; @@ -34,14 +33,18 @@ describe('service_settings (FKA tilemaptest)', function() { let mapConfig; let tilemapsConfig; - const manifestUrl = 'https://foobar/manifest'; - const manifestUrl2 = 'https://foobar_override/v1/manifest'; + const emsFileApiUrl = 'https://files.foobar'; + const emsTileApiUrl = 'https://tiles.foobar'; + + const emsTileApiUrl2 = 'https://tiles_override.foobar'; + const emsFileApiUrl2 = 'https://files_override.foobar'; beforeEach( ngMock.module('kibana', $provide => { $provide.decorator('mapConfig', () => { return { - manifestServiceUrl: manifestUrl, + emsFileApiUrl, + emsTileApiUrl, includeElasticMapsService: true, emsTileLayerId: { bright: 'road_map', @@ -53,7 +56,8 @@ describe('service_settings (FKA tilemaptest)', function() { }) ); - let manifestServiceUrlOriginal; + let emsTileApiUrlOriginal; + let emsFileApiUrlOriginal; let tilemapsConfigDeprecatedOriginal; let getManifestStub; beforeEach( @@ -61,26 +65,26 @@ describe('service_settings (FKA tilemaptest)', function() { serviceSettings = $injector.get('serviceSettings'); getManifestStub = serviceSettings.__debugStubManifestCalls(async url => { //simulate network calls - if (url.startsWith('https://foobar')) { - return EMS_CATALOGUE; - } else if (url.startsWith('https://tiles.foobar')) { - return EMS_TILES; - } else if (url.startsWith('https://files.foobar')) { - return EMS_FILES; - } else if (url.startsWith('https://raster-style.foobar')) { - if (url.includes('osm-bright-desaturated')) { + if (url.startsWith('https://tiles.foobar')) { + if (url.includes('/manifest')) { + return EMS_TILES; + } else if (url.includes('osm-bright-desaturated.json')) { return EMS_STYLE_ROAD_MAP_DESATURATED; - } else if (url.includes('osm-bright')) { + } else if (url.includes('osm-bright.json')) { return EMS_STYLE_ROAD_MAP_BRIGHT; - } else if (url.includes('dark-matter')) { + } else if (url.includes('dark-matter.json')) { return EMS_STYLE_DARK_MAP; } + } else if (url.startsWith('https://files.foobar')) { + return EMS_FILES; } }); mapConfig = $injector.get('mapConfig'); tilemapsConfig = $injector.get('tilemapsConfig'); - manifestServiceUrlOriginal = mapConfig.manifestServiceUrl; + emsTileApiUrlOriginal = mapConfig.emsTileApiUrl; + emsFileApiUrlOriginal = mapConfig.emsFileApiUrl; + tilemapsConfigDeprecatedOriginal = tilemapsConfig.deprecated; $rootScope.$digest(); }) @@ -88,7 +92,8 @@ describe('service_settings (FKA tilemaptest)', function() { afterEach(function() { getManifestStub.removeStub(); - mapConfig.manifestServiceUrl = manifestServiceUrlOriginal; + mapConfig.emsTileApiUrl = emsTileApiUrlOriginal; + mapConfig.emsFileApiUrl = emsFileApiUrlOriginal; tilemapsConfig.deprecated = tilemapsConfigDeprecatedOriginal; }); @@ -110,7 +115,7 @@ describe('service_settings (FKA tilemaptest)', function() { expect(attrs.url).to.contain('{z}'); const urlObject = url.parse(attrs.url, true); - expect(urlObject.hostname).to.be('raster-style.foobar'); + expect(urlObject.hostname).to.be('tiles.foobar'); expect(urlObject.query).to.have.property('my_app_name', 'kibana'); expect(urlObject.query).to.have.property('elastic_tile_service_tos', 'agree'); expect(urlObject.query).to.have.property('my_app_version'); @@ -161,7 +166,8 @@ describe('service_settings (FKA tilemaptest)', function() { }); it('when overridden, should continue to work', async () => { - mapConfig.manifestServiceUrl = manifestUrl2; + mapConfig.emsFileApiUrl = emsFileApiUrl2; + mapConfig.emsTileApiUrl = emsTileApiUrl2; serviceSettings.addQueryParams({ foo: 'bar' }); tilemapServices = await serviceSettings.getTMSServices(); await assertQuery({ foo: 'bar' }); @@ -187,11 +193,11 @@ describe('service_settings (FKA tilemaptest)', function() { id: 'road_map', name: 'Road Map - Bright', url: - 'https://raster-style.foobar/styles/osm-bright/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=1.2.3', + 'https://tiles.foobar/raster/styles/osm-bright/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=1.2.3', minZoom: 0, maxZoom: 10, attribution: - '

OpenStreetMap contributors | OpenMapTiles | MapTiler | Elastic Maps Service

', + 'OpenStreetMap contributors | OpenMapTiles | MapTiler | Elastic Maps Service', subdomains: [], }, ]; @@ -233,19 +239,19 @@ describe('service_settings (FKA tilemaptest)', function() { ); expect(desaturationFalse.url).to.equal( - 'https://raster-style.foobar/styles/osm-bright/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=1.2.3' + 'https://tiles.foobar/raster/styles/osm-bright/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=1.2.3' ); expect(desaturationFalse.maxZoom).to.equal(10); expect(desaturationTrue.url).to.equal( - 'https://raster-style.foobar/styles/osm-bright-desaturated/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=1.2.3' + 'https://tiles.foobar/raster/styles/osm-bright-desaturated/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=1.2.3' ); expect(desaturationTrue.maxZoom).to.equal(18); expect(darkThemeDesaturationFalse.url).to.equal( - 'https://raster-style.foobar/styles/dark-matter/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=1.2.3' + 'https://tiles.foobar/raster/styles/dark-matter/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=1.2.3' ); expect(darkThemeDesaturationFalse.maxZoom).to.equal(22); expect(darkThemeDesaturationTrue.url).to.equal( - 'https://raster-style.foobar/styles/dark-matter/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=1.2.3' + 'https://tiles.foobar/raster/styles/dark-matter/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=kibana&my_app_version=1.2.3' ); expect(darkThemeDesaturationTrue.maxZoom).to.equal(22); }); diff --git a/src/legacy/ui/public/vis/editors/default/controls/__snapshots__/metric_agg.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/controls/__snapshots__/metric_agg.test.tsx.snap index a176295260c4..b51c25952580 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/__snapshots__/metric_agg.test.tsx.snap +++ b/src/legacy/ui/public/vis/editors/default/controls/__snapshots__/metric_agg.test.tsx.snap @@ -16,9 +16,7 @@ exports[`MetricAggParamEditor should be rendered with default set of props 1`] = compressed={true} data-test-subj="visEditorSubAggMetric1" fullWidth={true} - hasNoInitialSelection={false} isInvalid={false} - isLoading={false} onChange={[Function]} options={ Array [ diff --git a/src/legacy/ui/public/vis/editors/default/controls/__snapshots__/top_aggregate.test.tsx.snap b/src/legacy/ui/public/vis/editors/default/controls/__snapshots__/top_aggregate.test.tsx.snap index 74c952dbf059..b3a2c058de97 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/__snapshots__/top_aggregate.test.tsx.snap +++ b/src/legacy/ui/public/vis/editors/default/controls/__snapshots__/top_aggregate.test.tsx.snap @@ -31,9 +31,7 @@ exports[`TopAggregateParamEditor should init with the default set of props 1`] = data-test-subj="visDefaultEditorAggregateWith" disabled={false} fullWidth={true} - hasNoInitialSelection={false} isInvalid={false} - isLoading={false} onBlur={[MockFunction]} onChange={[Function]} options={ diff --git a/src/legacy/ui/public/vis/editors/default/controls/precision.tsx b/src/legacy/ui/public/vis/editors/default/controls/precision.tsx index 88f389cb7c00..4fe9eede8465 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/precision.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/precision.tsx @@ -40,7 +40,7 @@ function PrecisionParamEditor({ agg, value, setValue }: AggParamEditorProps | React.MouseEvent) => setValue(Number(ev.currentTarget.value)) } diff --git a/src/legacy/ui/public/vis/map/service_settings.js b/src/legacy/ui/public/vis/map/service_settings.js index 1096aa8eb450..233ee526c439 100644 --- a/src/legacy/ui/public/vis/map/service_settings.js +++ b/src/legacy/ui/public/vis/map/service_settings.js @@ -48,7 +48,8 @@ uiModules this._emsClient = new EMSClient({ language: i18n.getLocale(), kbnVersion: kbnVersion, - manifestServiceUrl: mapConfig.manifestServiceUrl, + fileApiUrl: mapConfig.emsFileApiUrl, + tileApiUrl: mapConfig.emsTileApiUrl, htmlSanitizer: $sanitize, landingPageUrl: mapConfig.emsLandingPageUrl, }); diff --git a/src/legacy/ui/public/vislib/visualizations/point_series/_point_series.js b/src/legacy/ui/public/vislib/visualizations/point_series/_point_series.js index b9f50b6d941e..b882048a6b26 100644 --- a/src/legacy/ui/public/vislib/visualizations/point_series/_point_series.js +++ b/src/legacy/ui/public/vislib/visualizations/point_series/_point_series.js @@ -18,14 +18,14 @@ */ import _ from 'lodash'; -import { palettes } from '@elastic/eui/lib/services'; +import { euiPaletteColorBlind } from '@elastic/eui/lib/services'; const thresholdLineDefaults = { show: false, value: 10, width: 1, style: 'full', - color: palettes.euiPaletteColorBlind.colors[9], + color: euiPaletteColorBlind()[9], }; export class PointSeries { diff --git a/src/legacy/ui/ui_bundles/app_entry_template.js b/src/legacy/ui/ui_bundles/app_entry_template.js index f0ca97f47332..a1c3a153a196 100644 --- a/src/legacy/ui/ui_bundles/app_entry_template.js +++ b/src/legacy/ui/ui_bundles/app_entry_template.js @@ -28,14 +28,6 @@ export const appEntryTemplate = bundle => ` * context: ${bundle.getContext()} */ -// import global polyfills -import Symbol_observable from 'symbol-observable'; -import 'core-js/stable'; -import 'regenerator-runtime/runtime'; -import 'custom-event-polyfill'; -import 'whatwg-fetch'; -import 'abortcontroller-polyfill'; -import 'childnode-remove-polyfill'; ${apmImport()} import { i18n } from '@kbn/i18n'; import { CoreSystem } from '__kibanaCore__' diff --git a/src/legacy/ui/ui_render/bootstrap/template.js.hbs b/src/legacy/ui/ui_render/bootstrap/template.js.hbs index 85b6de26b951..72dd97ff5864 100644 --- a/src/legacy/ui/ui_render/bootstrap/template.js.hbs +++ b/src/legacy/ui/ui_render/bootstrap/template.js.hbs @@ -13,7 +13,10 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { window.onload = function () { var files = [ - '{{dllBundlePath}}/vendors.bundle.dll.js', + '{{dllBundlePath}}/vendors_runtime.bundle.dll.js', + {{#each dllJsChunks}} + '{{this}}', + {{/each}} '{{regularBundlePath}}/kbn-ui-shared-deps/{{sharedDepsFilename}}', '{{regularBundlePath}}/commons.bundle.js', '{{regularBundlePath}}/{{appId}}.bundle.js' diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index a935270d23fc..4158af19bd85 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -26,6 +26,7 @@ import { AppBootstrap } from './bootstrap'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { fromRoot } from '../../../core/server/utils'; import { getApmConfig } from '../apm'; +import { DllCompiler } from '../../../optimize/dynamic_dll_plugin'; /** * @typedef {import('../../server/kbn_server').default} KbnServer @@ -103,8 +104,14 @@ export function uiRenderMixin(kbnServer, server, config) { const basePath = config.get('server.basePath'); const regularBundlePath = `${basePath}/bundles`; const dllBundlePath = `${basePath}/built_assets/dlls`; + const dllStyleChunks = DllCompiler.getRawDllConfig().chunks.map( + chunk => `${dllBundlePath}/vendors${chunk}.style.dll.css` + ); + const dllJsChunks = DllCompiler.getRawDllConfig().chunks.map( + chunk => `${dllBundlePath}/vendors${chunk}.bundle.dll.js` + ); const styleSheetPaths = [ - `${dllBundlePath}/vendors.style.dll.css`, + ...dllStyleChunks, ...(darkMode ? [ `${basePath}/bundles/kbn-ui-shared-deps/${UiSharedDeps.darkCssDistFilename}`, @@ -132,6 +139,7 @@ export function uiRenderMixin(kbnServer, server, config) { appId: isCore ? 'core' : app.getId(), regularBundlePath, dllBundlePath, + dllJsChunks, styleSheetPaths, sharedDepsFilename: UiSharedDeps.distFilename, }, diff --git a/src/optimize/dynamic_dll_plugin/dll_compiler.js b/src/optimize/dynamic_dll_plugin/dll_compiler.js index 7d558367c032..9889c1f71c3b 100644 --- a/src/optimize/dynamic_dll_plugin/dll_compiler.js +++ b/src/optimize/dynamic_dll_plugin/dll_compiler.js @@ -23,6 +23,11 @@ import { notInNodeModules, inDllPluginPublic, } from './dll_allowed_modules'; +import { + dllEntryFileContentArrayToString, + dllEntryFileContentStringToArray, + dllMergeAllEntryFilesContent, +} from './dll_entry_template'; import { fromRoot } from '../../core/server/utils'; import { PUBLIC_PATH_PLACEHOLDER } from '../public_path_placeholder'; import fs from 'fs'; @@ -30,6 +35,8 @@ import webpack from 'webpack'; import { promisify } from 'util'; import path from 'path'; import del from 'del'; +import { chunk } from 'lodash'; +import seedrandom from 'seedrandom'; const readFileAsync = promisify(fs.readFile); const mkdirAsync = promisify(fs.mkdir); @@ -37,11 +44,17 @@ const accessAsync = promisify(fs.access); const writeFileAsync = promisify(fs.writeFile); export class DllCompiler { - static getRawDllConfig(uiBundles = {}, babelLoaderCacheDir = '', threadLoaderPoolConfig = {}) { + static getRawDllConfig( + uiBundles = {}, + babelLoaderCacheDir = '', + threadLoaderPoolConfig = {}, + chunks = Array.from(Array(4).keys()).map(chunkN => `_${chunkN}`) + ) { return { uiBundles, babelLoaderCacheDir, threadLoaderPoolConfig, + chunks, context: fromRoot('.'), entryName: 'vendors', dllName: '[name]', @@ -66,13 +79,49 @@ export class DllCompiler { } async init() { - await this.ensureEntryFileExists(); - await this.ensureManifestFileExists(); + await this.ensureEntryFilesExists(); + await this.ensureManifestFilesExists(); await this.ensureOutputPathExists(); } - async upsertEntryFile(content) { - await this.upsertFile(this.getEntryPath(), content); + seededShuffle(array) { + // Implementation based on https://github.com/TimothyGu/knuth-shuffle-seeded/blob/gh-pages/index.js#L46 + let currentIndex; + let temporaryValue; + let randomIndex; + const rand = seedrandom('predictable', { global: false }); + + if (array.constructor !== Array) throw new Error('Input is not an array'); + currentIndex = array.length; + + // While there remain elements to shuffle... + while (0 !== currentIndex) { + // Pick a remaining element... + randomIndex = Math.floor(rand() * currentIndex--); + + // And swap it with the current element. + temporaryValue = array[currentIndex]; + array[currentIndex] = array[randomIndex]; + array[randomIndex] = temporaryValue; + } + + return array; + } + + async upsertEntryFiles(content) { + const arrayContent = this.seededShuffle(dllEntryFileContentStringToArray(content)); + const chunks = chunk( + arrayContent, + Math.ceil(arrayContent.length / this.rawDllConfig.chunks.length) + ); + const entryPaths = this.getEntryPaths(); + + await Promise.all( + entryPaths.map( + async (entryPath, idx) => + await this.upsertFile(entryPath, dllEntryFileContentArrayToString(chunks[idx])) + ) + ); } async upsertFile(filePath, content = '') { @@ -80,38 +129,57 @@ export class DllCompiler { await writeFileAsync(filePath, content, 'utf8'); } - getDllPath() { - return this.resolvePath(`${this.rawDllConfig.entryName}${this.rawDllConfig.dllExt}`); + getDllPaths() { + return this.rawDllConfig.chunks.map(chunk => + this.resolvePath(`${this.rawDllConfig.entryName}${chunk}${this.rawDllConfig.dllExt}`) + ); } - getEntryPath() { - return this.resolvePath(`${this.rawDllConfig.entryName}${this.rawDllConfig.entryExt}`); + getEntryPaths() { + return this.rawDllConfig.chunks.map(chunk => + this.resolvePath(`${this.rawDllConfig.entryName}${chunk}${this.rawDllConfig.entryExt}`) + ); } - getManifestPath() { - return this.resolvePath(`${this.rawDllConfig.entryName}${this.rawDllConfig.manifestExt}`); + getManifestPaths() { + return this.rawDllConfig.chunks.map(chunk => + this.resolvePath(`${this.rawDllConfig.entryName}${chunk}${this.rawDllConfig.manifestExt}`) + ); } - getStylePath() { - return this.resolvePath(`${this.rawDllConfig.entryName}${this.rawDllConfig.styleExt}`); + getStylePaths() { + return this.rawDllConfig.chunks.map(chunk => + this.resolvePath(`${this.rawDllConfig.entryName}${chunk}${this.rawDllConfig.styleExt}`) + ); } - async ensureEntryFileExists() { - await this.ensureFileExists(this.getEntryPath()); + async ensureEntryFilesExists() { + const entryPaths = this.getEntryPaths(); + + await Promise.all(entryPaths.map(async entryPath => await this.ensureFileExists(entryPath))); } - async ensureManifestFileExists() { - await this.ensureFileExists( - this.getManifestPath(), - JSON.stringify({ - name: this.rawDllConfig.entryName, - content: {}, - }) + async ensureManifestFilesExists() { + const manifestPaths = this.getManifestPaths(); + + await Promise.all( + manifestPaths.map( + async (manifestPath, idx) => + await this.ensureFileExists( + manifestPath, + JSON.stringify({ + name: `${this.rawDllConfig.entryName}${this.rawDllConfig.chunks[idx]}`, + content: {}, + }) + ) + ) ); } async ensureStyleFileExists() { - await this.ensureFileExists(this.getStylePath()); + const stylePaths = this.getStylePaths(); + + await Promise.all(stylePaths.map(async stylePath => await this.ensureFileExists(stylePath))); } async ensureFileExists(filePath, content) { @@ -137,8 +205,10 @@ export class DllCompiler { await this.ensurePathExists(this.rawDllConfig.outputPath); } - dllExistsSync() { - return this.existsSync(this.getDllPath()); + dllsExistsSync() { + const dllPaths = this.getDllPaths(); + + return dllPaths.every(dllPath => this.existsSync(dllPath)); } existsSync(filePath) { @@ -149,8 +219,16 @@ export class DllCompiler { return path.resolve(this.rawDllConfig.outputPath, ...arguments); } - async readEntryFile() { - return await this.readFile(this.getEntryPath()); + async readEntryFiles() { + const entryPaths = this.getEntryPaths(); + + const entryFilesContent = await Promise.all( + entryPaths.map(async entryPath => await this.readFile(entryPath)) + ); + + // merge all the module contents from entry files again into + // sorted single one + return dllMergeAllEntryFilesContent(entryFilesContent); } async readFile(filePath, content) { @@ -160,7 +238,7 @@ export class DllCompiler { async run(dllEntries) { const dllConfig = this.dllConfigGenerator(this.rawDllConfig); - await this.upsertEntryFile(dllEntries); + await this.upsertEntryFiles(dllEntries); try { this.logWithMetadata( @@ -234,7 +312,7 @@ export class DllCompiler { // ignore if this module represents the // dll entry file - if (module.resource === this.getEntryPath()) { + if (this.getEntryPaths().includes(module.resource)) { return; } @@ -259,7 +337,6 @@ export class DllCompiler { // node_module or no? if (notInNodeModules(reason.module.resource)) { notAllowedModules.push(module.resource); - return; } }); } diff --git a/src/optimize/dynamic_dll_plugin/dll_config_model.js b/src/optimize/dynamic_dll_plugin/dll_config_model.js index ecf5def5aa6c..c7ab2fe30dd1 100644 --- a/src/optimize/dynamic_dll_plugin/dll_config_model.js +++ b/src/optimize/dynamic_dll_plugin/dll_config_model.js @@ -140,6 +140,13 @@ function generateDLL(config) { filename: dllStyleFilename, }), ], + // Single runtime for the dll bundles which assures that common transient dependencies won't be evaluated twice. + // The module cache will be shared, even when module code may be duplicated across chunks. + optimization: { + runtimeChunk: { + name: 'vendors_runtime', + }, + }, performance: { // NOTE: we are disabling this as those hints // are more tailored for the final bundles result @@ -158,6 +165,7 @@ function extendRawConfig(rawConfig) { const dllNoParseRules = rawConfig.uiBundles.getWebpackNoParseRules(); const dllDevMode = rawConfig.uiBundles.isDevMode(); const dllContext = rawConfig.context; + const dllChunks = rawConfig.chunks; const dllEntry = {}; const dllEntryName = rawConfig.entryName; const dllBundleName = rawConfig.dllName; @@ -176,7 +184,12 @@ function extendRawConfig(rawConfig) { const threadLoaderPoolConfig = rawConfig.threadLoaderPoolConfig; // Create webpack entry object key with the provided dllEntryName - dllEntry[dllEntryName] = [`${dllOutputPath}/${dllEntryName}${dllEntryExt}`]; + dllChunks.reduce((dllEntryObj, chunk) => { + dllEntryObj[`${dllEntryName}${chunk}`] = [ + `${dllOutputPath}/${dllEntryName}${chunk}${dllEntryExt}`, + ]; + return dllEntryObj; + }, dllEntry); // Export dll config map return { diff --git a/src/optimize/dynamic_dll_plugin/dll_entry_template.js b/src/optimize/dynamic_dll_plugin/dll_entry_template.js index 584bf0c9e3d3..0c286896d0b7 100644 --- a/src/optimize/dynamic_dll_plugin/dll_entry_template.js +++ b/src/optimize/dynamic_dll_plugin/dll_entry_template.js @@ -23,3 +23,19 @@ export function dllEntryTemplate(requirePaths = []) { .sort() .join('\n'); } + +export function dllEntryFileContentStringToArray(content = '') { + return content.split('\n'); +} + +export function dllEntryFileContentArrayToString(content = []) { + return content.join('\n'); +} + +export function dllMergeAllEntryFilesContent(content = []) { + return content + .join('\n') + .split('\n') + .sort() + .join('\n'); +} diff --git a/src/optimize/dynamic_dll_plugin/dynamic_dll_plugin.js b/src/optimize/dynamic_dll_plugin/dynamic_dll_plugin.js index cb941d2ba568..484c7dfbfd59 100644 --- a/src/optimize/dynamic_dll_plugin/dynamic_dll_plugin.js +++ b/src/optimize/dynamic_dll_plugin/dynamic_dll_plugin.js @@ -44,7 +44,7 @@ export class DynamicDllPlugin { async init() { await this.dllCompiler.init(); - this.entryPaths = await this.dllCompiler.readEntryFile(); + this.entryPaths = await this.dllCompiler.readEntryFiles(); } apply(compiler) { @@ -70,12 +70,14 @@ export class DynamicDllPlugin { bindDllReferencePlugin(compiler) { const rawDllConfig = this.dllCompiler.rawDllConfig; const dllContext = rawDllConfig.context; - const dllManifestPath = this.dllCompiler.getManifestPath(); + const dllManifestPaths = this.dllCompiler.getManifestPaths(); - new webpack.DllReferencePlugin({ - context: dllContext, - manifest: dllManifestPath, - }).apply(compiler); + dllManifestPaths.forEach(dllChunkManifestPath => { + new webpack.DllReferencePlugin({ + context: dllContext, + manifest: dllChunkManifestPath, + }).apply(compiler); + }); } registerInitBasicHooks(compiler) { @@ -192,7 +194,7 @@ export class DynamicDllPlugin { // then will be set to false compilation.needsDLLCompilation = this.afterCompilationEntryPaths !== this.entryPaths || - !this.dllCompiler.dllExistsSync() || + !this.dllCompiler.dllsExistsSync() || (this.isToForceDLLCreation() && this.performedCompilations === 0); this.entryPaths = this.afterCompilationEntryPaths; @@ -337,7 +339,9 @@ export class DynamicDllPlugin { // We need to purge the cache into the inputFileSystem // for every single built in previous compilation // that we rely in next ones. - mainCompiler.inputFileSystem.purge(this.dllCompiler.getManifestPath()); + this.dllCompiler + .getManifestPaths() + .forEach(chunkDllManifestPath => mainCompiler.inputFileSystem.purge(chunkDllManifestPath)); this.performedCompilations++; diff --git a/src/optimize/watch/watch_cache.ts b/src/optimize/watch/watch_cache.ts index 15957210b3d4..b6784c1734a1 100644 --- a/src/optimize/watch/watch_cache.ts +++ b/src/optimize/watch/watch_cache.ts @@ -170,22 +170,20 @@ export class WatchCache { * very large folders (with 84K+ files) cause a stack overflow. */ async function recursiveDelete(directory: string) { - const entries = await readdirAsync(directory, { withFileTypes: true }); - await Promise.all( - entries.map(entry => { - const absolutePath = path.join(directory, entry.name); - const result = entry.isDirectory() - ? recursiveDelete(absolutePath) - : unlinkAsync(absolutePath); - - // Ignore errors, if the file or directory doesn't exist. - return result.catch(e => { - if (e.code !== 'ENOENT') { - throw e; - } - }); - }) - ); - - return rmdirAsync(directory); + try { + const entries = await readdirAsync(directory, { withFileTypes: true }); + + await Promise.all( + entries.map(entry => { + const absolutePath = path.join(directory, entry.name); + return entry.isDirectory() ? recursiveDelete(absolutePath) : unlinkAsync(absolutePath); + }) + ); + + return rmdirAsync(directory); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + } } diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap index 7ab7d7653eb5..6432f8049641 100644 --- a/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap @@ -170,6 +170,9 @@ exports[`LanguageSwitcher should toggle off if language is lucene 1`] = ` "logstash": Object { "base": "https://www.elastic.co/guide/en/logstash/mocked-test-branch", }, + "management": Object { + "kibanaSearchSettings": "https://www.elastic.co/guide/en/kibana/mocked-test-branch/advanced-options.html#kibana-search-settings", + }, "metricbeat": Object { "base": "https://www.elastic.co/guide/en/beats/metricbeat/mocked-test-branch", }, @@ -186,7 +189,10 @@ exports[`LanguageSwitcher should toggle off if language is lucene 1`] = ` "scriptAggs": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-aggregations.html#_values_source", "scriptFields": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-request-script-fields.html", }, - "siem": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html", + "siem": Object { + "gettingStarted": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/install-siem.html", + "guide": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html", + }, "winlogbeat": Object { "base": "https://www.elastic.co/guide/en/beats/winlogbeat/mocked-test-branch", }, @@ -460,6 +466,9 @@ exports[`LanguageSwitcher should toggle on if language is kuery 1`] = ` "logstash": Object { "base": "https://www.elastic.co/guide/en/logstash/mocked-test-branch", }, + "management": Object { + "kibanaSearchSettings": "https://www.elastic.co/guide/en/kibana/mocked-test-branch/advanced-options.html#kibana-search-settings", + }, "metricbeat": Object { "base": "https://www.elastic.co/guide/en/beats/metricbeat/mocked-test-branch", }, @@ -476,7 +485,10 @@ exports[`LanguageSwitcher should toggle on if language is kuery 1`] = ` "scriptAggs": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-aggregations.html#_values_source", "scriptFields": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-request-script-fields.html", }, - "siem": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html", + "siem": Object { + "gettingStarted": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/install-siem.html", + "guide": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html", + }, "winlogbeat": Object { "base": "https://www.elastic.co/guide/en/beats/winlogbeat/mocked-test-branch", }, diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap index 6f5f9b395618..1fb39710f675 100644 --- a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap @@ -276,6 +276,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "logstash": Object { "base": "https://www.elastic.co/guide/en/logstash/mocked-test-branch", }, + "management": Object { + "kibanaSearchSettings": "https://www.elastic.co/guide/en/kibana/mocked-test-branch/advanced-options.html#kibana-search-settings", + }, "metricbeat": Object { "base": "https://www.elastic.co/guide/en/beats/metricbeat/mocked-test-branch", }, @@ -292,7 +295,10 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "scriptAggs": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-aggregations.html#_values_source", "scriptFields": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-request-script-fields.html", }, - "siem": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html", + "siem": Object { + "gettingStarted": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/install-siem.html", + "guide": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html", + }, "winlogbeat": Object { "base": "https://www.elastic.co/guide/en/beats/winlogbeat/mocked-test-branch", }, @@ -896,6 +902,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "logstash": Object { "base": "https://www.elastic.co/guide/en/logstash/mocked-test-branch", }, + "management": Object { + "kibanaSearchSettings": "https://www.elastic.co/guide/en/kibana/mocked-test-branch/advanced-options.html#kibana-search-settings", + }, "metricbeat": Object { "base": "https://www.elastic.co/guide/en/beats/metricbeat/mocked-test-branch", }, @@ -912,7 +921,10 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "scriptAggs": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-aggregations.html#_values_source", "scriptFields": "https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/search-request-script-fields.html", }, - "siem": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html", + "siem": Object { + "gettingStarted": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/install-siem.html", + "guide": "https://www.elastic.co/guide/en/siem/guide/mocked-test-branch/index.html", + }, "winlogbeat": Object { "base": "https://www.elastic.co/guide/en/beats/winlogbeat/mocked-test-branch", }, @@ -1070,11 +1082,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA aria-label="Start typing to search and filter the test page" autoComplete="off" autoFocus={false} - compressed={false} data-test-subj="queryInput" fullWidth={true} inputRef={[Function]} - isLoading={false} onChange={[Function]} onClick={[Function]} onKeyDown={[Function]} @@ -1092,9 +1102,7 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA onSelectLanguage={[Function]} /> } - compressed={false} fullWidth={true} - isLoading={false} >
- +
} - compressed={false} fullWidth={true} - isLoading={false} >
- +
} - compressed={false} fullWidth={true} - isLoading={false} >
- +
; intl: InjectedIntl; isLoading?: boolean; - prepend?: React.ReactNode; + prepend?: React.ComponentProps['prepend']; showQueryInput?: boolean; showDatePicker?: boolean; dateRangeFrom?: string; diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx index 16b22a164f2f..960a843f98ab 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.tsx @@ -58,7 +58,7 @@ interface Props { query: Query; disableAutoFocus?: boolean; screenTitle?: string; - prepend?: React.ReactNode; + prepend?: React.ComponentProps['prepend']; persistedLog?: PersistedLog; bubbleSubmitEvent?: boolean; placeholder?: string; diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js index 0d787fa56b40..3ec903d5b18e 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.test.js @@ -37,7 +37,7 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { describe('conflicts', () => { it('returns a field for each in response, no filtering', () => { const fields = readFieldCapsResponse(esResponse); - expect(fields).toHaveLength(24); + expect(fields).toHaveLength(25); }); it( @@ -68,8 +68,8 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { sandbox.spy(shouldReadFieldFromDocValuesNS, 'shouldReadFieldFromDocValues'); const fields = readFieldCapsResponse(esResponse); const conflictCount = fields.filter(f => f.type === 'conflict').length; - // +2 is for the object and nested fields which get filtered out of the final return value from readFieldCapsResponse - sinon.assert.callCount(shouldReadFieldFromDocValues, fields.length - conflictCount + 2); + // +1 is for the object field which is filtered out of the final return value from readFieldCapsResponse + sinon.assert.callCount(shouldReadFieldFromDocValues, fields.length - conflictCount + 1); }); it('converts es types to kibana types', () => { @@ -159,10 +159,12 @@ describe('index_patterns/field_capabilities/field_caps_response', () => { }); }); - it('does not include the field actually mapped as nested itself', () => { + it('returns the nested parent as not searchable or aggregatable', () => { const fields = readFieldCapsResponse(esResponse); const child = fields.find(f => f.name === 'nested_object_parent'); - expect(child).toBeUndefined(); + expect(child.type).toBe('nested'); + expect(child.aggregatable).toBe(false); + expect(child.searchable).toBe(false); }); it('should not confuse object children for multi or nested field children', () => { diff --git a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts index 2215bd8a95a1..0c8c2ce48fa8 100644 --- a/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts +++ b/src/plugins/data/server/index_patterns/fetcher/lib/field_capabilities/field_caps_response.ts @@ -195,6 +195,6 @@ export function readFieldCapsResponse(fieldCapsResponse: FieldCapsResponse): Fie }); return kibanaFormattedCaps.filter(field => { - return !['object', 'nested'].includes(field.type); + return !['object'].includes(field.type); }); } diff --git a/src/plugins/es_ui_shared/public/components/json_editor/index.ts b/src/plugins/es_ui_shared/public/components/json_editor/index.ts new file mode 100644 index 000000000000..81476a65f421 --- /dev/null +++ b/src/plugins/es_ui_shared/public/components/json_editor/index.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './json_editor'; + +export { OnJsonEditorUpdateHandler } from './use_json'; diff --git a/src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx b/src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx new file mode 100644 index 000000000000..8c63cc8494a8 --- /dev/null +++ b/src/plugins/es_ui_shared/public/components/json_editor/json_editor.tsx @@ -0,0 +1,111 @@ +/* + * 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, { useCallback } from 'react'; +import { EuiFormRow, EuiCodeEditor } from '@elastic/eui'; +import { debounce } from 'lodash'; + +import { isJSON } from '../../../static/validators/string'; +import { useJson, OnJsonEditorUpdateHandler } from './use_json'; + +interface Props { + onUpdate: OnJsonEditorUpdateHandler; + label?: string; + helpText?: React.ReactNode; + value?: string; + defaultValue?: { [key: string]: any }; + euiCodeEditorProps?: { [key: string]: any }; + error?: string | null; +} + +export const JsonEditor = React.memo( + ({ + label, + helpText, + onUpdate, + value, + defaultValue, + euiCodeEditorProps, + error: propsError, + }: Props) => { + const isControlled = value !== undefined; + + const { content, setContent, error: internalError } = useJson({ + defaultValue, + onUpdate, + isControlled, + }); + + const debouncedSetContent = useCallback(debounce(setContent, 300), [setContent]); + + // We let the consumer control the validation and the error message. + const error = isControlled ? propsError : internalError; + + const onEuiCodeEditorChange = useCallback( + (updated: string) => { + if (isControlled) { + onUpdate({ + data: { + raw: updated, + format() { + return JSON.parse(updated); + }, + }, + validate() { + return isJSON(updated); + }, + isValid: undefined, + }); + } else { + debouncedSetContent(updated); + } + }, + [isControlled] + ); + + return ( + + + + ); + } +); diff --git a/src/plugins/es_ui_shared/public/components/json_editor/use_json.ts b/src/plugins/es_ui_shared/public/components/json_editor/use_json.ts new file mode 100644 index 000000000000..1b5ca5d7f438 --- /dev/null +++ b/src/plugins/es_ui_shared/public/components/json_editor/use_json.ts @@ -0,0 +1,94 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useEffect, useState, useRef } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { isJSON } from '../../../static/validators/string'; + +export type OnJsonEditorUpdateHandler = (arg: { + data: { + raw: string; + format(): T; + }; + validate(): boolean; + isValid: boolean | undefined; +}) => void; + +interface Parameters { + onUpdate: OnJsonEditorUpdateHandler; + defaultValue?: T; + isControlled?: boolean; +} + +const stringifyJson = (json: { [key: string]: any }) => + Object.keys(json).length ? JSON.stringify(json, null, 2) : '{\n\n}'; + +export const useJson = ({ + defaultValue = {} as T, + onUpdate, + isControlled = false, +}: Parameters) => { + const didMount = useRef(false); + const [content, setContent] = useState(stringifyJson(defaultValue)); + const [error, setError] = useState(null); + + const validate = () => { + // We allow empty string as it will be converted to "{}"" + const isValid = content.trim() === '' ? true : isJSON(content); + if (!isValid) { + setError( + i18n.translate('esUi.validation.string.invalidJSONError', { + defaultMessage: 'Invalid JSON', + }) + ); + } else { + setError(null); + } + return isValid; + }; + + const formatContent = () => { + const isValid = validate(); + const data = isValid && content.trim() !== '' ? JSON.parse(content) : {}; + return data as T; + }; + + useEffect(() => { + if (didMount.current) { + const isValid = isControlled ? undefined : validate(); + onUpdate({ + data: { + raw: content, + format: formatContent, + }, + validate, + isValid, + }); + } else { + didMount.current = true; + } + }, [content]); + + return { + content, + setContent, + error, + }; +}; diff --git a/src/plugins/es_ui_shared/public/index.ts b/src/plugins/es_ui_shared/public/index.ts new file mode 100644 index 000000000000..a12c951ad13a --- /dev/null +++ b/src/plugins/es_ui_shared/public/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './components/json_editor'; diff --git a/src/plugins/es_ui_shared/static/forms/components/field.tsx b/src/plugins/es_ui_shared/static/forms/components/field.tsx index 5b9a6dc9de00..07fca1a7f759 100644 --- a/src/plugins/es_ui_shared/static/forms/components/field.tsx +++ b/src/plugins/es_ui_shared/static/forms/components/field.tsx @@ -17,12 +17,12 @@ * under the License. */ -import React from 'react'; +import React, { ComponentType } from 'react'; import { FieldHook, FIELD_TYPES } from '../hook_form_lib'; interface Props { field: FieldHook; - euiFieldProps?: Record; + euiFieldProps?: { [key: string]: any }; idAria?: string; [key: string]: any; } @@ -41,7 +41,7 @@ import { ToggleField, } from './fields'; -const mapTypeToFieldComponent = { +const mapTypeToFieldComponent: { [key: string]: ComponentType } = { [FIELD_TYPES.TEXT]: TextField, [FIELD_TYPES.TEXTAREA]: TextAreaField, [FIELD_TYPES.NUMBER]: NumericField, diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/checkbox_field.tsx b/src/plugins/es_ui_shared/static/forms/components/fields/checkbox_field.tsx index 0443b4ff09e6..c8ba9f5ac410 100644 --- a/src/plugins/es_ui_shared/static/forms/components/fields/checkbox_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/components/fields/checkbox_field.tsx @@ -35,7 +35,7 @@ export const CheckBoxField = ({ field, euiFieldProps = {}, ...rest }: Props) => return ( { + const { errorMessage } = getFieldValidityAndErrorMessage(field); + + const { label, helpText, value, setValue } = field; + + const onJsonUpdate: OnJsonEditorUpdateHandler = useCallback( + updatedJson => { + setValue(updatedJson.data.raw); + }, + [setValue] + ); + + return ( + + ); +}; diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/multi_select_field.tsx b/src/plugins/es_ui_shared/static/forms/components/fields/multi_select_field.tsx index 0f7332e3954e..e77337e4ecf5 100644 --- a/src/plugins/es_ui_shared/static/forms/components/fields/multi_select_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/components/fields/multi_select_field.tsx @@ -35,7 +35,7 @@ export const MultiSelectField = ({ field, euiFieldProps = {}, ...rest }: Props) return ( { return ( { return ( ; + euiFieldProps: { + options: Array< + { text: string | ReactNode; [key: string]: any } & OptionHTMLAttributes + >; + [key: string]: any; + }; idAria?: string; [key: string]: any; } -export const SelectField = ({ field, euiFieldProps = {}, ...rest }: Props) => { +export const SelectField = ({ field, euiFieldProps, ...rest }: Props) => { const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); return ( ; + euiFieldProps: { + options: EuiSuperSelectProps['options']; + [key: string]: any; + }; idAria?: string; [key: string]: any; } -export const SuperSelectField = ({ field, euiFieldProps = {}, ...rest }: Props) => { +export const SuperSelectField = ({ field, euiFieldProps = { options: [] }, ...rest }: Props) => { const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); return ( { field.setValue(value); }} - options={[]} isInvalid={isInvalid} data-test-subj="select" {...euiFieldProps} diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/text_area_field.tsx b/src/plugins/es_ui_shared/static/forms/components/fields/text_area_field.tsx index b9c6424a0065..c6fccb0c0e38 100644 --- a/src/plugins/es_ui_shared/static/forms/components/fields/text_area_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/components/fields/text_area_field.tsx @@ -35,7 +35,7 @@ export const TextAreaField = ({ field, euiFieldProps = {}, ...rest }: Props) => return ( { return ( { return ( ( + ...args: Parameters +): ReturnType> => { + const [{ value }] = args; + + if (typeof value !== 'string') { + return; + } + + if (!isJSON(value)) { + return { + code: 'ERR_JSON_FORMAT', + message, + }; + } +}; diff --git a/src/plugins/es_ui_shared/static/forms/helpers/field_validators/lowercase_string.ts b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/lowercase_string.ts new file mode 100644 index 000000000000..42de66b930ea --- /dev/null +++ b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/lowercase_string.ts @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ValidationFunc } from '../../hook_form_lib'; +import { isLowerCaseString } from '../../../validators/string'; +import { ERROR_CODE } from './types'; + +export const lowerCaseStringField = (message: string) => ( + ...args: Parameters +): ReturnType> => { + const [{ value }] = args; + + if (typeof value !== 'string') { + return; + } + + if (!isLowerCaseString(value)) { + return { + code: 'ERR_LOWERCASE_STRING', + message, + }; + } +}; diff --git a/src/plugins/es_ui_shared/static/forms/helpers/field_validators/number_greater_than.ts b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/number_greater_than.ts new file mode 100644 index 000000000000..767302a8328c --- /dev/null +++ b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/number_greater_than.ts @@ -0,0 +1,42 @@ +/* + * 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 { ValidationFunc, ValidationError } from '../../hook_form_lib'; +import { isNumberGreaterThan } from '../../../validators/number'; +import { ERROR_CODE } from './types'; + +export const numberGreaterThanField = ({ + than, + message, + allowEquality = false, +}: { + than: number; + message: string | ((err: Partial) => string); + allowEquality?: boolean; +}) => (...args: Parameters): ReturnType> => { + const [{ value }] = args; + + return isNumberGreaterThan(than, allowEquality)(value as number) + ? undefined + : { + code: 'ERR_GREATER_THAN_NUMBER', + than, + message: typeof message === 'function' ? message({ than }) : message, + }; +}; diff --git a/src/plugins/es_ui_shared/static/forms/helpers/field_validators/number_smaller_than.ts b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/number_smaller_than.ts new file mode 100644 index 000000000000..4eab3c5b0f10 --- /dev/null +++ b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/number_smaller_than.ts @@ -0,0 +1,42 @@ +/* + * 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 { ValidationFunc, ValidationError } from '../../hook_form_lib'; +import { isNumberSmallerThan } from '../../../validators/number'; +import { ERROR_CODE } from './types'; + +export const numberSmallerThanField = ({ + than, + message, + allowEquality = false, +}: { + than: number; + message: string | ((err: Partial) => string); + allowEquality?: boolean; +}) => (...args: Parameters): ReturnType> => { + const [{ value }] = args; + + return isNumberSmallerThan(than, allowEquality)(value as number) + ? undefined + : { + code: 'ERR_SMALLER_THAN_NUMBER', + than, + message: typeof message === 'function' ? message({ than }) : message, + }; +}; diff --git a/src/plugins/es_ui_shared/static/forms/helpers/field_validators/types.ts b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/types.ts index 25cf038ec227..d5bceac83613 100644 --- a/src/plugins/es_ui_shared/static/forms/helpers/field_validators/types.ts +++ b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/types.ts @@ -25,4 +25,8 @@ export type ERROR_CODE = | 'ERR_MIN_LENGTH' | 'ERR_MAX_LENGTH' | 'ERR_MIN_SELECTION' - | 'ERR_MAX_SELECTION'; + | 'ERR_MAX_SELECTION' + | 'ERR_LOWERCASE_STRING' + | 'ERR_JSON_FORMAT' + | 'ERR_SMALLER_THAN_NUMBER' + | 'ERR_GREATER_THAN_NUMBER'; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts index 06f5c2369df1..a8d24984cec7 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.ts @@ -17,10 +17,9 @@ * under the License. */ -import { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { FormData } from '../types'; -import { Subscription } from '../lib'; import { useFormContext } from '../form_context'; interface Props { @@ -28,14 +27,13 @@ interface Props { pathsToWatch?: string | string[]; } -export const FormDataProvider = ({ children, pathsToWatch }: Props) => { +export const FormDataProvider = React.memo(({ children, pathsToWatch }: Props) => { const [formData, setFormData] = useState({}); - const previousState = useRef({}); - const subscription = useRef(null); + const previousRawData = useRef({}); const form = useFormContext(); useEffect(() => { - subscription.current = form.__formData$.current.subscribe(data => { + const subscription = form.subscribe(({ data: { raw } }) => { // To avoid re-rendering the children for updates on the form data // that we are **not** interested in, we can specify one or multiple path(s) // to watch. @@ -43,19 +41,17 @@ export const FormDataProvider = ({ children, pathsToWatch }: Props) => { const valuesToWatchArray = Array.isArray(pathsToWatch) ? (pathsToWatch as string[]) : ([pathsToWatch] as string[]); - if (valuesToWatchArray.some(value => previousState.current[value] !== data[value])) { - previousState.current = data; - setFormData(data); + if (valuesToWatchArray.some(value => previousRawData.current[value] !== raw[value])) { + previousRawData.current = raw; + setFormData(raw); } } else { - setFormData(data); + setFormData(raw); } }); - return () => { - subscription.current!.unsubscribe(); - }; - }, [pathsToWatch]); + return subscription.unsubscribe; + }, [form, pathsToWatch]); return children(formData); -}; +}); diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/index.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/index.ts index b5f1d18ee9e6..1088a3f82aa4 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/index.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/index.ts @@ -19,5 +19,6 @@ export * from './form'; export * from './use_field'; +export * from './use_multi_fields'; export * from './use_array'; export * from './form_data_provider'; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx index 580ec7714027..021d52fbe9ed 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx @@ -17,80 +17,71 @@ * under the License. */ -import React, { useEffect, FunctionComponent } from 'react'; +import React, { FunctionComponent } from 'react'; import { FieldHook, FieldConfig } from '../types'; import { useField } from '../hooks'; import { useFormContext } from '../form_context'; -interface Props { +export interface Props { path: string; config?: FieldConfig; defaultValue?: unknown; component?: FunctionComponent | 'input'; componentProps?: Record; + onChange?: (value: unknown) => void; children?: (field: FieldHook) => JSX.Element; } -export const UseField = ({ - path, - config, - defaultValue, - component = 'input', - componentProps = {}, - children, -}: Props) => { - const form = useFormContext(); +export const UseField = React.memo( + ({ path, config, defaultValue, component, componentProps, onChange, children }: Props) => { + const form = useFormContext(); + component = component === undefined ? 'input' : component; + componentProps = componentProps === undefined ? {} : componentProps; - if (typeof defaultValue === 'undefined') { - defaultValue = form.getFieldDefaultValue(path); - } + if (typeof defaultValue === 'undefined') { + defaultValue = form.getFieldDefaultValue(path); + } - if (!config) { - config = form.__readFieldConfigFromSchema(path); - } + if (!config) { + config = form.__readFieldConfigFromSchema(path); + } - // Don't modify the config object - const configCopy = - typeof defaultValue !== 'undefined' ? { ...config, defaultValue } : { ...config }; + // Don't modify the config object + const configCopy = + typeof defaultValue !== 'undefined' ? { ...config, defaultValue } : { ...config }; - if (!configCopy.path) { - configCopy.path = path; - } else { - if (configCopy.path !== path) { - throw new Error( - `Field path mismatch. Got "${path}" but field config has "${configCopy.path}".` - ); + if (!configCopy.path) { + configCopy.path = path; + } else { + if (configCopy.path !== path) { + throw new Error( + `Field path mismatch. Got "${path}" but field config has "${configCopy.path}".` + ); + } } - } - const field = useField(form, path, configCopy); + const field = useField(form, path, configCopy, onChange); - // Remove field from form when it is unmounted or if its path changes - useEffect(() => { - return () => { - form.__removeField(path); - }; - }, [path]); + // Children prevails over anything else provided. + if (children) { + return children!(field); + } - // Children prevails over anything else provided. - if (children) { - return children!(field); - } + if (component === 'input') { + return ( + + ); + } - if (component === 'input') { - return ( - - ); + return component({ field, ...componentProps }); } - - return component({ field, ...componentProps }); -}; +); /** * Get a component providing some common props for all instances. diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.tsx new file mode 100644 index 000000000000..b84c5585e017 --- /dev/null +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.tsx @@ -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 React from 'react'; + +import { UseField, Props as UseFieldProps } from './use_field'; +import { FieldHook } from '../types'; + +type FieldsArray = Array<{ id: string } & Omit>; + +interface Props { + fields: { [key: string]: Omit }; + children: (fields: { [key: string]: FieldHook }) => JSX.Element; +} + +export const UseMultiFields = ({ fields, children }: Props) => { + const fieldsArray = Object.entries(fields).reduce( + (acc, [fieldId, field]) => [...acc, { id: fieldId, ...field }], + [] as FieldsArray + ); + + const hookFields: { [key: string]: FieldHook } = {}; + + const renderField = (index: number) => { + const { id } = fieldsArray[index]; + return ( + + {field => { + hookFields[id] = field; + return index === fieldsArray.length - 1 ? children(hookFields) : renderField(index + 1); + }} + + ); + }; + + if (!Boolean(fieldsArray.length)) { + return null; + } + + return renderField(0); +}; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/form_context.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/form_context.tsx index b7c6c39e7b0c..5dcd076b4153 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/form_context.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/form_context.tsx @@ -19,7 +19,7 @@ import React, { createContext, useContext } from 'react'; -import { FormHook } from './types'; +import { FormHook, FormData } from './types'; const FormContext = createContext | undefined>(undefined); @@ -32,7 +32,7 @@ export const FormProvider = ({ children, form }: Props) => ( {children} ); -export const useFormContext = function>() { +export const useFormContext = function() { const context = useContext(FormContext) as FormHook; if (context === undefined) { throw new Error('useFormContext must be used within a '); diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts index d7ef798bf2e0..80cddb513b20 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts @@ -17,12 +17,17 @@ * under the License. */ -import { useState, useEffect, useRef } from 'react'; +import { useState, useEffect, useRef, useMemo } from 'react'; import { FormHook, FieldHook, FieldConfig, FieldValidateResponse, ValidationError } from '../types'; import { FIELD_TYPES, VALIDATION_TYPES } from '../constants'; -export const useField = (form: FormHook, path: string, config: FieldConfig = {}) => { +export const useField = ( + form: FormHook, + path: string, + config: FieldConfig = {}, + valueChangeListener?: (value: unknown) => void +) => { const { type = FIELD_TYPES.TEXT, defaultValue = '', @@ -37,17 +42,25 @@ export const useField = (form: FormHook, path: string, config: FieldConfig = {}) deserializer = (value: unknown) => value, } = config; - const [value, setStateValue] = useState( - typeof defaultValue === 'function' ? deserializer(defaultValue()) : deserializer(defaultValue) + const initialValue = useMemo( + () => + typeof defaultValue === 'function' + ? deserializer(defaultValue()) + : deserializer(defaultValue), + [defaultValue] ); + + const [value, setStateValue] = useState(initialValue); const [errors, setErrors] = useState([]); const [isPristine, setPristine] = useState(true); const [isValidating, setValidating] = useState(false); const [isChangingValue, setIsChangingValue] = useState(false); + const [isValidated, setIsValidated] = useState(false); const validateCounter = useRef(0); const changeCounter = useRef(0); const inflightValidation = useRef | null>(null); const debounceTimeout = useRef(null); + const isUnmounted = useRef(false); // -- HELPERS // ---------------------------------- @@ -77,7 +90,10 @@ export const useField = (form: FormHook, path: string, config: FieldConfig = {}) if (isEmptyString) { return inputValue; } - return formatters.reduce((output, formatter) => formatter(output), inputValue); + + const formData = form.getFormData({ unflatten: false }); + + return formatters.reduce((output, formatter) => formatter(output, formData), inputValue); }; const onValueChange = async () => { @@ -92,12 +108,23 @@ export const useField = (form: FormHook, path: string, config: FieldConfig = {}) setIsChangingValue(true); } + const newValue = serializeOutput(value); + + // Notify listener + if (valueChangeListener) { + valueChangeListener(newValue); + } + // Update the form data observable - form.__updateFormDataAt(path, serializeOutput(value)); + form.__updateFormDataAt(path, newValue); // Validate field(s) and set form.isValid flag await form.__validateFields(fieldsToValidateOnChange); + if (isUnmounted.current) { + return; + } + /** * If we have set a delay to display the error message after the field value has changed, * we first check that this is the last "change iteration" (=== the last keystroke from the user) @@ -263,6 +290,7 @@ export const useField = (form: FormHook, path: string, config: FieldConfig = {}) validationType, } = validationData; + setIsValidated(true); setValidating(true); // By the time our validate function has reached completion, it’s possible @@ -276,12 +304,10 @@ export const useField = (form: FormHook, path: string, config: FieldConfig = {}) // This is the most recent invocation setValidating(false); // Update the errors array - setErrors(previousErrors => { - // First filter out the validation type we are currently validating - const filteredErrors = filterErrors(previousErrors, validationType); - return [...filteredErrors, ..._validationErrors]; - }); + const filteredErrors = filterErrors(errors, validationType); + setErrors([...filteredErrors, ..._validationErrors]); } + return { isValid: _validationErrors.length === 0, errors: _validationErrors, @@ -359,6 +385,22 @@ export const useField = (form: FormHook, path: string, config: FieldConfig = {}) return errorMessages ? errorMessages : null; }; + const reset: FieldHook['reset'] = (resetOptions = { resetValue: true }) => { + const { resetValue = true } = resetOptions; + + setPristine(true); + setValidating(false); + setIsChangingValue(false); + setIsValidated(false); + setErrors([]); + + if (resetValue) { + setValue(initialValue); + return initialValue; + } + return value; + }; + const serializeOutput: FieldHook['__serializeOutput'] = (rawValue = value) => serializer(rawValue); @@ -390,6 +432,7 @@ export const useField = (form: FormHook, path: string, config: FieldConfig = {}) form, isPristine, isValidating, + isValidated, isChangingValue, onChange, getErrorsMessages, @@ -397,10 +440,32 @@ export const useField = (form: FormHook, path: string, config: FieldConfig = {}) setErrors: _setErrors, clearErrors, validate, + reset, __serializeOutput: serializeOutput, }; - form.__addField(field); + form.__addField(field); // Executed first (1) + + useEffect(() => { + /** + * NOTE: effect cleanup actually happens *after* the new component has been mounted, + * but before the next effect callback is run. + * Ref: https://kentcdodds.com/blog/understanding-reacts-key-prop + * + * This means that, the "form.__addField(field)" outside the effect will be called *before* + * the cleanup `form.__removeField(path);` creating a race condition. + * + * TODO: See how we could refactor "use_field" & "use_form" to avoid having the + * `form.__addField(field)` call outside the effect. + */ + form.__addField(field); // Executed third (3) + + return () => { + // Remove field from the form when it is unmounted or if its path changes. + isUnmounted.current = true; + form.__removeField(path); // Executed second (2) + }; + }, [path]); return field; }; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts index 3902b0615a33..d8b2f35e117a 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.ts @@ -17,11 +17,11 @@ * under the License. */ -import { useState, useRef } from 'react'; +import { useState, useRef, useEffect, useMemo } from 'react'; import { get } from 'lodash'; -import { FormHook, FormData, FieldConfig, FieldsMap, FormConfig } from '../types'; -import { mapFormFields, flattenObject, unflattenObject, Subject } from '../lib'; +import { FormHook, FieldHook, FormData, FieldConfig, FieldsMap, FormConfig } from '../types'; +import { mapFormFields, flattenObject, unflattenObject, Subject, Subscription } from '../lib'; const DEFAULT_ERROR_DISPLAY_TIMEOUT = 500; const DEFAULT_OPTIONS = { @@ -29,35 +29,54 @@ const DEFAULT_OPTIONS = { stripEmptyFields: true, }; -interface UseFormReturn { +interface UseFormReturn { form: FormHook; } -export function useForm( +export function useForm( formConfig: FormConfig | undefined = {} ): UseFormReturn { const { onSubmit, schema, - defaultValue = {}, serializer = (data: any) => data, deserializer = (data: any) => data, options = {}, } = formConfig; + + const formDefaultValue = + formConfig.defaultValue === undefined || Object.keys(formConfig.defaultValue).length === 0 + ? {} + : Object.entries(formConfig.defaultValue as object) + .filter(({ 1: value }) => value !== undefined) + .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}); + const formOptions = { ...DEFAULT_OPTIONS, ...options }; - const defaultValueDeserialized = - Object.keys(defaultValue).length === 0 ? defaultValue : deserializer(defaultValue); - const [isSubmitted, setSubmitted] = useState(false); + const defaultValueDeserialized = useMemo(() => deserializer(formDefaultValue), [ + formConfig.defaultValue, + ]); + + const [isSubmitted, setIsSubmitted] = useState(false); const [isSubmitting, setSubmitting] = useState(false); - const [isValid, setIsValid] = useState(true); + const [isValid, setIsValid] = useState(undefined); const fieldsRefs = useRef({}); + const formUpdateSubscribers = useRef([]); + const isUnmounted = useRef(false); // formData$ is an observable we can subscribe to in order to receive live // update of the raw form data. As an observable it does not trigger any React // render(). // The component is the one in charge of reading this observable // and updating its state to trigger the necessary view render. - const formData$ = useRef>(new Subject(flattenObject(defaultValue) as T)); + const formData$ = useRef>(new Subject(flattenObject(formDefaultValue) as T)); + + useEffect(() => { + return () => { + formUpdateSubscribers.current.forEach(subscription => subscription.unsubscribe()); + formUpdateSubscribers.current = []; + isUnmounted.current = true; + }; + }, []); // -- HELPERS // ---------------------------------- @@ -75,6 +94,12 @@ export function useForm( return fields; }; + const updateFormDataAt: FormHook['__updateFormDataAt'] = (path, value) => { + const currentFormData = formData$.current.value; + formData$.current.next({ ...currentFormData, [path]: value }); + return formData$.current.value; + }; + // -- API // ---------------------------------- const getFormData: FormHook['getFormData'] = (getDataOptions = { unflatten: true }) => @@ -90,43 +115,76 @@ export function useForm( {} as T ); - const updateFormDataAt: FormHook['__updateFormDataAt'] = (path, value) => { - const currentFormData = formData$.current.value; - formData$.current.next({ ...currentFormData, [path]: value }); - return formData$.current.value; + const getErrors: FormHook['getErrors'] = () => { + if (isValid === true) { + return []; + } + + return fieldsToArray().reduce((acc, field) => { + const fieldError = field.getErrorsMessages(); + if (fieldError === null) { + return acc; + } + return [...acc, fieldError]; + }, [] as string[]); + }; + + const isFieldValid = (field: FieldHook) => + field.getErrorsMessages() === null && !field.isValidating; + + const updateFormValidity = () => { + const fieldsArray = fieldsToArray(); + const areAllFieldsValidated = fieldsArray.every(field => field.isValidated); + + if (!areAllFieldsValidated) { + // If *not* all the fiels have been validated, the validity of the form is unknown, thus still "undefined" + return undefined; + } + + const isFormValid = fieldsArray.every(isFieldValid); + + setIsValid(isFormValid); + return isFormValid; }; - /** - * When a field value changes, validateFields() is called with the field name + any other fields - * declared in the "fieldsToValidateOnChange" (see the field config). - * - * When this method is called _without_ providing any fieldNames, we only need to validate fields that are pristine - * as the fields that are dirty have already been validated when their value changed. - */ const validateFields: FormHook['__validateFields'] = async fieldNames => { const fieldsToValidate = fieldNames - ? fieldNames.map(name => fieldsRefs.current[name]).filter(field => field !== undefined) - : fieldsToArray().filter(field => field.isPristine); // only validate fields that haven't been changed + .map(name => fieldsRefs.current[name]) + .filter(field => field !== undefined); - const formData = getFormData({ unflatten: false }); + if (fieldsToValidate.length === 0) { + // Nothing to validate + return { areFieldsValid: true, isFormValid: true }; + } + const formData = getFormData({ unflatten: false }); await Promise.all(fieldsToValidate.map(field => field.validate({ formData }))); - const isFormValid = fieldsToArray().every( - field => field.getErrorsMessages() === null && !field.isValidating - ); - setIsValid(isFormValid); + const isFormValid = updateFormValidity(); + const areFieldsValid = fieldsToValidate.every(isFieldValid); - return isFormValid; + return { areFieldsValid, isFormValid }; + }; + + const validateAllFields = async (): Promise => { + const fieldsToValidate = fieldsToArray().filter(field => !field.isValidated); + + if (fieldsToValidate.length === 0) { + // Nothing left to validate, all fields are already validated. + return isValid!; + } + + const { isFormValid } = await validateFields(fieldsToValidate.map(field => field.path)); + + return isFormValid!; }; const addField: FormHook['__addField'] = field => { fieldsRefs.current[field.path] = field; - // Only update the formData if the path does not exist (it is the _first_ time - // the field is added), to avoid entering an infinite loop when the form is re-rendered. if (!{}.hasOwnProperty.call(formData$.current.value, field.path)) { - updateFormDataAt(field.path, field.__serializeOutput()); + const fieldValue = field.__serializeOutput(); + updateFormDataAt(field.path, fieldValue); } }; @@ -143,10 +201,16 @@ export function useForm( }; const setFieldValue: FormHook['setFieldValue'] = (fieldName, value) => { + if (fieldsRefs.current[fieldName] === undefined) { + return; + } fieldsRefs.current[fieldName].setValue(value); }; const setFieldErrors: FormHook['setFieldErrors'] = (fieldName, errors) => { + if (fieldsRefs.current[fieldName] === undefined) { + return; + } fieldsRefs.current[fieldName].setErrors(errors); }; @@ -167,20 +231,58 @@ export function useForm( } if (!isSubmitted) { - setSubmitted(true); // User has attempted to submit the form at least once + setIsSubmitted(true); // User has attempted to submit the form at least once } setSubmitting(true); - const isFormValid = await validateFields(); + const isFormValid = await validateAllFields(); const formData = serializer(getFormData() as T); if (onSubmit) { - await onSubmit(formData, isFormValid); + await onSubmit(formData, isFormValid!); } setSubmitting(false); - return { data: formData, isValid: isFormValid }; + return { data: formData, isValid: isFormValid! }; + }; + + const subscribe: FormHook['subscribe'] = handler => { + const format = () => serializer(getFormData() as T); + + const subscription = formData$.current.subscribe(raw => { + if (!isUnmounted.current) { + handler({ isValid, data: { raw, format }, validate: validateAllFields }); + } + }); + + formUpdateSubscribers.current.push(subscription); + return subscription; + }; + + /** + * Reset all the fields of the form to their default values + * and reset all the states to their original value. + */ + const reset: FormHook['reset'] = (resetOptions = { resetValues: true }) => { + const { resetValues = true } = resetOptions; + const currentFormData = { ...formData$.current.value } as FormData; + Object.entries(fieldsRefs.current).forEach(([path, field]) => { + // By resetting the form, some field might be unmounted. In order + // to avoid a race condition, we check that the field still exists. + const isFieldMounted = fieldsRefs.current[path] !== undefined; + if (isFieldMounted) { + const fieldValue = field.reset({ resetValue: resetValues }); + currentFormData[path] = fieldValue; + } + }); + if (resetValues) { + formData$.current.next(currentFormData as T); + } + + setIsSubmitted(false); + setSubmitting(false); + setIsValid(undefined); }; const form: FormHook = { @@ -188,11 +290,14 @@ export function useForm( isSubmitting, isValid, submit: submitForm, + subscribe, setFieldValue, setFieldErrors, getFields, getFormData, + getErrors, getFieldDefaultValue, + reset, __options: formOptions, __formData$: formData$, __updateFormDataAt: updateFormDataAt, diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/subject.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/subject.ts index 4c0169cb526e..7365f234d39e 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/subject.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/subject.ts @@ -51,7 +51,9 @@ export class Subject { } next(value: T) { - this.value = value; - this.callbacks.forEach(fn => fn(value)); + if (value !== this.value) { + this.value = value; + this.callbacks.forEach(fn => fn(value)); + } } } diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.ts index 62867a0c07a6..65cd7792a018 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/lib/utils.ts @@ -33,7 +33,7 @@ export const flattenObject = ( ): Record => Object.entries(object).reduce((acc, [key, value]) => { const updatedPaths = [...paths, key]; - if (value !== null && typeof value === 'object') { + if (value !== null && !Array.isArray(value) && typeof value === 'object') { return flattenObject(value, to, updatedPaths); } acc[updatedPaths.join('.')] = value; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts index 994602013235..8dc1e59b40c3 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/types.ts @@ -18,40 +18,47 @@ */ import { ReactNode, ChangeEvent, FormEvent, MouseEvent, MutableRefObject } from 'react'; -import { Subject } from './lib'; +import { Subject, Subscription } from './lib'; // This type will convert all optional property to required ones // Comes from https://github.com/microsoft/TypeScript/issues/15012#issuecomment-365453623 -type Required = T extends object ? { [P in keyof T]-?: NonNullable } : T; +type Required = T extends FormData ? { [P in keyof T]-?: NonNullable } : T; -export interface FormHook { +export interface FormHook { readonly isSubmitted: boolean; readonly isSubmitting: boolean; - readonly isValid: boolean; + readonly isValid: boolean | undefined; submit: (e?: FormEvent | MouseEvent) => Promise<{ data: T; isValid: boolean }>; + subscribe: (handler: OnUpdateHandler) => Subscription; setFieldValue: (fieldName: string, value: FieldValue) => void; setFieldErrors: (fieldName: string, errors: ValidationError[]) => void; getFields: () => FieldsMap; getFormData: (options?: { unflatten?: boolean }) => T; getFieldDefaultValue: (fieldName: string) => unknown; + /* Returns a list of all errors in the form */ + getErrors: () => string[]; + reset: (options?: { resetValues?: boolean }) => void; readonly __options: Required; readonly __formData$: MutableRefObject>; __addField: (field: FieldHook) => void; __removeField: (fieldNames: string | string[]) => void; - __validateFields: (fieldNames?: string[]) => Promise; + __validateFields: ( + fieldNames: string[] + ) => Promise<{ areFieldsValid: boolean; isFormValid: boolean | undefined }>; __updateFormDataAt: (field: string, value: unknown) => T; __readFieldConfigFromSchema: (fieldName: string) => FieldConfig; } -export interface FormSchema { +export interface FormSchema { [key: string]: FormSchemaEntry; } -type FormSchemaEntry = + +type FormSchemaEntry = | FieldConfig | Array> | { [key: string]: FieldConfig | Array> | FormSchemaEntry }; -export interface FormConfig { +export interface FormConfig { onSubmit?: (data: T, isFormValid: boolean) => void; schema?: FormSchema; defaultValue?: Partial; @@ -60,6 +67,17 @@ export interface FormConfig { options?: FormOptions; } +export interface OnFormUpdateArg { + data: { + raw: { [key: string]: any }; + format: () => T; + }; + validate: () => Promise; + isValid?: boolean; +} + +export type OnUpdateHandler = (arg: OnFormUpdateArg) => void; + export interface FormOptions { errorDisplayDelay?: number; /** @@ -78,6 +96,7 @@ export interface FieldHook { readonly errors: ValidationError[]; readonly isPristine: boolean; readonly isValidating: boolean; + readonly isValidated: boolean; readonly isChangingValue: boolean; readonly form: FormHook; getErrorsMessages: (args?: { @@ -93,16 +112,17 @@ export interface FieldHook { value?: unknown; validationType?: string; }) => FieldValidateResponse | Promise; + reset: (options?: { resetValue: boolean }) => unknown; __serializeOutput: (rawValue?: unknown) => unknown; } -export interface FieldConfig { +export interface FieldConfig { readonly path?: string; readonly label?: string; readonly labelAppend?: string | ReactNode; readonly helpText?: string | ReactNode; readonly type?: HTMLInputElement['type']; - readonly defaultValue?: unknown; + readonly defaultValue?: ValueType; readonly validations?: Array>; readonly formatters?: FormatterFunc[]; readonly deserializer?: SerializerFunc; @@ -124,13 +144,17 @@ export interface ValidationError { [key: string]: any; } -export type ValidationFunc = (data: { +export interface ValidationFuncArg { path: string; - value: unknown; + value: V; form: FormHook; formData: T; errors: readonly ValidationError[]; -}) => ValidationError | void | undefined | Promise | void | undefined>; +} + +export type ValidationFunc = ( + data: ValidationFuncArg +) => ValidationError | void | undefined | Promise | void | undefined>; export interface FieldValidateResponse { isValid: boolean; @@ -143,13 +167,13 @@ export interface FormData { [key: string]: any; } -type FormatterFunc = (value: any) => unknown; +type FormatterFunc = (value: any, formData: FormData) => unknown; // We set it as unknown as a form field can be any of any type // string | number | boolean | string[] ... type FieldValue = unknown; -export interface ValidationConfig { +export interface ValidationConfig { validator: ValidationFunc; type?: string; exitOnFail?: boolean; diff --git a/src/plugins/es_ui_shared/static/validators/number/greater_than.ts b/src/plugins/es_ui_shared/static/validators/number/greater_than.ts new file mode 100644 index 000000000000..fa9024204e72 --- /dev/null +++ b/src/plugins/es_ui_shared/static/validators/number/greater_than.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 const isNumberGreaterThan = (than: number, allowEquality = false) => (value: number) => + allowEquality ? value >= than : value > than; diff --git a/src/plugins/es_ui_shared/static/validators/number/index.ts b/src/plugins/es_ui_shared/static/validators/number/index.ts new file mode 100644 index 000000000000..64a0cdd1b5a1 --- /dev/null +++ b/src/plugins/es_ui_shared/static/validators/number/index.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './greater_than'; + +export * from './smaller_than'; diff --git a/src/plugins/es_ui_shared/static/validators/number/smaller_than.ts b/src/plugins/es_ui_shared/static/validators/number/smaller_than.ts new file mode 100644 index 000000000000..50b43890ebf0 --- /dev/null +++ b/src/plugins/es_ui_shared/static/validators/number/smaller_than.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 const isNumberSmallerThan = (than: number, allowEquality = false) => (value: number) => + allowEquality ? value <= than : value < than; diff --git a/src/plugins/es_ui_shared/static/validators/string/index.ts b/src/plugins/es_ui_shared/static/validators/string/index.ts index 0ddf6fdfc33e..1e80ca070063 100644 --- a/src/plugins/es_ui_shared/static/validators/string/index.ts +++ b/src/plugins/es_ui_shared/static/validators/string/index.ts @@ -25,3 +25,4 @@ export * from './is_empty'; export * from './is_url'; export * from './starts_with'; export * from './is_json'; +export * from './is_lowercase'; diff --git a/src/plugins/es_ui_shared/static/validators/string/is_lowercase.ts b/src/plugins/es_ui_shared/static/validators/string/is_lowercase.ts new file mode 100644 index 000000000000..3d765a750a81 --- /dev/null +++ b/src/plugins/es_ui_shared/static/validators/string/is_lowercase.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const isLowerCaseString = (value: string) => value.toLowerCase() === value; diff --git a/src/plugins/home/public/index.ts b/src/plugins/home/public/index.ts index 25e94c20c347..ca05c8b5f760 100644 --- a/src/plugins/home/public/index.ts +++ b/src/plugins/home/public/index.ts @@ -23,7 +23,7 @@ export { HomePublicPluginSetup, HomePublicPluginStart, } from './plugin'; -export { FeatureCatalogueEntry, FeatureCatalogueCategory } from './services'; +export { FeatureCatalogueEntry, FeatureCatalogueCategory, Environment } from './services'; import { HomePublicPlugin } from './plugin'; export const plugin = () => new HomePublicPlugin(); diff --git a/src/plugins/home/public/plugin.test.mocks.ts b/src/plugins/home/public/plugin.test.mocks.ts index a48ea8f79513..461930ddfb80 100644 --- a/src/plugins/home/public/plugin.test.mocks.ts +++ b/src/plugins/home/public/plugin.test.mocks.ts @@ -18,8 +18,11 @@ */ import { featureCatalogueRegistryMock } from './services/feature_catalogue/feature_catalogue_registry.mock'; +import { environmentServiceMock } from './services/environment/environment.mock'; export const registryMock = featureCatalogueRegistryMock.create(); +export const environmentMock = environmentServiceMock.create(); jest.doMock('./services', () => ({ FeatureCatalogueRegistry: jest.fn(() => registryMock), + EnvironmentService: jest.fn(() => environmentMock), })); diff --git a/src/plugins/home/public/plugin.test.ts b/src/plugins/home/public/plugin.test.ts index fad6e8cf47bf..34502d7d2c6c 100644 --- a/src/plugins/home/public/plugin.test.ts +++ b/src/plugins/home/public/plugin.test.ts @@ -17,13 +17,15 @@ * under the License. */ -import { registryMock } from './plugin.test.mocks'; +import { registryMock, environmentMock } from './plugin.test.mocks'; import { HomePublicPlugin } from './plugin'; describe('HomePublicPlugin', () => { beforeEach(() => { registryMock.setup.mockClear(); registryMock.start.mockClear(); + environmentMock.setup.mockClear(); + environmentMock.start.mockClear(); }); describe('setup', () => { @@ -32,6 +34,12 @@ describe('HomePublicPlugin', () => { expect(setup).toHaveProperty('featureCatalogue'); expect(setup.featureCatalogue).toHaveProperty('register'); }); + + test('wires up and returns environment service', async () => { + const setup = await new HomePublicPlugin().setup(); + expect(setup).toHaveProperty('environment'); + expect(setup.environment).toHaveProperty('update'); + }); }); describe('start', () => { @@ -45,5 +53,15 @@ describe('HomePublicPlugin', () => { }); expect(start.featureCatalogue.get).toBeDefined(); }); + + test('wires up and returns environment service', async () => { + const service = new HomePublicPlugin(); + await service.setup(); + const start = await service.start({ + application: { capabilities: { catalogue: {} } }, + } as any); + expect(environmentMock.start).toHaveBeenCalled(); + expect(start.environment.get).toBeDefined(); + }); }); }); diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts index 40f2047ef001..39a7f2382690 100644 --- a/src/plugins/home/public/plugin.ts +++ b/src/plugins/home/public/plugin.ts @@ -19,6 +19,9 @@ import { CoreStart, Plugin } from 'src/core/public'; import { + EnvironmentService, + EnvironmentServiceSetup, + EnvironmentServiceStart, FeatureCatalogueRegistry, FeatureCatalogueRegistrySetup, FeatureCatalogueRegistryStart, @@ -26,10 +29,12 @@ import { export class HomePublicPlugin implements Plugin { private readonly featuresCatalogueRegistry = new FeatureCatalogueRegistry(); + private readonly environmentService = new EnvironmentService(); public async setup() { return { featureCatalogue: { ...this.featuresCatalogueRegistry.setup() }, + environment: { ...this.environmentService.setup() }, }; } @@ -40,6 +45,7 @@ export class HomePublicPlugin implements Plugin => { + const setup = { + update: jest.fn(), + }; + return setup; +}; + +const createStartMock = (): jest.Mocked => { + const start = { + get: jest.fn(), + }; + return start; +}; + +const createMock = (): jest.Mocked> => { + const service = { + setup: jest.fn(), + start: jest.fn(), + }; + service.setup.mockImplementation(createSetupMock); + service.start.mockImplementation(createStartMock); + return service; +}; + +export const environmentServiceMock = { + createSetup: createSetupMock, + createStart: createStartMock, + create: createMock, +}; diff --git a/src/plugins/home/public/services/environment/environment.test.ts b/src/plugins/home/public/services/environment/environment.test.ts new file mode 100644 index 000000000000..f42eba782a76 --- /dev/null +++ b/src/plugins/home/public/services/environment/environment.test.ts @@ -0,0 +1,47 @@ +/* + * 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 { EnvironmentService } from './environment'; + +describe('EnvironmentService', () => { + describe('setup', () => { + test('allows multiple update calls', () => { + const setup = new EnvironmentService().setup(); + expect(() => { + setup.update({ ml: true }); + setup.update({ apmUi: true }); + }).not.toThrow(); + }); + }); + + describe('start', () => { + test('returns default values', () => { + const service = new EnvironmentService(); + expect(service.start().get()).toEqual({ ml: false, cloud: false, apmUi: false }); + }); + + test('returns last state of update calls', () => { + const service = new EnvironmentService(); + const setup = service.setup(); + setup.update({ ml: true, cloud: true }); + setup.update({ ml: false, apmUi: true }); + expect(service.start().get()).toEqual({ ml: false, cloud: true, apmUi: true }); + }); + }); +}); diff --git a/src/plugins/home/public/services/environment/environment.ts b/src/plugins/home/public/services/environment/environment.ts new file mode 100644 index 000000000000..36c1afbca5e7 --- /dev/null +++ b/src/plugins/home/public/services/environment/environment.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. + */ + +/** @public */ +export interface Environment { + /** + * Flag whether the home app should advertize cloud features + */ + readonly cloud: boolean; + /** + * Flag whether the home app should advertize apm features + */ + readonly apmUi: boolean; + /** + * Flag whether the home app should advertize ml features + */ + readonly ml: boolean; +} + +export class EnvironmentService { + private environment = { + cloud: false, + apmUi: false, + ml: false, + }; + + public setup() { + return { + /** + * Update the environment to influence how the home app is presenting available features. + * This API should not be extended for new features and will be removed in future versions + * in favor of display specific extension apis. + * @deprecated + * @param update + */ + update: (update: Partial) => { + this.environment = Object.assign({}, this.environment, update); + }, + }; + } + + public start() { + return { + /** + * Retrieve the current environment home is running in. This API is only intended for internal + * use and is only exposed during a transition period of migrating the home app to the new platform. + * @deprecated + */ + get: (): Environment => this.environment, + }; + } +} + +export type EnvironmentServiceSetup = ReturnType; +export type EnvironmentServiceStart = ReturnType; diff --git a/src/plugins/home/public/services/environment/index.ts b/src/plugins/home/public/services/environment/index.ts new file mode 100644 index 000000000000..ed20f6adb96c --- /dev/null +++ b/src/plugins/home/public/services/environment/index.ts @@ -0,0 +1,25 @@ +/* + * 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 { + EnvironmentService, + Environment, + EnvironmentServiceSetup, + EnvironmentServiceStart, +} from './environment'; diff --git a/src/plugins/home/public/services/index.ts b/src/plugins/home/public/services/index.ts index 3621b0912393..a6542dd066a6 100644 --- a/src/plugins/home/public/services/index.ts +++ b/src/plugins/home/public/services/index.ts @@ -18,3 +18,4 @@ */ export * from './feature_catalogue'; +export * from './environment'; diff --git a/src/plugins/kibana_react/public/field_icon/__snapshots__/field_icon.test.tsx.snap b/src/plugins/kibana_react/public/field_icon/__snapshots__/field_icon.test.tsx.snap index 5abce10c5be6..870dbdc53326 100644 --- a/src/plugins/kibana_react/public/field_icon/__snapshots__/field_icon.test.tsx.snap +++ b/src/plugins/kibana_react/public/field_icon/__snapshots__/field_icon.test.tsx.snap @@ -11,7 +11,7 @@ exports[`FieldIcon renders a blackwhite icon for a string 1`] = ` exports[`FieldIcon renders a colored icon for a number 1`] = ` @@ -20,7 +20,7 @@ exports[`FieldIcon renders a colored icon for a number 1`] = ` exports[`FieldIcon renders an icon for an unknown type 1`] = ` @@ -30,7 +30,7 @@ exports[`FieldIcon renders with className if provided 1`] = ` diff --git a/src/plugins/kibana_react/public/field_icon/field_icon.tsx b/src/plugins/kibana_react/public/field_icon/field_icon.tsx index f9bdf3a25ada..7c44fe89d0e7 100644 --- a/src/plugins/kibana_react/public/field_icon/field_icon.tsx +++ b/src/plugins/kibana_react/public/field_icon/field_icon.tsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { palettes, EuiIcon } from '@elastic/eui'; +import { euiPaletteColorBlind, EuiIcon } from '@elastic/eui'; import { IconSize } from '@elastic/eui/src/components/icon/icon'; interface IconMapEntry { @@ -36,6 +36,7 @@ interface FieldIconProps { | 'number' | '_source' | 'string' + | 'nested' | string; label?: string; size?: IconSize; @@ -43,7 +44,7 @@ interface FieldIconProps { className?: string; } -const { colors } = palettes.euiPaletteColorBlind; +const colors = euiPaletteColorBlind(); // defaultIcon => a unknown datatype const defaultIcon = { icon: 'questionInCircle', color: colors[0] }; @@ -61,6 +62,7 @@ export const typeToEuiIconMap: Partial> = { number: { icon: 'number', color: colors[0] }, _source: { icon: 'editorCodeBlock', color: colors[3] }, string: { icon: 'string', color: colors[4] }, + nested: { icon: 'nested', color: colors[2] }, }; /** diff --git a/src/plugins/kibana_react/public/saved_objects/__snapshots__/saved_object_save_modal.test.tsx.snap b/src/plugins/kibana_react/public/saved_objects/__snapshots__/saved_object_save_modal.test.tsx.snap index 978705a3ad09..18f84f41d5d9 100644 --- a/src/plugins/kibana_react/public/saved_objects/__snapshots__/saved_object_save_modal.test.tsx.snap +++ b/src/plugins/kibana_react/public/saved_objects/__snapshots__/saved_object_save_modal.test.tsx.snap @@ -43,11 +43,9 @@ exports[`SavedObjectSaveModal should render matching snapshot 1`] = ` > diff --git a/src/plugins/vis_type_timeseries/kibana.json b/src/plugins/vis_type_timeseries/kibana.json index f9a368e85ed4..d77f4ac92da1 100644 --- a/src/plugins/vis_type_timeseries/kibana.json +++ b/src/plugins/vis_type_timeseries/kibana.json @@ -2,5 +2,6 @@ "id": "metrics", "version": "8.0.0", "kibanaVersion": "kibana", - "server": true -} \ No newline at end of file + "server": true, + "optionalPlugins": ["usageCollection"] +} diff --git a/src/plugins/vis_type_timeseries/server/index.ts b/src/plugins/vis_type_timeseries/server/index.ts index 599726612a93..dfb2394af237 100644 --- a/src/plugins/vis_type_timeseries/server/index.ts +++ b/src/plugins/vis_type_timeseries/server/index.ts @@ -30,6 +30,8 @@ export const config = { export type VisTypeTimeseriesConfig = TypeOf; +export { ValidationTelemetryServiceSetup } from './validation_telemetry'; + export function plugin(initializerContext: PluginInitializerContext) { return new VisTypeTimeseriesPlugin(initializerContext); } diff --git a/src/plugins/vis_type_timeseries/server/plugin.ts b/src/plugins/vis_type_timeseries/server/plugin.ts index f508aa250454..dcd0cd500bbc 100644 --- a/src/plugins/vis_type_timeseries/server/plugin.ts +++ b/src/plugins/vis_type_timeseries/server/plugin.ts @@ -35,11 +35,17 @@ import { GetVisData, GetVisDataOptions, } from '../../../legacy/core_plugins/vis_type_timeseries/server'; +import { ValidationTelemetryService } from './validation_telemetry/validation_telemetry_service'; +import { UsageCollectionSetup } from '../../usage_collection/server'; export interface LegacySetup { server: Server; } +interface VisTypeTimeseriesPluginSetupDependencies { + usageCollection?: UsageCollectionSetup; +} + export interface VisTypeTimeseriesSetup { /** @deprecated */ __legacy: { @@ -61,11 +67,14 @@ export interface Framework { } export class VisTypeTimeseriesPlugin implements Plugin { + private validationTelementryService: ValidationTelemetryService; + constructor(private readonly initializerContext: PluginInitializerContext) { this.initializerContext = initializerContext; + this.validationTelementryService = new ValidationTelemetryService(); } - public setup(core: CoreSetup, plugins: any) { + public setup(core: CoreSetup, plugins: VisTypeTimeseriesPluginSetupDependencies) { const logger = this.initializerContext.logger.get('visTypeTimeseries'); const config$ = this.initializerContext.config.create(); // Global config contains things like the ES shard timeout @@ -82,8 +91,13 @@ export class VisTypeTimeseriesPlugin implements Plugin { return { __legacy: { config$, - registerLegacyAPI: once((__LEGACY: LegacySetup) => { - init(framework, __LEGACY); + registerLegacyAPI: once(async (__LEGACY: LegacySetup) => { + const validationTelemetrySetup = await this.validationTelementryService.setup(core, { + ...plugins, + globalConfig$, + }); + + await init(framework, __LEGACY, validationTelemetrySetup); }), }, getVisData: async (requestContext: RequestHandlerContext, options: GetVisDataOptions) => { diff --git a/src/plugins/vis_type_timeseries/server/validation_telemetry/index.ts b/src/plugins/vis_type_timeseries/server/validation_telemetry/index.ts new file mode 100644 index 000000000000..140f61fa2f3f --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/validation_telemetry/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './validation_telemetry_service'; diff --git a/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.ts new file mode 100644 index 000000000000..136f5b9e5cfa --- /dev/null +++ b/src/plugins/vis_type_timeseries/server/validation_telemetry/validation_telemetry_service.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. + */ + +import { APICaller, CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; +import { UsageCollectionSetup } from '../../../usage_collection/server'; + +export interface ValidationTelemetryServiceSetup { + logFailedValidation: () => void; +} + +export class ValidationTelemetryService implements Plugin { + private kibanaIndex: string = ''; + async setup( + core: CoreSetup, + { + usageCollection, + globalConfig$, + }: { + usageCollection?: UsageCollectionSetup; + globalConfig$: PluginInitializerContext['config']['legacy']['globalConfig$']; + } + ) { + globalConfig$.subscribe(config => { + this.kibanaIndex = config.kibana.index; + }); + if (usageCollection) { + usageCollection.registerCollector( + usageCollection.makeUsageCollector({ + type: 'tsvb-validation', + isReady: () => this.kibanaIndex !== '', + fetch: async (callCluster: APICaller) => { + try { + const response = await callCluster('get', { + index: this.kibanaIndex, + id: 'tsvb-validation-telemetry:tsvb-validation-telemetry', + ignore: [404], + }); + return { + failed_validations: + response?._source?.['tsvb-validation-telemetry']?.failedRequests || 0, + }; + } catch (err) { + return { + failed_validations: 0, + }; + } + }, + }) + ); + } + const internalRepository = core.savedObjects.createInternalRepository(); + + return { + logFailedValidation: async () => { + try { + await internalRepository.incrementCounter( + 'tsvb-validation-telemetry', + 'tsvb-validation-telemetry', + 'failedRequests' + ); + } catch (e) { + // swallow error, validation telemetry shouldn't fail anything else + } + }, + }; + } + start() {} +} diff --git a/tasks/config/karma.js b/tasks/config/karma.js index 0acd452530b3..ec37277cae0f 100644 --- a/tasks/config/karma.js +++ b/tasks/config/karma.js @@ -21,6 +21,7 @@ import { dirname } from 'path'; import { times } from 'lodash'; import { makeJunitReportPath } from '@kbn/test'; import * as UiSharedDeps from '@kbn/ui-shared-deps'; +import { DllCompiler } from '../../src/optimize/dynamic_dll_plugin'; const TOTAL_CI_SHARDS = 4; const ROOT = dirname(require.resolve('../../package.json')); @@ -54,7 +55,10 @@ module.exports = function(grunt) { 'http://localhost:5610/test_bundle/built_css.css', `http://localhost:5610/bundles/kbn-ui-shared-deps/${UiSharedDeps.distFilename}`, - 'http://localhost:5610/built_assets/dlls/vendors.bundle.dll.js', + 'http://localhost:5610/built_assets/dlls/vendors_runtime.bundle.dll.js', + ...DllCompiler.getRawDllConfig().chunks.map( + chunk => `http://localhost:5610/built_assets/dlls/vendors${chunk}.bundle.dll.js` + ), shardNum === undefined ? `http://localhost:5610/bundles/tests.bundle.js` @@ -63,7 +67,9 @@ module.exports = function(grunt) { // this causes tilemap tests to fail, probably because the eui styles haven't been // included in the karma harness a long some time, if ever // `http://localhost:5610/bundles/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`, - 'http://localhost:5610/built_assets/dlls/vendors.style.dll.css', + ...DllCompiler.getRawDllConfig().chunks.map( + chunk => `http://localhost:5610/built_assets/dlls/vendors${chunk}.style.dll.css` + ), 'http://localhost:5610/bundles/tests.style.css', ]; } diff --git a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js index 25a533d39dd8..555056173ec6 100644 --- a/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js +++ b/test/api_integration/apis/index_patterns/fields_for_wildcard_route/response.js @@ -71,6 +71,14 @@ export default function({ getService }) { name: 'foo', readFromDocValues: true, }, + { + aggregatable: false, + esTypes: ['nested'], + name: 'nestedField', + readFromDocValues: false, + searchable: false, + type: 'nested', + }, { aggregatable: false, esTypes: ['keyword'], @@ -153,6 +161,14 @@ export default function({ getService }) { name: 'foo', readFromDocValues: true, }, + { + aggregatable: false, + esTypes: ['nested'], + name: 'nestedField', + readFromDocValues: false, + searchable: false, + type: 'nested', + }, { aggregatable: false, esTypes: ['keyword'], diff --git a/test/functional/apps/context/_date_nanos_custom_timestamp.js b/test/functional/apps/context/_date_nanos_custom_timestamp.js new file mode 100644 index 000000000000..3901fa936e71 --- /dev/null +++ b/test/functional/apps/context/_date_nanos_custom_timestamp.js @@ -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 expect from '@kbn/expect'; + +const TEST_INDEX_PATTERN = 'date_nanos_custom_timestamp'; +const TEST_DEFAULT_CONTEXT_SIZE = 1; +const TEST_STEP_SIZE = 3; + +export default function({ getService, getPageObjects }) { + const kibanaServer = getService('kibanaServer'); + const docTable = getService('docTable'); + const PageObjects = getPageObjects(['common', 'context', 'timePicker', 'discover']); + const esArchiver = getService('esArchiver'); + + describe('context view for date_nanos with custom timestamp', () => { + before(async function() { + await esArchiver.loadIfNeeded('date_nanos_custom'); + await kibanaServer.uiSettings.replace({ defaultIndex: TEST_INDEX_PATTERN }); + await kibanaServer.uiSettings.update({ + 'context:defaultSize': `${TEST_DEFAULT_CONTEXT_SIZE}`, + 'context:step': `${TEST_STEP_SIZE}`, + }); + }); + + after(function unloadMakelogs() { + return esArchiver.unload('date_nanos_custom'); + }); + + it('displays predessors - anchor - successors in right order ', async function() { + await PageObjects.context.navigateTo(TEST_INDEX_PATTERN, '1'); + const actualRowsText = await docTable.getRowsText(); + const expectedRowsText = [ + 'Oct 21, 2019 @ 08:30:04.828733000 -', + 'Oct 21, 2019 @ 00:30:04.828740000 -', + 'Oct 21, 2019 @ 00:30:04.828723000 -', + ]; + expect(actualRowsText).to.eql(expectedRowsText); + }); + }); +} diff --git a/test/functional/apps/context/index.js b/test/functional/apps/context/index.js index 9e1b04ad4587..c3c938c62373 100644 --- a/test/functional/apps/context/index.js +++ b/test/functional/apps/context/index.js @@ -42,5 +42,6 @@ export default function({ getService, getPageObjects, loadTestFile }) { loadTestFile(require.resolve('./_filters')); loadTestFile(require.resolve('./_size')); loadTestFile(require.resolve('./_date_nanos')); + loadTestFile(require.resolve('./_date_nanos_custom_timestamp')); }); } diff --git a/test/functional/apps/dashboard/dashboard_clone.js b/test/functional/apps/dashboard/dashboard_clone.js index 2a955a2dc90b..f5485c1db206 100644 --- a/test/functional/apps/dashboard/dashboard_clone.js +++ b/test/functional/apps/dashboard/dashboard_clone.js @@ -21,6 +21,7 @@ import expect from '@kbn/expect'; export default function({ getService, getPageObjects }) { const retry = getService('retry'); + const listingTable = getService('listingTable'); const PageObjects = getPageObjects(['dashboard', 'header', 'common']); describe('dashboard clone', function describeIndexTests() { @@ -40,10 +41,12 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.clickClone(); await PageObjects.dashboard.confirmClone(); - - const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName( + await PageObjects.dashboard.gotoDashboardLandingPage(); + const countOfDashboards = await listingTable.searchAndGetItemsCount( + 'dashboard', clonedDashboardName ); + expect(countOfDashboards).to.equal(1); }); @@ -70,8 +73,10 @@ export default function({ getService, getPageObjects }) { it("and doesn't save", async () => { await PageObjects.dashboard.cancelClone(); + await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName( + const countOfDashboards = await listingTable.searchAndGetItemsCount( + 'dashboard', dashboardName ); expect(countOfDashboards).to.equal(1); @@ -85,8 +90,10 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.expectDuplicateTitleWarningDisplayed({ displayed: true }); await PageObjects.dashboard.confirmClone(); await PageObjects.dashboard.waitForRenderComplete(); + await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName( + const countOfDashboards = await listingTable.searchAndGetItemsCount( + 'dashboard', dashboardName + ' Copy' ); expect(countOfDashboards).to.equal(2); diff --git a/test/functional/apps/dashboard/dashboard_filter_bar.js b/test/functional/apps/dashboard/dashboard_filter_bar.js index 5dcb18374c51..6d2a30fa8532 100644 --- a/test/functional/apps/dashboard/dashboard_filter_bar.js +++ b/test/functional/apps/dashboard/dashboard_filter_bar.js @@ -27,7 +27,7 @@ export default function({ getService, getPageObjects }) { const pieChart = getService('pieChart'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); - const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'visualize']); + const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'visualize', 'timePicker']); describe('dashboard filter bar', () => { before(async () => { @@ -91,7 +91,7 @@ export default function({ getService, getPageObjects }) { await filterBar.ensureFieldEditorModalIsClosed(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.setTimepickerInDataRange(); + await PageObjects.timePicker.setDefaultDataRange(); }); it('are not selected by default', async function() { @@ -136,7 +136,7 @@ export default function({ getService, getPageObjects }) { await filterBar.ensureFieldEditorModalIsClosed(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.setTimepickerInDataRange(); + await PageObjects.timePicker.setDefaultDataRange(); }); it('are added when a cell magnifying glass is clicked', async function() { diff --git a/test/functional/apps/dashboard/dashboard_filtering.js b/test/functional/apps/dashboard/dashboard_filtering.js index bd31bb010f26..1cb9f1490d44 100644 --- a/test/functional/apps/dashboard/dashboard_filtering.js +++ b/test/functional/apps/dashboard/dashboard_filtering.js @@ -34,7 +34,7 @@ export default function({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const dashboardPanelActions = getService('dashboardPanelActions'); - const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'visualize']); + const PageObjects = getPageObjects(['common', 'dashboard', 'header', 'visualize', 'timePicker']); describe('dashboard filtering', function() { this.tags('smoke'); @@ -52,7 +52,7 @@ export default function({ getService, getPageObjects }) { describe('adding a filter that excludes all data', () => { before(async () => { await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.setTimepickerInDataRange(); + await PageObjects.timePicker.setDefaultDataRange(); await dashboardAddPanel.addEveryVisualization('"Filter Bytes Test"'); await dashboardAddPanel.addEverySavedSearch('"Filter Bytes Test"'); @@ -234,7 +234,7 @@ export default function({ getService, getPageObjects }) { it('visualization saved with a query filters data', async () => { await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.setTimepickerInDataRange(); + await PageObjects.timePicker.setDefaultDataRange(); await dashboardAddPanel.addVisualization('Rendering-Test:-animal-sounds-pie'); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/test/functional/apps/dashboard/dashboard_listing.js b/test/functional/apps/dashboard/dashboard_listing.js index 179f10223afb..e3e835109da2 100644 --- a/test/functional/apps/dashboard/dashboard_listing.js +++ b/test/functional/apps/dashboard/dashboard_listing.js @@ -22,6 +22,7 @@ import expect from '@kbn/expect'; export default function({ getService, getPageObjects }) { const PageObjects = getPageObjects(['dashboard', 'header', 'common']); const browser = getService('browser'); + const listingTable = getService('listingTable'); describe('dashboard listing page', function describeIndexTests() { const dashboardName = 'Dashboard Listing Test'; @@ -41,7 +42,8 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.saveDashboard(dashboardName); await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName( + const countOfDashboards = await listingTable.searchAndGetItemsCount( + 'dashboard', dashboardName ); expect(countOfDashboards).to.equal(1); @@ -53,7 +55,8 @@ export default function({ getService, getPageObjects }) { }); it('is not shown when there are no dashboards shown during a search', async function() { - const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName( + const countOfDashboards = await listingTable.searchAndGetItemsCount( + 'dashboard', 'gobeldeguck' ); expect(countOfDashboards).to.equal(0); @@ -65,9 +68,9 @@ export default function({ getService, getPageObjects }) { describe('delete', function() { it('default confirm action is cancel', async function() { - await PageObjects.dashboard.searchForDashboardWithName(dashboardName); - await PageObjects.dashboard.checkDashboardListingSelectAllCheckbox(); - await PageObjects.dashboard.clickDeleteSelectedDashboards(); + await listingTable.searchForItemWithName(dashboardName); + await listingTable.checkListingSelectAllCheckbox(); + await listingTable.clickDeleteSelected(); await PageObjects.common.expectConfirmModalOpenState(true); @@ -75,19 +78,21 @@ export default function({ getService, getPageObjects }) { await PageObjects.common.expectConfirmModalOpenState(false); - const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName( + const countOfDashboards = await listingTable.searchAndGetItemsCount( + 'dashboard', dashboardName ); expect(countOfDashboards).to.equal(1); }); it('succeeds on confirmation press', async function() { - await PageObjects.dashboard.checkDashboardListingSelectAllCheckbox(); - await PageObjects.dashboard.clickDeleteSelectedDashboards(); + await listingTable.checkListingSelectAllCheckbox(); + await listingTable.clickDeleteSelected(); await PageObjects.common.clickConfirmOnModal(); - const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName( + const countOfDashboards = await listingTable.searchAndGetItemsCount( + 'dashboard', dashboardName ); expect(countOfDashboards).to.equal(0); @@ -96,44 +101,45 @@ export default function({ getService, getPageObjects }) { describe('search', function() { before(async () => { - await PageObjects.dashboard.clearSearchValue(); + await listingTable.clearSearchFilter(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.saveDashboard('Two Words'); + await PageObjects.dashboard.gotoDashboardLandingPage(); }); it('matches on the first word', async function() { - await PageObjects.dashboard.searchForDashboardWithName('Two'); - const countOfDashboards = await PageObjects.dashboard.getCountOfDashboardsInListingTable(); + await listingTable.searchForItemWithName('Two'); + const countOfDashboards = await listingTable.getItemsCount('dashboard'); expect(countOfDashboards).to.equal(1); }); it('matches the second word', async function() { - await PageObjects.dashboard.searchForDashboardWithName('Words'); - const countOfDashboards = await PageObjects.dashboard.getCountOfDashboardsInListingTable(); + await listingTable.searchForItemWithName('Words'); + const countOfDashboards = await listingTable.getItemsCount('dashboard'); expect(countOfDashboards).to.equal(1); }); it('matches the second word prefix', async function() { - await PageObjects.dashboard.searchForDashboardWithName('Wor'); - const countOfDashboards = await PageObjects.dashboard.getCountOfDashboardsInListingTable(); + await listingTable.searchForItemWithName('Wor'); + const countOfDashboards = await listingTable.getItemsCount('dashboard'); expect(countOfDashboards).to.equal(1); }); it('does not match mid word', async function() { - await PageObjects.dashboard.searchForDashboardWithName('ords'); - const countOfDashboards = await PageObjects.dashboard.getCountOfDashboardsInListingTable(); + await listingTable.searchForItemWithName('ords'); + const countOfDashboards = await listingTable.getItemsCount('dashboard'); expect(countOfDashboards).to.equal(0); }); it('is case insensitive', async function() { - await PageObjects.dashboard.searchForDashboardWithName('two words'); - const countOfDashboards = await PageObjects.dashboard.getCountOfDashboardsInListingTable(); + await listingTable.searchForItemWithName('two words'); + const countOfDashboards = await listingTable.getItemsCount('dashboard'); expect(countOfDashboards).to.equal(1); }); it('is using AND operator', async function() { - await PageObjects.dashboard.searchForDashboardWithName('three words'); - const countOfDashboards = await PageObjects.dashboard.getCountOfDashboardsInListingTable(); + await listingTable.searchForItemWithName('three words'); + const countOfDashboards = await listingTable.getItemsCount('dashboard'); expect(countOfDashboards).to.equal(0); }); }); @@ -176,7 +182,7 @@ export default function({ getService, getPageObjects }) { }); it('preloads search filter bar when there is no match', async function() { - const searchFilter = await PageObjects.dashboard.getSearchFilterValue(); + const searchFilter = await listingTable.getSearchFilterValue(); expect(searchFilter).to.equal('"nodashboardsnamedme"'); }); @@ -196,7 +202,7 @@ export default function({ getService, getPageObjects }) { }); it('preloads search filter bar when there is more than one match', async function() { - const searchFilter = await PageObjects.dashboard.getSearchFilterValue(); + const searchFilter = await listingTable.getSearchFilterValue(); expect(searchFilter).to.equal('"two words"'); }); diff --git a/test/functional/apps/dashboard/dashboard_save.js b/test/functional/apps/dashboard/dashboard_save.js index 23bb784c79cd..2ea1389b89ad 100644 --- a/test/functional/apps/dashboard/dashboard_save.js +++ b/test/functional/apps/dashboard/dashboard_save.js @@ -19,8 +19,9 @@ import expect from '@kbn/expect'; -export default function({ getPageObjects }) { +export default function({ getPageObjects, getService }) { const PageObjects = getPageObjects(['dashboard', 'header']); + const listingTable = getService('listingTable'); describe('dashboard save', function describeIndexTests() { this.tags('smoke'); @@ -47,8 +48,10 @@ export default function({ getPageObjects }) { it('does not save on reject confirmation', async function() { await PageObjects.dashboard.cancelSave(); + await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName( + const countOfDashboards = await listingTable.searchAndGetItemsCount( + 'dashboard', dashboardName ); expect(countOfDashboards).to.equal(1); @@ -68,15 +71,17 @@ export default function({ getPageObjects }) { // wait till it finishes reloading or it might reload the url after simulating the // dashboard landing page click. await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName( + const countOfDashboards = await listingTable.searchAndGetItemsCount( + 'dashboard', dashboardName ); expect(countOfDashboards).to.equal(2); }); it('Does not warn when you save an existing dashboard with the title it already has, and that title is a duplicate', async function() { - await PageObjects.dashboard.selectDashboard(dashboardName); + await listingTable.clickItemLink('dashboard', dashboardName); await PageObjects.header.awaitGlobalLoadingIndicatorHidden(); await PageObjects.dashboard.switchToEditMode(); await PageObjects.dashboard.saveDashboard(dashboardName); @@ -121,8 +126,10 @@ export default function({ getPageObjects }) { // wait till it finishes reloading or it might reload the url after simulating the // dashboard landing page click. await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.gotoDashboardLandingPage(); - const countOfDashboards = await PageObjects.dashboard.getDashboardCountWithName( + const countOfDashboards = await listingTable.searchAndGetItemsCount( + 'dashboard', dashboardNameEnterKey ); expect(countOfDashboards).to.equal(1); diff --git a/test/functional/apps/dashboard/dashboard_snapshots.js b/test/functional/apps/dashboard/dashboard_snapshots.js index 9900881e4690..3a09b46a713c 100644 --- a/test/functional/apps/dashboard/dashboard_snapshots.js +++ b/test/functional/apps/dashboard/dashboard_snapshots.js @@ -20,7 +20,7 @@ import expect from '@kbn/expect'; export default function({ getService, getPageObjects, updateBaselines }) { - const PageObjects = getPageObjects(['dashboard', 'header', 'visualize', 'common']); + const PageObjects = getPageObjects(['dashboard', 'header', 'visualize', 'common', 'timePicker']); const screenshot = getService('screenshots'); const browser = getService('browser'); const esArchiver = getService('esArchiver'); @@ -48,7 +48,7 @@ export default function({ getService, getPageObjects, updateBaselines }) { it('compare TSVB snapshot', async () => { await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.setTimepickerInLogstashDataRange(); + await PageObjects.timePicker.setLogstashDataRange(); await dashboardAddPanel.addVisualization('Rendering Test: tsvb-ts'); await PageObjects.common.closeToast(); @@ -71,7 +71,7 @@ export default function({ getService, getPageObjects, updateBaselines }) { it('compare area chart snapshot', async () => { await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.setTimepickerInLogstashDataRange(); + await PageObjects.timePicker.setLogstashDataRange(); await dashboardAddPanel.addVisualization('Rendering Test: area with not filter'); await PageObjects.common.closeToast(); diff --git a/test/functional/apps/dashboard/dashboard_state.js b/test/functional/apps/dashboard/dashboard_state.js index 3b9e404e9b94..b9172990c501 100644 --- a/test/functional/apps/dashboard/dashboard_state.js +++ b/test/functional/apps/dashboard/dashboard_state.js @@ -34,6 +34,7 @@ export default function({ getService, getPageObjects }) { 'discover', 'tileMap', 'visChart', + 'timePicker', ]); const testSubjects = getService('testSubjects'); const browser = getService('browser'); @@ -58,7 +59,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.setTimepickerInHistoricalDataRange(); + await PageObjects.timePicker.setHistoricalDataRange(); await dashboardAddPanel.addVisualization(AREA_CHART_VIS_NAME); await PageObjects.dashboard.saveDashboard('Overridden colors'); @@ -83,7 +84,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.header.clickDiscover(); - await PageObjects.dashboard.setTimepickerInHistoricalDataRange(); + await PageObjects.timePicker.setHistoricalDataRange(); await PageObjects.discover.clickFieldListItemAdd('bytes'); await PageObjects.discover.saveSearch('my search'); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -147,7 +148,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.setTimepickerInHistoricalDataRange(); + await PageObjects.timePicker.setHistoricalDataRange(); await dashboardAddPanel.addVisualization('Visualization TileMap'); await PageObjects.dashboard.saveDashboard('No local edits'); diff --git a/test/functional/apps/dashboard/dashboard_time_picker.js b/test/functional/apps/dashboard/dashboard_time_picker.js index 0b73bc224ab7..b99de9fee6db 100644 --- a/test/functional/apps/dashboard/dashboard_time_picker.js +++ b/test/functional/apps/dashboard/dashboard_time_picker.js @@ -44,7 +44,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.addVisualizations([PIE_CHART_VIS_NAME]); await pieChart.expectPieSliceCount(0); - await PageObjects.dashboard.setTimepickerInHistoricalDataRange(); + await PageObjects.timePicker.setHistoricalDataRange(); await pieChart.expectPieSliceCount(10); }); @@ -95,7 +95,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.addVisualizations([PIE_CHART_VIS_NAME]); - // Same date range as `setTimepickerInHistoricalDataRange` + // Same date range as `timePicker.setHistoricalDataRange()` await PageObjects.timePicker.setAbsoluteRange( '2015-09-19 06:31:44.000', '2015-09-23 18:31:44.000' diff --git a/test/functional/apps/dashboard/panel_controls.js b/test/functional/apps/dashboard/panel_controls.js index 683f3683e65e..f30f58913bd9 100644 --- a/test/functional/apps/dashboard/panel_controls.js +++ b/test/functional/apps/dashboard/panel_controls.js @@ -33,7 +33,13 @@ export default function({ getService, getPageObjects }) { const dashboardReplacePanel = getService('dashboardReplacePanel'); const dashboardVisualizations = getService('dashboardVisualizations'); const renderable = getService('renderable'); - const PageObjects = getPageObjects(['dashboard', 'header', 'visualize', 'discover']); + const PageObjects = getPageObjects([ + 'dashboard', + 'header', + 'visualize', + 'discover', + 'timePicker', + ]); const dashboardName = 'Dashboard Panel Controls Test'; describe('dashboard panel controls', function viewEditModeTests() { @@ -52,7 +58,7 @@ export default function({ getService, getPageObjects }) { let intialDimensions; before(async () => { await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.setTimepickerInHistoricalDataRange(); + await PageObjects.timePicker.setHistoricalDataRange(); await dashboardAddPanel.addVisualization(PIE_CHART_VIS_NAME); await dashboardAddPanel.addVisualization(LINE_CHART_VIS_NAME); intialDimensions = await PageObjects.dashboard.getPanelDimensions(); @@ -110,7 +116,7 @@ export default function({ getService, getPageObjects }) { describe('panel edit controls', function() { before(async () => { await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.dashboard.setTimepickerInHistoricalDataRange(); + await PageObjects.timePicker.setHistoricalDataRange(); await dashboardAddPanel.addVisualization(PIE_CHART_VIS_NAME); }); diff --git a/test/functional/apps/dashboard/view_edit.js b/test/functional/apps/dashboard/view_edit.js index 212044d89825..a0b972f3ab63 100644 --- a/test/functional/apps/dashboard/view_edit.js +++ b/test/functional/apps/dashboard/view_edit.js @@ -68,7 +68,7 @@ export default function({ getService, getPageObjects }) { }); it('when time changed is stored with dashboard', async function() { - await PageObjects.dashboard.setTimepickerInDataRange(); + await PageObjects.timePicker.setDefaultDataRange(); const originalTime = await PageObjects.timePicker.getTimeConfig(); @@ -196,7 +196,7 @@ export default function({ getService, getPageObjects }) { describe('and preserves edits on cancel', function() { it('when time changed is stored with dashboard', async function() { await PageObjects.dashboard.gotoDashboardEditMode(dashboardName); - await PageObjects.dashboard.setTimepickerInDataRange(); + await PageObjects.timePicker.setDefaultDataRange(); await PageObjects.dashboard.saveDashboard(dashboardName, true); await PageObjects.dashboard.switchToEditMode(); await PageObjects.timePicker.setAbsoluteRange( diff --git a/test/functional/apps/home/_sample_data.js b/test/functional/apps/home/_sample_data.ts similarity index 94% rename from test/functional/apps/home/_sample_data.js rename to test/functional/apps/home/_sample_data.ts index 4aa862a4a038..8088b5a0f9da 100644 --- a/test/functional/apps/home/_sample_data.js +++ b/test/functional/apps/home/_sample_data.ts @@ -19,8 +19,9 @@ import expect from '@kbn/expect'; import moment from 'moment'; +import { FtrProviderContext } from '../../ftr_provider_context'; -export default function({ getService, getPageObjects }) { +export default function({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const find = getService('find'); const log = getService('log'); @@ -76,9 +77,8 @@ export default function({ getService, getPageObjects }) { expect(isInstalled).to.be(true); }); - // FLAKY: https://github.com/elastic/kibana/issues/40670 - describe.skip('dashboard', () => { - afterEach(async () => { + describe('dashboard', () => { + beforeEach(async () => { await PageObjects.common.navigateToUrl('home', 'tutorial_directory/sampleData'); await PageObjects.header.waitUntilLoadingHasFinished(); }); @@ -99,7 +99,6 @@ export default function({ getService, getPageObjects }) { await PageObjects.home.launchSampleDataSet('flights'); await PageObjects.header.waitUntilLoadingHasFinished(); await renderable.waitForRender(); - log.debug('Checking pie charts rendered'); await pieChart.expectPieSliceCount(4); log.debug('Checking area, bar and heatmap charts rendered'); @@ -142,6 +141,11 @@ export default function({ getService, getPageObjects }) { // needs to be in describe block so it is run after 'dashboard describe block' describe('uninstall', () => { + beforeEach(async () => { + await PageObjects.common.navigateToUrl('home', 'tutorial_directory/sampleData'); + await PageObjects.header.waitUntilLoadingHasFinished(); + }); + it('should uninstall flights sample data set', async () => { await PageObjects.home.removeSampleDataSet('flights'); const isInstalled = await PageObjects.home.isSampleDataSetInstalled('flights'); diff --git a/test/functional/apps/visualize/_data_table_notimeindex_filters.ts b/test/functional/apps/visualize/_data_table_notimeindex_filters.ts new file mode 100644 index 000000000000..eb83e80e2bbe --- /dev/null +++ b/test/functional/apps/visualize/_data_table_notimeindex_filters.ts @@ -0,0 +1,90 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ getService, getPageObjects }: FtrProviderContext) { + const log = getService('log'); + const filterBar = getService('filterBar'); + const renderable = getService('renderable'); + const dashboardAddPanel = getService('dashboardAddPanel'); + const PageObjects = getPageObjects([ + 'common', + 'visualize', + 'header', + 'dashboard', + 'timePicker', + 'visEditor', + 'visChart', + ]); + + describe('data table with index without time filter filters', function indexPatternCreation() { + const vizName1 = 'Visualization DataTable w/o time filter'; + + before(async function() { + log.debug('navigateToApp visualize'); + await PageObjects.visualize.navigateToNewVisualization(); + log.debug('clickDataTable'); + await PageObjects.visualize.clickDataTable(); + log.debug('clickNewSearch'); + await PageObjects.visualize.clickNewSearch( + PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED + ); + log.debug('Bucket = Split Rows'); + await PageObjects.visEditor.clickBucket('Split rows'); + log.debug('Aggregation = Histogram'); + await PageObjects.visEditor.selectAggregation('Histogram'); + log.debug('Field = bytes'); + await PageObjects.visEditor.selectField('bytes'); + log.debug('Interval = 2000'); + await PageObjects.visEditor.setInterval('2000', { type: 'numeric' }); + await PageObjects.visEditor.clickGo(); + }); + + it('should be able to save and load', async function() { + await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); + + await PageObjects.visualize.loadSavedVisualization(vizName1); + await PageObjects.visChart.waitForVisualization(); + }); + + it('timefilter should be disabled', async () => { + const isOff = await PageObjects.timePicker.isOff(); + expect(isOff).to.be(true); + }); + + // test to cover bug #54548 - add this visualization to a dashboard and filter + it('should add to dashboard and allow filtering', async function() { + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.clickNewDashboard(); + await dashboardAddPanel.addVisualization(vizName1); + + // hover and click on cell to filter + await PageObjects.visChart.filterOnTableCell('1', '2'); + + await PageObjects.header.waitUntilLoadingHasFinished(); + await renderable.waitForRender(); + const filterCount = await filterBar.getFilterCount(); + expect(filterCount).to.be(1); + + await filterBar.removeAllFilters(); + }); + }); +} diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index 2a13b6fea915..68285971e5c4 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -47,6 +47,7 @@ export default function({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_area_chart')); loadTestFile(require.resolve('./_data_table')); loadTestFile(require.resolve('./_data_table_nontimeindex')); + loadTestFile(require.resolve('./_data_table_notimeindex_filters')); }); describe('', function() { diff --git a/test/functional/fixtures/es_archiver/date_nanos_custom/data.json b/test/functional/fixtures/es_archiver/date_nanos_custom/data.json new file mode 100644 index 000000000000..73cba70a8b93 --- /dev/null +++ b/test/functional/fixtures/es_archiver/date_nanos_custom/data.json @@ -0,0 +1,56 @@ +{ + "type": "doc", + "value": { + "id": "index-pattern:date_nanos_custom_timestamp", + "index": ".kibana", + "source": { + "index-pattern": { + "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"test\",\"type\":\"string\",\"esTypes\":[\"text\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"test.keyword\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\":\"test\"}}},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date_nanos\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "timeFieldName": "timestamp", + "title": "date_nanos_custom_timestamp" + }, + "references": [ + ], + "type": "index-pattern", + "updated_at": "2020-01-09T21:43:20.283Z" + } + } +} + +{ + "type": "doc", + "value": { + "id": "1", + "index": "date_nanos_custom_timestamp", + "source": { + "test": "1", + "timestamp": "2019-10-21 00:30:04.828740" + } + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "date_nanos_custom_timestamp", + "source": { + "test": "1", + "timestamp": "2019-10-21 08:30:04.828733" + } + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "date_nanos_custom_timestamp", + "source": { + "test": "1", + "timestamp": "2019-10-21 00:30:04.828723" + } + } +} + + diff --git a/test/functional/fixtures/es_archiver/date_nanos_custom/mappings.json b/test/functional/fixtures/es_archiver/date_nanos_custom/mappings.json new file mode 100644 index 000000000000..98af509c4e6f --- /dev/null +++ b/test/functional/fixtures/es_archiver/date_nanos_custom/mappings.json @@ -0,0 +1,31 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "date_nanos_custom_timestamp", + "mappings": { + "properties": { + "test": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "timestamp": { + "format": "yyyy-MM-dd HH:mm:ss.SSSSSS", + "type": "date_nanos" + } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/test/functional/page_objects/dashboard_page.js b/test/functional/page_objects/dashboard_page.ts similarity index 60% rename from test/functional/page_objects/dashboard_page.js rename to test/functional/page_objects/dashboard_page.ts index b0f1a3304a9b..af0a0160a81d 100644 --- a/test/functional/page_objects/dashboard_page.js +++ b/test/functional/page_objects/dashboard_page.ts @@ -17,89 +17,85 @@ * under the License. */ -import _ from 'lodash'; import { DashboardConstants } from '../../../src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_constants'; export const PIE_CHART_VIS_NAME = 'Visualization PieChart'; export const AREA_CHART_VIS_NAME = 'Visualization漢字 AreaChart'; export const LINE_CHART_VIS_NAME = 'Visualization漢字 LineChart'; +import { FtrProviderContext } from '../ftr_provider_context'; -export function DashboardPageProvider({ getService, getPageObjects }) { +export function DashboardPageProvider({ getService, getPageObjects }: FtrProviderContext) { const log = getService('log'); const find = getService('find'); const retry = getService('retry'); - const config = getService('config'); const browser = getService('browser'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const testSubjects = getService('testSubjects'); const dashboardAddPanel = getService('dashboardAddPanel'); const renderable = getService('renderable'); - const PageObjects = getPageObjects(['common', 'header', 'settings', 'visualize', 'timePicker']); - - const defaultFindTimeout = config.get('timeouts.find'); + const listingTable = getService('listingTable'); + const PageObjects = getPageObjects(['common', 'header', 'visualize']); + + interface SaveDashboardOptions { + waitDialogIsClosed: boolean; + needsConfirm?: boolean; + storeTimeWithDashboard?: boolean; + saveAsNew?: boolean; + } class DashboardPage { async initTests({ kibanaIndex = 'dashboard/legacy', defaultIndex = 'logstash-*' } = {}) { log.debug('load kibana index with visualizations and log data'); await esArchiver.load(kibanaIndex); - await kibanaServer.uiSettings.replace({ - defaultIndex: defaultIndex, - }); + await kibanaServer.uiSettings.replace({ defaultIndex }); await PageObjects.common.navigateToApp('dashboard'); } - async preserveCrossAppState() { + public async preserveCrossAppState() { const url = await browser.getCurrentUrl(); await browser.get(url, false); await PageObjects.header.waitUntilLoadingHasFinished(); } - async selectDefaultIndex(indexName) { - await PageObjects.settings.navigateTo(); - await PageObjects.settings.clickKibanaIndexPatterns(); - await find.clickByPartialLinkText(indexName); - await PageObjects.settings.clickDefaultIndexButton(); - } - - async clickFullScreenMode() { + public async clickFullScreenMode() { log.debug(`clickFullScreenMode`); await testSubjects.click('dashboardFullScreenMode'); await testSubjects.exists('exitFullScreenModeLogo'); await this.waitForRenderComplete(); } - async fullScreenModeMenuItemExists() { + public async fullScreenModeMenuItemExists() { return await testSubjects.exists('dashboardFullScreenMode'); } - async exitFullScreenTextButtonExists() { + public async exitFullScreenTextButtonExists() { return await testSubjects.exists('exitFullScreenModeText'); } - async getExitFullScreenTextButton() { + public async getExitFullScreenTextButton() { return await testSubjects.find('exitFullScreenModeText'); } - async exitFullScreenLogoButtonExists() { + public async exitFullScreenLogoButtonExists() { return await testSubjects.exists('exitFullScreenModeLogo'); } - async getExitFullScreenLogoButton() { + public async getExitFullScreenLogoButton() { return await testSubjects.find('exitFullScreenModeLogo'); } - async clickExitFullScreenLogoButton() { + public async clickExitFullScreenLogoButton() { await testSubjects.click('exitFullScreenModeLogo'); await this.waitForRenderComplete(); } - async clickExitFullScreenTextButton() { + public async clickExitFullScreenTextButton() { await testSubjects.click('exitFullScreenModeText'); await this.waitForRenderComplete(); } - async getDashboardIdFromCurrentUrl() { + public async getDashboardIdFromCurrentUrl() { const currentUrl = await browser.getCurrentUrl(); const urlSubstring = 'kibana#/dashboard/'; const startOfIdIndex = currentUrl.indexOf(urlSubstring) + urlSubstring.length; @@ -115,25 +111,25 @@ export function DashboardPageProvider({ getService, getPageObjects }) { * Returns true if already on the dashboard landing page (that page doesn't have a link to itself). * @returns {Promise} */ - async onDashboardLandingPage() { + public async onDashboardLandingPage() { log.debug(`onDashboardLandingPage`); return await testSubjects.exists('dashboardLandingPage', { timeout: 5000, }); } - async expectExistsDashboardLandingPage() { + public async expectExistsDashboardLandingPage() { log.debug(`expectExistsDashboardLandingPage`); await testSubjects.existOrFail('dashboardLandingPage'); } - async clickDashboardBreadcrumbLink() { + public async clickDashboardBreadcrumbLink() { log.debug('clickDashboardBreadcrumbLink'); await find.clickByCssSelector(`a[href="#${DashboardConstants.LANDING_PAGE_PATH}"]`); await this.expectExistsDashboardLandingPage(); } - async gotoDashboardLandingPage() { + public async gotoDashboardLandingPage() { log.debug('gotoDashboardLandingPage'); const onPage = await this.onDashboardLandingPage(); if (!onPage) { @@ -141,26 +137,26 @@ export function DashboardPageProvider({ getService, getPageObjects }) { } } - async clickClone() { + public async clickClone() { log.debug('Clicking clone'); await testSubjects.click('dashboardClone'); } - async getCloneTitle() { + public async getCloneTitle() { return await testSubjects.getAttribute('clonedDashboardTitle', 'value'); } - async confirmClone() { + public async confirmClone() { log.debug('Confirming clone'); await testSubjects.click('cloneConfirmButton'); } - async cancelClone() { + public async cancelClone() { log.debug('Canceling clone'); await testSubjects.click('cloneCancelButton'); } - async setClonedDashboardTitle(title) { + public async setClonedDashboardTitle(title: string) { await testSubjects.setValue('clonedDashboardTitle', title); } @@ -168,7 +164,7 @@ export function DashboardPageProvider({ getService, getPageObjects }) { * Asserts that the duplicate title warning is either displayed or not displayed. * @param { displayed: boolean } */ - async expectDuplicateTitleWarningDisplayed({ displayed }) { + public async expectDuplicateTitleWarningDisplayed({ displayed = true }) { if (displayed) { await testSubjects.existOrFail('titleDupicateWarnMsg'); } else { @@ -180,18 +176,16 @@ export function DashboardPageProvider({ getService, getPageObjects }) { * Asserts that the toolbar pagination (count and arrows) is either displayed or not displayed. * @param { displayed: boolean } */ - async expectToolbarPaginationDisplayed({ displayed }) { + public async expectToolbarPaginationDisplayed({ displayed = true }) { const subjects = ['btnPrevPage', 'btnNextPage', 'toolBarPagerText']; if (displayed) { - return await Promise.all(subjects.map(async subj => await testSubjects.existOrFail(subj))); + await Promise.all(subjects.map(async subj => await testSubjects.existOrFail(subj))); } else { - return await Promise.all( - subjects.map(async subj => await testSubjects.missingOrFail(subj)) - ); + await Promise.all(subjects.map(async subj => await testSubjects.missingOrFail(subj))); } } - async switchToEditMode() { + public async switchToEditMode() { log.debug('Switching to edit mode'); await testSubjects.click('dashboardEditMode'); // wait until the count of dashboard panels equals the count of toggle menu icons @@ -204,66 +198,34 @@ export function DashboardPageProvider({ getService, getPageObjects }) { }); } - async getIsInViewMode() { + public async getIsInViewMode() { log.debug('getIsInViewMode'); return await testSubjects.exists('dashboardEditMode'); } - async clickCancelOutOfEditMode() { + public async clickCancelOutOfEditMode() { log.debug('clickCancelOutOfEditMode'); - return await testSubjects.click('dashboardViewOnlyMode'); + await testSubjects.click('dashboardViewOnlyMode'); } - async clickNewDashboard() { - // One or the other will eventually show up on the landing page, depending on whether there are - // dashboards. - await retry.try(async () => { - const createNewItemButtonExists = await testSubjects.exists('newItemButton'); - if (createNewItemButtonExists) { - return await testSubjects.click('newItemButton'); - } - const createNewItemPromptExists = await this.getCreateDashboardPromptExists(); - if (createNewItemPromptExists) { - return await this.clickCreateDashboardPrompt(); - } - - throw new Error( - 'Page is still loading... waiting for create new prompt or button to appear' - ); - }); + public async clickNewDashboard() { + await listingTable.clickNewButton('createDashboardPromptButton'); } - async clickCreateDashboardPrompt() { + public async clickCreateDashboardPrompt() { await testSubjects.click('createDashboardPromptButton'); } - async getCreateDashboardPromptExists() { + public async getCreateDashboardPromptExists() { return await testSubjects.exists('createDashboardPromptButton'); } - async checkDashboardListingRow(id) { - await testSubjects.click(`checkboxSelectRow-${id}`); - } - - async checkDashboardListingSelectAllCheckbox() { - const element = await testSubjects.find('checkboxSelectAll'); - const isSelected = await element.isSelected(); - if (!isSelected) { - log.debug(`checking checkbox "checkboxSelectAll"`); - await testSubjects.click('checkboxSelectAll'); - } - } - - async clickDeleteSelectedDashboards() { - await testSubjects.click('deleteSelectedItems'); - } - - async isOptionsOpen() { + public async isOptionsOpen() { log.debug('isOptionsOpen'); return await testSubjects.exists('dashboardOptionsMenu'); } - async openOptions() { + public async openOptions() { log.debug('openOptions'); const isOpen = await this.isOptionsOpen(); if (!isOpen) { @@ -272,36 +234,36 @@ export function DashboardPageProvider({ getService, getPageObjects }) { } // avoids any 'Object with id x not found' errors when switching tests. - async clearSavedObjectsFromAppLinks() { + public async clearSavedObjectsFromAppLinks() { await PageObjects.header.clickVisualize(); await PageObjects.visualize.gotoLandingPage(); await PageObjects.header.clickDashboard(); await this.gotoDashboardLandingPage(); } - async isMarginsOn() { + public async isMarginsOn() { log.debug('isMarginsOn'); await this.openOptions(); return await testSubjects.getAttribute('dashboardMarginsCheckbox', 'checked'); } - async useMargins(on = true) { + public async useMargins(on = true) { await this.openOptions(); const isMarginsOn = await this.isMarginsOn(); - if (isMarginsOn !== on) { + if (isMarginsOn !== 'on') { return await testSubjects.click('dashboardMarginsCheckbox'); } } - async gotoDashboardEditMode(dashboardName) { + public async gotoDashboardEditMode(dashboardName: string) { await this.loadSavedDashboard(dashboardName); await this.switchToEditMode(); } - async renameDashboard(dashName) { - log.debug(`Naming dashboard ` + dashName); + public async renameDashboard(dashboardName: string) { + log.debug(`Naming dashboard ` + dashboardName); await testSubjects.click('dashboardRenameButton'); - await testSubjects.setValue('savedObjectTitle', dashName); + await testSubjects.setValue('savedObjectTitle', dashboardName); } /** @@ -309,11 +271,14 @@ export function DashboardPageProvider({ getService, getPageObjects }) { * verify that the save was successful, close the toast and return the * toast message * - * @param dashName {String} + * @param dashboardName {String} * @param saveOptions {{storeTimeWithDashboard: boolean, saveAsNew: boolean, needsConfirm: false, waitDialogIsClosed: boolean }} */ - async saveDashboard(dashName, saveOptions = { waitDialogIsClosed: true }) { - await this.enterDashboardTitleAndClickSave(dashName, saveOptions); + public async saveDashboard( + dashboardName: string, + saveOptions: SaveDashboardOptions = { waitDialogIsClosed: true } + ) { + await this.enterDashboardTitleAndClickSave(dashboardName, saveOptions); if (saveOptions.needsConfirm) { await this.clickSave(); @@ -328,37 +293,24 @@ export function DashboardPageProvider({ getService, getPageObjects }) { return message; } - async deleteDashboard(dashboardName, dashboardId) { - await this.gotoDashboardLandingPage(); - await this.searchForDashboardWithName(dashboardName); - await this.checkDashboardListingRow(dashboardId); - await this.clickDeleteSelectedDashboards(); - await PageObjects.common.clickConfirmOnModal(); - } - - async cancelSave() { + public async cancelSave() { log.debug('Canceling save'); await testSubjects.click('saveCancelButton'); } - async clickSave() { + public async clickSave() { log.debug('DashboardPage.clickSave'); await testSubjects.click('confirmSaveSavedObjectButton'); } - async pressEnterKey() { - log.debug('DashboardPage.pressEnterKey'); - await PageObjects.common.pressEnterKey(); - } - /** * * @param dashboardTitle {String} * @param saveOptions {{storeTimeWithDashboard: boolean, saveAsNew: boolean, waitDialogIsClosed: boolean}} */ - async enterDashboardTitleAndClickSave( - dashboardTitle, - saveOptions = { waitDialogIsClosed: true } + public async enterDashboardTitleAndClickSave( + dashboardTitle: string, + saveOptions: SaveDashboardOptions = { waitDialogIsClosed: true } ) { await testSubjects.click('dashboardSaveMenuItem'); const modalDialog = await testSubjects.find('savedObjectSaveModal'); @@ -380,128 +332,66 @@ export function DashboardPageProvider({ getService, getPageObjects }) { } } - async ensureDuplicateTitleCallout() { + public async ensureDuplicateTitleCallout() { await testSubjects.existOrFail('titleDupicateWarnMsg'); } /** * @param dashboardTitle {String} */ - async enterDashboardTitleAndPressEnter(dashboardTitle) { + public async enterDashboardTitleAndPressEnter(dashboardTitle: string) { await testSubjects.click('dashboardSaveMenuItem'); const modalDialog = await testSubjects.find('savedObjectSaveModal'); log.debug('entering new title'); await testSubjects.setValue('savedObjectTitle', dashboardTitle); - await this.pressEnterKey(); + await PageObjects.common.pressEnterKey(); await testSubjects.waitForDeleted(modalDialog); } - async selectDashboard(dashName) { - await testSubjects.click(`dashboardListingTitleLink-${dashName.split(' ').join('-')}`); - } - - async clearSearchValue() { - log.debug(`clearSearchValue`); - - await this.gotoDashboardLandingPage(); - - await retry.try(async () => { - const searchFilter = await this.getSearchFilter(); - await searchFilter.clearValue(); - await PageObjects.common.pressEnterKey(); - }); - } - - async getSearchFilterValue() { - const searchFilter = await this.getSearchFilter(); - return await searchFilter.getAttribute('value'); - } - - async getSearchFilter() { - const searchFilter = await find.allByCssSelector('.euiFieldSearch'); - return searchFilter[0]; - } - - async searchForDashboardWithName(dashName) { - log.debug(`searchForDashboardWithName: ${dashName}`); - - await this.gotoDashboardLandingPage(); - - await retry.try(async () => { - const searchFilter = await this.getSearchFilter(); - await searchFilter.clearValue(); - await searchFilter.click(); - // Note: this replacement of - to space is to preserve original logic but I'm not sure why or if it's needed. - await searchFilter.type(dashName.replace('-', ' ')); - await PageObjects.common.pressEnterKey(); - await find.waitForDeletedByCssSelector('.euiBasicTable-loading', 5000); - }); - - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async getCountOfDashboardsInListingTable() { - const dashboardTitles = await find.allByCssSelector( - '[data-test-subj^="dashboardListingTitleLink"]' - ); - return dashboardTitles.length; - } - - async getDashboardCountWithName(dashName) { - log.debug(`getDashboardCountWithName: ${dashName}`); - - await this.searchForDashboardWithName(dashName); - const links = await testSubjects.findAll( - `dashboardListingTitleLink-${dashName.replace(/ /g, '-')}` - ); - return links.length; - } - // use the search filter box to narrow the results down to a single // entry, or at least to a single page of results - async loadSavedDashboard(dashName) { - log.debug(`Load Saved Dashboard ${dashName}`); + public async loadSavedDashboard(dashboardName: string) { + log.debug(`Load Saved Dashboard ${dashboardName}`); await this.gotoDashboardLandingPage(); - await this.searchForDashboardWithName(dashName); + await listingTable.searchForItemWithName(dashboardName); await retry.try(async () => { - await this.selectDashboard(dashName); + await listingTable.clickItemLink('dashboard', dashboardName); await PageObjects.header.waitUntilLoadingHasFinished(); // check Dashboard landing page is not present await testSubjects.missingOrFail('dashboardLandingPage', { timeout: 10000 }); }); } - async getPanelTitles() { + public async getPanelTitles() { log.debug('in getPanelTitles'); const titleObjects = await testSubjects.findAll('dashboardPanelTitle'); return await Promise.all(titleObjects.map(async title => await title.getVisibleText())); } - async getPanelDimensions() { + public async getPanelDimensions() { const panels = await find.allByCssSelector('.react-grid-item'); // These are gridster-defined elements and classes - async function getPanelDimensions(panel) { - const size = await panel.getSize(); - return { - width: size.width, - height: size.height, - }; - } - - const getDimensionsPromises = _.map(panels, getPanelDimensions); - return await Promise.all(getDimensionsPromises); + return await Promise.all( + panels.map(async panel => { + const size = await panel.getSize(); + return { + width: size.width, + height: size.height, + }; + }) + ); } - async getPanelCount() { + public async getPanelCount() { log.debug('getPanelCount'); const panels = await testSubjects.findAll('embeddablePanel'); return panels.length; } - getTestVisualizations() { + public getTestVisualizations() { return [ { name: PIE_CHART_VIS_NAME, description: 'PieChart' }, { name: 'Visualization☺ VerticalBarChart', description: 'VerticalBarChart' }, @@ -513,69 +403,45 @@ export function DashboardPageProvider({ getService, getPageObjects }) { ]; } - getTestVisualizationNames() { + public getTestVisualizationNames() { return this.getTestVisualizations().map(visualization => visualization.name); } - getTestVisualizationDescriptions() { + public getTestVisualizationDescriptions() { return this.getTestVisualizations().map(visualization => visualization.description); } - async getDashboardPanels() { + public async getDashboardPanels() { return await testSubjects.findAll('embeddablePanel'); } - async addVisualizations(visualizations) { + public async addVisualizations(visualizations: string[]) { await dashboardAddPanel.addVisualizations(visualizations); } - async setTimepickerInHistoricalDataRange() { - await PageObjects.timePicker.setDefaultAbsoluteRange(); - } - - async setTimepickerInDataRange() { - const fromTime = 'Jan 1, 2018 @ 00:00:00.000'; - const toTime = 'Apr 13, 2018 @ 00:00:00.000'; - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - } - - async setTimepickerInLogstashDataRange() { - const fromTime = 'Apr 9, 2018 @ 00:00:00.000'; - const toTime = 'Apr 13, 2018 @ 00:00:00.000'; - await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - } - - async setSaveAsNewCheckBox(checked) { + public async setSaveAsNewCheckBox(checked: boolean) { log.debug('saveAsNewCheckbox: ' + checked); - const saveAsNewCheckbox = await testSubjects.find('saveAsNewCheckbox'); + let saveAsNewCheckbox = await testSubjects.find('saveAsNewCheckbox'); const isAlreadyChecked = (await saveAsNewCheckbox.getAttribute('aria-checked')) === 'true'; if (isAlreadyChecked !== checked) { log.debug('Flipping save as new checkbox'); - const saveAsNewCheckbox = await testSubjects.find('saveAsNewCheckbox'); + saveAsNewCheckbox = await testSubjects.find('saveAsNewCheckbox'); await retry.try(() => saveAsNewCheckbox.click()); } } - async setStoreTimeWithDashboard(checked) { + public async setStoreTimeWithDashboard(checked: boolean) { log.debug('Storing time with dashboard: ' + checked); - const storeTimeCheckbox = await testSubjects.find('storeTimeWithDashboard'); + let storeTimeCheckbox = await testSubjects.find('storeTimeWithDashboard'); const isAlreadyChecked = (await storeTimeCheckbox.getAttribute('aria-checked')) === 'true'; if (isAlreadyChecked !== checked) { log.debug('Flipping store time checkbox'); - const storeTimeCheckbox = await testSubjects.find('storeTimeWithDashboard'); + storeTimeCheckbox = await testSubjects.find('storeTimeWithDashboard'); await retry.try(() => storeTimeCheckbox.click()); } } - async getFilterDescriptions(timeout = defaultFindTimeout) { - const filters = await find.allByCssSelector( - '.filter-bar > .filter > .filter-description', - timeout - ); - return _.map(filters, async filter => await filter.getVisibleText()); - } - - async getSharedItemsCount() { + public async getSharedItemsCount() { log.debug('in getSharedItemsCount'); const attributeName = 'data-shared-items-count'; const element = await find.byCssSelector(`[${attributeName}]`); @@ -586,13 +452,14 @@ export function DashboardPageProvider({ getService, getPageObjects }) { throw new Error('no element'); } - async waitForRenderComplete() { + public async waitForRenderComplete() { log.debug('waitForRenderComplete'); const count = await this.getSharedItemsCount(); + // eslint-disable-next-line radix await renderable.waitForRender(parseInt(count)); } - async getSharedContainerData() { + public async getSharedContainerData() { log.debug('getSharedContainerData'); const sharedContainer = await find.byCssSelector('[data-shared-items-container]'); return { @@ -602,7 +469,7 @@ export function DashboardPageProvider({ getService, getPageObjects }) { }; } - async getPanelSharedItemData() { + public async getPanelSharedItemData() { log.debug('in getPanelSharedItemData'); const sharedItems = await find.allByCssSelector('[data-shared-item]'); return await Promise.all( @@ -615,17 +482,17 @@ export function DashboardPageProvider({ getService, getPageObjects }) { ); } - async checkHideTitle() { + public async checkHideTitle() { log.debug('ensure that you can click on hide title checkbox'); await this.openOptions(); return await testSubjects.click('dashboardPanelTitlesCheckbox'); } - async expectMissingSaveOption() { + public async expectMissingSaveOption() { await testSubjects.missingOrFail('dashboardSaveMenuItem'); } - async getNotLoadedVisualizations(vizList) { + public async getNotLoadedVisualizations(vizList: string[]) { const checkList = []; for (const name of vizList) { const isPresent = await testSubjects.exists( diff --git a/test/functional/page_objects/home_page.ts b/test/functional/page_objects/home_page.ts index cf9eb4332c3e..a641fbda023c 100644 --- a/test/functional/page_objects/home_page.ts +++ b/test/functional/page_objects/home_page.ts @@ -22,7 +22,6 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function HomePageProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); - const find = getService('find'); class HomePage { async clickSynopsis(title: string) { @@ -38,12 +37,15 @@ export function HomePageProvider({ getService }: FtrProviderContext) { } async isSampleDataSetInstalled(id: string) { - return await testSubjects.exists(`removeSampleDataSet${id}`); + return !(await testSubjects.exists(`addSampleDataSet${id}`)); } async addSampleDataSet(id: string) { - await testSubjects.click(`addSampleDataSet${id}`); - await this._waitForSampleDataLoadingAction(id); + const isInstalled = await this.isSampleDataSetInstalled(id); + if (!isInstalled) { + await testSubjects.click(`addSampleDataSet${id}`); + await this._waitForSampleDataLoadingAction(id); + } } async removeSampleDataSet(id: string) { @@ -62,13 +64,8 @@ export function HomePageProvider({ getService }: FtrProviderContext) { } async launchSampleDataSet(id: string) { - if (await find.existsByCssSelector(`#sampleDataLinks${id}`)) { - // omits cloud test failures - await find.clickByCssSelectorWhenNotDisabled(`#sampleDataLinks${id}`); - await find.clickByCssSelector('.euiContextMenuItem:nth-of-type(1)'); - } else { - await testSubjects.click(`launchSampleDataSet${id}`); - } + await this.addSampleDataSet(id); + await testSubjects.click(`launchSampleDataSet${id}`); } async loadSavedObjects() { diff --git a/test/functional/page_objects/index.ts b/test/functional/page_objects/index.ts index 5526243ea2bb..4ba8ddb03591 100644 --- a/test/functional/page_objects/index.ts +++ b/test/functional/page_objects/index.ts @@ -22,7 +22,6 @@ import { CommonPageProvider } from './common_page'; import { ConsolePageProvider } from './console_page'; // @ts-ignore not TS yet import { ContextPageProvider } from './context_page'; -// @ts-ignore not TS yet import { DashboardPageProvider } from './dashboard_page'; // @ts-ignore not TS yet import { DiscoverPageProvider } from './discover_page'; diff --git a/test/functional/page_objects/time_picker.js b/test/functional/page_objects/time_picker.js index 8717517f4486..2394abc9c218 100644 --- a/test/functional/page_objects/time_picker.js +++ b/test/functional/page_objects/time_picker.js @@ -124,6 +124,11 @@ export function TimePickerPageProvider({ getService, getPageObjects }) { await this.setAbsoluteRange(this.defaultStartTime, this.defaultEndTime); } + async isOff() { + const element = await find.byClassName('euiDatePickerRange--readOnly'); + return !!element; + } + async isQuickSelectMenuOpen() { return await testSubjects.exists('superDatePickerQuickMenu'); } @@ -264,6 +269,22 @@ export function TimePickerPageProvider({ getService, getPageObjects }) { await this.closeQuickSelectTimeMenu(); } + + async setHistoricalDataRange() { + await this.setDefaultAbsoluteRange(); + } + + async setDefaultDataRange() { + const fromTime = 'Jan 1, 2018 @ 00:00:00.000'; + const toTime = 'Apr 13, 2018 @ 00:00:00.000'; + await this.setAbsoluteRange(fromTime, toTime); + } + + async setLogstashDataRange() { + const fromTime = 'Apr 9, 2018 @ 00:00:00.000'; + const toTime = 'Apr 13, 2018 @ 00:00:00.000'; + await this.setAbsoluteRange(fromTime, toTime); + } } return new TimePickerPage(); diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts index 138e5758ede7..0f14489a39db 100644 --- a/test/functional/page_objects/visualize_chart_page.ts +++ b/test/functional/page_objects/visualize_chart_page.ts @@ -204,8 +204,7 @@ export function VisualizeChartPageProvider({ getService, getPageObjects }: FtrPr public async filterLegend(name: string) { await this.toggleLegend(); await testSubjects.click(`legend-${name}`); - const filters = await testSubjects.find(`legend-${name}-filters`); - const [filterIn] = await filters.findAllByCssSelector(`input`); + const filterIn = await testSubjects.find(`legend-${name}-filterIn`); await filterIn.click(); await this.waitForVisualizationRenderingStabilized(); } diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts index 7e512975356f..30e13d551fa2 100644 --- a/test/functional/page_objects/visualize_editor_page.ts +++ b/test/functional/page_objects/visualize_editor_page.ts @@ -97,8 +97,9 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP } public async clickSplitDirection(direction: string) { - const control = await testSubjects.find('visEditorSplitBy'); - const radioBtn = await control.findByCssSelector(`[title="${direction}"]`); + const radioBtn = await find.byCssSelector( + `[data-test-subj="visEditorSplitBy"][title="${direction}"]` + ); await radioBtn.click(); } diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index 4ba64ea771ef..0071b8d993f7 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -44,15 +44,7 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide } public async clickNewVisualization() { - // newItemButton button is only visible when there are items in the listing table is displayed. - let exists = await testSubjects.exists('newItemButton'); - if (exists) { - return await testSubjects.click('newItemButton'); - } - - exists = await testSubjects.exists('createVisualizationPromptButton'); - // no viz exist, click createVisualizationPromptButton to create new dashboard - return await this.createVisualizationPromptButton(); + await listingTable.clickNewButton('createVisualizationPromptButton'); } public async createVisualizationPromptButton() { diff --git a/test/functional/services/dashboard/visualizations.js b/test/functional/services/dashboard/visualizations.js index 5e722ccce897..f7a6fb7d2f69 100644 --- a/test/functional/services/dashboard/visualizations.js +++ b/test/functional/services/dashboard/visualizations.js @@ -24,7 +24,14 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) { const queryBar = getService('queryBar'); const testSubjects = getService('testSubjects'); const dashboardAddPanel = getService('dashboardAddPanel'); - const PageObjects = getPageObjects(['dashboard', 'visualize', 'visEditor', 'header', 'discover']); + const PageObjects = getPageObjects([ + 'dashboard', + 'visualize', + 'visEditor', + 'header', + 'discover', + 'timePicker', + ]); return new (class DashboardVisualizations { async createAndAddTSVBVisualization(name) { @@ -43,7 +50,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) { log.debug(`createSavedSearch(${name})`); await PageObjects.header.clickDiscover(); - await PageObjects.dashboard.setTimepickerInHistoricalDataRange(); + await PageObjects.timePicker.setHistoricalDataRange(); if (query) { await queryBar.setQuery(query); diff --git a/test/functional/services/listing_table.ts b/test/functional/services/listing_table.ts index ec886cf694f2..c7667ae7b404 100644 --- a/test/functional/services/listing_table.ts +++ b/test/functional/services/listing_table.ts @@ -25,20 +25,35 @@ export function ListingTableProvider({ getService, getPageObjects }: FtrProvider const log = getService('log'); const retry = getService('retry'); const { common, header } = getPageObjects(['common', 'header']); + const prefixMap = { visualize: 'vis', dashboard: 'dashboard' }; + /** + * This class provides functions for dashboard and visualize landing pages + */ class ListingTable { - public async getSearchFilter() { + private async getSearchFilter() { const searchFilter = await find.allByCssSelector('.euiFieldSearch'); return searchFilter[0]; } - public async clearFilter() { + /** + * Returns search input value on landing page + */ + public async getSearchFilterValue() { + const searchFilter = await this.getSearchFilter(); + return await searchFilter.getAttribute('value'); + } + + /** + * Clears search input on landing page + */ + public async clearSearchFilter() { const searchFilter = await this.getSearchFilter(); await searchFilter.clearValue(); await searchFilter.click(); } - public async getAllVisualizationNamesOnCurrentPage(): Promise { + private async getAllItemsNamesOnCurrentPage(): Promise { const visualizationNames = []; const links = await find.allByCssSelector('.kuiLink'); for (let i = 0; i < links.length; i++) { @@ -48,14 +63,39 @@ export function ListingTableProvider({ getService, getPageObjects }: FtrProvider return visualizationNames; } + /** + * Navigates through all pages on Landing page and returns array of items names + */ + public async getAllItemsNames(): Promise { + log.debug('ListingTable.getAllItemsNames'); + let morePages = true; + let visualizationNames: string[] = []; + while (morePages) { + visualizationNames = visualizationNames.concat(await this.getAllItemsNamesOnCurrentPage()); + morePages = !((await testSubjects.getAttribute('pagerNextButton', 'disabled')) === 'true'); + if (morePages) { + await testSubjects.click('pagerNextButton'); + await header.waitUntilLoadingHasFinished(); + } + } + return visualizationNames; + } + + /** + * Returns items count on landing page + * @param appName 'visualize' | 'dashboard' + */ public async getItemsCount(appName: 'visualize' | 'dashboard'): Promise { - const prefixMap = { visualize: 'vis', dashboard: 'dashboard' }; const elements = await find.allByCssSelector( `[data-test-subj^="${prefixMap[appName]}ListingTitleLink"]` ); return elements.length; } + /** + * Types name into search field on Landing page and waits till search completed + * @param name item name + */ public async searchForItemWithName(name: string) { log.debug(`searchForItemWithName: ${name}`); @@ -71,10 +111,51 @@ export function ListingTableProvider({ getService, getPageObjects }: FtrProvider await header.waitUntilLoadingHasFinished(); } + /** + * Searches for item on Landing page and retruns items count that match `ListingTitleLink-${name}` pattern + * @param appName 'visualize' | 'dashboard' + * @param name item name + */ + public async searchAndGetItemsCount(appName: 'visualize' | 'dashboard', name: string) { + await this.searchForItemWithName(name); + const links = await testSubjects.findAll( + `${prefixMap[appName]}ListingTitleLink-${name.replace(/ /g, '-')}` + ); + return links.length; + } + public async clickDeleteSelected() { await testSubjects.click('deleteSelectedItems'); } + public async clickItemCheckbox(id: string) { + await testSubjects.click(`checkboxSelectRow-${id}`); + } + + /** + * Searches for item by name, selects checbox and deletes it + * @param name item name + * @param id row id + */ + public async deleteItem(name: string, id: string) { + await this.searchForItemWithName(name); + await this.clickItemCheckbox(id); + await this.clickDeleteSelected(); + await common.clickConfirmOnModal(); + } + + /** + * Clicks item on Landing page by link name if it is present + * @param appName 'dashboard' | 'visualize' + * @param name item name + */ + public async clickItemLink(appName: 'dashboard' | 'visualize', name: string) { + await testSubjects.click(`${appName}ListingTitleLink-${name.split(' ').join('-')}`); + } + + /** + * Checks 'SelectAll' checkbox on + */ public async checkListingSelectAllCheckbox() { const element = await testSubjects.find('checkboxSelectAll'); const isSelected = await element.isSelected(); @@ -84,21 +165,20 @@ export function ListingTableProvider({ getService, getPageObjects }: FtrProvider } } - public async getAllVisualizationNames(): Promise { - log.debug('ListingTable.getAllVisualizationNames'); - let morePages = true; - let visualizationNames: string[] = []; - while (morePages) { - visualizationNames = visualizationNames.concat( - await this.getAllVisualizationNamesOnCurrentPage() - ); - morePages = !((await testSubjects.getAttribute('pagerNextButton', 'disabled')) === 'true'); - if (morePages) { - await testSubjects.click('pagerNextButton'); - await header.waitUntilLoadingHasFinished(); + /** + * Clicks NewItem button on Landing page + * @param promptBtnTestSubj testSubj locator for Prompt button + */ + public async clickNewButton(promptBtnTestSubj: string): Promise { + await retry.try(async () => { + // newItemButton button is only visible when there are items in the listing table is displayed. + if (await testSubjects.exists('newItemButton')) { + await testSubjects.click('newItemButton'); + } else { + // no items exist, click createPromptButton to create new dashboard/visualization + await testSubjects.click(promptBtnTestSubj); } - } - return visualizationNames; + }); } } 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 02c507dbb3ed..96efd952e6ba 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "17.3.1", + "@elastic/eui": "18.2.0", "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 67ad28c083db..7693d6f9c07b 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "17.3.1", + "@elastic/eui": "18.2.0", "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 b22a1ff2d417..bf58535e5799 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "17.3.1", + "@elastic/eui": "18.2.0", "react": "^16.12.0" }, "scripts": { 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 8c91826d7b45..98dd9ab51da9 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 @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "17.3.1", + "@elastic/eui": "18.2.0", "react": "^16.12.0" }, "scripts": { diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts index a3c9d9d63e35..231458fad155 100644 --- a/test/plugin_functional/test_suites/core_plugins/applications.ts +++ b/test/plugin_functional/test_suites/core_plugins/applications.ts @@ -27,12 +27,18 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider const browser = getService('browser'); const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); + const find = getService('find'); const loadingScreenNotShown = async () => expect(await testSubjects.exists('kbnLoadingMessage')).to.be(false); const loadingScreenShown = () => testSubjects.existOrFail('kbnLoadingMessage'); + const getAppWrapperWidth = async () => { + const wrapper = await find.byClassName('app-wrapper'); + return (await wrapper.getSize()).width; + }; + const getKibanaUrl = (pathname?: string, search?: string) => url.format({ protocol: 'http:', @@ -99,12 +105,20 @@ export default function({ getService, getPageObjects }: PluginFunctionalProvider await PageObjects.common.navigateToApp('chromeless'); await loadingScreenNotShown(); expect(await testSubjects.exists('headerGlobalNav')).to.be(false); + + const wrapperWidth = await getAppWrapperWidth(); + const windowWidth = (await browser.getWindowSize()).width; + expect(wrapperWidth).to.eql(windowWidth); }); it('navigating away from chromeless application shows chrome', async () => { await PageObjects.common.navigateToApp('foo'); await loadingScreenNotShown(); expect(await testSubjects.exists('headerGlobalNav')).to.be(true); + + const wrapperWidth = await getAppWrapperWidth(); + const windowWidth = (await browser.getWindowSize()).width; + expect(wrapperWidth).to.be.below(windowWidth); }); it.skip('can navigate from NP apps to legacy apps', async () => { diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 7e86d2f1dc43..71e3bdd6c8c8 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -4,6 +4,7 @@ "xpack.actions": "legacy/plugins/actions", "xpack.advancedUiActions": "plugins/advanced_ui_actions", "xpack.alerting": "legacy/plugins/alerting", + "xpack.triggersActionsUI": "legacy/plugins/triggers_actions_ui", "xpack.apm": "legacy/plugins/apm", "xpack.beatsManagement": "legacy/plugins/beats_management", "xpack.canvas": "legacy/plugins/canvas", diff --git a/x-pack/index.js b/x-pack/index.js index 56547f89b1e9..83a7b5540334 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -42,6 +42,7 @@ import { transform } from './legacy/plugins/transform'; import { actions } from './legacy/plugins/actions'; import { alerting } from './legacy/plugins/alerting'; import { lens } from './legacy/plugins/lens'; +import { triggersActionsUI } from './legacy/plugins/triggers_actions_ui'; module.exports = function(kibana) { return [ @@ -83,5 +84,6 @@ module.exports = function(kibana) { snapshotRestore(kibana), actions(kibana), alerting(kibana), + triggersActionsUI(kibana), ]; }; diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts index 4aaecc8e9d7d..74263c603c11 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts @@ -49,7 +49,7 @@ beforeEach(() => { describe('actionTypeRegistry.get() works', () => { test('action type static data is as expected', () => { expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('email'); + expect(actionType.name).toEqual('Email'); }); }); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts index dd2bd328ce53..94d7852e76fa 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts @@ -118,7 +118,9 @@ export function getActionType(params: GetActionTypeParams): ActionType { const { logger, configurationUtilities } = params; return { id: '.email', - name: 'email', + name: i18n.translate('xpack.actions.builtin.emailTitle', { + defaultMessage: 'Email', + }), validate: { config: schema.object(ConfigSchemaProps, { validate: curry(validateConfig)(configurationUtilities), diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts index 1da8b06e1587..dbac84ef681f 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts @@ -37,7 +37,7 @@ beforeEach(() => { describe('actionTypeRegistry.get() works', () => { test('action type static data is as expected', () => { expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('index'); + expect(actionType.name).toEqual('Index'); }); }); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts index 0e9fe0483ee1..ddf33ba63f71 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts @@ -38,7 +38,9 @@ const ParamsSchema = schema.object({ export function getActionType({ logger }: { logger: Logger }): ActionType { return { id: '.index', - name: 'index', + name: i18n.translate('xpack.actions.builtin.esIndexTitle', { + defaultMessage: 'Index', + }), validate: { config: ConfigSchema, params: ParamsSchema, diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts index cb3548524ebb..f60fdf7fef95 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts @@ -38,7 +38,7 @@ beforeAll(() => { describe('get()', () => { test('should return correct action type', () => { expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('pagerduty'); + expect(actionType.name).toEqual('PagerDuty'); }); }); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts index 250c169278c5..b26621702cf5 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts @@ -96,7 +96,9 @@ export function getActionType({ }): ActionType { return { id: '.pagerduty', - name: 'pagerduty', + name: i18n.translate('xpack.actions.builtin.pagerdutyTitle', { + defaultMessage: 'PagerDuty', + }), validate: { config: schema.object(configSchemaProps, { validate: curry(valdiateActionTypeConfig)(configurationUtilities), diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts index c59ddf97017f..8f28b9e8f512 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts @@ -25,7 +25,7 @@ beforeAll(() => { describe('get()', () => { test('returns action type', () => { expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('server-log'); + expect(actionType.name).toEqual('Server log'); }); }); @@ -98,6 +98,6 @@ describe('execute()', () => { config: {}, secrets: {}, }); - expect(mockedLogger.info).toHaveBeenCalledWith('server-log: message text here'); + expect(mockedLogger.info).toHaveBeenCalledWith('Server log: message text here'); }); }); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts index 0edf409e4d46..34b8602eeba3 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts @@ -12,7 +12,7 @@ import { Logger } from '../../../../../../src/core/server'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; import { withoutControlCharacters } from './lib/string_utils'; -const ACTION_NAME = 'server-log'; +const ACTION_NAME = 'Server log'; // params definition diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts index a2b0db8bdb70..aebc9c499359 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts @@ -29,7 +29,7 @@ beforeAll(() => { describe('action registeration', () => { test('returns action type', () => { expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('slack'); + expect(actionType.name).toEqual('Slack'); }); }); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts index 92611d6f162f..b8989e59a225 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts @@ -49,7 +49,9 @@ export function getActionType({ }): ActionType { return { id: '.slack', - name: 'slack', + name: i18n.translate('xpack.actions.builtin.slackTitle', { + defaultMessage: 'Slack', + }), validate: { secrets: schema.object(secretsSchemaProps, { validate: curry(valdiateActionTypeConfig)(configurationUtilities), diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts index 64dd3a485f8e..b95fef97ac7b 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts @@ -25,7 +25,7 @@ beforeAll(() => { describe('actionType', () => { test('exposes the action as `webhook` on its Id and Name', () => { expect(actionType.id).toEqual('.webhook'); - expect(actionType.name).toEqual('webhook'); + expect(actionType.name).toEqual('Webhook'); }); }); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts index 06fe2fb0e591..fa88d3c72c16 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts @@ -56,7 +56,9 @@ export function getActionType({ }): ActionType { return { id: '.webhook', - name: 'webhook', + name: i18n.translate('xpack.actions.builtin.webhookTitle', { + defaultMessage: 'Webhook', + }), validate: { config: schema.object(configSchemaProps, { validate: curry(valdiateActionTypeConfig)(configurationUtilities), diff --git a/x-pack/legacy/plugins/alerting/README.md b/x-pack/legacy/plugins/alerting/README.md index 30d34bd3b436..d5e9dcb76caa 100644 --- a/x-pack/legacy/plugins/alerting/README.md +++ b/x-pack/legacy/plugins/alerting/README.md @@ -32,6 +32,14 @@ When security is enabled, an SSL connection to Elasticsearch is required in orde When security is enabled, users who create alerts will need the `manage_api_key` cluster privilege. There is currently work in progress to remove this requirement. +Note that the `manage_own_api_key` cluster privilege is not enough - it can be used to create API keys, but not invalidate them, and the alerting plugin currently both creates and invalidates APIs keys as part of it's processing. When using only the `manage_own_api_key` privilege, you will see the following message logged in the server when the alerting plugin attempts to invalidate an API key: + +``` +[error][alerting][plugins] Failed to invalidate API Key: [security_exception] \ + action [cluster:admin/xpack/security/api_key/invalidate] \ + is unauthorized for user [user-name-here] +``` + ## Alert types ### Methods diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx index bc020815cc9c..d69fa5d895b9 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx @@ -26,7 +26,7 @@ interface CytoscapeProps { children?: ReactNode; elements: cytoscape.ElementDefinition[]; serviceName?: string; - style: CSSProperties; + style?: CSSProperties; } function useCytoscape(options: cytoscape.CytoscapeOptions) { @@ -69,8 +69,8 @@ export function Cytoscape({ // Set up cytoscape event handlers useEffect(() => { - if (cy) { - cy.on('data', event => { + const dataHandler: cytoscape.EventHandler = event => { + if (cy) { // Add the "primary" class to the node if its id matches the serviceName. if (cy.nodes().length > 0 && serviceName) { cy.nodes().removeClass('primary'); @@ -80,8 +80,30 @@ export function Cytoscape({ if (event.cy.elements().length > 0) { cy.layout(cytoscapeOptions.layout as cytoscape.LayoutOptions).run(); } - }); + } + }; + const mouseoverHandler: cytoscape.EventHandler = event => { + event.target.addClass('hover'); + event.target.connectedEdges().addClass('nodeHover'); + }; + const mouseoutHandler: cytoscape.EventHandler = event => { + event.target.removeClass('hover'); + event.target.connectedEdges().removeClass('nodeHover'); + }; + + if (cy) { + cy.on('data', dataHandler); + cy.on('mouseover', 'edge, node', mouseoverHandler); + cy.on('mouseout', 'edge, node', mouseoutHandler); } + + return () => { + if (cy) { + cy.removeListener('data', undefined, dataHandler); + cy.removeListener('mouseover', 'edge, node', mouseoverHandler); + cy.removeListener('mouseout', 'edge, node', mouseoutHandler); + } + }; }, [cy, serviceName]); return ( diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx new file mode 100644 index 000000000000..a8c45c83a382 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable @elastic/eui/href-or-on-click */ + +import { EuiButton, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { MouseEvent } from 'react'; +import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { getAPMHref } from '../../../shared/Links/apm/APMLink'; + +interface ButtonsProps { + focusedServiceName?: string; + onFocusClick?: (event: MouseEvent) => void; + selectedNodeServiceName: string; +} + +export function Buttons({ + focusedServiceName, + onFocusClick = () => {}, + selectedNodeServiceName +}: ButtonsProps) { + const currentSearch = useUrlParams().urlParams.kuery ?? ''; + const detailsUrl = getAPMHref( + `/services/${selectedNodeServiceName}/transactions`, + currentSearch + ); + const focusUrl = getAPMHref( + `/services/${selectedNodeServiceName}/service-map`, + currentSearch + ); + + const isAlreadyFocused = focusedServiceName === selectedNodeServiceName; + + return ( + <> + + + {i18n.translate('xpack.apm.serviceMap.serviceDetailsButtonText', { + defaultMessage: 'Service Details' + })} + + + + + {i18n.translate('xpack.apm.serviceMap.focusMapButtonText', { + defaultMessage: 'Focus map' + })} + + + + ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx new file mode 100644 index 000000000000..1c5443e404f9 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/Info.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import styled from 'styled-components'; +import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; + +const ItemRow = styled.div` + line-height: 2; +`; + +const ItemTitle = styled.dt` + color: ${lightTheme.textColors.subdued}; +`; + +const ItemDescription = styled.dd``; + +interface InfoProps { + type: string; + subtype?: string; +} + +export function Info({ type, subtype }: InfoProps) { + const listItems = [ + { + title: i18n.translate('xpack.apm.serviceMap.typePopoverMetric', { + defaultMessage: 'Type' + }), + description: type + }, + { + title: i18n.translate('xpack.apm.serviceMap.subtypePopoverMetric', { + defaultMessage: 'Subtype' + }), + description: subtype + } + ]; + + return ( + <> + {listItems.map( + ({ title, description }) => + description && ( + + {title} + {description} + + ) + )} + + ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx new file mode 100644 index 000000000000..8ce6d9d57c4a --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/ServiceMetricList.tsx @@ -0,0 +1,177 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiFlexGroup, + EuiLoadingSpinner, + EuiFlexItem, + EuiBadge +} from '@elastic/eui'; +import lightTheme from '@elastic/eui/dist/eui_theme_light.json'; +import { i18n } from '@kbn/i18n'; +import { isNumber } from 'lodash'; +import React from 'react'; +import styled from 'styled-components'; +import { ServiceNodeMetrics } from '../../../../../server/lib/service_map/get_service_map_service_node_info'; +import { + asDuration, + asPercent, + toMicroseconds, + tpmUnit +} from '../../../../utils/formatters'; +import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useFetcher } from '../../../../hooks/useFetcher'; + +function LoadingSpinner() { + return ( + + + + ); +} + +const ItemRow = styled('tr')` + line-height: 2; +`; + +const ItemTitle = styled('td')` + color: ${lightTheme.textColors.subdued}; + padding-right: 1rem; +`; + +const ItemDescription = styled('td')` + text-align: right; +`; + +const na = i18n.translate('xpack.apm.serviceMap.NotAvailableMetric', { + defaultMessage: 'N/A' +}); + +interface MetricListProps { + serviceName: string; +} + +export function ServiceMetricList({ serviceName }: MetricListProps) { + const { + urlParams: { start, end, environment } + } = useUrlParams(); + + const { data = {} as ServiceNodeMetrics, status } = useFetcher( + callApmApi => { + if (serviceName && start && end) { + return callApmApi({ + pathname: '/api/apm/service-map/service/{serviceName}', + params: { + path: { + serviceName + }, + query: { + start, + end, + environment + } + } + }); + } + }, + [serviceName, start, end, environment], + { + preservePreviousData: false + } + ); + + const { + avgTransactionDuration, + avgRequestsPerMinute, + avgErrorsPerMinute, + avgCpuUsage, + avgMemoryUsage, + numInstances + } = data; + const isLoading = status === 'loading'; + + const listItems = [ + { + title: i18n.translate( + 'xpack.apm.serviceMap.avgTransDurationPopoverMetric', + { + defaultMessage: 'Trans. duration (avg.)' + } + ), + description: isNumber(avgTransactionDuration) + ? asDuration(toMicroseconds(avgTransactionDuration, 'milliseconds')) + : na + }, + { + title: i18n.translate( + 'xpack.apm.serviceMap.avgReqPerMinutePopoverMetric', + { + defaultMessage: 'Req. per minute (avg.)' + } + ), + description: isNumber(avgRequestsPerMinute) + ? `${avgRequestsPerMinute.toFixed(2)} ${tpmUnit('request')}` + : na + }, + { + title: i18n.translate( + 'xpack.apm.serviceMap.avgErrorsPerMinutePopoverMetric', + { + defaultMessage: 'Errors per minute (avg.)' + } + ), + description: avgErrorsPerMinute?.toFixed(2) ?? na + }, + { + title: i18n.translate('xpack.apm.serviceMap.avgCpuUsagePopoverMetric', { + defaultMessage: 'CPU usage (avg.)' + }), + description: isNumber(avgCpuUsage) ? asPercent(avgCpuUsage, 1) : na + }, + { + title: i18n.translate( + 'xpack.apm.serviceMap.avgMemoryUsagePopoverMetric', + { + defaultMessage: 'Memory usage (avg.)' + } + ), + description: isNumber(avgMemoryUsage) ? asPercent(avgMemoryUsage, 1) : na + } + ]; + return isLoading ? ( + + ) : ( + <> + {numInstances && numInstances > 1 && ( + +
+ + {i18n.translate('xpack.apm.serviceMap.numInstancesMetric', { + values: { numInstances }, + defaultMessage: '{numInstances} instances' + })} + +
+
+ )} + + + + {listItems.map(({ title, description }) => ( + + {title} + {description} + + ))} + +
+ + ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx new file mode 100644 index 000000000000..dfb78aaa0214 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiPopover, + EuiTitle +} from '@elastic/eui'; +import cytoscape from 'cytoscape'; +import React, { + CSSProperties, + useContext, + useEffect, + useState, + useCallback +} from 'react'; +import { CytoscapeContext } from '../Cytoscape'; +import { Buttons } from './Buttons'; +import { Info } from './Info'; +import { ServiceMetricList } from './ServiceMetricList'; + +const popoverMinWidth = 280; + +interface PopoverProps { + focusedServiceName?: string; +} + +export function Popover({ focusedServiceName }: PopoverProps) { + const cy = useContext(CytoscapeContext); + const [selectedNode, setSelectedNode] = useState< + cytoscape.NodeSingular | undefined + >(undefined); + const onFocusClick = useCallback(() => setSelectedNode(undefined), [ + setSelectedNode + ]); + + useEffect(() => { + const selectHandler: cytoscape.EventHandler = event => { + setSelectedNode(event.target); + }; + const unselectHandler: cytoscape.EventHandler = () => { + setSelectedNode(undefined); + }; + + if (cy) { + cy.on('select', 'node', selectHandler); + cy.on('unselect', 'node', unselectHandler); + cy.on('data viewport', unselectHandler); + } + + return () => { + if (cy) { + cy.removeListener('select', 'node', selectHandler); + cy.removeListener('unselect', 'node', unselectHandler); + cy.removeListener('data viewport', undefined, unselectHandler); + } + }; + }, [cy]); + + const renderedHeight = selectedNode?.renderedHeight() ?? 0; + const renderedWidth = selectedNode?.renderedWidth() ?? 0; + const { x, y } = selectedNode?.renderedPosition() ?? { x: 0, y: 0 }; + const isOpen = !!selectedNode; + const selectedNodeServiceName: string = selectedNode?.data('id'); + const isService = selectedNode?.data('type') === 'service'; + const triggerStyle: CSSProperties = { + background: 'transparent', + height: renderedHeight, + position: 'absolute', + width: renderedWidth + }; + const trigger =
; + + const zoom = cy?.zoom() ?? 1; + const height = selectedNode?.height() ?? 0; + const translateY = y - (zoom + 1) * (height / 2); + const popoverStyle: CSSProperties = { + position: 'absolute', + transform: `translate(${x}px, ${translateY}px)` + }; + const data = selectedNode?.data() ?? {}; + const label = data.label || selectedNodeServiceName; + + return ( + {}} + isOpen={isOpen} + style={popoverStyle} + > + + + +

{label}

+
+ +
+ + + {isService ? ( + + ) : ( + + )} + + {isService && ( + + )} +
+
+ ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts index d4e792ccf761..1a6247388a65 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts @@ -3,9 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import cytoscape from 'cytoscape'; import theme from '@elastic/eui/dist/eui_theme_light.json'; -import { icons, defaultIcon } from './icons'; +import cytoscape from 'cytoscape'; +import { defaultIcon, iconForNode } from './icons'; const layout = { name: 'dagre', @@ -13,8 +13,8 @@ const layout = { rankDir: 'LR' }; -function isDatabaseOrExternal(agentName: string) { - return !agentName; +function isService(el: cytoscape.NodeSingular) { + return el.data('type') === 'service'; } const style: cytoscape.Stylesheet[] = [ @@ -27,11 +27,11 @@ const style: cytoscape.Stylesheet[] = [ // // @ts-ignore 'background-image': (el: cytoscape.NodeSingular) => - icons[el.data('agentName')] || defaultIcon, + iconForNode(el) ?? defaultIcon, 'background-height': (el: cytoscape.NodeSingular) => - isDatabaseOrExternal(el.data('agentName')) ? '40%' : '80%', + isService(el) ? '80%' : '40%', 'background-width': (el: cytoscape.NodeSingular) => - isDatabaseOrExternal(el.data('agentName')) ? '40%' : '80%', + isService(el) ? '80%' : '40%', 'border-color': (el: cytoscape.NodeSingular) => el.hasClass('primary') ? theme.euiColorSecondary @@ -47,7 +47,7 @@ const style: cytoscape.Stylesheet[] = [ 'min-zoomed-font-size': theme.euiSizeL, 'overlay-opacity': 0, shape: (el: cytoscape.NodeSingular) => - isDatabaseOrExternal(el.data('agentName')) ? 'diamond' : 'ellipse', + isService(el) ? 'ellipse' : 'diamond', 'text-background-color': theme.euiColorLightestShade, 'text-background-opacity': 0, 'text-background-padding': theme.paddingSizes.xs, @@ -90,7 +90,6 @@ const style: cytoscape.Stylesheet[] = [ export const cytoscapeOptions: cytoscape.CytoscapeOptions = { autoungrabify: true, - autounselectify: true, boxSelectionEnabled: false, layout, maxZoom: 3, diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/get_cytoscape_elements.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/get_cytoscape_elements.ts index c9caa27af41c..106e9a1d82f2 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/get_cytoscape_elements.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/get_cytoscape_elements.ts @@ -101,7 +101,17 @@ export function getCytoscapeElements( `/services/${node['service.name']}/service-map`, search ), - agentName: node['agent.name'] || node['agent.name'] + agentName: node['agent.name'] || node['agent.name'], + type: 'service' + }; + } + + if ('span.type' in node) { + data = { + // For nodes with span.type "db", convert it to "database". Otherwise leave it as-is. + type: node['span.type'] === 'db' ? 'database' : node['span.type'], + // Externals should not have a subtype so make it undefined if the type is external. + subtype: node['span.type'] !== 'external' && node['span.subtype'] }; } diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons.ts index d5cfb49e458c..722f64c6a7e5 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons.ts @@ -5,7 +5,9 @@ */ import theme from '@elastic/eui/dist/eui_theme_light.json'; +import cytoscape from 'cytoscape'; import databaseIcon from './icons/database.svg'; +import documentsIcon from './icons/documents.svg'; import globeIcon from './icons/globe.svg'; function getAvatarIcon( @@ -24,10 +26,16 @@ function getAvatarIcon( } // The colors here are taken from the logos of the corresponding technologies -export const icons: { [key: string]: string } = { +const icons: { [key: string]: string } = { + cache: databaseIcon, database: databaseIcon, - dotnet: getAvatarIcon('.N', '#8562AD'), external: globeIcon, + messaging: documentsIcon, + resource: globeIcon +}; + +const serviceIcons: { [key: string]: string } = { + dotnet: getAvatarIcon('.N', '#8562AD'), go: getAvatarIcon('Go', '#00A9D6'), java: getAvatarIcon('Jv', '#41717E'), 'js-base': getAvatarIcon('JS', '#F0DB4E', theme.euiTextColor), @@ -37,3 +45,12 @@ export const icons: { [key: string]: string } = { }; export const defaultIcon = getAvatarIcon(); + +export function iconForNode(node: cytoscape.NodeSingular) { + const type = node.data('type'); + if (type === 'service') { + return serviceIcons[node.data('agentName') as string]; + } else { + return icons[type]; + } +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/documents.svg b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/documents.svg new file mode 100644 index 000000000000..b0648d14f20b --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/icons/documents.svg @@ -0,0 +1 @@ + diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx index d3cc2b14e2c6..a8e6f964f4d0 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -4,31 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiButton } from '@elastic/eui'; import theme from '@elastic/eui/dist/eui_theme_light.json'; +import { i18n } from '@kbn/i18n'; +import { ElementDefinition } from 'cytoscape'; +import { find, isEqual } from 'lodash'; import React, { - useMemo, + useCallback, useEffect, - useState, + useMemo, useRef, - useCallback + useState } from 'react'; -import { find, isEqual } from 'lodash'; -import { i18n } from '@kbn/i18n'; -import { EuiButton } from '@elastic/eui'; -import { ElementDefinition } from 'cytoscape'; import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; import { ServiceMapAPIResponse } from '../../../../server/lib/service_map/get_service_map'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useCallApmApi } from '../../../hooks/useCallApmApi'; +import { useDeepObjectIdentity } from '../../../hooks/useDeepObjectIdentity'; import { useLicense } from '../../../hooks/useLicense'; +import { useLocation } from '../../../hooks/useLocation'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { Controls } from './Controls'; import { Cytoscape } from './Cytoscape'; -import { PlatinumLicensePrompt } from './PlatinumLicensePrompt'; -import { useCallApmApi } from '../../../hooks/useCallApmApi'; -import { useDeepObjectIdentity } from '../../../hooks/useDeepObjectIdentity'; -import { useLocation } from '../../../hooks/useLocation'; -import { LoadingOverlay } from './LoadingOverlay'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; import { getCytoscapeElements } from './get_cytoscape_elements'; +import { LoadingOverlay } from './LoadingOverlay'; +import { PlatinumLicensePrompt } from './PlatinumLicensePrompt'; +import { Popover } from './Popover'; interface ServiceMapProps { serviceName?: string; @@ -205,6 +206,7 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { style={cytoscapeDivStyle} > + ) : ( diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap index 9b2a2c8f2490..0ddf23cb932f 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceOverview/__test__/__snapshots__/ServiceOverview.test.tsx.snap @@ -295,7 +295,7 @@ NodeList [ class="euiTableCellContent euiTableCellContent--overflowingContent" > - + diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap index ece396bc4cfc..5b1b9be33c37 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap @@ -808,8 +808,8 @@ Object { }, }, "serviceColors": Object { - "opbeans-node": "#3185fc", - "opbeans-ruby": "#00b3a4", + "opbeans-node": "#6092c0", + "opbeans-ruby": "#54b399", }, } `; @@ -1212,7 +1212,7 @@ Object { }, }, "serviceColors": Object { - "opbeans-ruby": "#3185fc", + "opbeans-ruby": "#6092c0", }, } `; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/__test__/__snapshots__/ManagedTable.test.js.snap b/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/__test__/__snapshots__/ManagedTable.test.js.snap index 59679bfe1164..655fc5a25b9e 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/__test__/__snapshots__/ManagedTable.test.js.snap +++ b/x-pack/legacy/plugins/apm/public/components/shared/ManagedTable/__test__/__snapshots__/ManagedTable.test.js.snap @@ -52,6 +52,7 @@ exports[`ManagedTable component should render a page-full of items, with default }, } } + tableLayout="fixed" /> `; @@ -99,5 +100,6 @@ exports[`ManagedTable component should render when specifying initial values 1`] }, } } + tableLayout="fixed" /> `; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/SelectWithPlaceholder/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/SelectWithPlaceholder/index.tsx index 25f8128b2721..a8e6bc0a648a 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/SelectWithPlaceholder/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/SelectWithPlaceholder/index.tsx @@ -20,7 +20,7 @@ export const SelectWithPlaceholder: typeof EuiSelect = props => ( {...props} options={[ { text: props.placeholder, value: NO_SELECTION }, - ...props.options + ...(props.options || []) ]} value={isEmpty(props.value) ? NO_SELECTION : props.value} onChange={e => { diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx index 84c2801a4504..51056fae5036 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Stacktrace/FrameHeading.tsx @@ -35,9 +35,13 @@ const FrameHeading: React.FC = ({ stackframe, isLibraryFrame }) => { ? LibraryFrameFileDetail : AppFrameFileDetail; const lineNumber = stackframe.line.number; + + const name = + 'filename' in stackframe ? stackframe.filename : stackframe.classname; + return ( - {stackframe.filename} in{' '} + {name} in{' '} {stackframe.function} {lineNumber > 0 && ( diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap index 557751a0f022..4c7d21d96808 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap @@ -3,7 +3,7 @@ exports[`when response has data Initially should have 3 legends 1`] = ` Array [ Object { - "color": "#3185fc", + "color": "#6092c0", "disabled": undefined, "onClick": [Function], "text": @@ -14,7 +14,7 @@ Array [ , }, Object { - "color": "#e6c220", + "color": "#d6bf57", "disabled": undefined, "onClick": [Function], "text": @@ -22,7 +22,7 @@ Array [ , }, Object { - "color": "#f98510", + "color": "#da8b45", "disabled": undefined, "onClick": [Function], "text": @@ -442,7 +442,7 @@ Array [ style={ Object { "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeDasharray": undefined, "strokeWidth": undefined, } @@ -463,9 +463,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -480,9 +480,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -497,9 +497,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -514,9 +514,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -531,9 +531,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -548,9 +548,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -565,9 +565,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -582,9 +582,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -599,9 +599,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -616,9 +616,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -633,9 +633,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -650,9 +650,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -667,9 +667,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -684,9 +684,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -701,9 +701,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -718,9 +718,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -735,9 +735,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -752,9 +752,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -769,9 +769,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -786,9 +786,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -803,9 +803,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -820,9 +820,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -837,9 +837,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -854,9 +854,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -871,9 +871,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -888,9 +888,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -905,9 +905,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -922,9 +922,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -939,9 +939,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -956,9 +956,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -973,9 +973,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -995,7 +995,7 @@ Array [ style={ Object { "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeDasharray": undefined, "strokeWidth": undefined, } @@ -1016,9 +1016,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1033,9 +1033,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1050,9 +1050,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1067,9 +1067,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1084,9 +1084,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1101,9 +1101,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1118,9 +1118,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1135,9 +1135,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1152,9 +1152,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1169,9 +1169,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1186,9 +1186,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1203,9 +1203,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1220,9 +1220,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1237,9 +1237,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1254,9 +1254,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1271,9 +1271,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1288,9 +1288,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1305,9 +1305,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1322,9 +1322,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1339,9 +1339,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1356,9 +1356,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1373,9 +1373,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1390,9 +1390,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1407,9 +1407,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1424,9 +1424,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1441,9 +1441,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1458,9 +1458,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1475,9 +1475,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1492,9 +1492,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1509,9 +1509,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1526,9 +1526,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -1548,7 +1548,7 @@ Array [ style={ Object { "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeDasharray": undefined, "strokeWidth": undefined, } @@ -1569,9 +1569,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1586,9 +1586,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1603,9 +1603,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1620,9 +1620,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1637,9 +1637,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1654,9 +1654,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1671,9 +1671,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1688,9 +1688,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1705,9 +1705,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1722,9 +1722,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1739,9 +1739,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1756,9 +1756,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1773,9 +1773,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1790,9 +1790,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1807,9 +1807,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1824,9 +1824,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1841,9 +1841,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1858,9 +1858,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1875,9 +1875,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1892,9 +1892,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1909,9 +1909,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1926,9 +1926,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1943,9 +1943,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1960,9 +1960,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1977,9 +1977,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -1994,9 +1994,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -2011,9 +2011,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -2028,9 +2028,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -2045,9 +2045,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -2062,9 +2062,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -2079,9 +2079,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -2651,7 +2651,7 @@ Array [ width: 11px; height: 11px; margin-right: 5.5px; - background: #3185fc; + background: #6092c0; border-radius: 100%; } @@ -2659,7 +2659,7 @@ Array [ width: 11px; height: 11px; margin-right: 5.5px; - background: #e6c220; + background: #d6bf57; border-radius: 100%; } @@ -2667,7 +2667,7 @@ Array [ width: 11px; height: 11px; margin-right: 5.5px; - background: #f98510; + background: #da8b45; border-radius: 100%; } @@ -2723,14 +2723,14 @@ Array [ onClick={[Function]} > @@ -2764,14 +2764,14 @@ Array [ onClick={[Function]} > @@ -2798,14 +2798,14 @@ Array [ onClick={[Function]} > @@ -2849,17 +2849,17 @@ exports[`when response has data when dragging without releasing should display S exports[`when response has data when setting hoverX should display tooltip 1`] = ` Array [ Object { - "color": "#3185fc", + "color": "#6092c0", "text": "Avg.", "value": 438704.4, }, Object { - "color": "#e6c220", + "color": "#d6bf57", "text": "95th", "value": 1557383.999999999, }, Object { - "color": "#f98510", + "color": "#da8b45", "text": "99th", "value": 1820377.1200000006, }, @@ -2891,7 +2891,7 @@ Array [ width: 8px; height: 8px; margin-right: 4px; - background: #3185fc; + background: #6092c0; border-radius: 100%; } @@ -2899,7 +2899,7 @@ Array [ width: 8px; height: 8px; margin-right: 4px; - background: #e6c220; + background: #d6bf57; border-radius: 100%; } @@ -2907,7 +2907,7 @@ Array [ width: 8px; height: 8px; margin-right: 4px; - background: #f98510; + background: #da8b45; border-radius: 100%; } @@ -3378,7 +3378,7 @@ Array [ style={ Object { "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeDasharray": undefined, "strokeWidth": undefined, } @@ -3399,9 +3399,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3416,9 +3416,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3433,9 +3433,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3450,9 +3450,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3467,9 +3467,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3484,9 +3484,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3501,9 +3501,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3518,9 +3518,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3535,9 +3535,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3552,9 +3552,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3569,9 +3569,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3586,9 +3586,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3603,9 +3603,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3620,9 +3620,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3637,9 +3637,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3654,9 +3654,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3671,9 +3671,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3688,9 +3688,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3705,9 +3705,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3722,9 +3722,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3739,9 +3739,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3756,9 +3756,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3773,9 +3773,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3790,9 +3790,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3807,9 +3807,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3824,9 +3824,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3841,9 +3841,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3858,9 +3858,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3875,9 +3875,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3892,9 +3892,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3909,9 +3909,9 @@ Array [ r={0.5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -3931,7 +3931,7 @@ Array [ style={ Object { "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeDasharray": undefined, "strokeWidth": undefined, } @@ -3952,9 +3952,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -3969,9 +3969,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -3986,9 +3986,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4003,9 +4003,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4020,9 +4020,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4037,9 +4037,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4054,9 +4054,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4071,9 +4071,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4088,9 +4088,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4105,9 +4105,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4122,9 +4122,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4139,9 +4139,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4156,9 +4156,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4173,9 +4173,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4190,9 +4190,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4207,9 +4207,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4224,9 +4224,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4241,9 +4241,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4258,9 +4258,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4275,9 +4275,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4292,9 +4292,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4309,9 +4309,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4326,9 +4326,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4343,9 +4343,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4360,9 +4360,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4377,9 +4377,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4394,9 +4394,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4411,9 +4411,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4428,9 +4428,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4445,9 +4445,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4462,9 +4462,9 @@ Array [ r={0.5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -4484,7 +4484,7 @@ Array [ style={ Object { "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeDasharray": undefined, "strokeWidth": undefined, } @@ -4505,9 +4505,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4522,9 +4522,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4539,9 +4539,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4556,9 +4556,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4573,9 +4573,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4590,9 +4590,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4607,9 +4607,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4624,9 +4624,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4641,9 +4641,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4658,9 +4658,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4675,9 +4675,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4692,9 +4692,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4709,9 +4709,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4726,9 +4726,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4743,9 +4743,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4760,9 +4760,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4777,9 +4777,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4794,9 +4794,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4811,9 +4811,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4828,9 +4828,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4845,9 +4845,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4862,9 +4862,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4879,9 +4879,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4896,9 +4896,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4913,9 +4913,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4930,9 +4930,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4947,9 +4947,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4964,9 +4964,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4981,9 +4981,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -4998,9 +4998,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -5015,9 +5015,9 @@ Array [ r={0.5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -5072,9 +5072,9 @@ Array [ r={5} style={ Object { - "fill": "#f98510", + "fill": "#da8b45", "opacity": 1, - "stroke": "#f98510", + "stroke": "#da8b45", "strokeWidth": 1, } } @@ -5089,9 +5089,9 @@ Array [ r={5} style={ Object { - "fill": "#e6c220", + "fill": "#d6bf57", "opacity": 1, - "stroke": "#e6c220", + "stroke": "#d6bf57", "strokeWidth": 1, } } @@ -5106,9 +5106,9 @@ Array [ r={5} style={ Object { - "fill": "#3185fc", + "fill": "#6092c0", "opacity": 1, - "stroke": "#3185fc", + "stroke": "#6092c0", "strokeWidth": 1, } } @@ -5158,7 +5158,7 @@ Array [ className="c3" > @@ -5174,14 +5174,14 @@ Array [ fontSize="12px" > @@ -5204,7 +5204,7 @@ Array [ className="c3" > @@ -5220,14 +5220,14 @@ Array [ fontSize="12px" > @@ -5250,7 +5250,7 @@ Array [ className="c3" > @@ -5266,14 +5266,14 @@ Array [ fontSize="12px" > @@ -5830,7 +5830,7 @@ Array [ width: 11px; height: 11px; margin-right: 5.5px; - background: #3185fc; + background: #6092c0; border-radius: 100%; } @@ -5838,7 +5838,7 @@ Array [ width: 11px; height: 11px; margin-right: 5.5px; - background: #e6c220; + background: #d6bf57; border-radius: 100%; } @@ -5846,7 +5846,7 @@ Array [ width: 11px; height: 11px; margin-right: 5.5px; - background: #f98510; + background: #da8b45; border-radius: 100%; } @@ -5902,14 +5902,14 @@ Array [ onClick={[Function]} > @@ -5943,14 +5943,14 @@ Array [ onClick={[Function]} > @@ -5977,14 +5977,14 @@ Array [ onClick={[Function]} > diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap b/x-pack/legacy/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap index da71e264ac09..f1c7d4826fe0 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Histogram/__test__/__snapshots__/Histogram.test.js.snap @@ -434,11 +434,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571433} @@ -453,11 +453,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571433} @@ -472,11 +472,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.62857142857142} @@ -491,11 +491,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571433} @@ -510,11 +510,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571405} @@ -529,11 +529,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.62857142857142} @@ -548,11 +548,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571405} @@ -567,11 +567,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571405} @@ -586,11 +586,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571377} @@ -605,11 +605,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571462} @@ -624,11 +624,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.62857142857149} @@ -643,11 +643,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.62857142857149} @@ -662,11 +662,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.62857142857149} @@ -681,11 +681,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571433} @@ -700,11 +700,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571433} @@ -719,11 +719,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571433} @@ -738,11 +738,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571547} @@ -757,11 +757,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.62857142857149} @@ -776,11 +776,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571547} @@ -795,11 +795,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571433} @@ -814,11 +814,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571433} @@ -833,11 +833,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571377} @@ -852,11 +852,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.62857142857149} @@ -871,11 +871,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.62857142857149} @@ -890,11 +890,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571604} @@ -909,11 +909,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.62857142857149} @@ -928,11 +928,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.62857142857149} @@ -947,11 +947,11 @@ exports[`Histogram Initially should have default markup 1`] = ` onMouseOver={[Function]} style={ Object { - "fill": "#98c2fd", + "fill": "#afc8df", "opacity": 1, "rx": "0px", "ry": "0px", - "stroke": "#98c2fd", + "stroke": "#afc8df", } } width={22.628571428571377} diff --git a/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx b/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx index 0bd389678260..62cdbd3bbc99 100644 --- a/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx +++ b/x-pack/legacy/plugins/apm/public/context/LicenseContext/index.tsx @@ -16,8 +16,9 @@ export const LicenseContext = React.createContext( export function LicenseProvider({ children }: { children: React.ReactChild }) { const { license$ } = useApmPluginContext().plugins.licensing; - const license = useObservable(license$, { isActive: true } as ILicense); - const hasInvalidLicense = !license.isActive; + const license = useObservable(license$); + // if license is not loaded yet, consider it valid + const hasInvalidLicense = license?.isActive === false; // if license is invalid show an error message if (hasInvalidLicense) { diff --git a/x-pack/legacy/plugins/apm/public/legacy_register_feature.js b/x-pack/legacy/plugins/apm/public/legacy_register_feature.ts similarity index 53% rename from x-pack/legacy/plugins/apm/public/legacy_register_feature.js rename to x-pack/legacy/plugins/apm/public/legacy_register_feature.ts index 6e98d0784bee..f12865399054 100644 --- a/x-pack/legacy/plugins/apm/public/legacy_register_feature.js +++ b/x-pack/legacy/plugins/apm/public/legacy_register_feature.ts @@ -4,13 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { npStart } from 'ui/new_platform'; -import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; +import { npSetup } from 'ui/new_platform'; import { featureCatalogueEntry } from './new-platform/featureCatalogueEntry'; -const { core } = npStart; -const apmUiEnabled = core.injectedMetadata.getInjectedVar('apmUiEnabled'); +const { + core, + plugins: { home } +} = npSetup; +const apmUiEnabled = core.injectedMetadata.getInjectedVar( + 'apmUiEnabled' +) as boolean; if (apmUiEnabled) { - FeatureCatalogueRegistryProvider.register(() => featureCatalogueEntry); + home.featureCatalogue.register(featureCatalogueEntry); } + +home.environment.update({ + apmUi: apmUiEnabled +}); diff --git a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx index 216af91fbb59..de6cbc7d7a33 100644 --- a/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx +++ b/x-pack/legacy/plugins/apm/public/new-platform/plugin.tsx @@ -17,6 +17,7 @@ import { Plugin, PluginInitializerContext } from '../../../../../../src/core/public'; +import { featureCatalogueEntry } from './featureCatalogueEntry'; import { DataPublicPluginSetup } from '../../../../../../src/plugins/data/public'; import { HomePublicPluginSetup } from '../../../../../../src/plugins/home/public'; import { LicensingPluginSetup } from '../../../../../plugins/licensing/public'; @@ -32,7 +33,6 @@ import { UrlParamsProvider } from '../context/UrlParamsContext'; import { createStaticIndexPattern } from '../services/rest/index_pattern'; import { px, unit, units } from '../style/variables'; import { history } from '../utils/history'; -import { featureCatalogueEntry } from './featureCatalogueEntry'; import { getConfigFromInjectedMetadata } from './getConfigFromInjectedMetadata'; import { setHelpExtension } from './setHelpExtension'; import { toggleAppLinkInNav } from './toggleAppLinkInNav'; diff --git a/x-pack/legacy/plugins/apm/public/selectors/__tests__/chartSelectors.test.ts b/x-pack/legacy/plugins/apm/public/selectors/__tests__/chartSelectors.test.ts index 2b0263f69db8..252c49cc09fb 100644 --- a/x-pack/legacy/plugins/apm/public/selectors/__tests__/chartSelectors.test.ts +++ b/x-pack/legacy/plugins/apm/public/selectors/__tests__/chartSelectors.test.ts @@ -21,7 +21,7 @@ describe('chartSelectors', () => { it('should return anomalyScoreSeries', () => { const data = [{ x0: 0, x: 10 }]; expect(getAnomalyScoreSeries(data)).toEqual({ - areaColor: 'rgba(146,0,0,0.1)', + areaColor: 'rgba(231,102,76,0.1)', color: 'none', data: [{ x0: 0, x: 10 }], hideLegend: true, @@ -57,7 +57,7 @@ describe('chartSelectors', () => { getResponseTimeSeries({ apmTimeseries, anomalyTimeseries: undefined }) ).toEqual([ { - color: '#3185fc', + color: '#6092c0', data: [ { x: 0, y: 100 }, { x: 1000, y: 200 } @@ -67,7 +67,7 @@ describe('chartSelectors', () => { type: 'linemark' }, { - color: '#e6c220', + color: '#d6bf57', data: [ { x: 0, y: 200 }, { x: 1000, y: 300 } @@ -77,7 +77,7 @@ describe('chartSelectors', () => { type: 'linemark' }, { - color: '#f98510', + color: '#da8b45', data: [ { x: 0, y: 300 }, { x: 1000, y: 400 } diff --git a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts index 8c6ed2ebcec7..870660c429ca 100644 --- a/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts +++ b/x-pack/legacy/plugins/apm/server/lib/metrics/by_agent/shared/memory/index.ts @@ -43,7 +43,7 @@ const chartBase: ChartBase = { series }; -const percentUsedScript = { +export const percentMemoryUsedScript = { lang: 'expression', source: `1 - doc['${METRIC_SYSTEM_FREE_MEMORY}'] / doc['${METRIC_SYSTEM_TOTAL_MEMORY}']` }; @@ -59,8 +59,8 @@ export async function getMemoryChartData( serviceNodeName, chartBase, aggs: { - memoryUsedAvg: { avg: { script: percentUsedScript } }, - memoryUsedMax: { max: { script: percentUsedScript } } + memoryUsedAvg: { avg: { script: percentMemoryUsedScript } }, + memoryUsedMax: { max: { script: percentMemoryUsedScript } } }, additionalFilters: [ { diff --git a/x-pack/legacy/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts b/x-pack/legacy/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts index ea9af12ac7f9..d3711e9582d1 100644 --- a/x-pack/legacy/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts +++ b/x-pack/legacy/plugins/apm/server/lib/service_map/get_service_map_from_trace_ids.ts @@ -163,7 +163,8 @@ export async function getServiceMapFromTraceIds({ } /* if there is an outgoing span, create a new path */ - if (event['span.type'] == 'external' || event['span.type'] == 'messaging') { + if (event['destination.address'] != null + && event['destination.address'] != '') { def outgoingLocation = getDestination(event); def outgoingPath = new ArrayList(basePath); outgoingPath.add(outgoingLocation); diff --git a/x-pack/legacy/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts b/x-pack/legacy/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts new file mode 100644 index 000000000000..6c4d540103ce --- /dev/null +++ b/x-pack/legacy/plugins/apm/server/lib/service_map/get_service_map_service_node_info.ts @@ -0,0 +1,267 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { ESFilter } from '../../../typings/elasticsearch'; +import { rangeFilter } from '../helpers/range_filter'; +import { + PROCESSOR_EVENT, + SERVICE_ENVIRONMENT, + SERVICE_NAME, + TRANSACTION_DURATION, + METRIC_SYSTEM_CPU_PERCENT, + METRIC_SYSTEM_FREE_MEMORY, + METRIC_SYSTEM_TOTAL_MEMORY, + SERVICE_NODE_NAME +} from '../../../common/elasticsearch_fieldnames'; +import { percentMemoryUsedScript } from '../metrics/by_agent/shared/memory'; +import { PromiseReturnType } from '../../../typings/common'; + +interface Options { + setup: Setup & SetupTimeRange; + environment?: string; + serviceName: string; +} + +interface TaskParameters { + setup: Setup; + minutes: number; + filter: ESFilter[]; +} + +export type ServiceNodeMetrics = PromiseReturnType< + typeof getServiceMapServiceNodeInfo +>; + +export async function getServiceMapServiceNodeInfo({ + serviceName, + environment, + setup +}: Options & { serviceName: string; environment?: string }) { + const { start, end } = setup; + + const filter: ESFilter[] = [ + { range: rangeFilter(start, end) }, + { term: { [SERVICE_NAME]: serviceName } }, + ...(environment + ? [{ term: { [SERVICE_ENVIRONMENT]: SERVICE_ENVIRONMENT } }] + : []) + ]; + + const minutes = Math.abs((end - start) / (1000 * 60)); + + const taskParams = { + setup, + minutes, + filter + }; + + const [ + errorMetrics, + transactionMetrics, + cpuMetrics, + memoryMetrics, + instanceMetrics + ] = await Promise.all([ + getErrorMetrics(taskParams), + getTransactionMetrics(taskParams), + getCpuMetrics(taskParams), + getMemoryMetrics(taskParams), + getNumInstances(taskParams) + ]); + + return { + ...errorMetrics, + ...transactionMetrics, + ...cpuMetrics, + ...memoryMetrics, + ...instanceMetrics + }; +} + +async function getErrorMetrics({ setup, minutes, filter }: TaskParameters) { + const { client, indices } = setup; + + const response = await client.search({ + index: indices['apm_oss.errorIndices'], + body: { + size: 0, + query: { + bool: { + filter: filter.concat({ + term: { + [PROCESSOR_EVENT]: 'error' + } + }) + } + }, + track_total_hits: true + } + }); + + return { + avgErrorsPerMinute: + response.hits.total.value > 0 ? response.hits.total.value / minutes : null + }; +} + +async function getTransactionMetrics({ + setup, + filter, + minutes +}: TaskParameters) { + const { indices, client } = setup; + + const response = await client.search({ + index: indices['apm_oss.transactionIndices'], + body: { + size: 1, + query: { + bool: { + filter: filter.concat({ + term: { + [PROCESSOR_EVENT]: 'transaction' + } + }) + } + }, + track_total_hits: true, + aggs: { + duration: { + avg: { + field: TRANSACTION_DURATION + } + } + } + } + }); + + return { + avgTransactionDuration: response.aggregations?.duration.value, + avgRequestsPerMinute: + response.hits.total.value > 0 ? response.hits.total.value / minutes : null + }; +} + +async function getCpuMetrics({ setup, filter }: TaskParameters) { + const { indices, client } = setup; + + const response = await client.search({ + index: indices['apm_oss.metricsIndices'], + body: { + size: 0, + query: { + bool: { + filter: filter.concat([ + { + term: { + [PROCESSOR_EVENT]: 'metric' + } + }, + { + exists: { + field: METRIC_SYSTEM_CPU_PERCENT + } + } + ]) + } + }, + aggs: { + avgCpuUsage: { + avg: { + field: METRIC_SYSTEM_CPU_PERCENT + } + } + } + } + }); + + return { + avgCpuUsage: response.aggregations?.avgCpuUsage.value + }; +} + +async function getMemoryMetrics({ setup, filter }: TaskParameters) { + const { client, indices } = setup; + const response = await client.search({ + index: indices['apm_oss.metricsIndices'], + body: { + query: { + bool: { + filter: filter.concat([ + { + term: { + [PROCESSOR_EVENT]: 'metric' + } + }, + { + exists: { + field: METRIC_SYSTEM_FREE_MEMORY + } + }, + { + exists: { + field: METRIC_SYSTEM_TOTAL_MEMORY + } + } + ]) + } + }, + aggs: { + avgMemoryUsage: { + avg: { + script: percentMemoryUsedScript + } + } + } + } + }); + + return { + avgMemoryUsage: response.aggregations?.avgMemoryUsage.value + }; +} + +async function getNumInstances({ setup, filter }: TaskParameters) { + const { client, indices } = setup; + const response = await client.search({ + index: indices['apm_oss.transactionIndices'], + body: { + query: { + bool: { + filter: filter.concat([ + { + term: { + [PROCESSOR_EVENT]: 'transaction' + } + }, + { + exists: { + field: SERVICE_NODE_NAME + } + }, + { + exists: { + field: METRIC_SYSTEM_TOTAL_MEMORY + } + } + ]) + } + }, + aggs: { + instances: { + cardinality: { + field: SERVICE_NODE_NAME + } + } + } + } + }); + + return { + numInstances: response.aggregations?.instances.value || 1 + }; +} diff --git a/x-pack/legacy/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap b/x-pack/legacy/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap index acd5dc119b73..bbf2a6882c3c 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap +++ b/x-pack/legacy/plugins/apm/server/lib/services/__snapshots__/queries.test.ts.snap @@ -137,7 +137,6 @@ Object { "events": Object { "terms": Object { "field": "processor.event", - "size": 2, }, }, }, diff --git a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts index 8e578a839ae5..2f44b9231eae 100644 --- a/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts +++ b/x-pack/legacy/plugins/apm/server/lib/services/get_services/get_services_items.ts @@ -44,7 +44,7 @@ export async function getServicesItems( terms: { field: SERVICE_AGENT_NAME, size: 1 } }, events: { - terms: { field: PROCESSOR_EVENT, size: 2 } + terms: { field: PROCESSOR_EVENT } }, environments: { terms: { field: SERVICE_ENVIRONMENT } diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap index 18ce29982b61..580cafff95e0 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap @@ -6,7 +6,7 @@ Array [ Object { "body": Object { "aggs": Object { - "transactions": Object { + "transaction_groups": Object { "aggs": Object { "avg": Object { "avg": Object { @@ -42,12 +42,24 @@ Array [ }, }, }, - "terms": Object { - "field": "transaction.name", - "order": Object { - "sum": "desc", - }, - "size": 100, + "composite": Object { + "size": 10000, + "sources": Array [ + Object { + "service": Object { + "terms": Object { + "field": "service.name", + }, + }, + }, + Object { + "transaction": Object { + "terms": Object { + "field": "transaction.name", + }, + }, + }, + ], }, }, }, @@ -104,7 +116,7 @@ Array [ Object { "body": Object { "aggs": Object { - "transactions": Object { + "transaction_groups": Object { "aggs": Object { "avg": Object { "avg": Object { @@ -140,12 +152,22 @@ Array [ }, }, }, + "composite": Object { + "size": 10000, + "sources": Array [ + Object { + "transaction": Object { + "terms": Object { + "field": "transaction.name", + }, + }, + }, + ], + }, + }, + "transactions": Object { "terms": Object { "field": "transaction.name", - "order": Object { - "sum": "desc", - }, - "size": 100, }, }, }, diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap index e33255b5baa5..58fbe664ccca 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/queries.test.ts.snap @@ -4,7 +4,7 @@ exports[`transaction group queries fetches top traces 1`] = ` Object { "body": Object { "aggs": Object { - "transactions": Object { + "transaction_groups": Object { "aggs": Object { "avg": Object { "avg": Object { @@ -40,12 +40,24 @@ Object { }, }, }, - "terms": Object { - "field": "transaction.name", - "order": Object { - "sum": "desc", - }, - "size": "myIndex", + "composite": Object { + "size": 10000, + "sources": Array [ + Object { + "service": Object { + "terms": Object { + "field": "service.name", + }, + }, + }, + Object { + "transaction": Object { + "terms": Object { + "field": "transaction.name", + }, + }, + }, + ], }, }, }, @@ -98,7 +110,7 @@ exports[`transaction group queries fetches top transactions 1`] = ` Object { "body": Object { "aggs": Object { - "transactions": Object { + "transaction_groups": Object { "aggs": Object { "avg": Object { "avg": Object { @@ -134,12 +146,22 @@ Object { }, }, }, + "composite": Object { + "size": 10000, + "sources": Array [ + Object { + "transaction": Object { + "terms": Object { + "field": "transaction.name", + }, + }, + }, + ], + }, + }, + "transactions": Object { "terms": Object { "field": "transaction.name", - "order": Object { - "sum": "desc", - }, - "size": "myIndex", }, }, }, diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/transform.test.ts.snap b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/transform.test.ts.snap index 2d8b16e95f76..66b805ab2efc 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/transform.test.ts.snap +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/__snapshots__/transform.test.ts.snap @@ -3,12 +3,12 @@ exports[`transactionGroupsTransformer should match snapshot 1`] = ` Array [ Object { - "averageResponseTime": 255966.30555555556, - "impact": 4.3693406535517445, - "name": "POST /api/orders", - "p95": 320238.5, + "averageResponseTime": 48021.972616494, + "impact": 100, + "name": "GET /api", + "p95": 67138.18364917398, "sample": Object { - "@timestamp": "2018-11-18T20:43:32.010Z", + "@timestamp": "2018-11-18T20:53:44.070Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -16,52 +16,50 @@ Array [ }, "context": Object { "custom": Object { - "containerId": 4669, + "containerId": 5176, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 2413, + "pid": 3756, "ppid": 1, "title": "node /app/server.js", }, "request": Object { - "body": "[REDACTED]", "headers": Object { - "accept": "application/json", - "connection": "close", - "content-length": "129", - "content-type": "application/json", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-86c68779d8a65b06fb78e770ffc436a5-4aaea53dc1791183-01", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "user-agent": "python-requests/2.20.0", }, "http_version": "1.1", - "method": "POST", + "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.6", }, "url": Object { - "full": "http://opbeans-node:3000/api/orders", + "full": "http://opbeans-node:3000/api/types/3", "hostname": "opbeans-node", - "pathname": "/api/orders", + "pathname": "/api/types/3", "port": "3000", "protocol": "http:", - "raw": "/api/orders", + "raw": "/api/types/3", }, }, "response": Object { "headers": Object { "connection": "close", - "content-length": "13", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:43:32 GMT", - "etag": "W/\\"d-g9K2iK4ordyN88lGL4LmPlYNfhc\\"", + "content-type": "application/json;charset=UTF-8", + "date": "Sun, 18 Nov 2018 20:53:43 GMT", + "transfer-encoding": "chunked", "x-powered-by": "Express", }, - "status_code": 200, + "status_code": 404, }, "service": Object { "agent": Object { @@ -101,48 +99,48 @@ baz", "host": Object { "name": "b359e3afece8", }, + "parent": Object { + "id": "4aaea53dc1791183", + }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542573812010006, + "us": 1542574424070007, }, "trace": Object { - "id": "2b1252a338249daeecf6afb0c236e31b", + "id": "86c68779d8a65b06fb78e770ffc436a5", }, "transaction": Object { "duration": Object { - "us": 291572, + "us": 8684, }, - "id": "2c9f39e9ec4a0111", - "name": "POST /api/orders", - "result": "HTTP 2xx", + "id": "a78bca581dcd8ff8", + "name": "GET /api", + "result": "HTTP 4xx", "sampled": true, "span_count": Object { - "started": 16, + "started": 1, }, "type": "request", }, }, - "transactionsPerMinute": 5684.210526315789, + "transactionsPerMinute": 691926.3157894736, }, Object { - "averageResponseTime": 48021.972616494, - "impact": 100, - "name": "GET /api", - "p95": 67138.18364917398, + "averageResponseTime": 2651.8784461553205, + "impact": 15.770246496477105, + "name": "GET static file", + "p95": 6140.579335038363, "sample": Object { - "@timestamp": "2018-11-18T20:53:44.070Z", + "@timestamp": "2018-11-18T20:53:43.304Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", "version": "7.0.0-alpha1", }, "context": Object { - "custom": Object { - "containerId": 5176, - }, "process": Object { "argv": Array [ "/usr/local/bin/node", @@ -155,36 +153,37 @@ baz", "request": Object { "headers": Object { "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-86c68779d8a65b06fb78e770ffc436a5-4aaea53dc1791183-01", "host": "opbeans-node:3000", - "user-agent": "python-requests/2.20.0", + "user-agent": "curl/7.38.0", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.6", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/types/3", + "full": "http://opbeans-node:3000/", "hostname": "opbeans-node", - "pathname": "/api/types/3", + "pathname": "/", "port": "3000", "protocol": "http:", - "raw": "/api/types/3", + "raw": "/", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-type": "application/json;charset=UTF-8", + "accept-ranges": "bytes", + "cache-control": "public, max-age=0", + "connection": "keep-alive", + "content-length": "640", + "content-type": "text/html; charset=UTF-8", "date": "Sun, 18 Nov 2018 20:53:43 GMT", - "transfer-encoding": "chunked", + "etag": "W/\\"280-1670775e878\\"", + "last-modified": "Mon, 12 Nov 2018 10:27:07 GMT", "x-powered-by": "Express", }, - "status_code": 404, + "status_code": 200, }, "service": Object { "agent": Object { @@ -207,59 +206,43 @@ baz", "ip": "172.18.0.10", "platform": "linux", }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, }, "host": Object { "name": "b359e3afece8", }, - "parent": Object { - "id": "4aaea53dc1791183", - }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574424070007, + "us": 1542574423304006, }, "trace": Object { - "id": "86c68779d8a65b06fb78e770ffc436a5", + "id": "b303d2a4a007946b63b9db7fafe639a0", }, "transaction": Object { "duration": Object { - "us": 8684, + "us": 1801, }, - "id": "a78bca581dcd8ff8", - "name": "GET /api", - "result": "HTTP 4xx", + "id": "2869c13633534be5", + "name": "GET static file", + "result": "HTTP 2xx", "sampled": true, "span_count": Object { - "started": 1, + "started": 0, }, "type": "request", }, }, - "transactionsPerMinute": 691926.3157894736, + "transactionsPerMinute": 1977031.5789473683, }, Object { - "averageResponseTime": 33265.03326147213, - "impact": 10.256357027376065, - "name": "GET /api/orders", - "p95": 58827.489999999976, + "averageResponseTime": 32554.36257814184, + "impact": 14.344171563678346, + "name": "GET /api/stats", + "p95": 59356.73611111111, "sample": Object { - "@timestamp": "2018-11-18T20:53:40.973Z", + "@timestamp": "2018-11-18T20:53:42.560Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -267,7 +250,7 @@ baz", }, "context": Object { "custom": Object { - "containerId": 408, + "containerId": 207, }, "process": Object { "argv": Array [ @@ -280,35 +263,38 @@ baz", }, "request": Object { "headers": Object { - "connection": "close", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-63ccc3b0929dafb7f2fbcabdc7f7af25-821a787e73ab1563-01", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "if-none-match": "W/\\"77-uxKJrX5GSMJJWTKh3orUFAEVxSs\\"", + "referer": "http://opbeans-node:3000/dashboard", + "user-agent": "Chromeless 1.4.0", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.7", }, "url": Object { - "full": "http://opbeans-node:3000/api/orders", + "full": "http://opbeans-node:3000/api/stats", "hostname": "opbeans-node", - "pathname": "/api/orders", + "pathname": "/api/stats", "port": "3000", "protocol": "http:", - "raw": "/api/orders", + "raw": "/api/stats", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-length": "103612", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:53:40 GMT", - "etag": "W/\\"194bc-cOw6+iRf7XCeqMXHrle3IOig7tY\\"", + "connection": "keep-alive", + "date": "Sun, 18 Nov 2018 20:53:42 GMT", + "etag": "W/\\"77-uxKJrX5GSMJJWTKh3orUFAEVxSs\\"", "x-powered-by": "Express", }, - "status_code": 200, + "status_code": 304, }, "service": Object { "agent": Object { @@ -348,39 +334,42 @@ baz", "host": Object { "name": "b359e3afece8", }, + "parent": Object { + "id": "821a787e73ab1563", + }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574420973006, + "us": 1542574422560002, }, "trace": Object { - "id": "0afce85f593cbbdd09949936fe964f0f", + "id": "63ccc3b0929dafb7f2fbcabdc7f7af25", }, "transaction": Object { "duration": Object { - "us": 23040, + "us": 28753, }, - "id": "89f200353eb50539", - "name": "GET /api/orders", - "result": "HTTP 2xx", + "id": "fb754e7628da2fb5", + "name": "GET /api/stats", + "result": "HTTP 3xx", "sampled": true, "span_count": Object { - "started": 2, + "started": 7, }, "type": "request", }, }, - "transactionsPerMinute": 102536.84210526315, + "transactionsPerMinute": 146494.73684210525, }, Object { - "averageResponseTime": 32900.72714285714, - "impact": 2.1791207411745854, - "name": "GET /log-message", - "p95": 40444, + "averageResponseTime": 32159.926322043968, + "impact": 10.27904952170656, + "name": "GET /api/customers", + "p95": 59845.85714285714, "sample": Object { - "@timestamp": "2018-11-18T20:49:09.225Z", + "@timestamp": "2018-11-18T20:53:21.180Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -388,48 +377,51 @@ baz", }, "context": Object { "custom": Object { - "containerId": 321, + "containerId": 2531, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3142, + "pid": 3710, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "connection": "close", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-541025da8ecc2f51f21c1a4ad6992b77-ca18d9d4c3879519-01", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "user-agent": "python-requests/2.20.0", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.6", }, "url": Object { - "full": "http://opbeans-node:3000/log-message", + "full": "http://opbeans-node:3000/api/customers", "hostname": "opbeans-node", - "pathname": "/log-message", + "pathname": "/api/customers", "port": "3000", "protocol": "http:", - "raw": "/log-message", + "raw": "/api/customers", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-length": "24", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:49:09 GMT", - "etag": "W/\\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\\"", + "connection": "keep-alive", + "content-length": "186769", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:53:21 GMT", + "etag": "W/\\"2d991-yG3J8W/roH7fSxXTudZrO27Ax9s\\"", "x-powered-by": "Express", }, - "status_code": 500, + "status_code": 200, }, "service": Object { "agent": Object { @@ -469,39 +461,42 @@ baz", "host": Object { "name": "b359e3afece8", }, + "parent": Object { + "id": "ca18d9d4c3879519", + }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574149225004, + "us": 1542574401180002, }, "trace": Object { - "id": "ba18b741cdd3ac83eca89a5fede47577", + "id": "541025da8ecc2f51f21c1a4ad6992b77", }, "transaction": Object { "duration": Object { - "us": 32381, + "us": 18077, }, - "id": "b9a8f96d7554d09f", - "name": "GET /log-message", - "result": "HTTP 5xx", + "id": "94852b9dd1075982", + "name": "GET /api/customers", + "result": "HTTP 2xx", "sampled": true, "span_count": Object { - "started": 0, + "started": 2, }, "type": "request", }, }, - "transactionsPerMinute": 22105.263157894737, + "transactionsPerMinute": 106294.73684210525, }, Object { - "averageResponseTime": 32554.36257814184, - "impact": 14.344171563678346, - "name": "GET /api/stats", - "p95": 59356.73611111111, + "averageResponseTime": 33265.03326147213, + "impact": 10.256357027376065, + "name": "GET /api/orders", + "p95": 58827.489999999976, "sample": Object { - "@timestamp": "2018-11-18T20:53:42.560Z", + "@timestamp": "2018-11-18T20:53:40.973Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -509,7 +504,7 @@ baz", }, "context": Object { "custom": Object { - "containerId": 207, + "containerId": 408, }, "process": Object { "argv": Array [ @@ -522,38 +517,35 @@ baz", }, "request": Object { "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-63ccc3b0929dafb7f2fbcabdc7f7af25-821a787e73ab1563-01", + "connection": "close", "host": "opbeans-node:3000", - "if-none-match": "W/\\"77-uxKJrX5GSMJJWTKh3orUFAEVxSs\\"", - "referer": "http://opbeans-node:3000/dashboard", - "user-agent": "Chromeless 1.4.0", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.7", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/stats", + "full": "http://opbeans-node:3000/api/orders", "hostname": "opbeans-node", - "pathname": "/api/stats", + "pathname": "/api/orders", "port": "3000", "protocol": "http:", - "raw": "/api/stats", + "raw": "/api/orders", }, }, "response": Object { "headers": Object { - "connection": "keep-alive", - "date": "Sun, 18 Nov 2018 20:53:42 GMT", - "etag": "W/\\"77-uxKJrX5GSMJJWTKh3orUFAEVxSs\\"", + "connection": "close", + "content-length": "103612", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:53:40 GMT", + "etag": "W/\\"194bc-cOw6+iRf7XCeqMXHrle3IOig7tY\\"", "x-powered-by": "Express", }, - "status_code": 304, + "status_code": 200, }, "service": Object { "agent": Object { @@ -593,42 +585,39 @@ baz", "host": Object { "name": "b359e3afece8", }, - "parent": Object { - "id": "821a787e73ab1563", - }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574422560002, + "us": 1542574420973006, }, "trace": Object { - "id": "63ccc3b0929dafb7f2fbcabdc7f7af25", + "id": "0afce85f593cbbdd09949936fe964f0f", }, "transaction": Object { "duration": Object { - "us": 28753, - }, - "id": "fb754e7628da2fb5", - "name": "GET /api/stats", - "result": "HTTP 3xx", + "us": 23040, + }, + "id": "89f200353eb50539", + "name": "GET /api/orders", + "result": "HTTP 2xx", "sampled": true, "span_count": Object { - "started": 7, + "started": 2, }, "type": "request", }, }, - "transactionsPerMinute": 146494.73684210525, + "transactionsPerMinute": 102536.84210526315, }, Object { - "averageResponseTime": 32387.73641304348, - "impact": 2.2558112380477584, - "name": "GET /log-error", - "p95": 40061.1, + "averageResponseTime": 27516.89144558744, + "impact": 9.651458992731666, + "name": "GET /api/products/top", + "p95": 56064.679999999986, "sample": Object { - "@timestamp": "2018-11-18T20:52:51.462Z", + "@timestamp": "2018-11-18T20:52:57.316Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -636,48 +625,52 @@ baz", }, "context": Object { "custom": Object { - "containerId": 4877, + "containerId": 5113, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3659, + "pid": 3686, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "connection": "close", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-74f12e705936d66350f4741ebeb55189-fcebe94cd2136215-01", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "referer": "http://opbeans-node:3000/dashboard", + "user-agent": "Chromeless 1.4.0", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.7", }, "url": Object { - "full": "http://opbeans-node:3000/log-error", + "full": "http://opbeans-node:3000/api/products/top", "hostname": "opbeans-node", - "pathname": "/log-error", + "pathname": "/api/products/top", "port": "3000", "protocol": "http:", - "raw": "/log-error", + "raw": "/api/products/top", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-length": "24", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:52:51 GMT", - "etag": "W/\\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\\"", + "connection": "keep-alive", + "content-length": "282", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:52:57 GMT", + "etag": "W/\\"11a-lcI9zuMZYYsDRpEZgYqDYr96cKM\\"", "x-powered-by": "Express", }, - "status_code": 500, + "status_code": 200, }, "service": Object { "agent": Object { @@ -717,39 +710,42 @@ baz", "host": Object { "name": "b359e3afece8", }, + "parent": Object { + "id": "fcebe94cd2136215", + }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574371462005, + "us": 1542574377316005, }, "trace": Object { - "id": "15366d65659b5fc8f67ff127391b3aff", + "id": "74f12e705936d66350f4741ebeb55189", }, "transaction": Object { "duration": Object { - "us": 33367, + "us": 48781, }, - "id": "ec9c465c5042ded8", - "name": "GET /log-error", - "result": "HTTP 5xx", + "id": "be4bd5475d5d9e6f", + "name": "GET /api/products/top", + "result": "HTTP 2xx", "sampled": true, "span_count": Object { - "started": 0, + "started": 4, }, "type": "request", }, }, - "transactionsPerMinute": 23242.105263157893, + "transactionsPerMinute": 116652.63157894736, }, Object { - "averageResponseTime": 32159.926322043968, - "impact": 10.27904952170656, - "name": "GET /api/customers", - "p95": 59845.85714285714, + "averageResponseTime": 12683.190864600327, + "impact": 4.4239778504968, + "name": "GET /api/products", + "p95": 35009.67999999999, "sample": Object { - "@timestamp": "2018-11-18T20:53:21.180Z", + "@timestamp": "2018-11-18T20:53:43.477Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -757,48 +753,45 @@ baz", }, "context": Object { "custom": Object { - "containerId": 2531, + "containerId": 2857, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3710, + "pid": 3756, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-541025da8ecc2f51f21c1a4ad6992b77-ca18d9d4c3879519-01", + "connection": "close", "host": "opbeans-node:3000", - "user-agent": "python-requests/2.20.0", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.6", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/customers", + "full": "http://opbeans-node:3000/api/products", "hostname": "opbeans-node", - "pathname": "/api/customers", + "pathname": "/api/products", "port": "3000", "protocol": "http:", - "raw": "/api/customers", + "raw": "/api/products", }, }, "response": Object { "headers": Object { - "connection": "keep-alive", - "content-length": "186769", + "connection": "close", + "content-length": "1023", "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:53:21 GMT", - "etag": "W/\\"2d991-yG3J8W/roH7fSxXTudZrO27Ax9s\\"", + "date": "Sun, 18 Nov 2018 20:53:43 GMT", + "etag": "W/\\"3ff-VyOxcDApb+a/lnjkm9FeTOGSDrs\\"", "x-powered-by": "Express", }, "status_code": 200, @@ -841,25 +834,22 @@ baz", "host": Object { "name": "b359e3afece8", }, - "parent": Object { - "id": "ca18d9d4c3879519", - }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574401180002, + "us": 1542574423477006, }, "trace": Object { - "id": "541025da8ecc2f51f21c1a4ad6992b77", + "id": "bee00a8efb523ca4b72adad57f7caba3", }, "transaction": Object { "duration": Object { - "us": 18077, + "us": 6915, }, - "id": "94852b9dd1075982", - "name": "GET /api/customers", + "id": "d8fc6d3b8707b64c", + "name": "GET /api/products", "result": "HTTP 2xx", "sampled": true, "span_count": Object { @@ -868,15 +858,15 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 106294.73684210525, + "transactionsPerMinute": 116147.36842105263, }, Object { - "averageResponseTime": 27516.89144558744, - "impact": 9.651458992731666, - "name": "GET /api/products/top", - "p95": 56064.679999999986, + "averageResponseTime": 255966.30555555556, + "impact": 4.3693406535517445, + "name": "POST /api/orders", + "p95": 320238.5, "sample": Object { - "@timestamp": "2018-11-18T20:52:57.316Z", + "@timestamp": "2018-11-18T20:43:32.010Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -884,49 +874,49 @@ baz", }, "context": Object { "custom": Object { - "containerId": 5113, + "containerId": 4669, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3686, + "pid": 2413, "ppid": 1, "title": "node /app/server.js", }, "request": Object { + "body": "[REDACTED]", "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-74f12e705936d66350f4741ebeb55189-fcebe94cd2136215-01", + "accept": "application/json", + "connection": "close", + "content-length": "129", + "content-type": "application/json", "host": "opbeans-node:3000", - "referer": "http://opbeans-node:3000/dashboard", - "user-agent": "Chromeless 1.4.0", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", - "method": "GET", + "method": "POST", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.7", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/products/top", + "full": "http://opbeans-node:3000/api/orders", "hostname": "opbeans-node", - "pathname": "/api/products/top", + "pathname": "/api/orders", "port": "3000", "protocol": "http:", - "raw": "/api/products/top", + "raw": "/api/orders", }, }, "response": Object { "headers": Object { - "connection": "keep-alive", - "content-length": "282", + "connection": "close", + "content-length": "13", "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:52:57 GMT", - "etag": "W/\\"11a-lcI9zuMZYYsDRpEZgYqDYr96cKM\\"", + "date": "Sun, 18 Nov 2018 20:43:32 GMT", + "etag": "W/\\"d-g9K2iK4ordyN88lGL4LmPlYNfhc\\"", "x-powered-by": "Express", }, "status_code": 200, @@ -969,42 +959,39 @@ baz", "host": Object { "name": "b359e3afece8", }, - "parent": Object { - "id": "fcebe94cd2136215", - }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574377316005, + "us": 1542573812010006, }, "trace": Object { - "id": "74f12e705936d66350f4741ebeb55189", + "id": "2b1252a338249daeecf6afb0c236e31b", }, "transaction": Object { "duration": Object { - "us": 48781, + "us": 291572, }, - "id": "be4bd5475d5d9e6f", - "name": "GET /api/products/top", + "id": "2c9f39e9ec4a0111", + "name": "POST /api/orders", "result": "HTTP 2xx", "sampled": true, "span_count": Object { - "started": 4, + "started": 16, }, "type": "request", }, }, - "transactionsPerMinute": 116652.63157894736, + "transactionsPerMinute": 5684.210526315789, }, Object { - "averageResponseTime": 21331.714285714286, - "impact": 0.28817487960409877, - "name": "POST /api", - "p95": 30938, + "averageResponseTime": 17189.329210275926, + "impact": 3.424381787142002, + "name": "GET /api/products/:id/customers", + "p95": 39284.79999999999, "sample": Object { - "@timestamp": "2018-11-18T20:29:42.751Z", + "@timestamp": "2018-11-18T20:48:24.769Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1012,50 +999,51 @@ baz", }, "context": Object { "custom": Object { - "containerId": 2927, + "containerId": 1735, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 546, + "pid": 3100, "ppid": 1, "title": "node /app/server.js", }, "request": Object { - "body": "[REDACTED]", "headers": Object { - "accept": "application/json", - "connection": "close", - "content-length": "129", - "content-type": "application/json", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-28f178c354d17f400dea04bc4a7b3c57-68f5d1607cac7779-01", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "user-agent": "python-requests/2.20.0", }, "http_version": "1.1", - "method": "POST", + "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.6", }, "url": Object { - "full": "http://opbeans-node:3000/api/orders", + "full": "http://opbeans-node:3000/api/products/2/customers", "hostname": "opbeans-node", - "pathname": "/api/orders", + "pathname": "/api/products/2/customers", "port": "3000", "protocol": "http:", - "raw": "/api/orders", + "raw": "/api/products/2/customers", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-length": "0", - "date": "Sun, 18 Nov 2018 20:29:42 GMT", + "connection": "keep-alive", + "content-length": "186570", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:48:24 GMT", + "etag": "W/\\"2d8ca-Z9NzuHyGyxwtzpOkcIxBvzm24iw\\"", "x-powered-by": "Express", }, - "status_code": 400, + "status_code": 200, }, "service": Object { "agent": Object { @@ -1095,23 +1083,26 @@ baz", "host": Object { "name": "b359e3afece8", }, + "parent": Object { + "id": "68f5d1607cac7779", + }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542572982751005, + "us": 1542574104769029, }, "trace": Object { - "id": "8ed4d94ec8fc11b1ea1b0aa59c2320ff", + "id": "28f178c354d17f400dea04bc4a7b3c57", }, "transaction": Object { "duration": Object { - "us": 21083, + "us": 49338, }, - "id": "d67c2f7aa897110c", - "name": "POST /api", - "result": "HTTP 4xx", + "id": "2a87ae20ad04ee0c", + "name": "GET /api/products/:id/customers", + "result": "HTTP 2xx", "sampled": true, "span_count": Object { "started": 1, @@ -1119,15 +1110,15 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 4642.105263157894, + "transactionsPerMinute": 66378.94736842105, }, Object { - "averageResponseTime": 17189.329210275926, - "impact": 3.424381787142002, - "name": "GET /api/products/:id/customers", - "p95": 39284.79999999999, + "averageResponseTime": 11257.757916666667, + "impact": 2.558180605569336, + "name": "GET /api/types", + "p95": 35222.944444444445, "sample": Object { - "@timestamp": "2018-11-18T20:48:24.769Z", + "@timestamp": "2018-11-18T20:53:44.978Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1135,48 +1126,45 @@ baz", }, "context": Object { "custom": Object { - "containerId": 1735, + "containerId": 2193, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3100, + "pid": 3756, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-28f178c354d17f400dea04bc4a7b3c57-68f5d1607cac7779-01", + "connection": "close", "host": "opbeans-node:3000", - "user-agent": "python-requests/2.20.0", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.6", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/products/2/customers", + "full": "http://opbeans-node:3000/api/types", "hostname": "opbeans-node", - "pathname": "/api/products/2/customers", + "pathname": "/api/types", "port": "3000", "protocol": "http:", - "raw": "/api/products/2/customers", + "raw": "/api/types", }, }, "response": Object { "headers": Object { - "connection": "keep-alive", - "content-length": "186570", + "connection": "close", + "content-length": "112", "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:48:24 GMT", - "etag": "W/\\"2d8ca-Z9NzuHyGyxwtzpOkcIxBvzm24iw\\"", + "date": "Sun, 18 Nov 2018 20:53:44 GMT", + "etag": "W/\\"70-1z6hT7P1WHgBgS/BeUEVeHhOCQU\\"", "x-powered-by": "Express", }, "status_code": 200, @@ -1219,42 +1207,39 @@ baz", "host": Object { "name": "b359e3afece8", }, - "parent": Object { - "id": "68f5d1607cac7779", - }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574104769029, + "us": 1542574424978005, }, "trace": Object { - "id": "28f178c354d17f400dea04bc4a7b3c57", + "id": "0d84126973411c19b470f2d9eea958d3", }, "transaction": Object { "duration": Object { - "us": 49338, + "us": 7891, }, - "id": "2a87ae20ad04ee0c", - "name": "GET /api/products/:id/customers", + "id": "0f10668e4fb3adc7", + "name": "GET /api/types", "result": "HTTP 2xx", "sampled": true, "span_count": Object { - "started": 1, + "started": 2, }, "type": "request", }, }, - "transactionsPerMinute": 66378.94736842105, + "transactionsPerMinute": 75789.47368421052, }, Object { - "averageResponseTime": 12763.68806073154, - "impact": 1.7479924334286208, - "name": "GET /api/types/:id", - "p95": 30576.749999999996, + "averageResponseTime": 3504.5108924806746, + "impact": 2.3600993453143766, + "name": "GET *", + "p95": 11431.738095238095, "sample": Object { - "@timestamp": "2018-11-18T20:53:35.967Z", + "@timestamp": "2018-11-18T20:53:42.493Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1262,7 +1247,7 @@ baz", }, "context": Object { "custom": Object { - "containerId": 5345, + "containerId": 6446, }, "process": Object { "argv": Array [ @@ -1275,35 +1260,41 @@ baz", }, "request": Object { "headers": Object { - "connection": "close", + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "if-modified-since": "Mon, 12 Nov 2018 10:27:07 GMT", + "if-none-match": "W/\\"280-1670775e878\\"", + "upgrade-insecure-requests": "1", + "user-agent": "Chromeless 1.4.0", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.7", }, "url": Object { - "full": "http://opbeans-node:3000/api/types/1", + "full": "http://opbeans-node:3000/dashboard", "hostname": "opbeans-node", - "pathname": "/api/types/1", + "pathname": "/dashboard", "port": "3000", "protocol": "http:", - "raw": "/api/types/1", + "raw": "/dashboard", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-length": "217", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:53:35 GMT", - "etag": "W/\\"d9-cebOOHODBQMZd1wt+ZZBaSPgQLQ\\"", + "accept-ranges": "bytes", + "cache-control": "public, max-age=0", + "connection": "keep-alive", + "date": "Sun, 18 Nov 2018 20:53:42 GMT", + "etag": "W/\\"280-1670775e878\\"", + "last-modified": "Mon, 12 Nov 2018 10:27:07 GMT", "x-powered-by": "Express", }, - "status_code": 200, + "status_code": 304, }, "service": Object { "agent": Object { @@ -1348,34 +1339,34 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574415967005, + "us": 1542574422493006, }, "trace": Object { - "id": "2223b30b5cbaf2e221fcf70ac6d9abbe", + "id": "7efb6ade88cdea20cd96ca482681cde7", }, "transaction": Object { "duration": Object { - "us": 13064, + "us": 1901, }, - "id": "053436abacdec0a4", - "name": "GET /api/types/:id", - "result": "HTTP 2xx", + "id": "f5fc4621949b63fb", + "name": "GET *", + "result": "HTTP 3xx", "sampled": true, "span_count": Object { - "started": 2, + "started": 0, }, "type": "request", }, }, - "transactionsPerMinute": 45757.8947368421, + "transactionsPerMinute": 224684.21052631576, }, Object { - "averageResponseTime": 12683.190864600327, - "impact": 4.4239778504968, - "name": "GET /api/products", - "p95": 35009.67999999999, + "averageResponseTime": 32387.73641304348, + "impact": 2.2558112380477584, + "name": "GET /log-error", + "p95": 40061.1, "sample": Object { - "@timestamp": "2018-11-18T20:53:43.477Z", + "@timestamp": "2018-11-18T20:52:51.462Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1383,14 +1374,14 @@ baz", }, "context": Object { "custom": Object { - "containerId": 2857, + "containerId": 4877, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3756, + "pid": 3659, "ppid": 1, "title": "node /app/server.js", }, @@ -1407,24 +1398,24 @@ baz", "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/products", + "full": "http://opbeans-node:3000/log-error", "hostname": "opbeans-node", - "pathname": "/api/products", + "pathname": "/log-error", "port": "3000", "protocol": "http:", - "raw": "/api/products", + "raw": "/log-error", }, }, "response": Object { "headers": Object { "connection": "close", - "content-length": "1023", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:53:43 GMT", - "etag": "W/\\"3ff-VyOxcDApb+a/lnjkm9FeTOGSDrs\\"", + "content-length": "24", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:52:51 GMT", + "etag": "W/\\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\\"", "x-powered-by": "Express", }, - "status_code": 200, + "status_code": 500, }, "service": Object { "agent": Object { @@ -1469,34 +1460,34 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574423477006, + "us": 1542574371462005, }, "trace": Object { - "id": "bee00a8efb523ca4b72adad57f7caba3", + "id": "15366d65659b5fc8f67ff127391b3aff", }, "transaction": Object { "duration": Object { - "us": 6915, + "us": 33367, }, - "id": "d8fc6d3b8707b64c", - "name": "GET /api/products", - "result": "HTTP 2xx", + "id": "ec9c465c5042ded8", + "name": "GET /log-error", + "result": "HTTP 5xx", "sampled": true, "span_count": Object { - "started": 2, + "started": 0, }, "type": "request", }, }, - "transactionsPerMinute": 116147.36842105263, + "transactionsPerMinute": 23242.105263157893, }, Object { - "averageResponseTime": 11257.757916666667, - "impact": 2.558180605569336, - "name": "GET /api/types", - "p95": 35222.944444444445, + "averageResponseTime": 32900.72714285714, + "impact": 2.1791207411745854, + "name": "GET /log-message", + "p95": 40444, "sample": Object { - "@timestamp": "2018-11-18T20:53:44.978Z", + "@timestamp": "2018-11-18T20:49:09.225Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1504,14 +1495,14 @@ baz", }, "context": Object { "custom": Object { - "containerId": 2193, + "containerId": 321, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3756, + "pid": 3142, "ppid": 1, "title": "node /app/server.js", }, @@ -1528,24 +1519,24 @@ baz", "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/types", + "full": "http://opbeans-node:3000/log-message", "hostname": "opbeans-node", - "pathname": "/api/types", + "pathname": "/log-message", "port": "3000", "protocol": "http:", - "raw": "/api/types", + "raw": "/log-message", }, }, "response": Object { "headers": Object { "connection": "close", - "content-length": "112", - "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:53:44 GMT", - "etag": "W/\\"70-1z6hT7P1WHgBgS/BeUEVeHhOCQU\\"", + "content-length": "24", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:49:09 GMT", + "etag": "W/\\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\\"", "x-powered-by": "Express", }, - "status_code": 200, + "status_code": 500, }, "service": Object { "agent": Object { @@ -1590,34 +1581,34 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574424978005, + "us": 1542574149225004, }, "trace": Object { - "id": "0d84126973411c19b470f2d9eea958d3", + "id": "ba18b741cdd3ac83eca89a5fede47577", }, "transaction": Object { "duration": Object { - "us": 7891, + "us": 32381, }, - "id": "0f10668e4fb3adc7", - "name": "GET /api/types", - "result": "HTTP 2xx", + "id": "b9a8f96d7554d09f", + "name": "GET /log-message", + "result": "HTTP 5xx", "sampled": true, "span_count": Object { - "started": 2, + "started": 0, }, "type": "request", }, }, - "transactionsPerMinute": 75789.47368421052, + "transactionsPerMinute": 22105.263157894737, }, Object { - "averageResponseTime": 10584.05144193297, - "impact": 1.280810614916383, - "name": "GET /api/orders/:id", - "p95": 26555.399999999998, + "averageResponseTime": 10548.218597063622, + "impact": 1.8338763992340905, + "name": "GET /api/products/:id", + "p95": 28413.383333333328, "sample": Object { - "@timestamp": "2018-11-18T20:51:36.949Z", + "@timestamp": "2018-11-18T20:52:57.963Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1625,14 +1616,14 @@ baz", }, "context": Object { "custom": Object { - "containerId": 5999, + "containerId": 7184, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3475, + "pid": 3686, "ppid": 1, "title": "node /app/server.js", }, @@ -1649,22 +1640,24 @@ baz", "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/orders/183", + "full": "http://opbeans-node:3000/api/products/3", "hostname": "opbeans-node", - "pathname": "/api/orders/183", + "pathname": "/api/products/3", "port": "3000", "protocol": "http:", - "raw": "/api/orders/183", + "raw": "/api/products/3", }, }, "response": Object { "headers": Object { "connection": "close", - "content-length": "0", - "date": "Sun, 18 Nov 2018 20:51:36 GMT", + "content-length": "231", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:52:57 GMT", + "etag": "W/\\"e7-kkuzj37GZDzXDh0CWqh5Gan0VO4\\"", "x-powered-by": "Express", }, - "status_code": 404, + "status_code": 200, }, "service": Object { "agent": Object { @@ -1709,18 +1702,18 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574296949004, + "us": 1542574377963005, }, "trace": Object { - "id": "dab6421fa44a6869887e0edf32e1ad6f", + "id": "ca86ec845e412e4b4506a715d51548ec", }, "transaction": Object { "duration": Object { - "us": 5906, + "us": 6959, }, - "id": "937ef5588454f74a", - "name": "GET /api/orders/:id", - "result": "HTTP 4xx", + "id": "d324897ffb7ebcdc", + "name": "GET /api/products/:id", + "result": "HTTP 2xx", "sampled": true, "span_count": Object { "started": 1, @@ -1728,15 +1721,15 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 40515.789473684206, + "transactionsPerMinute": 58073.68421052631, }, Object { - "averageResponseTime": 10548.218597063622, - "impact": 1.8338763992340905, - "name": "GET /api/products/:id", - "p95": 28413.383333333328, + "averageResponseTime": 9868.217894736843, + "impact": 1.7722323960215767, + "name": "GET /api/customers/:id", + "p95": 27486.5, "sample": Object { - "@timestamp": "2018-11-18T20:52:57.963Z", + "@timestamp": "2018-11-18T20:52:56.797Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1744,7 +1737,7 @@ baz", }, "context": Object { "custom": Object { - "containerId": 7184, + "containerId": 8225, }, "process": Object { "argv": Array [ @@ -1757,32 +1750,35 @@ baz", }, "request": Object { "headers": Object { - "connection": "close", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-e6140d30363f18b585f5d3b753f4d025-aa82e2c847265626-01", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "user-agent": "python-requests/2.20.0", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.6", }, "url": Object { - "full": "http://opbeans-node:3000/api/products/3", + "full": "http://opbeans-node:3000/api/customers/700", "hostname": "opbeans-node", - "pathname": "/api/products/3", + "pathname": "/api/customers/700", "port": "3000", "protocol": "http:", - "raw": "/api/products/3", + "raw": "/api/customers/700", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-length": "231", + "connection": "keep-alive", + "content-length": "193", "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:52:57 GMT", - "etag": "W/\\"e7-kkuzj37GZDzXDh0CWqh5Gan0VO4\\"", + "date": "Sun, 18 Nov 2018 20:52:56 GMT", + "etag": "W/\\"c1-LbuhkuLzFyZ0H+7+JQGA5b0kvNs\\"", "x-powered-by": "Express", }, "status_code": 200, @@ -1825,22 +1821,25 @@ baz", "host": Object { "name": "b359e3afece8", }, + "parent": Object { + "id": "aa82e2c847265626", + }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574377963005, + "us": 1542574376797031, }, "trace": Object { - "id": "ca86ec845e412e4b4506a715d51548ec", + "id": "e6140d30363f18b585f5d3b753f4d025", }, "transaction": Object { "duration": Object { - "us": 6959, + "us": 9735, }, - "id": "d324897ffb7ebcdc", - "name": "GET /api/products/:id", + "id": "60e230d12f3f0960", + "name": "GET /api/customers/:id", "result": "HTTP 2xx", "sampled": true, "span_count": Object { @@ -1849,15 +1848,15 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 58073.68421052631, + "transactionsPerMinute": 59999.99999999999, }, Object { - "averageResponseTime": 9868.217894736843, - "impact": 1.7722323960215767, - "name": "GET /api/customers/:id", - "p95": 27486.5, + "averageResponseTime": 12763.68806073154, + "impact": 1.7479924334286208, + "name": "GET /api/types/:id", + "p95": 30576.749999999996, "sample": Object { - "@timestamp": "2018-11-18T20:52:56.797Z", + "@timestamp": "2018-11-18T20:53:35.967Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1865,48 +1864,45 @@ baz", }, "context": Object { "custom": Object { - "containerId": 8225, + "containerId": 5345, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3686, + "pid": 3756, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "elastic-apm-traceparent": "00-e6140d30363f18b585f5d3b753f4d025-aa82e2c847265626-01", + "connection": "close", "host": "opbeans-node:3000", - "user-agent": "python-requests/2.20.0", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.6", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/api/customers/700", + "full": "http://opbeans-node:3000/api/types/1", "hostname": "opbeans-node", - "pathname": "/api/customers/700", + "pathname": "/api/types/1", "port": "3000", "protocol": "http:", - "raw": "/api/customers/700", + "raw": "/api/types/1", }, }, "response": Object { "headers": Object { - "connection": "keep-alive", - "content-length": "193", + "connection": "close", + "content-length": "217", "content-type": "application/json; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:52:56 GMT", - "etag": "W/\\"c1-LbuhkuLzFyZ0H+7+JQGA5b0kvNs\\"", + "date": "Sun, 18 Nov 2018 20:53:35 GMT", + "etag": "W/\\"d9-cebOOHODBQMZd1wt+ZZBaSPgQLQ\\"", "x-powered-by": "Express", }, "status_code": 200, @@ -1949,42 +1945,39 @@ baz", "host": Object { "name": "b359e3afece8", }, - "parent": Object { - "id": "aa82e2c847265626", - }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574376797031, + "us": 1542574415967005, }, "trace": Object { - "id": "e6140d30363f18b585f5d3b753f4d025", + "id": "2223b30b5cbaf2e221fcf70ac6d9abbe", }, "transaction": Object { "duration": Object { - "us": 9735, + "us": 13064, }, - "id": "60e230d12f3f0960", - "name": "GET /api/customers/:id", + "id": "053436abacdec0a4", + "name": "GET /api/types/:id", "result": "HTTP 2xx", "sampled": true, "span_count": Object { - "started": 1, + "started": 2, }, "type": "request", }, }, - "transactionsPerMinute": 59999.99999999999, + "transactionsPerMinute": 45757.8947368421, }, Object { - "averageResponseTime": 5192.9, - "impact": 0, - "name": "POST unknown route", - "p95": 13230.5, + "averageResponseTime": 10584.05144193297, + "impact": 1.280810614916383, + "name": "GET /api/orders/:id", + "p95": 26555.399999999998, "sample": Object { - "@timestamp": "2018-11-18T18:43:50.994Z", + "@timestamp": "2018-11-18T20:51:36.949Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -1992,52 +1985,43 @@ baz", }, "context": Object { "custom": Object { - "containerId": 6102, + "containerId": 5999, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 19196, + "pid": 3475, "ppid": 1, "title": "node /app/server.js", }, "request": Object { - "body": "[REDACTED]", "headers": Object { - "accept": "*/*", - "accept-encoding": "gzip, deflate", - "content-length": "380", - "content-type": "multipart/form-data; boundary=2b2e40be188a4cb5a56c05a0c182f6c9", - "elastic-apm-traceparent": "00-19688959ea6cbccda8013c11566ea329-1fc3665eef2dcdfc-01", - "host": "172.18.0.9:3000", - "user-agent": "Python/3.7 aiohttp/3.3.2", - "x-forwarded-for": "172.18.0.11", + "connection": "close", + "host": "opbeans-node:3000", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", - "method": "POST", + "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.9", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://172.18.0.9:3000/api/orders/csv", - "hostname": "172.18.0.9", - "pathname": "/api/orders/csv", + "full": "http://opbeans-node:3000/api/orders/183", + "hostname": "opbeans-node", + "pathname": "/api/orders/183", "port": "3000", "protocol": "http:", - "raw": "/api/orders/csv", + "raw": "/api/orders/183", }, }, "response": Object { "headers": Object { - "connection": "keep-alive", - "content-length": "154", - "content-security-policy": "default-src 'self'", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 18:43:50 GMT", - "x-content-type-options": "nosniff", + "connection": "close", + "content-length": "0", + "date": "Sun, 18 Nov 2018 20:51:36 GMT", "x-powered-by": "Express", }, "status_code": 404, @@ -2080,92 +2064,87 @@ baz", "host": Object { "name": "b359e3afece8", }, - "parent": Object { - "id": "1fc3665eef2dcdfc", - }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542566630994005, + "us": 1542574296949004, }, "trace": Object { - "id": "19688959ea6cbccda8013c11566ea329", + "id": "dab6421fa44a6869887e0edf32e1ad6f", }, "transaction": Object { "duration": Object { - "us": 3467, + "us": 5906, }, - "id": "92c3ceea57899061", - "name": "POST unknown route", + "id": "937ef5588454f74a", + "name": "GET /api/orders/:id", "result": "HTTP 4xx", "sampled": true, "span_count": Object { - "started": 0, + "started": 1, }, "type": "request", }, }, - "transactionsPerMinute": 631.578947368421, + "transactionsPerMinute": 40515.789473684206, }, Object { - "averageResponseTime": 4694.005586592179, - "impact": 0.1498515000753004, - "name": "GET /is-it-coffee-time", - "p95": 11022.99999999992, + "averageResponseTime": 1422.926672899693, + "impact": 1.0027124806135428, + "name": "GET unknown route", + "p95": 2311.885238095238, "sample": Object { - "@timestamp": "2018-11-18T20:46:19.317Z", + "@timestamp": "2018-11-18T20:53:42.504Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", "version": "7.0.0-alpha1", }, "context": Object { - "custom": Object { - "containerId": 8593, - }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 2760, + "pid": 3756, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "connection": "close", + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", "host": "opbeans-node:3000", - "user-agent": "workload/2.4.3", + "referer": "http://opbeans-node:3000/dashboard", + "user-agent": "Chromeless 1.4.0", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.10", + "remote_address": "::ffff:172.18.0.7", }, "url": Object { - "full": "http://opbeans-node:3000/is-it-coffee-time", + "full": "http://opbeans-node:3000/rum-config.js", "hostname": "opbeans-node", - "pathname": "/is-it-coffee-time", + "pathname": "/rum-config.js", "port": "3000", "protocol": "http:", - "raw": "/is-it-coffee-time", + "raw": "/rum-config.js", }, }, "response": Object { "headers": Object { - "connection": "close", - "content-length": "148", - "content-security-policy": "default-src 'self'", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:46:19 GMT", - "x-content-type-options": "nosniff", + "connection": "keep-alive", + "content-length": "172", + "content-type": "text/javascript", + "date": "Sun, 18 Nov 2018 20:53:42 GMT", "x-powered-by": "Express", }, - "status_code": 500, + "status_code": 200, }, "service": Object { "agent": Object { @@ -2188,19 +2167,6 @@ baz", "ip": "172.18.0.10", "platform": "linux", }, - "tags": Object { - "foo": "bar", - "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", - "multi-line": "foo -bar -baz", - "this-is-a-very-long-tag-name-without-any-spaces": "test", - }, - "user": Object { - "email": "kimchy@elastic.co", - "id": "42", - "username": "kimchy", - }, }, "host": Object { "name": "b359e3afece8", @@ -2210,18 +2176,18 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542573979317007, + "us": 1542574422504004, }, "trace": Object { - "id": "821812b416de4c73ced87f8777fa46a6", + "id": "4399e7233e6e7b77e70c2fff111b8f28", }, "transaction": Object { "duration": Object { - "us": 4253, + "us": 911, }, - "id": "319a5c555a1ab207", - "name": "GET /is-it-coffee-time", - "result": "HTTP 5xx", + "id": "107881ae2be1b56d", + "name": "GET unknown route", + "result": "HTTP 2xx", "sampled": true, "span_count": Object { "started": 0, @@ -2229,15 +2195,15 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 11305.263157894737, + "transactionsPerMinute": 236431.5789473684, }, Object { - "averageResponseTime": 4549.889880952381, - "impact": 0.13543365054509587, - "name": "GET /throw-error", - "p95": 7719.700000000001, + "averageResponseTime": 21331.714285714286, + "impact": 0.28817487960409877, + "name": "POST /api", + "p95": 30938, "sample": Object { - "@timestamp": "2018-11-18T20:47:10.714Z", + "@timestamp": "2018-11-18T20:29:42.751Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -2245,49 +2211,50 @@ baz", }, "context": Object { "custom": Object { - "containerId": 7220, + "containerId": 2927, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 2895, + "pid": 546, "ppid": 1, "title": "node /app/server.js", }, "request": Object { + "body": "[REDACTED]", "headers": Object { + "accept": "application/json", "connection": "close", + "content-length": "129", + "content-type": "application/json", "host": "opbeans-node:3000", "user-agent": "workload/2.4.3", }, "http_version": "1.1", - "method": "GET", + "method": "POST", "socket": Object { "encrypted": false, "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/throw-error", + "full": "http://opbeans-node:3000/api/orders", "hostname": "opbeans-node", - "pathname": "/throw-error", + "pathname": "/api/orders", "port": "3000", "protocol": "http:", - "raw": "/throw-error", + "raw": "/api/orders", }, }, "response": Object { "headers": Object { "connection": "close", - "content-length": "148", - "content-security-policy": "default-src 'self'", - "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:47:10 GMT", - "x-content-type-options": "nosniff", + "content-length": "0", + "date": "Sun, 18 Nov 2018 20:29:42 GMT", "x-powered-by": "Express", }, - "status_code": 500, + "status_code": 400, }, "service": Object { "agent": Object { @@ -2332,34 +2299,34 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574030714012, + "us": 1542572982751005, }, "trace": Object { - "id": "6c0ef23e1f963f304ce440a909914d35", + "id": "8ed4d94ec8fc11b1ea1b0aa59c2320ff", }, "transaction": Object { "duration": Object { - "us": 4458, + "us": 21083, }, - "id": "ecd187dc53f09fbd", - "name": "GET /throw-error", - "result": "HTTP 5xx", + "id": "d67c2f7aa897110c", + "name": "POST /api", + "result": "HTTP 4xx", "sampled": true, "span_count": Object { - "started": 0, + "started": 1, }, "type": "request", }, }, - "transactionsPerMinute": 10610.526315789473, + "transactionsPerMinute": 4642.105263157894, }, Object { - "averageResponseTime": 3504.5108924806746, - "impact": 2.3600993453143766, - "name": "GET *", - "p95": 11431.738095238095, + "averageResponseTime": 4694.005586592179, + "impact": 0.1498515000753004, + "name": "GET /is-it-coffee-time", + "p95": 11022.99999999992, "sample": Object { - "@timestamp": "2018-11-18T20:53:42.493Z", + "@timestamp": "2018-11-18T20:46:19.317Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -2367,54 +2334,49 @@ baz", }, "context": Object { "custom": Object { - "containerId": 6446, + "containerId": 8593, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3756, + "pid": 2760, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", - "accept-encoding": "gzip, deflate", - "connection": "keep-alive", + "connection": "close", "host": "opbeans-node:3000", - "if-modified-since": "Mon, 12 Nov 2018 10:27:07 GMT", - "if-none-match": "W/\\"280-1670775e878\\"", - "upgrade-insecure-requests": "1", - "user-agent": "Chromeless 1.4.0", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", "method": "GET", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.7", + "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/dashboard", + "full": "http://opbeans-node:3000/is-it-coffee-time", "hostname": "opbeans-node", - "pathname": "/dashboard", + "pathname": "/is-it-coffee-time", "port": "3000", "protocol": "http:", - "raw": "/dashboard", + "raw": "/is-it-coffee-time", }, }, "response": Object { "headers": Object { - "accept-ranges": "bytes", - "cache-control": "public, max-age=0", - "connection": "keep-alive", - "date": "Sun, 18 Nov 2018 20:53:42 GMT", - "etag": "W/\\"280-1670775e878\\"", - "last-modified": "Mon, 12 Nov 2018 10:27:07 GMT", + "connection": "close", + "content-length": "148", + "content-security-policy": "default-src 'self'", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:46:19 GMT", + "x-content-type-options": "nosniff", "x-powered-by": "Express", }, - "status_code": 304, + "status_code": 500, }, "service": Object { "agent": Object { @@ -2459,18 +2421,18 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574422493006, + "us": 1542573979317007, }, "trace": Object { - "id": "7efb6ade88cdea20cd96ca482681cde7", + "id": "821812b416de4c73ced87f8777fa46a6", }, "transaction": Object { "duration": Object { - "us": 1901, + "us": 4253, }, - "id": "f5fc4621949b63fb", - "name": "GET *", - "result": "HTTP 3xx", + "id": "319a5c555a1ab207", + "name": "GET /is-it-coffee-time", + "result": "HTTP 5xx", "sampled": true, "span_count": Object { "started": 0, @@ -2478,15 +2440,15 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 224684.21052631576, + "transactionsPerMinute": 11305.263157894737, }, Object { - "averageResponseTime": 2742.4615384615386, - "impact": 0.08501028923348058, - "name": "OPTIONS unknown route", - "p95": 4370.000000000002, + "averageResponseTime": 4549.889880952381, + "impact": 0.13543365054509587, + "name": "GET /throw-error", + "p95": 7719.700000000001, "sample": Object { - "@timestamp": "2018-11-18T20:49:00.707Z", + "@timestamp": "2018-11-18T20:47:10.714Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", @@ -2494,50 +2456,49 @@ baz", }, "context": Object { "custom": Object { - "containerId": 3775, + "containerId": 7220, }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3142, + "pid": 2895, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { "connection": "close", - "content-length": "0", "host": "opbeans-node:3000", "user-agent": "workload/2.4.3", }, "http_version": "1.1", - "method": "OPTIONS", + "method": "GET", "socket": Object { "encrypted": false, "remote_address": "::ffff:172.18.0.10", }, "url": Object { - "full": "http://opbeans-node:3000/", + "full": "http://opbeans-node:3000/throw-error", "hostname": "opbeans-node", - "pathname": "/", + "pathname": "/throw-error", "port": "3000", "protocol": "http:", - "raw": "/", + "raw": "/throw-error", }, }, "response": Object { "headers": Object { - "allow": "GET,HEAD", "connection": "close", - "content-length": "8", + "content-length": "148", + "content-security-policy": "default-src 'self'", "content-type": "text/html; charset=utf-8", - "date": "Sun, 18 Nov 2018 20:49:00 GMT", - "etag": "W/\\"8-ZRAf8oNBS3Bjb/SU2GYZCmbtmXg\\"", + "date": "Sun, 18 Nov 2018 20:47:10 GMT", + "x-content-type-options": "nosniff", "x-powered-by": "Express", }, - "status_code": 200, + "status_code": 500, }, "service": Object { "agent": Object { @@ -2582,18 +2543,18 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574140707006, + "us": 1542574030714012, }, "trace": Object { - "id": "469e3e5f91ffe3195a8e58cdd1cdefa8", + "id": "6c0ef23e1f963f304ce440a909914d35", }, "transaction": Object { "duration": Object { - "us": 2371, + "us": 4458, }, - "id": "a8c87ebc7ec68bc0", - "name": "OPTIONS unknown route", - "result": "HTTP 2xx", + "id": "ecd187dc53f09fbd", + "name": "GET /throw-error", + "result": "HTTP 5xx", "sampled": true, "span_count": Object { "started": 0, @@ -2601,38 +2562,42 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 11494.736842105262, + "transactionsPerMinute": 10610.526315789473, }, Object { - "averageResponseTime": 2651.8784461553205, - "impact": 15.770246496477105, - "name": "GET static file", - "p95": 6140.579335038363, + "averageResponseTime": 2742.4615384615386, + "impact": 0.08501028923348058, + "name": "OPTIONS unknown route", + "p95": 4370.000000000002, "sample": Object { - "@timestamp": "2018-11-18T20:53:43.304Z", + "@timestamp": "2018-11-18T20:49:00.707Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", "version": "7.0.0-alpha1", }, "context": Object { + "custom": Object { + "containerId": 3775, + }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3756, + "pid": 3142, "ppid": 1, "title": "node /app/server.js", }, "request": Object { "headers": Object { - "accept": "*/*", + "connection": "close", + "content-length": "0", "host": "opbeans-node:3000", - "user-agent": "curl/7.38.0", + "user-agent": "workload/2.4.3", }, "http_version": "1.1", - "method": "GET", + "method": "OPTIONS", "socket": Object { "encrypted": false, "remote_address": "::ffff:172.18.0.10", @@ -2648,14 +2613,12 @@ baz", }, "response": Object { "headers": Object { - "accept-ranges": "bytes", - "cache-control": "public, max-age=0", - "connection": "keep-alive", - "content-length": "640", - "content-type": "text/html; charset=UTF-8", - "date": "Sun, 18 Nov 2018 20:53:43 GMT", - "etag": "W/\\"280-1670775e878\\"", - "last-modified": "Mon, 12 Nov 2018 10:27:07 GMT", + "allow": "GET,HEAD", + "connection": "close", + "content-length": "8", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:49:00 GMT", + "etag": "W/\\"8-ZRAf8oNBS3Bjb/SU2GYZCmbtmXg\\"", "x-powered-by": "Express", }, "status_code": 200, @@ -2681,6 +2644,19 @@ baz", "ip": "172.18.0.10", "platform": "linux", }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, }, "host": Object { "name": "b359e3afece8", @@ -2690,17 +2666,17 @@ baz", "name": "transaction", }, "timestamp": Object { - "us": 1542574423304006, + "us": 1542574140707006, }, "trace": Object { - "id": "b303d2a4a007946b63b9db7fafe639a0", + "id": "469e3e5f91ffe3195a8e58cdd1cdefa8", }, "transaction": Object { "duration": Object { - "us": 1801, + "us": 2371, }, - "id": "2869c13633534be5", - "name": "GET static file", + "id": "a8c87ebc7ec68bc0", + "name": "OPTIONS unknown route", "result": "HTTP 2xx", "sampled": true, "span_count": Object { @@ -2709,63 +2685,71 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 1977031.5789473683, + "transactionsPerMinute": 11494.736842105262, }, Object { - "averageResponseTime": 1422.926672899693, - "impact": 1.0027124806135428, - "name": "GET unknown route", - "p95": 2311.885238095238, + "averageResponseTime": 5192.9, + "impact": 0, + "name": "POST unknown route", + "p95": 13230.5, "sample": Object { - "@timestamp": "2018-11-18T20:53:42.504Z", + "@timestamp": "2018-11-18T18:43:50.994Z", "agent": Object { "hostname": "b359e3afece8", "type": "apm-server", "version": "7.0.0-alpha1", }, "context": Object { + "custom": Object { + "containerId": 6102, + }, "process": Object { "argv": Array [ "/usr/local/bin/node", "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", ], - "pid": 3756, + "pid": 19196, "ppid": 1, "title": "node /app/server.js", }, "request": Object { + "body": "[REDACTED]", "headers": Object { "accept": "*/*", "accept-encoding": "gzip, deflate", - "connection": "keep-alive", - "host": "opbeans-node:3000", - "referer": "http://opbeans-node:3000/dashboard", - "user-agent": "Chromeless 1.4.0", + "content-length": "380", + "content-type": "multipart/form-data; boundary=2b2e40be188a4cb5a56c05a0c182f6c9", + "elastic-apm-traceparent": "00-19688959ea6cbccda8013c11566ea329-1fc3665eef2dcdfc-01", + "host": "172.18.0.9:3000", + "user-agent": "Python/3.7 aiohttp/3.3.2", + "x-forwarded-for": "172.18.0.11", }, "http_version": "1.1", - "method": "GET", + "method": "POST", "socket": Object { "encrypted": false, - "remote_address": "::ffff:172.18.0.7", + "remote_address": "::ffff:172.18.0.9", }, "url": Object { - "full": "http://opbeans-node:3000/rum-config.js", - "hostname": "opbeans-node", - "pathname": "/rum-config.js", + "full": "http://172.18.0.9:3000/api/orders/csv", + "hostname": "172.18.0.9", + "pathname": "/api/orders/csv", "port": "3000", "protocol": "http:", - "raw": "/rum-config.js", + "raw": "/api/orders/csv", }, }, "response": Object { "headers": Object { "connection": "keep-alive", - "content-length": "172", - "content-type": "text/javascript", - "date": "Sun, 18 Nov 2018 20:53:42 GMT", + "content-length": "154", + "content-security-policy": "default-src 'self'", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 18:43:50 GMT", + "x-content-type-options": "nosniff", "x-powered-by": "Express", }, - "status_code": 200, + "status_code": 404, }, "service": Object { "agent": Object { @@ -2788,27 +2772,43 @@ baz", "ip": "172.18.0.10", "platform": "linux", }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, }, "host": Object { "name": "b359e3afece8", }, + "parent": Object { + "id": "1fc3665eef2dcdfc", + }, "processor": Object { "event": "transaction", "name": "transaction", }, "timestamp": Object { - "us": 1542574422504004, + "us": 1542566630994005, }, "trace": Object { - "id": "4399e7233e6e7b77e70c2fff111b8f28", + "id": "19688959ea6cbccda8013c11566ea329", }, "transaction": Object { "duration": Object { - "us": 911, + "us": 3467, }, - "id": "107881ae2be1b56d", - "name": "GET unknown route", - "result": "HTTP 2xx", + "id": "92c3ceea57899061", + "name": "POST unknown route", + "result": "HTTP 4xx", "sampled": true, "span_count": Object { "started": 0, @@ -2816,7 +2816,7 @@ baz", "type": "request", }, }, - "transactionsPerMinute": 236431.5789473684, + "transactionsPerMinute": 631.578947368421, }, ] `; diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts index b08bdc334fc8..a4885f288497 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -5,19 +5,21 @@ */ import { + SERVICE_NAME, TRANSACTION_DURATION, - TRANSACTION_SAMPLED + TRANSACTION_SAMPLED, + TRANSACTION_NAME } from '../../../common/elasticsearch_fieldnames'; +import { getTransactionGroupsProjection } from '../../../common/projections/transaction_groups'; +import { mergeProjection } from '../../../common/projections/util/merge_projection'; import { PromiseReturnType } from '../../../typings/common'; +import { SortOptions } from '../../../typings/elasticsearch/aggregations'; +import { Transaction } from '../../../typings/es_schemas/ui/Transaction'; import { Setup, SetupTimeRange, SetupUIFilters } from '../helpers/setup_request'; -import { getTransactionGroupsProjection } from '../../../common/projections/transaction_groups'; -import { mergeProjection } from '../../../common/projections/util/merge_projection'; -import { SortOptions } from '../../../typings/elasticsearch/aggregations'; -import { Transaction } from '../../../typings/es_schemas/ui/Transaction'; interface TopTransactionOptions { type: 'top_transactions'; @@ -38,7 +40,7 @@ export function transactionGroupsFetcher( options: Options, setup: Setup & SetupTimeRange & SetupUIFilters ) { - const { client, config } = setup; + const { client } = setup; const projection = getTransactionGroupsProjection({ setup, @@ -50,6 +52,13 @@ export function transactionGroupsFetcher( { '@timestamp': { order: 'desc' as const } } ]; + const isTopTraces = options.type === 'top_traces'; + + if (isTopTraces) { + // Delete the projection aggregation when searching for traces, as it should use the combined aggregation instead + delete projection.body.aggs; + } + const params = mergeProjection(projection, { body: { size: 0, @@ -60,19 +69,18 @@ export function transactionGroupsFetcher( } }, aggs: { - transactions: { - terms: { - ...projection.body.aggs.transactions.terms, - order: { sum: 'desc' as const }, - size: config['xpack.apm.ui.transactionGroupBucketSize'] + transaction_groups: { + composite: { + size: 10000, + sources: [ + ...(isTopTraces + ? [{ service: { terms: { field: SERVICE_NAME } } }] + : []), + { transaction: { terms: { field: TRANSACTION_NAME } } } + ] }, aggs: { - sample: { - top_hits: { - size: 1, - sort - } - }, + sample: { top_hits: { size: 1, sort } }, avg: { avg: { field: TRANSACTION_DURATION } }, p95: { percentiles: { field: TRANSACTION_DURATION, percents: [95] } diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/mock-responses/transactionGroupsResponse.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/mock-responses/transactionGroupsResponse.ts index 2632cc6e94b9..bc61f1cab149 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/mock-responses/transactionGroupsResponse.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/mock-responses/transactionGroupsResponse.ts @@ -12,12 +12,12 @@ export const transactionGroupsResponse = ({ _shards: { total: 44, successful: 44, skipped: 0, failed: 0 }, hits: { total: 131557, max_score: null, hits: [] }, aggregations: { - transactions: { + transaction_groups: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [ { - key: 'POST /api/orders', + key: { transaction: 'POST /api/orders' }, doc_count: 180, avg: { value: 255966.30555555556 }, p95: { values: { '95.0': 320238.5 } }, @@ -137,7 +137,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api', + key: { transaction: 'GET /api' }, doc_count: 21911, avg: { value: 48021.972616494 }, p95: { values: { '95.0': 67138.18364917398 } }, @@ -257,7 +257,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/orders', + key: { transaction: 'GET /api/orders' }, doc_count: 3247, avg: { value: 33265.03326147213 }, p95: { values: { '95.0': 58827.489999999976 } }, @@ -373,7 +373,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /log-message', + key: { transaction: 'GET /log-message' }, doc_count: 700, avg: { value: 32900.72714285714 }, p95: { values: { '95.0': 40444 } }, @@ -489,7 +489,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/stats', + key: { transaction: 'GET /api/stats' }, doc_count: 4639, avg: { value: 32554.36257814184 }, p95: { values: { '95.0': 59356.73611111111 } }, @@ -610,7 +610,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /log-error', + key: { transaction: 'GET /log-error' }, doc_count: 736, avg: { value: 32387.73641304348 }, p95: { values: { '95.0': 40061.1 } }, @@ -726,7 +726,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/customers', + key: { transaction: 'GET /api/customers' }, doc_count: 3366, avg: { value: 32159.926322043968 }, p95: { values: { '95.0': 59845.85714285714 } }, @@ -847,7 +847,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/products/top', + key: { transaction: 'GET /api/products/top' }, doc_count: 3694, avg: { value: 27516.89144558744 }, p95: { values: { '95.0': 56064.679999999986 } }, @@ -969,7 +969,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'POST /api', + key: { transaction: 'POST /api' }, doc_count: 147, avg: { value: 21331.714285714286 }, p95: { values: { '95.0': 30938 } }, @@ -1087,7 +1087,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/products/:id/customers', + key: { transaction: 'GET /api/products/:id/customers' }, doc_count: 2102, avg: { value: 17189.329210275926 }, p95: { values: { '95.0': 39284.79999999999 } }, @@ -1209,7 +1209,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/types/:id', + key: { transaction: 'GET /api/types/:id' }, doc_count: 1449, avg: { value: 12763.68806073154 }, p95: { values: { '95.0': 30576.749999999996 } }, @@ -1325,7 +1325,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/products', + key: { transaction: 'GET /api/products' }, doc_count: 3678, avg: { value: 12683.190864600327 }, p95: { values: { '95.0': 35009.67999999999 } }, @@ -1441,7 +1441,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/types', + key: { transaction: 'GET /api/types' }, doc_count: 2400, avg: { value: 11257.757916666667 }, p95: { values: { '95.0': 35222.944444444445 } }, @@ -1557,7 +1557,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/orders/:id', + key: { transaction: 'GET /api/orders/:id' }, doc_count: 1283, avg: { value: 10584.05144193297 }, p95: { values: { '95.0': 26555.399999999998 } }, @@ -1671,7 +1671,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/products/:id', + key: { transaction: 'GET /api/products/:id' }, doc_count: 1839, avg: { value: 10548.218597063622 }, p95: { values: { '95.0': 28413.383333333328 } }, @@ -1787,7 +1787,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /api/customers/:id', + key: { transaction: 'GET /api/customers/:id' }, doc_count: 1900, avg: { value: 9868.217894736843 }, p95: { values: { '95.0': 27486.5 } }, @@ -1908,7 +1908,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'POST unknown route', + key: { transaction: 'POST unknown route' }, doc_count: 20, avg: { value: 5192.9 }, p95: { values: { '95.0': 13230.5 } }, @@ -2034,7 +2034,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /is-it-coffee-time', + key: { transaction: 'GET /is-it-coffee-time' }, doc_count: 358, avg: { value: 4694.005586592179 }, p95: { values: { '95.0': 11022.99999999992 } }, @@ -2151,7 +2151,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET /throw-error', + key: { transaction: 'GET /throw-error' }, doc_count: 336, avg: { value: 4549.889880952381 }, p95: { values: { '95.0': 7719.700000000001 } }, @@ -2268,7 +2268,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET *', + key: { transaction: 'GET *' }, doc_count: 7115, avg: { value: 3504.5108924806746 }, p95: { values: { '95.0': 11431.738095238095 } }, @@ -2391,7 +2391,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'OPTIONS unknown route', + key: { transaction: 'OPTIONS unknown route' }, doc_count: 364, avg: { value: 2742.4615384615386 }, p95: { values: { '95.0': 4370.000000000002 } }, @@ -2509,7 +2509,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET static file', + key: { transaction: 'GET static file' }, doc_count: 62606, avg: { value: 2651.8784461553205 }, p95: { values: { '95.0': 6140.579335038363 } }, @@ -2614,7 +2614,7 @@ export const transactionGroupsResponse = ({ } }, { - key: 'GET unknown route', + key: { transaction: 'GET unknown route' }, doc_count: 7487, avg: { value: 1422.926672899693 }, p95: { values: { '95.0': 2311.885238095238 } }, diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.test.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.test.ts index 6acd34af2435..709fa3afdc12 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.test.ts @@ -21,7 +21,7 @@ describe('transactionGroupsTransformer', () => { it('should transform response correctly', () => { const bucket = { - key: 'POST /api/orders', + key: { transaction: 'POST /api/orders' }, doc_count: 180, avg: { value: 255966.30555555556 }, p95: { values: { '95.0': 320238.5 } }, @@ -36,7 +36,7 @@ describe('transactionGroupsTransformer', () => { const response = ({ aggregations: { - transactions: { + transaction_groups: { buckets: [bucket] } } @@ -58,7 +58,7 @@ describe('transactionGroupsTransformer', () => { it('should calculate impact from sum', () => { const getBucket = (sum: number) => ({ - key: 'POST /api/orders', + key: { transaction: 'POST /api/orders' }, doc_count: 180, avg: { value: 300000 }, p95: { values: { '95.0': 320000 } }, @@ -68,7 +68,9 @@ describe('transactionGroupsTransformer', () => { const response = ({ aggregations: { - transactions: { buckets: [getBucket(10), getBucket(20), getBucket(50)] } + transaction_groups: { + buckets: [getBucket(10), getBucket(20), getBucket(50)] + } } } as unknown) as ESResponse; @@ -76,6 +78,6 @@ describe('transactionGroupsTransformer', () => { transactionGroupsTransformer({ response, start: 100, end: 20000 }).map( bucket => bucket.impact ) - ).toEqual([0, 25, 100]); + ).toEqual([100, 25, 0]); }); }); diff --git a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts index 58a952baa823..0a03a88cbf4a 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transaction_groups/transform.ts @@ -5,6 +5,7 @@ */ import moment from 'moment'; +import { sortByOrder } from 'lodash'; import { ESResponse } from './fetcher'; function calculateRelativeImpacts(transactionGroups: ITransactionGroup[]) { @@ -24,9 +25,20 @@ function calculateRelativeImpacts(transactionGroups: ITransactionGroup[]) { })); } +const getBuckets = (response: ESResponse) => { + if (response.aggregations) { + return sortByOrder( + response.aggregations.transaction_groups.buckets, + ['sum.value'], + ['desc'] + ); + } + return []; +}; + export type ITransactionGroup = ReturnType; function getTransactionGroup( - bucket: Required['aggregations']['transactions']['buckets'][0], + bucket: ReturnType[0], minutes: number ) { const averageResponseTime = bucket.avg.value; @@ -35,7 +47,7 @@ function getTransactionGroup( const sample = bucket.sample.hits.hits[0]._source; return { - name: bucket.key as string, + name: bucket.key.transaction, sample, p95: bucket.p95.values['95.0'], averageResponseTime, @@ -53,7 +65,7 @@ export function transactionGroupsTransformer({ start: number; end: number; }): ITransactionGroup[] { - const buckets = response.aggregations?.transactions.buckets || []; + const buckets = getBuckets(response); const duration = moment.duration(end - start); const minutes = duration.asMinutes(); const transactionGroups = buckets.map(bucket => diff --git a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts index f49c1e022a07..476928a5bcb6 100644 --- a/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/transactions/breakdown/index.test.ts @@ -70,13 +70,13 @@ describe('getTransactionBreakdown', () => { expect(response.kpis[0]).toEqual({ name: 'app', - color: '#00b3a4', + color: '#54b399', percentage: 0.5408550899466306 }); expect(response.kpis[3]).toEqual({ name: 'postgresql', - color: '#490092', + color: '#9170b8', percentage: 0.047366859295002 }); }); diff --git a/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts b/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts index a9a8241da39d..cf27d20c2436 100644 --- a/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts +++ b/x-pack/legacy/plugins/apm/server/routes/create_apm_api.ts @@ -58,7 +58,7 @@ import { uiFiltersEnvironmentsRoute } from './ui_filters'; import { createApi } from './create_api'; -import { serviceMapRoute } from './service_map'; +import { serviceMapRoute, serviceMapServiceNodeRoute } from './service_map'; const createApmApi = () => { const api = createApi() @@ -123,7 +123,8 @@ const createApmApi = () => { .add(transactionByTraceIdRoute) // Service map - .add(serviceMapRoute); + .add(serviceMapRoute) + .add(serviceMapServiceNodeRoute); return api; }; diff --git a/x-pack/legacy/plugins/apm/server/routes/service_map.ts b/x-pack/legacy/plugins/apm/server/routes/service_map.ts index 94b176147f7a..584598805f8b 100644 --- a/x-pack/legacy/plugins/apm/server/routes/service_map.ts +++ b/x-pack/legacy/plugins/apm/server/routes/service_map.ts @@ -10,6 +10,7 @@ import { setupRequest } from '../lib/helpers/setup_request'; import { createRoute } from './create_route'; import { uiFiltersRt, rangeRt } from './default_api_types'; import { getServiceMap } from '../lib/service_map/get_service_map'; +import { getServiceMapServiceNodeInfo } from '../lib/service_map/get_service_map_service_node_info'; export const serviceMapRoute = createRoute(() => ({ path: '/api/apm/service-map', @@ -32,3 +33,35 @@ export const serviceMapRoute = createRoute(() => ({ return getServiceMap({ setup, serviceName, environment, after }); } })); + +export const serviceMapServiceNodeRoute = createRoute(() => ({ + path: `/api/apm/service-map/service/{serviceName}`, + params: { + path: t.type({ + serviceName: t.string + }), + query: t.intersection([ + rangeRt, + t.partial({ + environment: t.string + }) + ]) + }, + handler: async ({ context, request }) => { + if (!context.config['xpack.apm.serviceMapEnabled']) { + throw Boom.notFound(); + } + const setup = await setupRequest(context, request); + + const { + query: { environment }, + path: { serviceName } + } = context.params; + + return getServiceMapServiceNodeInfo({ + setup, + serviceName, + environment + }); + } +})); diff --git a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/fields/Stackframe.ts b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/fields/Stackframe.ts index a1b1a8198bb3..993fac46ad7c 100644 --- a/x-pack/legacy/plugins/apm/typings/es_schemas/raw/fields/Stackframe.ts +++ b/x-pack/legacy/plugins/apm/typings/es_schemas/raw/fields/Stackframe.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -interface IStackframeBase { - filename: string; +type IStackframeBase = { function?: string; library_frame?: boolean; exclude_from_grouping?: boolean; @@ -19,13 +18,13 @@ interface IStackframeBase { line: { number: number; }; -} +} & ({ classname: string } | { filename: string }); -export interface IStackframeWithLineContext extends IStackframeBase { +export type IStackframeWithLineContext = IStackframeBase & { line: { number: number; context: string; }; -} +}; export type IStackframe = IStackframeBase | IStackframeWithLineContext; 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 0e07c2b4960b..29cdcfccfc75 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 @@ -3,12 +3,15 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { EuiFieldText, EuiFieldTextProps, EuiFormRow } from '@elastic/eui'; +import { EuiFieldText, EuiFormRow } from '@elastic/eui'; import { CommonProps } from '@elastic/eui/src/components/common'; import { FormsyInputProps, withFormsy } from 'formsy-react'; import React, { Component, InputHTMLAttributes } from 'react'; -interface ComponentProps extends FormsyInputProps, CommonProps, EuiFieldTextProps { +interface ComponentProps + extends FormsyInputProps, + CommonProps, + Omit, 'onChange' | 'onBlur'> { instantValidation?: boolean; label: string; errorText: string; diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/palette.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/palette.js index 69f584af4155..d60dc13f0105 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/palette.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/palette.js @@ -61,7 +61,9 @@ const PaletteArgInput = ({ onValueChange, argValue, renderError }) => { const palette = astToPalette(argValue); - return ; + return ( + + ); }; PaletteArgInput.propTypes = { diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/shape.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/shape.js index c056e7d1f228..baa2127b03c3 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/shape.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/shape.js @@ -20,6 +20,7 @@ const ShapeArgInput = ({ onValueChange, argValue, typeInstance }) => ( value={argValue} onChange={onValueChange} shapes={typeInstance.options.shapes} + ariaLabel={typeInstance.displayName} /> diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/toggle.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/toggle.js index de19d3e29221..bcad4678e0b6 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/toggle.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/arguments/toggle.js @@ -12,7 +12,7 @@ import { ArgumentStrings } from '../../../i18n'; const { Toggle: strings } = ArgumentStrings; -const ToggleArgInput = ({ onValueChange, argValue, argId, renderError }) => { +const ToggleArgInput = ({ onValueChange, argValue, argId, renderError, typeInstance }) => { const handleChange = () => onValueChange(!argValue); if (typeof argValue !== 'boolean') { renderError(); @@ -26,6 +26,9 @@ const ToggleArgInput = ({ onValueChange, argValue, argId, renderError }) => { checked={argValue} onChange={handleChange} className="canvasArg__switch" + aria-label={typeInstance.displayName} + label="" + showLabel={false} /> ); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/chart.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/chart.ts index aca30780d77c..4c535a42c3c4 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/chart.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/chart.ts @@ -4,10 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { euiPaletteColorBlind } from '@elastic/eui'; import { TagFactory } from '../../../public/lib/tag'; import { TagStrings as strings } from '../../../i18n'; +const euiVisPalette = euiPaletteColorBlind(); export const chart: TagFactory = () => ({ name: strings.chart(), - color: '#FEB6DB', + color: euiVisPalette[4], }); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/filter.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/filter.ts index d3d251026e9b..5249856dec27 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/filter.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/filter.ts @@ -4,10 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import { euiPaletteColorBlind } from '@elastic/eui'; import { TagFactory } from '../../../public/lib/tag'; import { TagStrings as strings } from '../../../i18n'; +const euiVisPalette = euiPaletteColorBlind(); + export const filter: TagFactory = () => ({ name: strings.filter(), - color: '#3185FC', + color: euiVisPalette[1], }); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/graphic.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/graphic.ts index 325a531b219e..36d66801ef68 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/graphic.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/graphic.ts @@ -4,10 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { euiPaletteColorBlind } from '@elastic/eui'; import { TagFactory } from '../../../public/lib/tag'; import { TagStrings as strings } from '../../../i18n'; +const euiVisPalette = euiPaletteColorBlind(); export const graphic: TagFactory = () => ({ name: strings.graphic(), - color: '#E6C220', + color: euiVisPalette[5], }); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/proportion.js b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/proportion.js index ac6447ffd9dc..6a59a6795d45 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/proportion.js +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/proportion.js @@ -4,4 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const proportion = () => ({ name: 'proportion', color: '#490092' }); +import { euiPaletteColorBlind } from '@elastic/eui'; +const euiVisPalette = euiPaletteColorBlind(); + +export const proportion = () => ({ name: 'proportion', color: euiVisPalette[3] }); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/proportion.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/proportion.ts index e538b4bf5310..4d37ecfaa367 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/proportion.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/proportion.ts @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { euiPaletteColorBlind } from '@elastic/eui'; import { TagFactory } from '../../../public/lib/tag'; - import { TagStrings as strings } from '../../../i18n'; +const euiVisPalette = euiPaletteColorBlind(); export const proportion: TagFactory = () => ({ name: strings.proportion(), - color: '#490092', + color: euiVisPalette[3], }); diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/report.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/report.ts index 5df30581cd07..8dfbe1cb2161 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/report.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/uis/tags/report.ts @@ -4,10 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { euiPaletteColorBlind } from '@elastic/eui'; import { TagFactory } from '../../../public/lib/tag'; import { TagStrings as strings } from '../../../i18n'; +const euiVisPalette = euiPaletteColorBlind(); export const report: TagFactory = () => ({ name: strings.report(), - color: '#DB1374', + color: euiVisPalette[2], }); diff --git a/x-pack/legacy/plugins/canvas/i18n/components.ts b/x-pack/legacy/plugins/canvas/i18n/components.ts index c898db7467b4..d0a9051d7af8 100644 --- a/x-pack/legacy/plugins/canvas/i18n/components.ts +++ b/x-pack/legacy/plugins/canvas/i18n/components.ts @@ -912,6 +912,10 @@ export const ComponentStrings = { i18n.translate('xpack.canvas.textStylePicker.styleUnderlineOption', { defaultMessage: 'Underline', }), + getFontColorLabel: () => + i18n.translate('xpack.canvas.textStylePicker.fontColorLabel', { + defaultMessage: 'Font Color', + }), }, TimePicker: { getApplyButtonLabel: () => @@ -1007,7 +1011,11 @@ export const ComponentStrings = { getUSLetterButtonLabel: () => i18n.translate('xpack.canvas.workpadConfig.USLetterButtonLabel', { defaultMessage: 'US Letter', - description: 'This is referring to the dimentions of U.S. standard letter paper.', + description: 'This is referring to the dimensions of U.S. standard letter paper.', + }), + getBackgroundColorLabel: () => + i18n.translate('xpack.canvas.workpadConfig.backgroundColorLabel', { + defaultMessage: 'Background color', }), }, WorkpadCreate: { diff --git a/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot index 454ef0a79d10..35de0fb665be 100644 --- a/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/asset_manager/__examples__/__snapshots__/asset.examples.storyshot @@ -25,11 +25,6 @@ exports[`Storyshots components/Assets/Asset airplane 1`] = ` alt="Asset thumbnail" className="euiImage__img" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1Ni4zMSA1Ni4zMSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMDc4YTA7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPlBsYW5lIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNDkuNTEsNDguOTMsNDEuMjYsMjIuNTIsNTMuNzYsMTBhNS4yOSw1LjI5LDAsMCwwLTcuNDgtNy40N2wtMTIuNSwxMi41TDcuMzgsNi43OUEuNy43LDAsMCwwLDYuNjksN0wxLjIsMTIuNDVhLjcuNywwLDAsMCwwLDFMMTkuODUsMjlsLTcuMjQsNy4yNC03Ljc0LS42YS43MS43MSwwLDAsMC0uNTMuMkwxLjIxLDM5YS42Ny42NywwLDAsMCwuMDgsMUw5LjQ1LDQ2bC4wNywwYy4xMS4xMy4yMi4yNi4zNC4zOHMuMjUuMjMuMzguMzRhLjM2LjM2LDAsMCwwLDAsLjA3TDE2LjMzLDU1YS42OC42OCwwLDAsMCwxLC4wN0wyMC40OSw1MmEuNjcuNjcsMCwwLDAsLjE5LS41NGwtLjU5LTcuNzQsNy4yNC03LjI0TDQyLjg1LDU1LjA2YS42OC42OCwwLDAsMCwxLDBsNS41LTUuNUEuNjYuNjYsMCwwLDAsNDkuNTEsNDguOTNaIi8+PC9nPjwvZz48L3N2Zz4=" - style={ - Object { - "backgroundImage": "url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1Ni4zMSA1Ni4zMSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMDc4YTA7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPlBsYW5lIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNDkuNTEsNDguOTMsNDEuMjYsMjIuNTIsNTMuNzYsMTBhNS4yOSw1LjI5LDAsMCwwLTcuNDgtNy40N2wtMTIuNSwxMi41TDcuMzgsNi43OUEuNy43LDAsMCwwLDYuNjksN0wxLjIsMTIuNDVhLjcuNywwLDAsMCwwLDFMMTkuODUsMjlsLTcuMjQsNy4yNC03Ljc0LS42YS43MS43MSwwLDAsMC0uNTMuMkwxLjIxLDM5YS42Ny42NywwLDAsMCwuMDgsMUw5LjQ1LDQ2bC4wNywwYy4xMS4xMy4yMi4yNi4zNC4zOHMuMjUuMjMuMzguMzRhLjM2LjM2LDAsMCwwLDAsLjA3TDE2LjMzLDU1YS42OC42OCwwLDAsMCwxLC4wN0wyMC40OSw1MmEuNjcuNjcsMCwwLDAsLjE5LS41NGwtLjU5LTcuNzQsNy4yNC03LjI0TDQyLjg1LDU1LjA2YS42OC42OCwwLDAsMCwxLDBsNS41LTUuNUEuNjYuNjYsMCwwLDAsNDkuNTEsNDguOTNaIi8+PC9nPjwvZz48L3N2Zz4=)", - } - } />
@@ -224,11 +219,6 @@ exports[`Storyshots components/Assets/Asset marker 1`] = ` alt="Asset thumbnail" className="euiImage__img" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzOC4zOSA1Ny41NyI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMTliOGY7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPkxvY2F0aW9uIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMTkuMTksMUExOC4xOSwxOC4xOSwwLDAsMCwyLjk0LDI3LjM2aDBhMTkuNTEsMTkuNTEsMCwwLDAsMSwxLjc4TDE5LjE5LDU1LjU3LDM0LjM4LDI5LjIxQTE4LjE5LDE4LjE5LDAsMCwwLDE5LjE5LDFabTAsMjMuMjlhNS41Myw1LjUzLDAsMSwxLDUuNTMtNS41M0E1LjUzLDUuNTMsMCwwLDEsMTkuMTksMjQuMjlaIi8+PC9nPjwvZz48L3N2Zz4=" - style={ - Object { - "backgroundImage": "url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzOC4zOSA1Ny41NyI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiNmZmY7c3Ryb2tlOiMwMTliOGY7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9PC9zdHlsZT48L2RlZnM+PHRpdGxlPkxvY2F0aW9uIEljb248L3RpdGxlPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJMYXllcl8xLTIiIGRhdGEtbmFtZT0iTGF5ZXIgMSI+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMTkuMTksMUExOC4xOSwxOC4xOSwwLDAsMCwyLjk0LDI3LjM2aDBhMTkuNTEsMTkuNTEsMCwwLDAsMSwxLjc4TDE5LjE5LDU1LjU3LDM0LjM4LDI5LjIxQTE4LjE5LDE4LjE5LDAsMCwwLDE5LjE5LDFabTAsMjMuMjlhNS41Myw1LjUzLDAsMSwxLDUuNTMtNS41M0E1LjUzLDUuNTMsMCwwLDEsMTkuMTksMjQuMjlaIi8+PC9nPjwvZz48L3N2Zz4=)", - } - } /> diff --git a/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset.tsx b/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset.tsx index 579470649582..c1a2b0f0bf37 100644 --- a/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset.tsx @@ -92,7 +92,6 @@ export const Asset: FunctionComponent = props => { url={props.asset.value} fullScreenIconColor="dark" alt={strings.getThumbnailAltText()} - style={{ backgroundImage: `url(${props.asset.value})` }} /> ); diff --git a/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.scss b/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.scss index 5b281129f533..c8ab1323557b 100644 --- a/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.scss +++ b/x-pack/legacy/plugins/canvas/public/components/asset_manager/asset_manager.scss @@ -46,18 +46,20 @@ margin: -$euiSizeS; margin-bottom: 0; font-size: 0; // eliminates any extra space around img + height: 164px; } .canvasAsset__img { - background-repeat: no-repeat; - background-position: center; - background-size: contain; + display: flex; + align-items: center; + justify-content: center; + + height: 100%; img { width: auto; max-width: 100%; - height: 164px; // nice default proportions for typical 4x3 images - opacity: 0; // only show the background image (which will properly keep proportions) + max-height: 164px; // nice default proportions for typical 4x3 images } } } diff --git a/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot index badbf96029f1..8610ed2f1b4a 100644 --- a/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/color_palette/__examples__/__snapshots__/color_palette.examples.storyshot @@ -8,6 +8,7 @@ exports[`Storyshots components/Color/ColorPalette interactive 1`] = ` className="item-grid-row" > ); diff --git a/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx b/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx index 970f72da698b..717ec6d0faec 100644 --- a/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/shape_picker_popover/shape_picker_popover.tsx @@ -17,12 +17,13 @@ interface Props { }; onChange?: (key: string) => void; value?: string; + ariaLabel?: string; } -export const ShapePickerPopover = ({ shapes, onChange, value }: Props) => { +export const ShapePickerPopover = ({ shapes, onChange, value, ariaLabel }: Props) => { const button = (handleClick: (ev: MouseEvent) => void) => ( - + diff --git a/x-pack/legacy/plugins/canvas/public/components/tag/__examples__/__snapshots__/tag.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/tag/__examples__/__snapshots__/tag.examples.storyshot index 562feb8111e4..754724a957e2 100644 --- a/x-pack/legacy/plugins/canvas/public/components/tag/__examples__/__snapshots__/tag.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/tag/__examples__/__snapshots__/tag.examples.storyshot @@ -6,7 +6,7 @@ exports[`Storyshots components/Tags/Tag as badge 1`] = ` style={ Object { "backgroundColor": "#666666", - "color": "#FFFFFF", + "color": "#fff", } } > @@ -28,7 +28,7 @@ exports[`Storyshots components/Tags/Tag as badge with color 1`] = ` style={ Object { "backgroundColor": "#327b53", - "color": "#FFFFFF", + "color": "#fff", } } > diff --git a/x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/__snapshots__/tag_list.examples.storyshot b/x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/__snapshots__/tag_list.examples.storyshot index 9dcf55642c66..7671b0bfb493 100644 --- a/x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/__snapshots__/tag_list.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/tag_list/__examples__/__snapshots__/tag_list.examples.storyshot @@ -9,7 +9,7 @@ Array [ style={ Object { "backgroundColor": "#cc3b54", - "color": "#FFFFFF", + "color": "#fff", } } > @@ -28,7 +28,7 @@ Array [ style={ Object { "backgroundColor": "#5bc149", - "color": "#000000", + "color": "#000", } } > @@ -47,7 +47,7 @@ Array [ style={ Object { "backgroundColor": "#fbc545", - "color": "#000000", + "color": "#000", } } > @@ -172,7 +172,7 @@ Array [ style={ Object { "backgroundColor": "#cc3b54", - "color": "#FFFFFF", + "color": "#fff", } } > @@ -191,7 +191,7 @@ Array [ style={ Object { "backgroundColor": "#5bc149", - "color": "#000000", + "color": "#000", } } > @@ -210,7 +210,7 @@ Array [ style={ Object { "backgroundColor": "#fbc545", - "color": "#000000", + "color": "#000", } } > @@ -229,7 +229,7 @@ Array [ style={ Object { "backgroundColor": "#9b3067", - "color": "#FFFFFF", + "color": "#fff", } } > @@ -248,7 +248,7 @@ Array [ style={ Object { "backgroundColor": "#1819bd", - "color": "#FFFFFF", + "color": "#fff", } } > @@ -267,7 +267,7 @@ Array [ style={ Object { "backgroundColor": "#d41e93", - "color": "#FFFFFF", + "color": "#fff", } } > @@ -286,7 +286,7 @@ Array [ style={ Object { "backgroundColor": "#3486d2", - "color": "#000000", + "color": "#000", } } > @@ -305,7 +305,7 @@ Array [ style={ Object { "backgroundColor": "#b870d8", - "color": "#000000", + "color": "#000", } } > @@ -324,7 +324,7 @@ Array [ style={ Object { "backgroundColor": "#f4a4a7", - "color": "#000000", + "color": "#000", } } > @@ -343,7 +343,7 @@ Array [ style={ Object { "backgroundColor": "#072d6d", - "color": "#FFFFFF", + "color": "#fff", } } > diff --git a/x-pack/legacy/plugins/canvas/public/components/text_style_picker/text_style_picker.js b/x-pack/legacy/plugins/canvas/public/components/text_style_picker/text_style_picker.js index 1a4418147509..179455e15b36 100644 --- a/x-pack/legacy/plugins/canvas/public/components/text_style_picker/text_style_picker.js +++ b/x-pack/legacy/plugins/canvas/public/components/text_style_picker/text_style_picker.js @@ -127,6 +127,7 @@ export const TextStylePicker = ({ value={color} onChange={value => doChange('color', value)} colors={colors} + ariaLabel={strings.getFontColorLabel()} /> diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_color_picker/workpad_color_picker.tsx b/x-pack/legacy/plugins/canvas/public/components/workpad_color_picker/workpad_color_picker.tsx index 69401c89c79a..c81f3e78efdd 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_color_picker/workpad_color_picker.tsx +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_color_picker/workpad_color_picker.tsx @@ -6,9 +6,18 @@ import React from 'react'; import { ColorPickerPopover, Props } from '../color_picker_popover'; +import { ComponentStrings } from '../../../i18n'; + +const { WorkpadConfig: strings } = ComponentStrings; export const WorkpadColorPicker = (props: Props) => { - return ; + return ( + + ); }; WorkpadColorPicker.propTypes = ColorPickerPopover.propTypes; diff --git a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/color.js b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/color.js index 2a47150b4a1b..8d756dd8111b 100644 --- a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/color.js +++ b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/color.js @@ -13,10 +13,15 @@ import { ArgTypesStrings } from '../../../i18n'; const { Color: strings } = ArgTypesStrings; -const ColorArgInput = ({ onValueChange, argValue, workpad }) => ( +const ColorArgInput = ({ onValueChange, argValue, workpad, typeInstance }) => ( - + ); diff --git a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.examples.storyshot b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.examples.storyshot index 2915d3bfef57..649d11cb2dba 100644 --- a/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.examples.storyshot +++ b/x-pack/legacy/plugins/canvas/public/expression_types/arg_types/container_style/__examples__/__snapshots__/extended_template.examples.storyshot @@ -467,6 +467,7 @@ exports[`Storyshots arguments/ContainerStyle extended 1`] = ` className="euiPopover__anchor" >